oxlint-plugin-react-doctor 0.2.11-dev.402c7ea → 0.2.11-dev.b2934f9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +170 -0
- package/dist/index.js +838 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -3400,6 +3400,23 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
3400
3400
|
readonly recommendation?: string;
|
|
3401
3401
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
3402
3402
|
};
|
|
3403
|
+
}, {
|
|
3404
|
+
readonly key: "react-doctor/no-self-updating-effect";
|
|
3405
|
+
readonly id: "no-self-updating-effect";
|
|
3406
|
+
readonly source: "react-doctor";
|
|
3407
|
+
readonly originallyExternal: false;
|
|
3408
|
+
readonly rule: {
|
|
3409
|
+
readonly framework: "global";
|
|
3410
|
+
readonly category: "State & Effects";
|
|
3411
|
+
readonly id: string;
|
|
3412
|
+
readonly severity: RuleSeverity;
|
|
3413
|
+
readonly requires?: ReadonlyArray<string>;
|
|
3414
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
3415
|
+
readonly tags?: ReadonlyArray<string>;
|
|
3416
|
+
readonly defaultEnabled?: boolean;
|
|
3417
|
+
readonly recommendation?: string;
|
|
3418
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
3419
|
+
};
|
|
3403
3420
|
}, {
|
|
3404
3421
|
readonly key: "react-doctor/no-set-state";
|
|
3405
3422
|
readonly id: "no-set-state";
|
|
@@ -5525,6 +5542,74 @@ declare const REACT_DOCTOR_RULES: readonly [{
|
|
|
5525
5542
|
readonly recommendation?: string;
|
|
5526
5543
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5527
5544
|
};
|
|
5545
|
+
}, {
|
|
5546
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-error-apis";
|
|
5547
|
+
readonly id: "zod-v4-no-deprecated-error-apis";
|
|
5548
|
+
readonly source: "react-doctor";
|
|
5549
|
+
readonly originallyExternal: false;
|
|
5550
|
+
readonly rule: {
|
|
5551
|
+
readonly framework: "global";
|
|
5552
|
+
readonly category: "Architecture";
|
|
5553
|
+
readonly id: string;
|
|
5554
|
+
readonly severity: RuleSeverity;
|
|
5555
|
+
readonly requires?: ReadonlyArray<string>;
|
|
5556
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5557
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5558
|
+
readonly defaultEnabled?: boolean;
|
|
5559
|
+
readonly recommendation?: string;
|
|
5560
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5561
|
+
};
|
|
5562
|
+
}, {
|
|
5563
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-error-customization";
|
|
5564
|
+
readonly id: "zod-v4-no-deprecated-error-customization";
|
|
5565
|
+
readonly source: "react-doctor";
|
|
5566
|
+
readonly originallyExternal: false;
|
|
5567
|
+
readonly rule: {
|
|
5568
|
+
readonly framework: "global";
|
|
5569
|
+
readonly category: "Architecture";
|
|
5570
|
+
readonly id: string;
|
|
5571
|
+
readonly severity: RuleSeverity;
|
|
5572
|
+
readonly requires?: ReadonlyArray<string>;
|
|
5573
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5574
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5575
|
+
readonly defaultEnabled?: boolean;
|
|
5576
|
+
readonly recommendation?: string;
|
|
5577
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5578
|
+
};
|
|
5579
|
+
}, {
|
|
5580
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-schema-apis";
|
|
5581
|
+
readonly id: "zod-v4-no-deprecated-schema-apis";
|
|
5582
|
+
readonly source: "react-doctor";
|
|
5583
|
+
readonly originallyExternal: false;
|
|
5584
|
+
readonly rule: {
|
|
5585
|
+
readonly framework: "global";
|
|
5586
|
+
readonly category: "Architecture";
|
|
5587
|
+
readonly id: string;
|
|
5588
|
+
readonly severity: RuleSeverity;
|
|
5589
|
+
readonly requires?: ReadonlyArray<string>;
|
|
5590
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5591
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5592
|
+
readonly defaultEnabled?: boolean;
|
|
5593
|
+
readonly recommendation?: string;
|
|
5594
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5595
|
+
};
|
|
5596
|
+
}, {
|
|
5597
|
+
readonly key: "react-doctor/zod-v4-prefer-top-level-string-formats";
|
|
5598
|
+
readonly id: "zod-v4-prefer-top-level-string-formats";
|
|
5599
|
+
readonly source: "react-doctor";
|
|
5600
|
+
readonly originallyExternal: false;
|
|
5601
|
+
readonly rule: {
|
|
5602
|
+
readonly framework: "global";
|
|
5603
|
+
readonly category: "Architecture";
|
|
5604
|
+
readonly id: string;
|
|
5605
|
+
readonly severity: RuleSeverity;
|
|
5606
|
+
readonly requires?: ReadonlyArray<string>;
|
|
5607
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
5608
|
+
readonly tags?: ReadonlyArray<string>;
|
|
5609
|
+
readonly defaultEnabled?: boolean;
|
|
5610
|
+
readonly recommendation?: string;
|
|
5611
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
5612
|
+
};
|
|
5528
5613
|
}];
|
|
5529
5614
|
declare const EXTERNAL_RULES: readonly [{
|
|
5530
5615
|
readonly key: "react-hooks-js/set-state-in-render";
|
|
@@ -8855,6 +8940,23 @@ declare const RULES: readonly [{
|
|
|
8855
8940
|
readonly recommendation?: string;
|
|
8856
8941
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
8857
8942
|
};
|
|
8943
|
+
}, {
|
|
8944
|
+
readonly key: "react-doctor/no-self-updating-effect";
|
|
8945
|
+
readonly id: "no-self-updating-effect";
|
|
8946
|
+
readonly source: "react-doctor";
|
|
8947
|
+
readonly originallyExternal: false;
|
|
8948
|
+
readonly rule: {
|
|
8949
|
+
readonly framework: "global";
|
|
8950
|
+
readonly category: "State & Effects";
|
|
8951
|
+
readonly id: string;
|
|
8952
|
+
readonly severity: RuleSeverity;
|
|
8953
|
+
readonly requires?: ReadonlyArray<string>;
|
|
8954
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
8955
|
+
readonly tags?: ReadonlyArray<string>;
|
|
8956
|
+
readonly defaultEnabled?: boolean;
|
|
8957
|
+
readonly recommendation?: string;
|
|
8958
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
8959
|
+
};
|
|
8858
8960
|
}, {
|
|
8859
8961
|
readonly key: "react-doctor/no-set-state";
|
|
8860
8962
|
readonly id: "no-set-state";
|
|
@@ -10980,6 +11082,74 @@ declare const RULES: readonly [{
|
|
|
10980
11082
|
readonly recommendation?: string;
|
|
10981
11083
|
readonly create: (context: RuleContext) => RuleVisitors;
|
|
10982
11084
|
};
|
|
11085
|
+
}, {
|
|
11086
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-error-apis";
|
|
11087
|
+
readonly id: "zod-v4-no-deprecated-error-apis";
|
|
11088
|
+
readonly source: "react-doctor";
|
|
11089
|
+
readonly originallyExternal: false;
|
|
11090
|
+
readonly rule: {
|
|
11091
|
+
readonly framework: "global";
|
|
11092
|
+
readonly category: "Architecture";
|
|
11093
|
+
readonly id: string;
|
|
11094
|
+
readonly severity: RuleSeverity;
|
|
11095
|
+
readonly requires?: ReadonlyArray<string>;
|
|
11096
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
11097
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11098
|
+
readonly defaultEnabled?: boolean;
|
|
11099
|
+
readonly recommendation?: string;
|
|
11100
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11101
|
+
};
|
|
11102
|
+
}, {
|
|
11103
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-error-customization";
|
|
11104
|
+
readonly id: "zod-v4-no-deprecated-error-customization";
|
|
11105
|
+
readonly source: "react-doctor";
|
|
11106
|
+
readonly originallyExternal: false;
|
|
11107
|
+
readonly rule: {
|
|
11108
|
+
readonly framework: "global";
|
|
11109
|
+
readonly category: "Architecture";
|
|
11110
|
+
readonly id: string;
|
|
11111
|
+
readonly severity: RuleSeverity;
|
|
11112
|
+
readonly requires?: ReadonlyArray<string>;
|
|
11113
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
11114
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11115
|
+
readonly defaultEnabled?: boolean;
|
|
11116
|
+
readonly recommendation?: string;
|
|
11117
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11118
|
+
};
|
|
11119
|
+
}, {
|
|
11120
|
+
readonly key: "react-doctor/zod-v4-no-deprecated-schema-apis";
|
|
11121
|
+
readonly id: "zod-v4-no-deprecated-schema-apis";
|
|
11122
|
+
readonly source: "react-doctor";
|
|
11123
|
+
readonly originallyExternal: false;
|
|
11124
|
+
readonly rule: {
|
|
11125
|
+
readonly framework: "global";
|
|
11126
|
+
readonly category: "Architecture";
|
|
11127
|
+
readonly id: string;
|
|
11128
|
+
readonly severity: RuleSeverity;
|
|
11129
|
+
readonly requires?: ReadonlyArray<string>;
|
|
11130
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
11131
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11132
|
+
readonly defaultEnabled?: boolean;
|
|
11133
|
+
readonly recommendation?: string;
|
|
11134
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11135
|
+
};
|
|
11136
|
+
}, {
|
|
11137
|
+
readonly key: "react-doctor/zod-v4-prefer-top-level-string-formats";
|
|
11138
|
+
readonly id: "zod-v4-prefer-top-level-string-formats";
|
|
11139
|
+
readonly source: "react-doctor";
|
|
11140
|
+
readonly originallyExternal: false;
|
|
11141
|
+
readonly rule: {
|
|
11142
|
+
readonly framework: "global";
|
|
11143
|
+
readonly category: "Architecture";
|
|
11144
|
+
readonly id: string;
|
|
11145
|
+
readonly severity: RuleSeverity;
|
|
11146
|
+
readonly requires?: ReadonlyArray<string>;
|
|
11147
|
+
readonly disabledBy?: ReadonlyArray<string>;
|
|
11148
|
+
readonly tags?: ReadonlyArray<string>;
|
|
11149
|
+
readonly defaultEnabled?: boolean;
|
|
11150
|
+
readonly recommendation?: string;
|
|
11151
|
+
readonly create: (context: RuleContext) => RuleVisitors;
|
|
11152
|
+
};
|
|
10983
11153
|
}, {
|
|
10984
11154
|
readonly key: "react-hooks-js/set-state-in-render";
|
|
10985
11155
|
readonly source: "react-compiler";
|
package/dist/index.js
CHANGED
|
@@ -21089,6 +21089,363 @@ const noSecretsInClientCode = defineRule({
|
|
|
21089
21089
|
}
|
|
21090
21090
|
});
|
|
21091
21091
|
//#endregion
|
|
21092
|
+
//#region src/plugin/rules/state-and-effects/no-self-updating-effect.ts
|
|
21093
|
+
const doesConstructFreshReference = (node) => isNodeOfType(node, "ArrayExpression") || isNodeOfType(node, "ObjectExpression") || isNodeOfType(node, "NewExpression") || isNodeOfType(node, "Literal") && "regex" in node;
|
|
21094
|
+
const expressionReadsStateValue = (node, stateName) => {
|
|
21095
|
+
if (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")) return false;
|
|
21096
|
+
if (isNodeOfType(node, "Identifier")) return node.name === stateName;
|
|
21097
|
+
if (isNodeOfType(node, "MemberExpression")) {
|
|
21098
|
+
if (expressionReadsStateValue(node.object, stateName)) return true;
|
|
21099
|
+
return node.computed ? expressionReadsStateValue(node.property, stateName) : false;
|
|
21100
|
+
}
|
|
21101
|
+
if (isNodeOfType(node, "Property")) {
|
|
21102
|
+
if (node.computed && expressionReadsStateValue(node.key, stateName)) return true;
|
|
21103
|
+
return expressionReadsStateValue(node.value, stateName);
|
|
21104
|
+
}
|
|
21105
|
+
const nodeRecord = node;
|
|
21106
|
+
for (const childKey of Object.keys(nodeRecord)) {
|
|
21107
|
+
if (childKey === "parent" || childKey === "type") continue;
|
|
21108
|
+
const childValue = nodeRecord[childKey];
|
|
21109
|
+
if (Array.isArray(childValue)) {
|
|
21110
|
+
for (const childArrayItem of childValue) if (isAstNode(childArrayItem) && expressionReadsStateValue(childArrayItem, stateName)) return true;
|
|
21111
|
+
} else if (isAstNode(childValue) && expressionReadsStateValue(childValue, stateName)) return true;
|
|
21112
|
+
}
|
|
21113
|
+
return false;
|
|
21114
|
+
};
|
|
21115
|
+
const isNonSettlingSetterArgument = (setterCall, stateName) => {
|
|
21116
|
+
const firstArgument = setterCall.arguments?.[0];
|
|
21117
|
+
if (!firstArgument) return false;
|
|
21118
|
+
const argument = stripParenExpression(firstArgument);
|
|
21119
|
+
if (isNodeOfType(argument, "Identifier") && argument.name === stateName) return false;
|
|
21120
|
+
if (isNodeOfType(argument, "ArrowFunctionExpression") || isNodeOfType(argument, "FunctionExpression")) return true;
|
|
21121
|
+
if (doesConstructFreshReference(argument)) return true;
|
|
21122
|
+
return expressionReadsStateValue(argument, stateName);
|
|
21123
|
+
};
|
|
21124
|
+
const getUnconditionalSetterCall = (statement, setterNames) => {
|
|
21125
|
+
const expression = stripParenExpression(isNodeOfType(statement, "ExpressionStatement") ? statement.expression : statement);
|
|
21126
|
+
if (!isNodeOfType(expression, "CallExpression")) return null;
|
|
21127
|
+
if (!isNodeOfType(expression.callee, "Identifier")) return null;
|
|
21128
|
+
if (!setterNames.has(expression.callee.name)) return null;
|
|
21129
|
+
return expression;
|
|
21130
|
+
};
|
|
21131
|
+
const collectDependencyStateNames = (depsNode) => {
|
|
21132
|
+
const dependencyNames = /* @__PURE__ */ new Set();
|
|
21133
|
+
if (!isNodeOfType(depsNode, "ArrayExpression")) return dependencyNames;
|
|
21134
|
+
for (const element of depsNode.elements ?? []) if (isNodeOfType(element, "Identifier")) dependencyNames.add(element.name);
|
|
21135
|
+
return dependencyNames;
|
|
21136
|
+
};
|
|
21137
|
+
const isEarlyReturnGuard = (statement) => {
|
|
21138
|
+
if (!isNodeOfType(statement, "IfStatement")) return false;
|
|
21139
|
+
const consequent = statement.consequent;
|
|
21140
|
+
if (isNodeOfType(consequent, "ReturnStatement")) return true;
|
|
21141
|
+
if (isNodeOfType(consequent, "BlockStatement")) return (consequent.body ?? []).some((inner) => isNodeOfType(inner, "ReturnStatement"));
|
|
21142
|
+
return false;
|
|
21143
|
+
};
|
|
21144
|
+
const numericLiteralValue = (node) => {
|
|
21145
|
+
if (isNodeOfType(node, "Literal") && typeof node.value === "number") return node.value;
|
|
21146
|
+
if (isNodeOfType(node, "UnaryExpression") && node.operator === "-" && isNodeOfType(node.argument, "Literal") && typeof node.argument.value === "number") return -node.argument.value;
|
|
21147
|
+
return null;
|
|
21148
|
+
};
|
|
21149
|
+
const isStateLength = (node, stateName) => {
|
|
21150
|
+
const member = isNodeOfType(node, "ChainExpression") ? node.expression : node;
|
|
21151
|
+
return isNodeOfType(member, "MemberExpression") && !member.computed && isNodeOfType(member.property, "Identifier") && member.property.name === "length" && expressionReadsStateValue(member.object, stateName);
|
|
21152
|
+
};
|
|
21153
|
+
const isNullishLiteral = (node) => isNodeOfType(node, "Literal") && node.value === null || isNodeOfType(node, "Identifier") && node.name === "undefined";
|
|
21154
|
+
const numericComparisonHolds = (operator, left, right) => {
|
|
21155
|
+
switch (operator) {
|
|
21156
|
+
case "<": return left < right;
|
|
21157
|
+
case "<=": return left <= right;
|
|
21158
|
+
case ">": return left > right;
|
|
21159
|
+
case ">=": return left >= right;
|
|
21160
|
+
case "===":
|
|
21161
|
+
case "==": return left === right;
|
|
21162
|
+
case "!==":
|
|
21163
|
+
case "!=": return left !== right;
|
|
21164
|
+
default: return false;
|
|
21165
|
+
}
|
|
21166
|
+
};
|
|
21167
|
+
const guardExitsWhenStateEmpty = (test, stateName) => {
|
|
21168
|
+
const node = isNodeOfType(test, "ChainExpression") ? test.expression : test;
|
|
21169
|
+
if (isNodeOfType(node, "UnaryExpression") && node.operator === "!") return isStateLength(node.argument, stateName);
|
|
21170
|
+
if (isNodeOfType(node, "LogicalExpression")) {
|
|
21171
|
+
if (node.operator === "||") return guardExitsWhenStateEmpty(node.left, stateName) || guardExitsWhenStateEmpty(node.right, stateName);
|
|
21172
|
+
if (node.operator === "&&") return guardExitsWhenStateEmpty(node.left, stateName) && guardExitsWhenStateEmpty(node.right, stateName);
|
|
21173
|
+
return false;
|
|
21174
|
+
}
|
|
21175
|
+
if (isNodeOfType(node, "BinaryExpression")) {
|
|
21176
|
+
const leftIsLength = isStateLength(node.left, stateName);
|
|
21177
|
+
const rightIsLength = isStateLength(node.right, stateName);
|
|
21178
|
+
if (leftIsLength || rightIsLength) {
|
|
21179
|
+
const other = numericLiteralValue(leftIsLength ? node.right : node.left);
|
|
21180
|
+
if (other === null) return false;
|
|
21181
|
+
return leftIsLength ? numericComparisonHolds(node.operator, 0, other) : numericComparisonHolds(node.operator, other, 0);
|
|
21182
|
+
}
|
|
21183
|
+
if (node.operator === "==" || node.operator === "===") {
|
|
21184
|
+
if (expressionReadsStateValue(node.left, stateName) && isNullishLiteral(node.right)) return true;
|
|
21185
|
+
if (expressionReadsStateValue(node.right, stateName) && isNullishLiteral(node.left)) return true;
|
|
21186
|
+
}
|
|
21187
|
+
return false;
|
|
21188
|
+
}
|
|
21189
|
+
return false;
|
|
21190
|
+
};
|
|
21191
|
+
const isEmptyOrFalsyValue = (node) => {
|
|
21192
|
+
if (isNodeOfType(node, "ArrayExpression")) return (node.elements ?? []).length === 0;
|
|
21193
|
+
if (isNodeOfType(node, "ObjectExpression")) return (node.properties ?? []).length === 0;
|
|
21194
|
+
if (isNodeOfType(node, "Literal")) return node.value === null || node.value === "" || node.value === 0 || node.value === false;
|
|
21195
|
+
if (isNodeOfType(node, "Identifier")) return node.name === "undefined";
|
|
21196
|
+
return false;
|
|
21197
|
+
};
|
|
21198
|
+
const functionReturnExpression = (fn) => {
|
|
21199
|
+
if (!isNodeOfType(fn, "ArrowFunctionExpression") && !isNodeOfType(fn, "FunctionExpression")) return null;
|
|
21200
|
+
if (!isNodeOfType(fn.body, "BlockStatement")) return fn.body ? stripParenExpression(fn.body) : null;
|
|
21201
|
+
for (const statement of fn.body.body ?? []) if (isNodeOfType(statement, "ReturnStatement") && statement.argument) return stripParenExpression(statement.argument);
|
|
21202
|
+
return null;
|
|
21203
|
+
};
|
|
21204
|
+
const isLengthReducingUpdater = (node) => {
|
|
21205
|
+
if (!isNodeOfType(node, "ArrowFunctionExpression") && !isNodeOfType(node, "FunctionExpression")) return false;
|
|
21206
|
+
const firstParameter = node.params?.[0];
|
|
21207
|
+
if (!firstParameter || !isNodeOfType(firstParameter, "Identifier")) return false;
|
|
21208
|
+
const returned = functionReturnExpression(node);
|
|
21209
|
+
if (!returned || !isNodeOfType(returned, "CallExpression")) return false;
|
|
21210
|
+
const callee = returned.callee;
|
|
21211
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed) return false;
|
|
21212
|
+
if (!isNodeOfType(callee.object, "Identifier") || callee.object.name !== firstParameter.name) return false;
|
|
21213
|
+
if (!isNodeOfType(callee.property, "Identifier") || callee.property.name !== "slice") return false;
|
|
21214
|
+
const sliceStart = numericLiteralValue(returned.arguments?.[0]);
|
|
21215
|
+
return sliceStart !== null && sliceStart >= 1;
|
|
21216
|
+
};
|
|
21217
|
+
const writeProvablyConverges = (setterArgument, stateName, earlyReturnGuardTests) => {
|
|
21218
|
+
if (!isEmptyOrFalsyValue(setterArgument) && !isLengthReducingUpdater(setterArgument)) return false;
|
|
21219
|
+
return earlyReturnGuardTests.some((test) => guardExitsWhenStateEmpty(test, stateName));
|
|
21220
|
+
};
|
|
21221
|
+
const SYMBOLIC_DEPTH_LIMIT = 16;
|
|
21222
|
+
const unwrapChain = (node) => {
|
|
21223
|
+
let current = node;
|
|
21224
|
+
for (;;) {
|
|
21225
|
+
const withoutParens = stripParenExpression(current);
|
|
21226
|
+
if (withoutParens !== current) {
|
|
21227
|
+
current = withoutParens;
|
|
21228
|
+
continue;
|
|
21229
|
+
}
|
|
21230
|
+
if (isNodeOfType(current, "ChainExpression")) {
|
|
21231
|
+
current = current.expression;
|
|
21232
|
+
continue;
|
|
21233
|
+
}
|
|
21234
|
+
return current;
|
|
21235
|
+
}
|
|
21236
|
+
};
|
|
21237
|
+
const isUndefinedValue = (node) => isNodeOfType(node, "Identifier") && node.name === "undefined" || isNodeOfType(node, "Literal") && node.value === null;
|
|
21238
|
+
const literalsEqual = (a, b) => isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal") && a.value === b.value;
|
|
21239
|
+
const resolveValueNode = (node, writes, depth, seen) => {
|
|
21240
|
+
if (depth > SYMBOLIC_DEPTH_LIMIT) return null;
|
|
21241
|
+
const current = unwrapChain(node);
|
|
21242
|
+
if (isNodeOfType(current, "Identifier")) {
|
|
21243
|
+
if (seen.has(current.name)) return null;
|
|
21244
|
+
const written = writes.get(current.name);
|
|
21245
|
+
if (written) return resolveValueNode(written, writes, depth + 1, new Set(seen).add(current.name));
|
|
21246
|
+
return current;
|
|
21247
|
+
}
|
|
21248
|
+
if (isNodeOfType(current, "Literal") || isNodeOfType(current, "ArrayExpression") || isNodeOfType(current, "ObjectExpression")) return current;
|
|
21249
|
+
if (isNodeOfType(current, "MemberExpression") && !current.computed && isNodeOfType(current.property, "Identifier")) {
|
|
21250
|
+
const objectValue = resolveValueNode(current.object, writes, depth + 1, seen);
|
|
21251
|
+
if (objectValue && isNodeOfType(objectValue, "ObjectExpression")) {
|
|
21252
|
+
const propertyKey = current.property.name;
|
|
21253
|
+
const properties = objectValue.properties ?? [];
|
|
21254
|
+
for (let index = properties.length - 1; index >= 0; index--) {
|
|
21255
|
+
const property = properties[index];
|
|
21256
|
+
if (isNodeOfType(property, "SpreadElement")) return null;
|
|
21257
|
+
if (isNodeOfType(property, "Property") && !property.computed && (isNodeOfType(property.key, "Identifier") && property.key.name === propertyKey || isNodeOfType(property.key, "Literal") && property.key.value === propertyKey)) return resolveValueNode(property.value, writes, depth + 1, seen);
|
|
21258
|
+
}
|
|
21259
|
+
}
|
|
21260
|
+
return null;
|
|
21261
|
+
}
|
|
21262
|
+
return null;
|
|
21263
|
+
};
|
|
21264
|
+
const resolveToNumber = (node, writes, depth, seen) => {
|
|
21265
|
+
const value = resolveValueNode(node, writes, depth, seen);
|
|
21266
|
+
if (value && isNodeOfType(value, "Literal") && typeof value.value === "number") return value.value;
|
|
21267
|
+
const current = unwrapChain(node);
|
|
21268
|
+
if (isNodeOfType(current, "MemberExpression") && !current.computed && isNodeOfType(current.property, "Identifier") && current.property.name === "length") {
|
|
21269
|
+
const objectValue = resolveValueNode(current.object, writes, depth, seen);
|
|
21270
|
+
if (objectValue && isNodeOfType(objectValue, "ArrayExpression")) {
|
|
21271
|
+
const elements = objectValue.elements ?? [];
|
|
21272
|
+
if (!elements.some((element) => element && isNodeOfType(element, "SpreadElement"))) return elements.length;
|
|
21273
|
+
}
|
|
21274
|
+
}
|
|
21275
|
+
return null;
|
|
21276
|
+
};
|
|
21277
|
+
const provablyEqualAfterWrites = (left, right, writes, depth, seen) => {
|
|
21278
|
+
const leftNumber = resolveToNumber(left, writes, depth, seen);
|
|
21279
|
+
const rightNumber = resolveToNumber(right, writes, depth, seen);
|
|
21280
|
+
if (leftNumber !== null && rightNumber !== null) return leftNumber === rightNumber;
|
|
21281
|
+
const a = resolveValueNode(left, writes, depth, seen);
|
|
21282
|
+
const b = resolveValueNode(right, writes, depth, seen);
|
|
21283
|
+
if (!a || !b) return false;
|
|
21284
|
+
if (literalsEqual(a, b)) return true;
|
|
21285
|
+
if (isUndefinedValue(a) && isUndefinedValue(b)) return true;
|
|
21286
|
+
return isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier") && a.name === b.name;
|
|
21287
|
+
};
|
|
21288
|
+
const provablyFalsyAfterWrites = (node, writes, depth, seen) => {
|
|
21289
|
+
const value = resolveValueNode(node, writes, depth, seen);
|
|
21290
|
+
if (value) {
|
|
21291
|
+
if (isUndefinedValue(value)) return true;
|
|
21292
|
+
if (isNodeOfType(value, "Literal")) return value.value === null || value.value === 0 || value.value === false || value.value === "";
|
|
21293
|
+
}
|
|
21294
|
+
return resolveToNumber(node, writes, depth, seen) === 0;
|
|
21295
|
+
};
|
|
21296
|
+
const guardProvenAfterWrites = (test, writes, depth, seen) => {
|
|
21297
|
+
if (depth > SYMBOLIC_DEPTH_LIMIT) return false;
|
|
21298
|
+
const node = unwrapChain(test);
|
|
21299
|
+
if (isNodeOfType(node, "LogicalExpression")) {
|
|
21300
|
+
if (node.operator === "&&") return guardProvenAfterWrites(node.left, writes, depth + 1, seen) && guardProvenAfterWrites(node.right, writes, depth + 1, seen);
|
|
21301
|
+
if (node.operator === "||") return guardProvenAfterWrites(node.left, writes, depth + 1, seen) || guardProvenAfterWrites(node.right, writes, depth + 1, seen);
|
|
21302
|
+
return false;
|
|
21303
|
+
}
|
|
21304
|
+
if (isNodeOfType(node, "UnaryExpression") && node.operator === "!") return provablyFalsyAfterWrites(node.argument, writes, depth + 1, seen);
|
|
21305
|
+
if (isNodeOfType(node, "BinaryExpression")) {
|
|
21306
|
+
if (node.operator === "===" || node.operator === "==") return provablyEqualAfterWrites(node.left, node.right, writes, depth + 1, seen);
|
|
21307
|
+
const leftNumber = resolveToNumber(node.left, writes, depth + 1, seen);
|
|
21308
|
+
const rightNumber = resolveToNumber(node.right, writes, depth + 1, seen);
|
|
21309
|
+
if (leftNumber !== null && rightNumber !== null) return numericComparisonHolds(node.operator, leftNumber, rightNumber);
|
|
21310
|
+
return false;
|
|
21311
|
+
}
|
|
21312
|
+
const value = resolveValueNode(node, writes, depth + 1, seen);
|
|
21313
|
+
if (value) {
|
|
21314
|
+
if (isNodeOfType(value, "ArrayExpression") || isNodeOfType(value, "ObjectExpression")) return true;
|
|
21315
|
+
if (isNodeOfType(value, "Literal")) return Boolean(value.value);
|
|
21316
|
+
}
|
|
21317
|
+
return false;
|
|
21318
|
+
};
|
|
21319
|
+
const collectTopLevelWrites = (statements, setterNameToStateName, setterNames) => {
|
|
21320
|
+
const writes = /* @__PURE__ */ new Map();
|
|
21321
|
+
const setterCallNodes = /* @__PURE__ */ new Set();
|
|
21322
|
+
for (const statement of statements) {
|
|
21323
|
+
const setterCall = getUnconditionalSetterCall(statement, setterNames);
|
|
21324
|
+
if (!setterCall || !isNodeOfType(setterCall.callee, "Identifier")) continue;
|
|
21325
|
+
setterCallNodes.add(setterCall);
|
|
21326
|
+
const stateName = setterNameToStateName.get(setterCall.callee.name);
|
|
21327
|
+
if (!stateName) continue;
|
|
21328
|
+
const argument = setterCall.arguments?.[0];
|
|
21329
|
+
if (!argument) continue;
|
|
21330
|
+
const newValue = isNodeOfType(argument, "ArrowFunctionExpression") || isNodeOfType(argument, "FunctionExpression") ? functionReturnExpression(argument) : stripParenExpression(argument);
|
|
21331
|
+
if (newValue) writes.set(stateName, newValue);
|
|
21332
|
+
}
|
|
21333
|
+
return {
|
|
21334
|
+
writes,
|
|
21335
|
+
setterCallNodes
|
|
21336
|
+
};
|
|
21337
|
+
};
|
|
21338
|
+
const everySetterCall = (root, setterName, inspect) => {
|
|
21339
|
+
let ok = true;
|
|
21340
|
+
const visit = (node) => {
|
|
21341
|
+
if (!ok) return;
|
|
21342
|
+
if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === setterName && !inspect(node)) {
|
|
21343
|
+
ok = false;
|
|
21344
|
+
return;
|
|
21345
|
+
}
|
|
21346
|
+
const record = node;
|
|
21347
|
+
for (const key of Object.keys(record)) {
|
|
21348
|
+
if (key === "parent" || key === "type") continue;
|
|
21349
|
+
const child = record[key];
|
|
21350
|
+
if (Array.isArray(child)) {
|
|
21351
|
+
for (const item of child) if (isAstNode(item)) visit(item);
|
|
21352
|
+
} else if (isAstNode(child)) visit(child);
|
|
21353
|
+
if (!ok) return;
|
|
21354
|
+
}
|
|
21355
|
+
};
|
|
21356
|
+
visit(root);
|
|
21357
|
+
return ok;
|
|
21358
|
+
};
|
|
21359
|
+
const everyWriteToStateDrivesTowardEmpty = (callbackBody, setterName) => everySetterCall(callbackBody, setterName, (call) => {
|
|
21360
|
+
const argument = call.arguments?.[0];
|
|
21361
|
+
if (!argument) return true;
|
|
21362
|
+
const value = stripParenExpression(argument);
|
|
21363
|
+
return isEmptyOrFalsyValue(value) || isLengthReducingUpdater(value);
|
|
21364
|
+
});
|
|
21365
|
+
const everySetterCallIsTopLevel = (callbackBody, setterNames, topLevelSetterCalls) => {
|
|
21366
|
+
let safe = true;
|
|
21367
|
+
const visit = (node) => {
|
|
21368
|
+
if (!safe) return;
|
|
21369
|
+
if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && setterNames.has(node.callee.name) && !topLevelSetterCalls.has(node)) {
|
|
21370
|
+
safe = false;
|
|
21371
|
+
return;
|
|
21372
|
+
}
|
|
21373
|
+
const record = node;
|
|
21374
|
+
for (const key of Object.keys(record)) {
|
|
21375
|
+
if (key === "parent" || key === "type") continue;
|
|
21376
|
+
const child = record[key];
|
|
21377
|
+
if (Array.isArray(child)) {
|
|
21378
|
+
for (const item of child) if (isAstNode(item)) visit(item);
|
|
21379
|
+
} else if (isAstNode(child)) visit(child);
|
|
21380
|
+
if (!safe) return;
|
|
21381
|
+
}
|
|
21382
|
+
};
|
|
21383
|
+
visit(callbackBody);
|
|
21384
|
+
return safe;
|
|
21385
|
+
};
|
|
21386
|
+
const noSelfUpdatingEffect = defineRule({
|
|
21387
|
+
id: "no-self-updating-effect",
|
|
21388
|
+
severity: "warn",
|
|
21389
|
+
tags: ["test-noise"],
|
|
21390
|
+
recommendation: "Remove the feedback loop: derive the value during render, move the write into an event handler, or guard the update so it reaches a fixed point. See https://react.dev/learn/you-might-not-need-an-effect",
|
|
21391
|
+
create: (context) => {
|
|
21392
|
+
const checkFunctionScope = (functionBody) => {
|
|
21393
|
+
if (!functionBody || !isNodeOfType(functionBody, "BlockStatement")) return;
|
|
21394
|
+
const useStateBindings = collectUseStateBindings(functionBody);
|
|
21395
|
+
if (useStateBindings.length === 0) return;
|
|
21396
|
+
const setterNameToStateName = /* @__PURE__ */ new Map();
|
|
21397
|
+
for (const binding of useStateBindings) setterNameToStateName.set(binding.setterName, binding.valueName);
|
|
21398
|
+
const setterNames = new Set(setterNameToStateName.keys());
|
|
21399
|
+
for (const statement of functionBody.body ?? []) {
|
|
21400
|
+
if (!isNodeOfType(statement, "ExpressionStatement")) continue;
|
|
21401
|
+
const effectCall = statement.expression;
|
|
21402
|
+
if (!isNodeOfType(effectCall, "CallExpression")) continue;
|
|
21403
|
+
if (!isHookCall$1(effectCall, EFFECT_HOOK_NAMES$1)) continue;
|
|
21404
|
+
if ((effectCall.arguments?.length ?? 0) < 2) continue;
|
|
21405
|
+
const dependencyStateNames = collectDependencyStateNames(effectCall.arguments[1]);
|
|
21406
|
+
if (dependencyStateNames.size === 0) continue;
|
|
21407
|
+
const callback = getEffectCallback(effectCall);
|
|
21408
|
+
if (!callback) continue;
|
|
21409
|
+
const callbackStatements = getCallbackStatements(callback);
|
|
21410
|
+
const firstWriteIndex = callbackStatements.findIndex((candidate) => getUnconditionalSetterCall(candidate, setterNames) !== null);
|
|
21411
|
+
const guardCutoff = firstWriteIndex < 0 ? callbackStatements.length : firstWriteIndex;
|
|
21412
|
+
const earlyReturnGuardTests = callbackStatements.slice(0, guardCutoff).filter(isEarlyReturnGuard).map((guard) => guard.test);
|
|
21413
|
+
const { writes: topLevelWrites, setterCallNodes } = collectTopLevelWrites(callbackStatements, setterNameToStateName, setterNames);
|
|
21414
|
+
if (everySetterCallIsTopLevel(callback, setterNames, setterCallNodes) && earlyReturnGuardTests.some((test) => guardProvenAfterWrites(test, topLevelWrites, 0, /* @__PURE__ */ new Set()))) continue;
|
|
21415
|
+
const reportedStateNames = /* @__PURE__ */ new Set();
|
|
21416
|
+
for (const callbackStatement of callbackStatements) {
|
|
21417
|
+
const setterCall = getUnconditionalSetterCall(callbackStatement, setterNames);
|
|
21418
|
+
if (!setterCall || !isNodeOfType(setterCall.callee, "Identifier")) continue;
|
|
21419
|
+
const stateName = setterNameToStateName.get(setterCall.callee.name);
|
|
21420
|
+
if (!stateName || !dependencyStateNames.has(stateName)) continue;
|
|
21421
|
+
if (reportedStateNames.has(stateName)) continue;
|
|
21422
|
+
if (!isNonSettlingSetterArgument(setterCall, stateName)) continue;
|
|
21423
|
+
const firstArgument = setterCall.arguments?.[0];
|
|
21424
|
+
if (firstArgument && writeProvablyConverges(stripParenExpression(firstArgument), stateName, earlyReturnGuardTests) && everyWriteToStateDrivesTowardEmpty(callback, setterCall.callee.name)) continue;
|
|
21425
|
+
reportedStateNames.add(stateName);
|
|
21426
|
+
context.report({
|
|
21427
|
+
node: setterCall,
|
|
21428
|
+
message: `${setterCall.callee.name}() runs unconditionally inside this effect, which depends on \`${stateName}\` — setting the same state the effect reacts to re-runs the effect on every commit and causes a render loop. Derive the value during render, move the write into an event handler, or guard the update so it settles.`
|
|
21429
|
+
});
|
|
21430
|
+
}
|
|
21431
|
+
}
|
|
21432
|
+
};
|
|
21433
|
+
return {
|
|
21434
|
+
FunctionDeclaration(node) {
|
|
21435
|
+
const functionName = node.id?.name;
|
|
21436
|
+
if (!functionName || !isUppercaseName(functionName) && !isReactHookName(functionName)) return;
|
|
21437
|
+
checkFunctionScope(node.body);
|
|
21438
|
+
},
|
|
21439
|
+
VariableDeclarator(node) {
|
|
21440
|
+
const isHookAssignment = isNodeOfType(node.id, "Identifier") && isReactHookName(node.id.name) && (isNodeOfType(node.init, "ArrowFunctionExpression") || isNodeOfType(node.init, "FunctionExpression"));
|
|
21441
|
+
if (!isComponentAssignment(node) && !isHookAssignment) return;
|
|
21442
|
+
if (!isNodeOfType(node.init, "ArrowFunctionExpression") && !isNodeOfType(node.init, "FunctionExpression")) return;
|
|
21443
|
+
checkFunctionScope(node.init.body);
|
|
21444
|
+
}
|
|
21445
|
+
};
|
|
21446
|
+
}
|
|
21447
|
+
});
|
|
21448
|
+
//#endregion
|
|
21092
21449
|
//#region src/plugin/utils/get-parent-component.ts
|
|
21093
21450
|
const getParentComponent = (node) => {
|
|
21094
21451
|
let ancestor = node.parent;
|
|
@@ -26311,6 +26668,7 @@ const REACT_NATIVE_TEXT_COMPONENT_KEYWORDS = new Set([
|
|
|
26311
26668
|
"Description",
|
|
26312
26669
|
"Body"
|
|
26313
26670
|
]);
|
|
26671
|
+
const REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS = new Set(["fbt", "fbs"]);
|
|
26314
26672
|
const DEPRECATED_RN_MODULE_REPLACEMENTS = new Map([
|
|
26315
26673
|
["AsyncStorage", "@react-native-async-storage/async-storage"],
|
|
26316
26674
|
["Picker", "@react-native-picker/picker"],
|
|
@@ -27143,10 +27501,29 @@ const getRawTextDescription = (child) => {
|
|
|
27143
27501
|
}
|
|
27144
27502
|
return "text content";
|
|
27145
27503
|
};
|
|
27504
|
+
const resolveTextBoundaryName = (openingElement) => {
|
|
27505
|
+
if (isNodeOfType(openingElement.name, "JSXNamespacedName")) return openingElement.name.namespace.name;
|
|
27506
|
+
return resolveJsxElementName(openingElement);
|
|
27507
|
+
};
|
|
27146
27508
|
const isTextHandlingComponent = (elementName) => {
|
|
27147
27509
|
if (REACT_NATIVE_TEXT_COMPONENTS.has(elementName)) return true;
|
|
27148
27510
|
return [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS].some((keyword) => elementName.includes(keyword));
|
|
27149
27511
|
};
|
|
27512
|
+
const isTransparentTextWrapper = (elementName) => elementName !== null && REACT_NATIVE_TEXT_TRANSPARENT_COMPONENTS.has(elementName);
|
|
27513
|
+
const isInsideTextHandlingComponent = (node) => {
|
|
27514
|
+
let parentNode = node.parent;
|
|
27515
|
+
while (parentNode) {
|
|
27516
|
+
if (!isNodeOfType(parentNode, "JSXElement")) {
|
|
27517
|
+
parentNode = parentNode.parent;
|
|
27518
|
+
continue;
|
|
27519
|
+
}
|
|
27520
|
+
const parentName = resolveTextBoundaryName(parentNode.openingElement);
|
|
27521
|
+
if (parentName && isTextHandlingComponent(parentName)) return true;
|
|
27522
|
+
if (!isTransparentTextWrapper(parentName)) return false;
|
|
27523
|
+
parentNode = parentNode.parent;
|
|
27524
|
+
}
|
|
27525
|
+
return false;
|
|
27526
|
+
};
|
|
27150
27527
|
const rnNoRawText = defineRule({
|
|
27151
27528
|
id: "rn-no-raw-text",
|
|
27152
27529
|
requires: ["react-native"],
|
|
@@ -27160,9 +27537,10 @@ const rnNoRawText = defineRule({
|
|
|
27160
27537
|
},
|
|
27161
27538
|
JSXElement(node) {
|
|
27162
27539
|
if (isDomComponentFile) return;
|
|
27163
|
-
const elementName =
|
|
27540
|
+
const elementName = resolveTextBoundaryName(node.openingElement);
|
|
27164
27541
|
if (elementName && isTextHandlingComponent(elementName)) return;
|
|
27165
27542
|
if (isInsidePlatformOsWebBranch(node)) return;
|
|
27543
|
+
if (isTransparentTextWrapper(elementName) && isInsideTextHandlingComponent(node)) return;
|
|
27166
27544
|
for (const child of node.children ?? []) {
|
|
27167
27545
|
if (!isRawTextContent(child)) continue;
|
|
27168
27546
|
context.report({
|
|
@@ -33001,6 +33379,410 @@ const voidDomElementsNoChildren = defineRule({
|
|
|
33001
33379
|
})
|
|
33002
33380
|
});
|
|
33003
33381
|
//#endregion
|
|
33382
|
+
//#region src/plugin/rules/zod/utils/zod-ast.ts
|
|
33383
|
+
const ZOD_MODULE = "zod";
|
|
33384
|
+
const getStaticPropertyName = (member) => {
|
|
33385
|
+
const property = member.property;
|
|
33386
|
+
if (!member.computed && isNodeOfType(property, "Identifier")) return property.name;
|
|
33387
|
+
if (member.computed && isNodeOfType(property, "Literal") && typeof property.value === "string") return property.value;
|
|
33388
|
+
return null;
|
|
33389
|
+
};
|
|
33390
|
+
const getImportInfoForIdentifier = (identifier) => {
|
|
33391
|
+
const specifier = findVariableInitializer(identifier, identifier.name)?.initializer;
|
|
33392
|
+
if (!specifier) return null;
|
|
33393
|
+
const declaration = specifier.parent;
|
|
33394
|
+
if (!declaration || !isNodeOfType(declaration, "ImportDeclaration")) return null;
|
|
33395
|
+
if (declaration.source?.value !== ZOD_MODULE) return null;
|
|
33396
|
+
if (isNodeOfType(specifier, "ImportNamespaceSpecifier")) return {
|
|
33397
|
+
imported: null,
|
|
33398
|
+
isDefault: false,
|
|
33399
|
+
isNamespace: true
|
|
33400
|
+
};
|
|
33401
|
+
if (isNodeOfType(specifier, "ImportDefaultSpecifier")) return {
|
|
33402
|
+
imported: null,
|
|
33403
|
+
isDefault: true,
|
|
33404
|
+
isNamespace: false
|
|
33405
|
+
};
|
|
33406
|
+
if (isNodeOfType(specifier, "ImportSpecifier")) {
|
|
33407
|
+
const imported = specifier.imported;
|
|
33408
|
+
if (isNodeOfType(imported, "Identifier")) return {
|
|
33409
|
+
imported: imported.name,
|
|
33410
|
+
isDefault: false,
|
|
33411
|
+
isNamespace: false
|
|
33412
|
+
};
|
|
33413
|
+
if (isNodeOfType(imported, "Literal") && typeof imported.value === "string") return {
|
|
33414
|
+
imported: imported.value,
|
|
33415
|
+
isDefault: false,
|
|
33416
|
+
isNamespace: false
|
|
33417
|
+
};
|
|
33418
|
+
}
|
|
33419
|
+
return null;
|
|
33420
|
+
};
|
|
33421
|
+
const isZodNamespaceIdentifier = (node) => {
|
|
33422
|
+
const inner = stripParenExpression(node);
|
|
33423
|
+
if (!isNodeOfType(inner, "Identifier")) return false;
|
|
33424
|
+
const info = getImportInfoForIdentifier(inner);
|
|
33425
|
+
return Boolean(info && (info.isNamespace || info.isDefault || info.imported === "z"));
|
|
33426
|
+
};
|
|
33427
|
+
const getZodNamedImport = (node) => {
|
|
33428
|
+
const inner = stripParenExpression(node);
|
|
33429
|
+
if (!isNodeOfType(inner, "Identifier")) return null;
|
|
33430
|
+
const info = getImportInfoForIdentifier(inner);
|
|
33431
|
+
if (!info || info.isNamespace || info.isDefault) return null;
|
|
33432
|
+
return info.imported;
|
|
33433
|
+
};
|
|
33434
|
+
const getZodNamespaceMemberName = (node) => {
|
|
33435
|
+
const inner = stripParenExpression(node);
|
|
33436
|
+
if (!isNodeOfType(inner, "MemberExpression")) return null;
|
|
33437
|
+
if (!isZodNamespaceIdentifier(inner.object)) return null;
|
|
33438
|
+
return getStaticPropertyName(inner);
|
|
33439
|
+
};
|
|
33440
|
+
const isZodFactoryCall = (callExpression, factoryNames) => {
|
|
33441
|
+
const factoryName = getZodFactoryCallName(callExpression);
|
|
33442
|
+
return factoryName !== null && factoryNames.has(factoryName);
|
|
33443
|
+
};
|
|
33444
|
+
const getZodFactoryCallName = (callExpression) => {
|
|
33445
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
33446
|
+
if (isNodeOfType(callee, "Identifier")) return getZodNamedImport(callee);
|
|
33447
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
33448
|
+
const memberName = getStaticPropertyName(callee);
|
|
33449
|
+
if (memberName === null) return null;
|
|
33450
|
+
if (!isZodNamespaceIdentifier(callee.object)) return null;
|
|
33451
|
+
return memberName;
|
|
33452
|
+
};
|
|
33453
|
+
const getMethodCall = (callExpression) => {
|
|
33454
|
+
const callee = stripParenExpression(callExpression.callee);
|
|
33455
|
+
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
33456
|
+
const methodName = getStaticPropertyName(callee);
|
|
33457
|
+
if (!methodName) return null;
|
|
33458
|
+
return {
|
|
33459
|
+
methodName,
|
|
33460
|
+
receiver: callee.object
|
|
33461
|
+
};
|
|
33462
|
+
};
|
|
33463
|
+
const isDirectMethodCallOnZodFactory = (callExpression, factoryNames, methodNames) => {
|
|
33464
|
+
const methodCall = getMethodCall(callExpression);
|
|
33465
|
+
if (!methodCall || !methodNames.has(methodCall.methodName)) return false;
|
|
33466
|
+
const receiver = stripParenExpression(methodCall.receiver);
|
|
33467
|
+
return isNodeOfType(receiver, "CallExpression") && isZodFactoryCall(receiver, factoryNames);
|
|
33468
|
+
};
|
|
33469
|
+
const isObjectExpressionWithAnyProperty = (node, propertyNames) => {
|
|
33470
|
+
if (!node) return false;
|
|
33471
|
+
const inner = stripParenExpression(node);
|
|
33472
|
+
if (!isNodeOfType(inner, "ObjectExpression")) return false;
|
|
33473
|
+
return inner.properties.some((property) => {
|
|
33474
|
+
if (!isNodeOfType(property, "Property")) return false;
|
|
33475
|
+
const key = property.key;
|
|
33476
|
+
if (isNodeOfType(key, "Identifier")) return propertyNames.has(key.name);
|
|
33477
|
+
return isNodeOfType(key, "Literal") && typeof key.value === "string" && propertyNames.has(key.value);
|
|
33478
|
+
});
|
|
33479
|
+
};
|
|
33480
|
+
//#endregion
|
|
33481
|
+
//#region src/plugin/rules/zod/zod-v4-no-deprecated-error-apis.ts
|
|
33482
|
+
const DEPRECATED_ZOD_ERROR_MEMBERS = new Set([
|
|
33483
|
+
"addIssue",
|
|
33484
|
+
"addIssues",
|
|
33485
|
+
"errors",
|
|
33486
|
+
"flatten",
|
|
33487
|
+
"formErrors",
|
|
33488
|
+
"format"
|
|
33489
|
+
]);
|
|
33490
|
+
const ZOD_ERROR_API_MESSAGE = "Zod 4 removes or deprecates this ZodError API; use `error.issues` or the new top-level error formatting helpers.";
|
|
33491
|
+
const isZodErrorReference = (node) => {
|
|
33492
|
+
const inner = stripParenExpression(node);
|
|
33493
|
+
if (isNodeOfType(inner, "Identifier")) return getZodNamedImport(inner) === "ZodError";
|
|
33494
|
+
if (!isNodeOfType(inner, "MemberExpression")) return false;
|
|
33495
|
+
return getZodNamespaceMemberName(inner) === "ZodError";
|
|
33496
|
+
};
|
|
33497
|
+
const isDirectZodErrorValue = (node) => {
|
|
33498
|
+
const inner = stripParenExpression(node);
|
|
33499
|
+
if (isNodeOfType(inner, "NewExpression")) return isZodErrorReference(inner.callee);
|
|
33500
|
+
if (!isNodeOfType(inner, "CallExpression")) return false;
|
|
33501
|
+
const methodCall = getMethodCall(inner);
|
|
33502
|
+
return methodCall?.methodName === "create" && isZodErrorReference(methodCall.receiver);
|
|
33503
|
+
};
|
|
33504
|
+
const isDeprecatedZodErrorMemberAccess = (node) => {
|
|
33505
|
+
if (!isNodeOfType(node, "MemberExpression")) return false;
|
|
33506
|
+
const memberExpression = node;
|
|
33507
|
+
const propertyName = getStaticPropertyName(memberExpression);
|
|
33508
|
+
return propertyName !== null && DEPRECATED_ZOD_ERROR_MEMBERS.has(propertyName) && isDirectZodErrorValue(memberExpression.object);
|
|
33509
|
+
};
|
|
33510
|
+
const isZodErrorCreateCall = (callExpression) => {
|
|
33511
|
+
const methodCall = getMethodCall(callExpression);
|
|
33512
|
+
return methodCall?.methodName === "create" && isZodErrorReference(methodCall.receiver);
|
|
33513
|
+
};
|
|
33514
|
+
const isReceiverOfDeprecatedZodErrorMember = (callExpression) => {
|
|
33515
|
+
const parent = callExpression.parent;
|
|
33516
|
+
if (!parent || !isNodeOfType(parent, "MemberExpression")) return false;
|
|
33517
|
+
if (stripParenExpression(parent.object) !== callExpression) return false;
|
|
33518
|
+
return isDeprecatedZodErrorMemberAccess(parent);
|
|
33519
|
+
};
|
|
33520
|
+
const zodV4NoDeprecatedErrorApis = defineRule({
|
|
33521
|
+
id: "zod-v4-no-deprecated-error-apis",
|
|
33522
|
+
requires: ["zod:4"],
|
|
33523
|
+
tags: ["migration-hint"],
|
|
33524
|
+
severity: "warn",
|
|
33525
|
+
recommendation: "Replace deprecated ZodError helpers with the Zod 4 functions: `z.treeifyError()`, `z.flattenError()`, `z.prettifyError()`, or direct `error.issues` access.",
|
|
33526
|
+
create: (context) => ({
|
|
33527
|
+
CallExpression(node) {
|
|
33528
|
+
if (isZodErrorCreateCall(node) && isReceiverOfDeprecatedZodErrorMember(node)) return;
|
|
33529
|
+
if (!isZodErrorCreateCall(node) && !isDeprecatedZodErrorMemberAccess(node.callee)) return;
|
|
33530
|
+
context.report({
|
|
33531
|
+
node,
|
|
33532
|
+
message: ZOD_ERROR_API_MESSAGE
|
|
33533
|
+
});
|
|
33534
|
+
},
|
|
33535
|
+
MemberExpression(node) {
|
|
33536
|
+
const parent = node.parent;
|
|
33537
|
+
if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
|
|
33538
|
+
if (!isDeprecatedZodErrorMemberAccess(node)) return;
|
|
33539
|
+
context.report({
|
|
33540
|
+
node,
|
|
33541
|
+
message: ZOD_ERROR_API_MESSAGE
|
|
33542
|
+
});
|
|
33543
|
+
}
|
|
33544
|
+
})
|
|
33545
|
+
});
|
|
33546
|
+
//#endregion
|
|
33547
|
+
//#region src/plugin/rules/zod/zod-v4-no-deprecated-error-customization.ts
|
|
33548
|
+
const ZOD_FACTORIES_WITH_ERROR_PARAMS = new Set([
|
|
33549
|
+
"any",
|
|
33550
|
+
"array",
|
|
33551
|
+
"bigint",
|
|
33552
|
+
"boolean",
|
|
33553
|
+
"date",
|
|
33554
|
+
"enum",
|
|
33555
|
+
"literal",
|
|
33556
|
+
"map",
|
|
33557
|
+
"nativeEnum",
|
|
33558
|
+
"never",
|
|
33559
|
+
"null",
|
|
33560
|
+
"number",
|
|
33561
|
+
"object",
|
|
33562
|
+
"record",
|
|
33563
|
+
"set",
|
|
33564
|
+
"string",
|
|
33565
|
+
"tuple",
|
|
33566
|
+
"undefined",
|
|
33567
|
+
"union",
|
|
33568
|
+
"unknown",
|
|
33569
|
+
"void"
|
|
33570
|
+
]);
|
|
33571
|
+
const DROPPED_ERROR_OPTION_PROPERTIES = new Set([
|
|
33572
|
+
"errorMap",
|
|
33573
|
+
"invalid_type_error",
|
|
33574
|
+
"required_error"
|
|
33575
|
+
]);
|
|
33576
|
+
const FACTORIES_WITH_LEGACY_FIRST_ARG_MESSAGE = new Set([
|
|
33577
|
+
"bigint",
|
|
33578
|
+
"boolean",
|
|
33579
|
+
"date",
|
|
33580
|
+
"number",
|
|
33581
|
+
"string"
|
|
33582
|
+
]);
|
|
33583
|
+
const ERROR_MAP_PROPERTY = new Set(["errorMap"]);
|
|
33584
|
+
const PARSE_METHODS = new Set([
|
|
33585
|
+
"parse",
|
|
33586
|
+
"safeParse",
|
|
33587
|
+
"parseAsync",
|
|
33588
|
+
"safeParseAsync"
|
|
33589
|
+
]);
|
|
33590
|
+
const firstArgumentIsMessageString = (callExpression) => {
|
|
33591
|
+
const firstArgument = callExpression.arguments[0];
|
|
33592
|
+
const inner = firstArgument ? stripParenExpression(firstArgument) : null;
|
|
33593
|
+
return Boolean(inner && isNodeOfType(inner, "Literal") && typeof inner.value === "string");
|
|
33594
|
+
};
|
|
33595
|
+
const factoryUsesDeprecatedErrorParameter = (callExpression) => {
|
|
33596
|
+
const factoryName = getZodFactoryCallName(callExpression);
|
|
33597
|
+
if (factoryName === null || !ZOD_FACTORIES_WITH_ERROR_PARAMS.has(factoryName)) return false;
|
|
33598
|
+
return FACTORIES_WITH_LEGACY_FIRST_ARG_MESSAGE.has(factoryName) && firstArgumentIsMessageString(callExpression) || callExpression.arguments.some((argument) => isObjectExpressionWithAnyProperty(argument, DROPPED_ERROR_OPTION_PROPERTIES));
|
|
33599
|
+
};
|
|
33600
|
+
const parseCallUsesErrorMap = (callExpression) => {
|
|
33601
|
+
const methodCall = getMethodCall(callExpression);
|
|
33602
|
+
if (!methodCall || !PARSE_METHODS.has(methodCall.methodName)) return false;
|
|
33603
|
+
const receiver = stripParenExpression(methodCall.receiver);
|
|
33604
|
+
if (!isNodeOfType(receiver, "CallExpression")) return false;
|
|
33605
|
+
if (!isZodFactoryCall(receiver, ZOD_FACTORIES_WITH_ERROR_PARAMS)) return false;
|
|
33606
|
+
return isObjectExpressionWithAnyProperty(callExpression.arguments[1], ERROR_MAP_PROPERTY);
|
|
33607
|
+
};
|
|
33608
|
+
const zodV4NoDeprecatedErrorCustomization = defineRule({
|
|
33609
|
+
id: "zod-v4-no-deprecated-error-customization",
|
|
33610
|
+
requires: ["zod:4"],
|
|
33611
|
+
tags: ["migration-hint"],
|
|
33612
|
+
severity: "warn",
|
|
33613
|
+
recommendation: "Use Zod 4's unified `{ error }` callback/object customization instead of string message parameters, `invalid_type_error`, `required_error`, or `errorMap`.",
|
|
33614
|
+
create: (context) => ({ CallExpression(node) {
|
|
33615
|
+
if (!factoryUsesDeprecatedErrorParameter(node) && !parseCallUsesErrorMap(node)) return;
|
|
33616
|
+
context.report({
|
|
33617
|
+
node,
|
|
33618
|
+
message: "Zod 4 replaces message parameters, `invalid_type_error`, `required_error`, and `errorMap` with the unified `error` API."
|
|
33619
|
+
});
|
|
33620
|
+
} })
|
|
33621
|
+
});
|
|
33622
|
+
//#endregion
|
|
33623
|
+
//#region src/plugin/rules/zod/zod-v4-no-deprecated-schema-apis.ts
|
|
33624
|
+
const OBJECT_FACTORY = new Set(["object"]);
|
|
33625
|
+
const OBJECT_METHODS = new Set([
|
|
33626
|
+
"deepPartial",
|
|
33627
|
+
"merge",
|
|
33628
|
+
"nonstrict",
|
|
33629
|
+
"passthrough",
|
|
33630
|
+
"strict",
|
|
33631
|
+
"strip"
|
|
33632
|
+
]);
|
|
33633
|
+
const NUMBER_FACTORY = new Set(["number"]);
|
|
33634
|
+
const NUMBER_METHODS = new Set(["safe"]);
|
|
33635
|
+
const FUNCTION_FACTORY = new Set(["function"]);
|
|
33636
|
+
const FUNCTION_CHAIN_METHODS = new Set(["args", "returns"]);
|
|
33637
|
+
const DEPRECATED_TOP_LEVEL_FACTORIES = new Set([
|
|
33638
|
+
"nativeEnum",
|
|
33639
|
+
"ostring",
|
|
33640
|
+
"onumber",
|
|
33641
|
+
"oboolean",
|
|
33642
|
+
"oarray",
|
|
33643
|
+
"promise"
|
|
33644
|
+
]);
|
|
33645
|
+
const FACTORIES_WITH_DROPPED_CREATE = new Set([
|
|
33646
|
+
"any",
|
|
33647
|
+
"array",
|
|
33648
|
+
"bigint",
|
|
33649
|
+
"boolean",
|
|
33650
|
+
"date",
|
|
33651
|
+
"enum",
|
|
33652
|
+
"function",
|
|
33653
|
+
"literal",
|
|
33654
|
+
"map",
|
|
33655
|
+
"nativeEnum",
|
|
33656
|
+
"never",
|
|
33657
|
+
"null",
|
|
33658
|
+
"number",
|
|
33659
|
+
"object",
|
|
33660
|
+
"optional",
|
|
33661
|
+
"promise",
|
|
33662
|
+
"record",
|
|
33663
|
+
"set",
|
|
33664
|
+
"string",
|
|
33665
|
+
"tuple",
|
|
33666
|
+
"undefined",
|
|
33667
|
+
"union",
|
|
33668
|
+
"unknown",
|
|
33669
|
+
"void"
|
|
33670
|
+
]);
|
|
33671
|
+
const ENUM_PROPERTY_ALIASES = new Set(["Enum", "Values"]);
|
|
33672
|
+
const ENUM_FACTORY = new Set(["enum"]);
|
|
33673
|
+
const RECORD_FACTORY = new Set(["record"]);
|
|
33674
|
+
const LITERAL_FACTORY = new Set(["literal"]);
|
|
33675
|
+
const reportSchemaMigration = (context, node) => {
|
|
33676
|
+
context.report({
|
|
33677
|
+
node,
|
|
33678
|
+
message: "This Zod API is deprecated or changed in Zod 4; migrate to the recommended Zod 4 schema API."
|
|
33679
|
+
});
|
|
33680
|
+
};
|
|
33681
|
+
const isCallToDeprecatedTopLevelFactory = (callExpression) => isZodFactoryCall(callExpression, DEPRECATED_TOP_LEVEL_FACTORIES);
|
|
33682
|
+
const isCallToDroppedCreateFactory = (callExpression) => {
|
|
33683
|
+
const methodCall = getMethodCall(callExpression);
|
|
33684
|
+
if (!methodCall || methodCall.methodName !== "create") return false;
|
|
33685
|
+
const receiver = stripParenExpression(methodCall.receiver);
|
|
33686
|
+
const namespaceMemberName = getZodNamespaceMemberName(receiver);
|
|
33687
|
+
if (namespaceMemberName !== null) return FACTORIES_WITH_DROPPED_CREATE.has(namespaceMemberName);
|
|
33688
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
33689
|
+
const imported = getZodNamedImport(receiver);
|
|
33690
|
+
return imported !== null && FACTORIES_WITH_DROPPED_CREATE.has(imported);
|
|
33691
|
+
};
|
|
33692
|
+
const isSingleArgumentRecordCall = (callExpression) => callExpression.arguments.length === 1 && isZodFactoryCall(callExpression, RECORD_FACTORY);
|
|
33693
|
+
const isDeprecatedFunctionChainCall = (callExpression) => isDirectMethodCallOnZodFactory(callExpression, FUNCTION_FACTORY, FUNCTION_CHAIN_METHODS);
|
|
33694
|
+
const isSymbolLiteralArgument = (node) => {
|
|
33695
|
+
if (!node) return false;
|
|
33696
|
+
const inner = stripParenExpression(node);
|
|
33697
|
+
if (isNodeOfType(inner, "CallExpression")) {
|
|
33698
|
+
const callee = stripParenExpression(inner.callee);
|
|
33699
|
+
return isNodeOfType(callee, "Identifier") && callee.name === "Symbol";
|
|
33700
|
+
}
|
|
33701
|
+
if (!isNodeOfType(inner, "MemberExpression")) return false;
|
|
33702
|
+
const object = stripParenExpression(inner.object);
|
|
33703
|
+
return isNodeOfType(object, "Identifier") && object.name === "Symbol";
|
|
33704
|
+
};
|
|
33705
|
+
const isLiteralSymbolCall = (callExpression) => callExpression.arguments.length > 0 && isZodFactoryCall(callExpression, LITERAL_FACTORY) && isSymbolLiteralArgument(callExpression.arguments[0]);
|
|
33706
|
+
const isDroppedEnumAliasAccess = (memberExpression) => {
|
|
33707
|
+
const propertyName = getStaticPropertyName(memberExpression);
|
|
33708
|
+
if (propertyName === null || !ENUM_PROPERTY_ALIASES.has(propertyName)) return false;
|
|
33709
|
+
const receiver = stripParenExpression(memberExpression.object);
|
|
33710
|
+
return isNodeOfType(receiver, "CallExpression") && isZodFactoryCall(receiver, ENUM_FACTORY);
|
|
33711
|
+
};
|
|
33712
|
+
const isRefineSecondArgumentFunction = (callExpression) => {
|
|
33713
|
+
const methodCall = getMethodCall(callExpression);
|
|
33714
|
+
if (!methodCall || methodCall.methodName !== "refine") return false;
|
|
33715
|
+
const receiver = stripParenExpression(methodCall.receiver);
|
|
33716
|
+
if (!isNodeOfType(receiver, "CallExpression")) return false;
|
|
33717
|
+
if (!isZodFactoryCall(receiver, FACTORIES_WITH_DROPPED_CREATE)) return false;
|
|
33718
|
+
const secondArgument = callExpression.arguments[1];
|
|
33719
|
+
return isNodeOfType(secondArgument, "FunctionExpression") || isNodeOfType(secondArgument, "ArrowFunctionExpression");
|
|
33720
|
+
};
|
|
33721
|
+
const isZodNamespaceImportMemberCreate = (memberExpression) => {
|
|
33722
|
+
if (getStaticPropertyName(memberExpression) !== "create") return false;
|
|
33723
|
+
const receiver = stripParenExpression(memberExpression.object);
|
|
33724
|
+
if (!isNodeOfType(receiver, "MemberExpression")) return false;
|
|
33725
|
+
const factoryName = getStaticPropertyName(receiver);
|
|
33726
|
+
return factoryName !== null && FACTORIES_WITH_DROPPED_CREATE.has(factoryName) && isZodNamespaceIdentifier(receiver.object);
|
|
33727
|
+
};
|
|
33728
|
+
const zodV4NoDeprecatedSchemaApis = defineRule({
|
|
33729
|
+
id: "zod-v4-no-deprecated-schema-apis",
|
|
33730
|
+
requires: ["zod:4"],
|
|
33731
|
+
tags: ["migration-hint"],
|
|
33732
|
+
severity: "warn",
|
|
33733
|
+
recommendation: "Migrate Zod 4 schema APIs that were deprecated, changed, or removed: use top-level factories such as `z.enum()`, object helpers such as `z.strictObject()`, the new `z.function({ input, output })` form, and explicit key/value schemas for `z.record()`.",
|
|
33734
|
+
create: (context) => ({
|
|
33735
|
+
CallExpression(node) {
|
|
33736
|
+
if (isCallToDeprecatedTopLevelFactory(node) || isCallToDroppedCreateFactory(node) || isSingleArgumentRecordCall(node) || isLiteralSymbolCall(node) || isDeprecatedFunctionChainCall(node) || isDirectMethodCallOnZodFactory(node, OBJECT_FACTORY, OBJECT_METHODS) || isDirectMethodCallOnZodFactory(node, NUMBER_FACTORY, NUMBER_METHODS) || isRefineSecondArgumentFunction(node)) reportSchemaMigration(context, node);
|
|
33737
|
+
},
|
|
33738
|
+
MemberExpression(node) {
|
|
33739
|
+
const parent = node.parent;
|
|
33740
|
+
if (parent && isNodeOfType(parent, "CallExpression") && stripParenExpression(parent.callee) === node) return;
|
|
33741
|
+
if (isDroppedEnumAliasAccess(node) || isZodNamespaceImportMemberCreate(node)) reportSchemaMigration(context, node);
|
|
33742
|
+
}
|
|
33743
|
+
})
|
|
33744
|
+
});
|
|
33745
|
+
//#endregion
|
|
33746
|
+
//#region src/plugin/rules/zod/zod-v4-prefer-top-level-string-formats.ts
|
|
33747
|
+
const ZOD_STRING_FACTORY = new Set(["string"]);
|
|
33748
|
+
const STRING_FORMAT_METHODS = new Set([
|
|
33749
|
+
"base64",
|
|
33750
|
+
"base64url",
|
|
33751
|
+
"cidr",
|
|
33752
|
+
"cidrv4",
|
|
33753
|
+
"cidrv6",
|
|
33754
|
+
"cuid",
|
|
33755
|
+
"cuid2",
|
|
33756
|
+
"date",
|
|
33757
|
+
"datetime",
|
|
33758
|
+
"duration",
|
|
33759
|
+
"email",
|
|
33760
|
+
"emoji",
|
|
33761
|
+
"ip",
|
|
33762
|
+
"ipv4",
|
|
33763
|
+
"ipv6",
|
|
33764
|
+
"jwt",
|
|
33765
|
+
"nanoid",
|
|
33766
|
+
"time",
|
|
33767
|
+
"ulid",
|
|
33768
|
+
"url",
|
|
33769
|
+
"uuid"
|
|
33770
|
+
]);
|
|
33771
|
+
const zodV4PreferTopLevelStringFormats = defineRule({
|
|
33772
|
+
id: "zod-v4-prefer-top-level-string-formats",
|
|
33773
|
+
requires: ["zod:4"],
|
|
33774
|
+
tags: ["migration-hint"],
|
|
33775
|
+
severity: "warn",
|
|
33776
|
+
recommendation: "Replace deprecated `z.string().<format>()` calls with Zod 4 top-level string format APIs like `z.email()`, `z.uuid()`, `z.ipv4()`, or `z.cidrv4()`.",
|
|
33777
|
+
create: (context) => ({ CallExpression(node) {
|
|
33778
|
+
if (!isDirectMethodCallOnZodFactory(node, ZOD_STRING_FACTORY, STRING_FORMAT_METHODS)) return;
|
|
33779
|
+
context.report({
|
|
33780
|
+
node,
|
|
33781
|
+
message: "Zod 4 deprecates string format methods on `z.string()`; use the matching top-level Zod format API instead."
|
|
33782
|
+
});
|
|
33783
|
+
} })
|
|
33784
|
+
});
|
|
33785
|
+
//#endregion
|
|
33004
33786
|
//#region src/plugin/rule-registry.ts
|
|
33005
33787
|
const reactDoctorRules = [
|
|
33006
33788
|
{
|
|
@@ -35115,6 +35897,17 @@ const reactDoctorRules = [
|
|
|
35115
35897
|
category: "Security"
|
|
35116
35898
|
}
|
|
35117
35899
|
},
|
|
35900
|
+
{
|
|
35901
|
+
key: "react-doctor/no-self-updating-effect",
|
|
35902
|
+
id: "no-self-updating-effect",
|
|
35903
|
+
source: "react-doctor",
|
|
35904
|
+
originallyExternal: false,
|
|
35905
|
+
rule: {
|
|
35906
|
+
...noSelfUpdatingEffect,
|
|
35907
|
+
framework: "global",
|
|
35908
|
+
category: "State & Effects"
|
|
35909
|
+
}
|
|
35910
|
+
},
|
|
35118
35911
|
{
|
|
35119
35912
|
key: "react-doctor/no-set-state",
|
|
35120
35913
|
id: "no-set-state",
|
|
@@ -36526,6 +37319,50 @@ const reactDoctorRules = [
|
|
|
36526
37319
|
framework: "global",
|
|
36527
37320
|
category: "Correctness"
|
|
36528
37321
|
}
|
|
37322
|
+
},
|
|
37323
|
+
{
|
|
37324
|
+
key: "react-doctor/zod-v4-no-deprecated-error-apis",
|
|
37325
|
+
id: "zod-v4-no-deprecated-error-apis",
|
|
37326
|
+
source: "react-doctor",
|
|
37327
|
+
originallyExternal: false,
|
|
37328
|
+
rule: {
|
|
37329
|
+
...zodV4NoDeprecatedErrorApis,
|
|
37330
|
+
framework: "global",
|
|
37331
|
+
category: "Architecture"
|
|
37332
|
+
}
|
|
37333
|
+
},
|
|
37334
|
+
{
|
|
37335
|
+
key: "react-doctor/zod-v4-no-deprecated-error-customization",
|
|
37336
|
+
id: "zod-v4-no-deprecated-error-customization",
|
|
37337
|
+
source: "react-doctor",
|
|
37338
|
+
originallyExternal: false,
|
|
37339
|
+
rule: {
|
|
37340
|
+
...zodV4NoDeprecatedErrorCustomization,
|
|
37341
|
+
framework: "global",
|
|
37342
|
+
category: "Architecture"
|
|
37343
|
+
}
|
|
37344
|
+
},
|
|
37345
|
+
{
|
|
37346
|
+
key: "react-doctor/zod-v4-no-deprecated-schema-apis",
|
|
37347
|
+
id: "zod-v4-no-deprecated-schema-apis",
|
|
37348
|
+
source: "react-doctor",
|
|
37349
|
+
originallyExternal: false,
|
|
37350
|
+
rule: {
|
|
37351
|
+
...zodV4NoDeprecatedSchemaApis,
|
|
37352
|
+
framework: "global",
|
|
37353
|
+
category: "Architecture"
|
|
37354
|
+
}
|
|
37355
|
+
},
|
|
37356
|
+
{
|
|
37357
|
+
key: "react-doctor/zod-v4-prefer-top-level-string-formats",
|
|
37358
|
+
id: "zod-v4-prefer-top-level-string-formats",
|
|
37359
|
+
source: "react-doctor",
|
|
37360
|
+
originallyExternal: false,
|
|
37361
|
+
rule: {
|
|
37362
|
+
...zodV4PreferTopLevelStringFormats,
|
|
37363
|
+
framework: "global",
|
|
37364
|
+
category: "Architecture"
|
|
37365
|
+
}
|
|
36529
37366
|
}
|
|
36530
37367
|
];
|
|
36531
37368
|
const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id, rule.rule]));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "oxlint-plugin-react-doctor",
|
|
3
|
-
"version": "0.2.11-dev.
|
|
3
|
+
"version": "0.2.11-dev.b2934f9",
|
|
4
4
|
"description": "oxlint plugin for React Doctor: diagnose React codebases for security, performance, correctness, accessibility, bundle-size, and architecture issues",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"accessibility",
|