oxlint-plugin-react-doctor 0.6.2-dev.173cc0a → 0.6.2-dev.59e8178
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.js +109 -351
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -588,7 +588,7 @@ const SETTER_PATTERN = /^set[A-Z]/;
|
|
|
588
588
|
const RENDER_FUNCTION_PATTERN = /^render[A-Z]/;
|
|
589
589
|
const UPPERCASE_PATTERN = /^[A-Z]/;
|
|
590
590
|
const REACT_HANDLER_PROP_PATTERN = /^on[A-Z]/;
|
|
591
|
-
const HOOK_NAME_PATTERN
|
|
591
|
+
const HOOK_NAME_PATTERN = /^use[A-Z]/;
|
|
592
592
|
const HANDLER_FUNCTION_NAME_PATTERN = /^(?:on|handle)[A-Z]/;
|
|
593
593
|
const EFFECT_HOOK_NAMES$1 = new Set(["useEffect", "useLayoutEffect"]);
|
|
594
594
|
const HOOKS_WITH_DEPS = new Set([
|
|
@@ -4928,8 +4928,7 @@ const isValidTypeValue = (rawValue, settings) => {
|
|
|
4928
4928
|
if (rawValue === "reset") return settings.reset;
|
|
4929
4929
|
return false;
|
|
4930
4930
|
};
|
|
4931
|
-
const isProvenValidExpression = (
|
|
4932
|
-
const expression = stripParenExpression(rawExpression);
|
|
4931
|
+
const isProvenValidExpression = (expression, settings, resolvedBindings = /* @__PURE__ */ new Set()) => {
|
|
4933
4932
|
if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return isValidTypeValue(expression.value, settings);
|
|
4934
4933
|
if (isNodeOfType(expression, "TemplateLiteral")) {
|
|
4935
4934
|
const staticValue = getStaticTemplateLiteralValue(expression);
|
|
@@ -5096,20 +5095,11 @@ const resolveSettings$46 = (settings) => {
|
|
|
5096
5095
|
ignoreExclusiveCheckedAttribute: ruleSettings.ignoreExclusiveCheckedAttribute ?? false
|
|
5097
5096
|
};
|
|
5098
5097
|
};
|
|
5099
|
-
const isTruthyDisabledJsxValue = (attribute) => {
|
|
5100
|
-
if (!attribute.value) return true;
|
|
5101
|
-
if (isNodeOfType(attribute.value, "JSXExpressionContainer")) {
|
|
5102
|
-
const expression = attribute.value.expression;
|
|
5103
|
-
return isNodeOfType(expression, "Literal") && expression.value === true;
|
|
5104
|
-
}
|
|
5105
|
-
return false;
|
|
5106
|
-
};
|
|
5107
5098
|
const collectFromJsxAttributes = (attributes) => {
|
|
5108
5099
|
let checkedNode = null;
|
|
5109
5100
|
let defaultCheckedNode = null;
|
|
5110
5101
|
let hasOnChangeOrReadOnly = false;
|
|
5111
5102
|
let hasSpread = false;
|
|
5112
|
-
let hasTruthyDisabled = false;
|
|
5113
5103
|
for (const attribute of attributes) {
|
|
5114
5104
|
if (isNodeOfType(attribute, "JSXSpreadAttribute")) {
|
|
5115
5105
|
hasSpread = true;
|
|
@@ -5120,14 +5110,12 @@ const collectFromJsxAttributes = (attributes) => {
|
|
|
5120
5110
|
if (name === "checked") checkedNode = attribute;
|
|
5121
5111
|
else if (name === "defaultChecked" && !defaultCheckedNode) defaultCheckedNode = attribute;
|
|
5122
5112
|
else if (name === "onChange" || name === "readOnly") hasOnChangeOrReadOnly = true;
|
|
5123
|
-
else if (name === "disabled" && isTruthyDisabledJsxValue(attribute)) hasTruthyDisabled = true;
|
|
5124
5113
|
}
|
|
5125
5114
|
return {
|
|
5126
5115
|
checkedNode,
|
|
5127
5116
|
defaultCheckedNode,
|
|
5128
5117
|
hasOnChangeOrReadOnly,
|
|
5129
|
-
hasSpread
|
|
5130
|
-
hasTruthyDisabled
|
|
5118
|
+
hasSpread
|
|
5131
5119
|
};
|
|
5132
5120
|
};
|
|
5133
5121
|
const collectFromObjectProperties = (objectExpression) => {
|
|
@@ -5135,7 +5123,6 @@ const collectFromObjectProperties = (objectExpression) => {
|
|
|
5135
5123
|
let defaultCheckedNode = null;
|
|
5136
5124
|
let hasOnChangeOrReadOnly = false;
|
|
5137
5125
|
let hasSpread = false;
|
|
5138
|
-
let hasTruthyDisabled = false;
|
|
5139
5126
|
for (const property of objectExpression.properties) {
|
|
5140
5127
|
if (isNodeOfType(property, "SpreadElement")) {
|
|
5141
5128
|
hasSpread = true;
|
|
@@ -5150,17 +5137,12 @@ const collectFromObjectProperties = (objectExpression) => {
|
|
|
5150
5137
|
if (propertyName === "checked") checkedNode = property;
|
|
5151
5138
|
else if (propertyName === "defaultChecked" && !defaultCheckedNode) defaultCheckedNode = property;
|
|
5152
5139
|
else if (propertyName === "onChange" || propertyName === "readOnly") hasOnChangeOrReadOnly = true;
|
|
5153
|
-
else if (propertyName === "disabled") {
|
|
5154
|
-
const propertyValue = property.value;
|
|
5155
|
-
if (isNodeOfType(propertyValue, "Literal") && propertyValue.value === true) hasTruthyDisabled = true;
|
|
5156
|
-
}
|
|
5157
5140
|
}
|
|
5158
5141
|
return {
|
|
5159
5142
|
checkedNode,
|
|
5160
5143
|
defaultCheckedNode,
|
|
5161
5144
|
hasOnChangeOrReadOnly,
|
|
5162
|
-
hasSpread
|
|
5163
|
-
hasTruthyDisabled
|
|
5145
|
+
hasSpread
|
|
5164
5146
|
};
|
|
5165
5147
|
};
|
|
5166
5148
|
const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
@@ -5176,7 +5158,7 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
|
|
|
5176
5158
|
node: presence.checkedNode,
|
|
5177
5159
|
message: EXCLUSIVE_MESSAGE
|
|
5178
5160
|
});
|
|
5179
|
-
if (presence.checkedNode && !presence.hasOnChangeOrReadOnly && !presence.hasSpread && !
|
|
5161
|
+
if (presence.checkedNode && !presence.hasOnChangeOrReadOnly && !presence.hasSpread && !settings.ignoreMissingProperties) context.report({
|
|
5180
5162
|
node: presence.checkedNode,
|
|
5181
5163
|
message: MISSING_MESSAGE$1
|
|
5182
5164
|
});
|
|
@@ -6450,7 +6432,7 @@ const displayName = defineRule({
|
|
|
6450
6432
|
},
|
|
6451
6433
|
ArrowFunctionExpression(node) {
|
|
6452
6434
|
if (!containsJsx$1(node)) return;
|
|
6453
|
-
if (isNodeOfType(node.parent, "ArrowFunctionExpression")
|
|
6435
|
+
if (isNodeOfType(node.parent, "ArrowFunctionExpression")) {
|
|
6454
6436
|
reportAt(node);
|
|
6455
6437
|
return;
|
|
6456
6438
|
}
|
|
@@ -7367,7 +7349,7 @@ const TRANSPARENT_WRAPPER_TYPES = new Set([
|
|
|
7367
7349
|
"ParenthesizedExpression",
|
|
7368
7350
|
"ChainExpression"
|
|
7369
7351
|
]);
|
|
7370
|
-
const unwrapExpression$
|
|
7352
|
+
const unwrapExpression$2 = (node) => {
|
|
7371
7353
|
let current = node;
|
|
7372
7354
|
while (TRANSPARENT_WRAPPER_TYPES.has(current.type)) {
|
|
7373
7355
|
const inner = current.expression;
|
|
@@ -7493,7 +7475,7 @@ const symbolHasStableHookOrigin = (symbol) => {
|
|
|
7493
7475
|
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
|
|
7494
7476
|
const initializerRaw = declarator.init;
|
|
7495
7477
|
if (!initializerRaw) return false;
|
|
7496
|
-
const initializer = unwrapExpression$
|
|
7478
|
+
const initializer = unwrapExpression$2(initializerRaw);
|
|
7497
7479
|
if (symbol.kind === "const") {
|
|
7498
7480
|
if (isNodeOfType(initializer, "Literal") && (initializer.value === null || typeof initializer.value === "number" || typeof initializer.value === "string" || typeof initializer.value === "boolean")) return true;
|
|
7499
7481
|
if (isNodeOfType(initializer, "TemplateLiteral") && getStaticTemplateLiteralValue(initializer) !== null) return true;
|
|
@@ -7513,13 +7495,13 @@ const symbolHasStableHookOrigin = (symbol) => {
|
|
|
7513
7495
|
return false;
|
|
7514
7496
|
};
|
|
7515
7497
|
const symbolHasUseEffectEventOrigin = (symbol) => {
|
|
7516
|
-
const initializer = symbol.initializer ? unwrapExpression$
|
|
7498
|
+
const initializer = symbol.initializer ? unwrapExpression$2(symbol.initializer) : null;
|
|
7517
7499
|
if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
|
|
7518
7500
|
return getHookName(initializer.callee) === "useEffectEvent";
|
|
7519
7501
|
};
|
|
7520
7502
|
const getFunctionValueNode = (symbol) => {
|
|
7521
7503
|
if (symbol.kind === "function" && isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return symbol.declarationNode;
|
|
7522
|
-
const initializer = symbol.initializer ? unwrapExpression$
|
|
7504
|
+
const initializer = symbol.initializer ? unwrapExpression$2(symbol.initializer) : null;
|
|
7523
7505
|
if (initializer && (isNodeOfType(initializer, "FunctionExpression") || isNodeOfType(initializer, "ArrowFunctionExpression"))) return initializer;
|
|
7524
7506
|
return null;
|
|
7525
7507
|
};
|
|
@@ -7615,14 +7597,6 @@ const flattenReferenceRootName = (reference) => {
|
|
|
7615
7597
|
if (isNodeOfType(referencedIdentifier, "JSXIdentifier")) return referencedIdentifier.name;
|
|
7616
7598
|
return "";
|
|
7617
7599
|
};
|
|
7618
|
-
const REF_CURRENT_SEGMENT = ".current";
|
|
7619
|
-
const truncateAtRefCurrent = (chain) => {
|
|
7620
|
-
const refCurrentIndex = chain.indexOf(REF_CURRENT_SEGMENT);
|
|
7621
|
-
if (refCurrentIndex === -1) return chain;
|
|
7622
|
-
const segmentEndIndex = refCurrentIndex + 8;
|
|
7623
|
-
if (segmentEndIndex === chain.length || chain[segmentEndIndex] === ".") return chain.slice(0, refCurrentIndex);
|
|
7624
|
-
return chain;
|
|
7625
|
-
};
|
|
7626
7600
|
const computeDepKey = (reference) => {
|
|
7627
7601
|
const referencedIdentifier = reference.identifier;
|
|
7628
7602
|
let parent = referencedIdentifier.parent ?? null;
|
|
@@ -7653,22 +7627,21 @@ const computeDepKey = (reference) => {
|
|
|
7653
7627
|
const declarator = outermost.parent;
|
|
7654
7628
|
if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declarator.init === outermost) {
|
|
7655
7629
|
const destructuredPath = getDestructuredPropertyPath(declarator.id);
|
|
7656
|
-
if (destructuredPath) return
|
|
7630
|
+
if (destructuredPath) return `${fullName}.${destructuredPath}`;
|
|
7657
7631
|
}
|
|
7658
|
-
const truncatedName = truncateAtRefCurrent(fullName);
|
|
7659
|
-
if (truncatedName !== fullName) return truncatedName;
|
|
7660
7632
|
if (reference.flag !== "read") {
|
|
7661
7633
|
const lastDotIndex = fullName.lastIndexOf(".");
|
|
7662
7634
|
if (lastDotIndex !== -1) return fullName.slice(0, lastDotIndex);
|
|
7663
7635
|
}
|
|
7636
|
+
if (fullName.endsWith(".current")) return fullName.slice(0, -8);
|
|
7664
7637
|
return fullName;
|
|
7665
7638
|
};
|
|
7666
7639
|
const computeDeclaredDepKey = (entry) => {
|
|
7667
|
-
const stripped = unwrapExpression$
|
|
7640
|
+
const stripped = unwrapExpression$2(entry);
|
|
7668
7641
|
if (isNodeOfType(stripped, "Identifier")) return stripped.name;
|
|
7669
7642
|
if (isNodeOfType(stripped, "MemberExpression")) return stringifyMemberChain(stripped);
|
|
7670
7643
|
if (isNodeOfType(stripped, "CallExpression") && (stripped.arguments?.length ?? 0) === 0) {
|
|
7671
|
-
const callee = unwrapExpression$
|
|
7644
|
+
const callee = unwrapExpression$2(stripped.callee);
|
|
7672
7645
|
if (isNodeOfType(callee, "Identifier")) return callee.name;
|
|
7673
7646
|
if (isNodeOfType(callee, "MemberExpression") && !hasComputedMemberExpression(callee)) return stringifyMemberChain(callee);
|
|
7674
7647
|
}
|
|
@@ -7676,16 +7649,16 @@ const computeDeclaredDepKey = (entry) => {
|
|
|
7676
7649
|
};
|
|
7677
7650
|
const depsArrayContainsIdentifier = (depsArgument, identifierName) => {
|
|
7678
7651
|
if (!depsArgument) return false;
|
|
7679
|
-
const strippedDepsArgument = unwrapExpression$
|
|
7652
|
+
const strippedDepsArgument = unwrapExpression$2(depsArgument);
|
|
7680
7653
|
if (!isNodeOfType(strippedDepsArgument, "ArrayExpression")) return false;
|
|
7681
7654
|
return strippedDepsArgument.elements.some((element) => {
|
|
7682
7655
|
if (!element) return false;
|
|
7683
|
-
const strippedElement = unwrapExpression$
|
|
7656
|
+
const strippedElement = unwrapExpression$2(element);
|
|
7684
7657
|
return isNodeOfType(strippedElement, "Identifier") && strippedElement.name === identifierName;
|
|
7685
7658
|
});
|
|
7686
7659
|
};
|
|
7687
7660
|
const stringifyMemberChain = (node) => {
|
|
7688
|
-
const stripped = unwrapExpression$
|
|
7661
|
+
const stripped = unwrapExpression$2(node);
|
|
7689
7662
|
if (isNodeOfType(stripped, "Identifier")) return stripped.name;
|
|
7690
7663
|
if (isNodeOfType(stripped, "ThisExpression")) return "this";
|
|
7691
7664
|
if (isNodeOfType(stripped, "MemberExpression")) {
|
|
@@ -7727,13 +7700,13 @@ const hasBroaderDeclaredDependency = (declaredKey, declaredKeys) => {
|
|
|
7727
7700
|
return false;
|
|
7728
7701
|
};
|
|
7729
7702
|
const getMemberRootIdentifier = (node) => {
|
|
7730
|
-
const stripped = unwrapExpression$
|
|
7703
|
+
const stripped = unwrapExpression$2(node);
|
|
7731
7704
|
if (isNodeOfType(stripped, "Identifier")) return stripped;
|
|
7732
7705
|
if (isNodeOfType(stripped, "MemberExpression")) return getMemberRootIdentifier(stripped.object);
|
|
7733
7706
|
return null;
|
|
7734
7707
|
};
|
|
7735
7708
|
const hasComputedMemberExpression = (node) => {
|
|
7736
|
-
const stripped = unwrapExpression$
|
|
7709
|
+
const stripped = unwrapExpression$2(node);
|
|
7737
7710
|
if (!isNodeOfType(stripped, "MemberExpression")) return false;
|
|
7738
7711
|
if (stripped.computed) return true;
|
|
7739
7712
|
return hasComputedMemberExpression(stripped.object);
|
|
@@ -7749,7 +7722,7 @@ const getRootSymbol = (node, scopes) => {
|
|
|
7749
7722
|
return rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
7750
7723
|
};
|
|
7751
7724
|
const getDeclaredDepSymbolSource = (node) => {
|
|
7752
|
-
const stripped = unwrapExpression$
|
|
7725
|
+
const stripped = unwrapExpression$2(node);
|
|
7753
7726
|
if (isNodeOfType(stripped, "CallExpression") && (stripped.arguments?.length ?? 0) === 0) return stripped.callee;
|
|
7754
7727
|
return node;
|
|
7755
7728
|
};
|
|
@@ -7759,7 +7732,7 @@ const isRegExpLiteral = (node) => {
|
|
|
7759
7732
|
};
|
|
7760
7733
|
const isUnstableInitializer = (node) => {
|
|
7761
7734
|
if (!node) return false;
|
|
7762
|
-
const stripped = unwrapExpression$
|
|
7735
|
+
const stripped = unwrapExpression$2(node);
|
|
7763
7736
|
if (isRegExpLiteral(stripped)) return true;
|
|
7764
7737
|
if (isNodeOfType(stripped, "ConditionalExpression")) return isUnstableInitializer(stripped.consequent) || isUnstableInitializer(stripped.alternate);
|
|
7765
7738
|
if (isNodeOfType(stripped, "LogicalExpression")) return isUnstableInitializer(stripped.left) || isUnstableInitializer(stripped.right);
|
|
@@ -7851,9 +7824,9 @@ const findRefCurrentInCleanup = (callback, scopes) => {
|
|
|
7851
7824
|
};
|
|
7852
7825
|
findReturn(callback);
|
|
7853
7826
|
if (!cleanupFunction) return null;
|
|
7854
|
-
let
|
|
7827
|
+
let refCurrentName = null;
|
|
7855
7828
|
const visitCleanup = (node) => {
|
|
7856
|
-
if (
|
|
7829
|
+
if (refCurrentName) return;
|
|
7857
7830
|
if (isNodeOfType(node, "MemberExpression")) {
|
|
7858
7831
|
const candidateName = getRefCurrentNameFromMemberExpression(node);
|
|
7859
7832
|
if (candidateName) {
|
|
@@ -7861,10 +7834,7 @@ const findRefCurrentInCleanup = (callback, scopes) => {
|
|
|
7861
7834
|
const symbol = rootIdentifier ? scopes.symbolFor(rootIdentifier) : null;
|
|
7862
7835
|
const callbackScope = scopes.ownScopeFor(callback) ?? scopes.scopeFor(callback);
|
|
7863
7836
|
if (!symbol || !isDescendantScope(symbol.scope, callbackScope)) {
|
|
7864
|
-
|
|
7865
|
-
refCurrentName: candidateName,
|
|
7866
|
-
refSymbol: symbol
|
|
7867
|
-
};
|
|
7837
|
+
refCurrentName = candidateName;
|
|
7868
7838
|
return;
|
|
7869
7839
|
}
|
|
7870
7840
|
}
|
|
@@ -7879,19 +7849,7 @@ const findRefCurrentInCleanup = (callback, scopes) => {
|
|
|
7879
7849
|
}
|
|
7880
7850
|
};
|
|
7881
7851
|
visitCleanup(cleanupFunction);
|
|
7882
|
-
return
|
|
7883
|
-
};
|
|
7884
|
-
const hasRefCurrentAssignmentInComponent = (refSymbol) => {
|
|
7885
|
-
if (!refSymbol) return false;
|
|
7886
|
-
for (const reference of refSymbol.references) {
|
|
7887
|
-
const memberParent = reference.identifier.parent;
|
|
7888
|
-
if (!memberParent || !isNodeOfType(memberParent, "MemberExpression")) continue;
|
|
7889
|
-
if (memberParent.object !== reference.identifier) continue;
|
|
7890
|
-
if (getRefCurrentNameFromMemberExpression(memberParent) === null) continue;
|
|
7891
|
-
const assignmentParent = memberParent.parent;
|
|
7892
|
-
if (assignmentParent && isNodeOfType(assignmentParent, "AssignmentExpression") && assignmentParent.left === memberParent) return true;
|
|
7893
|
-
}
|
|
7894
|
-
return false;
|
|
7852
|
+
return refCurrentName;
|
|
7895
7853
|
};
|
|
7896
7854
|
const hasRefCurrentAssignment = (callback, refCurrentName) => {
|
|
7897
7855
|
let didAssignRefCurrent = false;
|
|
@@ -7930,21 +7888,9 @@ const hasMemberCallForRoot = (node, rootName) => {
|
|
|
7930
7888
|
const visit = (current) => {
|
|
7931
7889
|
if (didFindMemberCall) return;
|
|
7932
7890
|
if (isNodeOfType(current, "CallExpression")) {
|
|
7933
|
-
|
|
7934
|
-
|
|
7935
|
-
|
|
7936
|
-
let doesChainPassThroughCurrent = false;
|
|
7937
|
-
while (chainObject && isNodeOfType(chainObject, "MemberExpression")) {
|
|
7938
|
-
if (isNodeOfType(chainObject.property, "Identifier") && chainObject.property.name === "current") {
|
|
7939
|
-
doesChainPassThroughCurrent = true;
|
|
7940
|
-
break;
|
|
7941
|
-
}
|
|
7942
|
-
chainObject = unwrapExpression$3(chainObject.object);
|
|
7943
|
-
}
|
|
7944
|
-
if (!doesChainPassThroughCurrent && chainObject && isNodeOfType(chainObject, "Identifier") && chainObject.name === rootName) {
|
|
7945
|
-
didFindMemberCall = true;
|
|
7946
|
-
return;
|
|
7947
|
-
}
|
|
7891
|
+
if (getMemberRootIdentifier(unwrapExpression$2(current.callee))?.name === rootName) {
|
|
7892
|
+
didFindMemberCall = true;
|
|
7893
|
+
return;
|
|
7948
7894
|
}
|
|
7949
7895
|
}
|
|
7950
7896
|
const record = current;
|
|
@@ -8005,7 +7951,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
8005
7951
|
return;
|
|
8006
7952
|
}
|
|
8007
7953
|
const depsArgumentRaw = node.arguments[depsArgumentIndex];
|
|
8008
|
-
const callbackExpression = unwrapExpression$
|
|
7954
|
+
const callbackExpression = unwrapExpression$2(callbackArgument);
|
|
8009
7955
|
let callbackToAnalyze = null;
|
|
8010
7956
|
const forcedCaptureKeys = /* @__PURE__ */ new Set();
|
|
8011
7957
|
if (isNodeOfType(callbackExpression, "ArrowFunctionExpression") || isNodeOfType(callbackExpression, "FunctionExpression")) callbackToAnalyze = callbackExpression;
|
|
@@ -8013,7 +7959,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
8013
7959
|
const callbackSymbol = context.scopes.symbolFor(callbackExpression);
|
|
8014
7960
|
const functionValueNode = callbackSymbol ? getFunctionValueNode(callbackSymbol) : null;
|
|
8015
7961
|
if (functionValueNode) callbackToAnalyze = functionValueNode;
|
|
8016
|
-
else if (callbackSymbol?.initializer && isNodeOfType(unwrapExpression$
|
|
7962
|
+
else if (callbackSymbol?.initializer && isNodeOfType(unwrapExpression$2(callbackSymbol.initializer), "CallExpression")) forcedCaptureKeys.add(callbackExpression.name);
|
|
8017
7963
|
else if (depsArgumentRaw) {
|
|
8018
7964
|
if (depsArrayContainsIdentifier(depsArgumentRaw, callbackExpression.name)) return;
|
|
8019
7965
|
context.report({
|
|
@@ -8046,11 +7992,11 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
8046
7992
|
message: buildAssignmentMessage(assignment.name)
|
|
8047
7993
|
});
|
|
8048
7994
|
if (outerAssignments.length > 0) return;
|
|
8049
|
-
const
|
|
7995
|
+
const refCurrentName = findRefCurrentInCleanup(callbackToAnalyze, context.scopes);
|
|
8050
7996
|
const shouldCheckRefCleanup = EFFECT_HOOKS_ALLOWING_EXTRA_REACTIVE_DEPS.has(hookName) || Boolean(additionalHooksRegex && additionalHooksRegex.test(hookName));
|
|
8051
|
-
if (
|
|
7997
|
+
if (refCurrentName && shouldCheckRefCleanup && !hasRefCurrentAssignment(callbackToAnalyze, refCurrentName)) context.report({
|
|
8052
7998
|
node: callbackToAnalyze,
|
|
8053
|
-
message: buildRefCleanupMessage(
|
|
7999
|
+
message: buildRefCleanupMessage(refCurrentName)
|
|
8054
8000
|
});
|
|
8055
8001
|
}
|
|
8056
8002
|
if (!depsArgumentRaw) {
|
|
@@ -8070,16 +8016,8 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
8070
8016
|
});
|
|
8071
8017
|
return;
|
|
8072
8018
|
}
|
|
8073
|
-
const depsArgument = unwrapExpression$
|
|
8074
|
-
if (isNodeOfType(depsArgument, "Identifier") && depsArgument.name === "undefined") {
|
|
8075
|
-
if (isAutoDependenciesHook(hookName)) return;
|
|
8076
|
-
if (HOOKS_REQUIRING_DEPS_ARRAY.has(hookName)) context.report({
|
|
8077
|
-
node: depsArgument,
|
|
8078
|
-
message: buildMissingDepArrayMessage(hookName)
|
|
8079
|
-
});
|
|
8080
|
-
return;
|
|
8081
|
-
}
|
|
8082
|
-
if (isNodeOfType(depsArgument, "Literal") && depsArgument.value === null) {
|
|
8019
|
+
const depsArgument = unwrapExpression$2(depsArgumentRaw);
|
|
8020
|
+
if (isNodeOfType(depsArgument, "Literal") && depsArgument.value === null || isNodeOfType(depsArgument, "Identifier") && depsArgument.name === "undefined") {
|
|
8083
8021
|
if (isAutoDependenciesHook(hookName)) return;
|
|
8084
8022
|
if (HOOKS_REQUIRING_DEPS_ARRAY.has(hookName)) {
|
|
8085
8023
|
context.report({
|
|
@@ -8088,6 +8026,19 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
8088
8026
|
});
|
|
8089
8027
|
return;
|
|
8090
8028
|
}
|
|
8029
|
+
const nonArrayCaptureKeys = callbackToAnalyze !== null ? new Set(collectCaptureDepKeys(callbackToAnalyze, context.scopes).keys) : /* @__PURE__ */ new Set();
|
|
8030
|
+
for (const forcedCaptureKey of forcedCaptureKeys) nonArrayCaptureKeys.add(forcedCaptureKey);
|
|
8031
|
+
if (nonArrayCaptureKeys.size > 0) {
|
|
8032
|
+
context.report({
|
|
8033
|
+
node: depsArgument,
|
|
8034
|
+
message: buildNonArrayDepsMessage(hookName)
|
|
8035
|
+
});
|
|
8036
|
+
context.report({
|
|
8037
|
+
node: depsArgument,
|
|
8038
|
+
message: buildMissingDepMessage(hookName, [...nonArrayCaptureKeys].join(", "))
|
|
8039
|
+
});
|
|
8040
|
+
}
|
|
8041
|
+
return;
|
|
8091
8042
|
}
|
|
8092
8043
|
if (!isNodeOfType(depsArgument, "ArrayExpression")) {
|
|
8093
8044
|
context.report({
|
|
@@ -8106,11 +8057,11 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
8106
8057
|
for (const forcedCaptureKey of forcedCaptureKeys) captureKeys.add(forcedCaptureKey);
|
|
8107
8058
|
const hasLiteralDepElement = depsArgument.elements.some((element) => {
|
|
8108
8059
|
if (!element) return false;
|
|
8109
|
-
return isLiteralOrEmptyTemplate(unwrapExpression$
|
|
8060
|
+
return isLiteralOrEmptyTemplate(unwrapExpression$2(element));
|
|
8110
8061
|
});
|
|
8111
8062
|
const hasNonStringLiteralDep = depsArgument.elements.some((element) => {
|
|
8112
8063
|
if (!element) return false;
|
|
8113
|
-
return isNonStringLiteral(unwrapExpression$
|
|
8064
|
+
return isNonStringLiteral(unwrapExpression$2(element));
|
|
8114
8065
|
});
|
|
8115
8066
|
if (hasNonStringLiteralDep) context.report({
|
|
8116
8067
|
node: depsArgument,
|
|
@@ -8131,7 +8082,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
8131
8082
|
});
|
|
8132
8083
|
continue;
|
|
8133
8084
|
}
|
|
8134
|
-
const stripped = unwrapExpression$
|
|
8085
|
+
const stripped = unwrapExpression$2(elementNode);
|
|
8135
8086
|
if (isLiteralOrEmptyTemplate(stripped)) continue;
|
|
8136
8087
|
if (isNodeOfType(stripped, "Identifier")) {
|
|
8137
8088
|
const depSymbol = context.scopes.symbolFor(stripped);
|
|
@@ -8273,7 +8224,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
|
|
|
8273
8224
|
if (missingCaptureKeys.length > 0 && isOuterFunctionScopeDep(reportNode, callbackToAnalyze ?? callbackArgument, context.scopes)) continue;
|
|
8274
8225
|
const rootSymbol = getRootSymbol(reportNode, context.scopes);
|
|
8275
8226
|
if (rootSymbol && missingCaptureKeys.length > 0 && isRecursiveInitializerCapture(rootSymbol, callbackToAnalyze ?? callbackArgument)) continue;
|
|
8276
|
-
if (isNodeOfType(unwrapExpression$
|
|
8227
|
+
if (isNodeOfType(unwrapExpression$2(reportNode), "CallExpression")) {
|
|
8277
8228
|
context.report({
|
|
8278
8229
|
node: reportNode,
|
|
8279
8230
|
message: buildComplexDepMessage(hookName)
|
|
@@ -9297,9 +9248,9 @@ const resolveSettings$37 = (settings) => {
|
|
|
9297
9248
|
};
|
|
9298
9249
|
};
|
|
9299
9250
|
const isWordBoundary = (text, start, end) => {
|
|
9300
|
-
const
|
|
9301
|
-
const startsBoundary = start === 0 || !
|
|
9302
|
-
const endsBoundary = end === text.length || !
|
|
9251
|
+
const isAlphanumeric = (charCode) => charCode >= 48 && charCode <= 57 || charCode >= 65 && charCode <= 90 || charCode >= 97 && charCode <= 122;
|
|
9252
|
+
const startsBoundary = start === 0 || !isAlphanumeric(text.charCodeAt(start - 1));
|
|
9253
|
+
const endsBoundary = end === text.length || !isAlphanumeric(text.charCodeAt(end));
|
|
9303
9254
|
return startsBoundary && endsBoundary;
|
|
9304
9255
|
};
|
|
9305
9256
|
const containsRedundantWord = (altText, words) => {
|
|
@@ -9696,7 +9647,7 @@ const isAtomFromJotai = (callExpression) => {
|
|
|
9696
9647
|
if (!isImportedFromModule(callExpression, localName, "jotai")) return false;
|
|
9697
9648
|
return getImportedNameFromModule(callExpression, localName, "jotai") === "atom";
|
|
9698
9649
|
};
|
|
9699
|
-
const isFunctionExpressionLike$
|
|
9650
|
+
const isFunctionExpressionLike$1 = (node) => Boolean(node && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression")));
|
|
9700
9651
|
const getFirstParameterName = (fn) => {
|
|
9701
9652
|
const parameters = fn.params ?? [];
|
|
9702
9653
|
if (parameters.length !== 1) return null;
|
|
@@ -9829,7 +9780,7 @@ const jotaiDerivedAtomReturnsFreshObject = defineRule({
|
|
|
9829
9780
|
const args = node.arguments ?? [];
|
|
9830
9781
|
if (args.length === 0) return;
|
|
9831
9782
|
const reader = args[0];
|
|
9832
|
-
if (!isFunctionExpressionLike$
|
|
9783
|
+
if (!isFunctionExpressionLike$1(reader)) return;
|
|
9833
9784
|
const getParameterName = getFirstParameterName(reader);
|
|
9834
9785
|
if (!getParameterName) return;
|
|
9835
9786
|
const freshReturn = getFreshReturnForFunction(reader);
|
|
@@ -9927,14 +9878,14 @@ const getHandlerNamedBindingName = (functionNode) => {
|
|
|
9927
9878
|
const containingFunctionIsComponentOrHook = (functionNode) => {
|
|
9928
9879
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) {
|
|
9929
9880
|
const declaredName = functionNode.id.name;
|
|
9930
|
-
return COMPONENT_NAME_PATTERN.test(declaredName) || HOOK_NAME_PATTERN
|
|
9881
|
+
return COMPONENT_NAME_PATTERN.test(declaredName) || HOOK_NAME_PATTERN.test(declaredName);
|
|
9931
9882
|
}
|
|
9932
9883
|
let cursor = functionNode.parent ?? null;
|
|
9933
9884
|
while (cursor && isNodeOfType(cursor, "CallExpression")) cursor = cursor.parent ?? null;
|
|
9934
9885
|
if (!cursor) return false;
|
|
9935
9886
|
if (!isNodeOfType(cursor, "VariableDeclarator")) return false;
|
|
9936
9887
|
if (!isNodeOfType(cursor.id, "Identifier")) return false;
|
|
9937
|
-
return COMPONENT_NAME_PATTERN.test(cursor.id.name) || HOOK_NAME_PATTERN
|
|
9888
|
+
return COMPONENT_NAME_PATTERN.test(cursor.id.name) || HOOK_NAME_PATTERN.test(cursor.id.name);
|
|
9938
9889
|
};
|
|
9939
9890
|
const isBindingInvokedOnRenderPath = (root, bindingName) => {
|
|
9940
9891
|
let didFindRenderPathInvocation = false;
|
|
@@ -15648,7 +15599,6 @@ const nextjsMissingMetadata = defineRule({
|
|
|
15648
15599
|
const filename = normalizeFilename(context.filename ?? "");
|
|
15649
15600
|
if (!PAGE_FILE_PATTERN.test(filename)) return;
|
|
15650
15601
|
if (INTERNAL_PAGE_PATH_PATTERN.test(filename)) return;
|
|
15651
|
-
if ((programNode.body ?? []).some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === "use client")) return;
|
|
15652
15602
|
if (programNode.body?.some((statement) => {
|
|
15653
15603
|
if (!isNodeOfType(statement, "ExportNamedDeclaration")) return false;
|
|
15654
15604
|
const declaration = statement.declaration;
|
|
@@ -17378,38 +17328,14 @@ const resolvesToRefFactoryCall = (identifier) => {
|
|
|
17378
17328
|
});
|
|
17379
17329
|
return found;
|
|
17380
17330
|
};
|
|
17381
|
-
const
|
|
17382
|
-
if (!node) return null;
|
|
17383
|
-
if (isNodeOfType(node, "ChainExpression")) return unwrapExpression$2(node.expression);
|
|
17384
|
-
if (isNodeOfType(node, "TSNonNullExpression")) return unwrapExpression$2(node.expression);
|
|
17385
|
-
return node;
|
|
17386
|
-
};
|
|
17387
|
-
const resolvesToRefCurrentAlias = (identifier, visitedAliasNames) => {
|
|
17388
|
-
if (visitedAliasNames.has(identifier.name)) return false;
|
|
17389
|
-
const root = findProgramRoot(identifier);
|
|
17390
|
-
if (!root) return false;
|
|
17391
|
-
const nextVisited = new Set([...visitedAliasNames, identifier.name]);
|
|
17392
|
-
let found = false;
|
|
17393
|
-
walkAst(root, (child) => {
|
|
17394
|
-
if (found) return false;
|
|
17395
|
-
if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && child.id.name === identifier.name) {
|
|
17396
|
-
const init = unwrapExpression$2(child.init);
|
|
17397
|
-
if (init && isNodeOfType(init, "MemberExpression") && isNodeOfType(init.property, "Identifier") && init.property.name === "current" && isRefLikeReceiver(init.object, nextVisited)) {
|
|
17398
|
-
found = true;
|
|
17399
|
-
return false;
|
|
17400
|
-
}
|
|
17401
|
-
}
|
|
17402
|
-
});
|
|
17403
|
-
return found;
|
|
17404
|
-
};
|
|
17405
|
-
const isRefLikeReceiver = (receiver, visitedAliasNames = /* @__PURE__ */ new Set()) => {
|
|
17331
|
+
const isRefLikeReceiver = (receiver) => {
|
|
17406
17332
|
if (!receiver) return false;
|
|
17407
|
-
if (isNodeOfType(receiver, "ChainExpression")) return isRefLikeReceiver(receiver.expression
|
|
17408
|
-
if (isNodeOfType(receiver, "TSNonNullExpression")) return isRefLikeReceiver(receiver.expression
|
|
17409
|
-
if (isNodeOfType(receiver, "Identifier")) return hasRefLikeName(receiver.name) || resolvesToRefFactoryCall(receiver)
|
|
17333
|
+
if (isNodeOfType(receiver, "ChainExpression")) return isRefLikeReceiver(receiver.expression);
|
|
17334
|
+
if (isNodeOfType(receiver, "TSNonNullExpression")) return isRefLikeReceiver(receiver.expression);
|
|
17335
|
+
if (isNodeOfType(receiver, "Identifier")) return hasRefLikeName(receiver.name) || resolvesToRefFactoryCall(receiver);
|
|
17410
17336
|
if (isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.property, "Identifier")) {
|
|
17411
17337
|
if (hasRefLikeName(receiver.property.name)) return true;
|
|
17412
|
-
if (receiver.property.name === "current") return isRefLikeReceiver(receiver.object
|
|
17338
|
+
if (receiver.property.name === "current") return isRefLikeReceiver(receiver.object);
|
|
17413
17339
|
}
|
|
17414
17340
|
return false;
|
|
17415
17341
|
};
|
|
@@ -17544,19 +17470,6 @@ const getScopeForNode = (node, manager) => {
|
|
|
17544
17470
|
//#endregion
|
|
17545
17471
|
//#region src/plugin/rules/state-and-effects/utils/effect/ast.ts
|
|
17546
17472
|
const getChildKeys = (node) => VISITOR_KEYS[node.type] ?? Object.keys(node).filter((key) => key !== "parent");
|
|
17547
|
-
const HOOK_NAME_PATTERN = /^use[A-Z0-9]/;
|
|
17548
|
-
const isInsideCallbackArgumentOf = (identifier, initializer) => {
|
|
17549
|
-
if (!isNodeOfType(initializer, "CallExpression") && !isNodeOfType(initializer, "NewExpression")) return false;
|
|
17550
|
-
if (isNodeOfType(initializer, "CallExpression") && isNodeOfType(initializer.callee, "Identifier") && HOOK_NAME_PATTERN.test(initializer.callee.name)) return false;
|
|
17551
|
-
const callbackArguments = (initializer.arguments ?? []).filter((argument) => isFunctionLike$1(argument));
|
|
17552
|
-
if (callbackArguments.length === 0) return false;
|
|
17553
|
-
let node = identifier;
|
|
17554
|
-
while (node && node !== initializer) {
|
|
17555
|
-
if (callbackArguments.includes(node)) return true;
|
|
17556
|
-
node = node.parent;
|
|
17557
|
-
}
|
|
17558
|
-
return false;
|
|
17559
|
-
};
|
|
17560
17473
|
const ascend = (analysis, ref, visit, visited = /* @__PURE__ */ new Set()) => {
|
|
17561
17474
|
if (visited.has(ref)) return;
|
|
17562
17475
|
const result = visit(ref);
|
|
@@ -17569,10 +17482,7 @@ const ascend = (analysis, ref, visit, visited = /* @__PURE__ */ new Set()) => {
|
|
|
17569
17482
|
const defNode = def.node;
|
|
17570
17483
|
const next = defNode.init ?? defNode.body;
|
|
17571
17484
|
if (!next) continue;
|
|
17572
|
-
for (const innerRef of getDownstreamRefs(analysis, next))
|
|
17573
|
-
if (isInsideCallbackArgumentOf(innerRef.identifier, next)) continue;
|
|
17574
|
-
ascend(analysis, innerRef, visit, visited);
|
|
17575
|
-
}
|
|
17485
|
+
for (const innerRef of getDownstreamRefs(analysis, next)) ascend(analysis, innerRef, visit, visited);
|
|
17576
17486
|
}
|
|
17577
17487
|
};
|
|
17578
17488
|
const descend = (node, visit, visited = /* @__PURE__ */ new Set()) => {
|
|
@@ -18021,8 +17931,7 @@ const noAriaHiddenOnFocusable = defineRule({
|
|
|
18021
17931
|
if (isNodeOfType(value, "Literal") && value.value !== "true") return;
|
|
18022
17932
|
if (isNodeOfType(value, "JSXExpressionContainer")) {
|
|
18023
17933
|
const expression = value.expression;
|
|
18024
|
-
if (
|
|
18025
|
-
if (expression.value !== true && expression.value !== "true") return;
|
|
17934
|
+
if (isNodeOfType(expression, "Literal") && !expression.value) return;
|
|
18026
17935
|
}
|
|
18027
17936
|
}
|
|
18028
17937
|
const tag = getElementType(node, context.settings);
|
|
@@ -18277,30 +18186,10 @@ const isArrayFromCall = (node) => {
|
|
|
18277
18186
|
*
|
|
18278
18187
|
* Used both for `<receiver>.map(...)` and for `Array.from(<length>, fn)`.
|
|
18279
18188
|
*/
|
|
18280
|
-
const
|
|
18281
|
-
const programRoot = findProgramRoot(referenceNode);
|
|
18282
|
-
if (!programRoot) return false;
|
|
18283
|
-
let didFindReassignment = false;
|
|
18284
|
-
walkAst(programRoot, (child) => {
|
|
18285
|
-
if (didFindReassignment) return false;
|
|
18286
|
-
if (isNodeOfType(child, "AssignmentExpression") && isNodeOfType(child.left, "Identifier") && child.left.name === bindingName) {
|
|
18287
|
-
didFindReassignment = true;
|
|
18288
|
-
return false;
|
|
18289
|
-
}
|
|
18290
|
-
});
|
|
18291
|
-
return didFindReassignment;
|
|
18292
|
-
};
|
|
18293
|
-
const isStaticPlaceholderReceiver = (receiver, depth = 0) => {
|
|
18189
|
+
const isStaticPlaceholderReceiver = (receiver) => {
|
|
18294
18190
|
if (isArrayFromCall(receiver)) return true;
|
|
18295
18191
|
if (isArrayConstructorCallWithNumericLength(receiver)) return true;
|
|
18296
18192
|
if (isAllLiteralArrayExpression(receiver)) return true;
|
|
18297
|
-
if (isNodeOfType(receiver, "Identifier")) {
|
|
18298
|
-
if (depth >= TYPE_RESOLUTION_DEPTH_LIMIT) return false;
|
|
18299
|
-
const binding = findVariableInitializer(receiver, receiver.name);
|
|
18300
|
-
if (!binding?.initializer) return false;
|
|
18301
|
-
if (isBindingReassigned(receiver, receiver.name)) return false;
|
|
18302
|
-
return isStaticPlaceholderReceiver(binding.initializer, depth + 1);
|
|
18303
|
-
}
|
|
18304
18193
|
if (isNodeOfType(receiver, "CallExpression")) {
|
|
18305
18194
|
const callee = receiver.callee;
|
|
18306
18195
|
if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "fill" && isArrayConstructorCallWithNumericLength(callee.object)) return true;
|
|
@@ -18326,7 +18215,6 @@ const isArrayFromLengthObjectCall = (node) => {
|
|
|
18326
18215
|
if (!(isNodeOfType(key, "Identifier") && key.name === "length" || isNodeOfType(key, "Literal") && key.value === "length")) continue;
|
|
18327
18216
|
if (isNumericLiteralOrUndefined(prop.value)) return true;
|
|
18328
18217
|
if (isNodeOfType(prop.value, "Identifier")) return true;
|
|
18329
|
-
if (isNodeOfType(prop.value, "MemberExpression") && isNodeOfType(prop.value.property, "Identifier") && prop.value.property.name === "length") return true;
|
|
18330
18218
|
}
|
|
18331
18219
|
return false;
|
|
18332
18220
|
};
|
|
@@ -18435,11 +18323,10 @@ const isInsideStaticPlaceholderMap = (node) => isInsideIteratorMapMatching(node,
|
|
|
18435
18323
|
/**
|
|
18436
18324
|
* Walk up from a JSXAttribute node looking for the enclosing iterator
|
|
18437
18325
|
* callback (`.map(cb)`, `.flatMap(cb)`, `.forEach(cb)`, `Array.from(_, cb)`)
|
|
18438
|
-
* and return the
|
|
18439
|
-
* `arr.map((item, index) => …)
|
|
18440
|
-
* `arr.map(({ id, label }, index) => …)`.
|
|
18326
|
+
* and return the first parameter's name. The first param is the per-item
|
|
18327
|
+
* value, e.g. `item` in `arr.map((item, index) => …)`.
|
|
18441
18328
|
*/
|
|
18442
|
-
const
|
|
18329
|
+
const findIteratorItemName$1 = (node) => {
|
|
18443
18330
|
let current = node;
|
|
18444
18331
|
while (current.parent) {
|
|
18445
18332
|
const parent = current.parent;
|
|
@@ -18449,20 +18336,18 @@ const findIteratorItemNames = (node) => {
|
|
|
18449
18336
|
const isArrayFromCallback = isArrayFromCall(parent) && parent.arguments.length >= 2 && parent.arguments[1] === current;
|
|
18450
18337
|
if (isIteratorMethodCall || isArrayFromCallback) {
|
|
18451
18338
|
const first = (current.params ?? [])[0];
|
|
18452
|
-
if (
|
|
18453
|
-
|
|
18454
|
-
collectPatternNames(first, names);
|
|
18455
|
-
return names.size > 0 ? names : null;
|
|
18339
|
+
if (first && isNodeOfType(first, "Identifier")) return first.name;
|
|
18340
|
+
return null;
|
|
18456
18341
|
}
|
|
18457
18342
|
}
|
|
18458
18343
|
current = parent;
|
|
18459
18344
|
}
|
|
18460
18345
|
return null;
|
|
18461
18346
|
};
|
|
18462
|
-
const templateLiteralHasIteratorIdentity = (template,
|
|
18347
|
+
const templateLiteralHasIteratorIdentity = (template, itemName) => {
|
|
18463
18348
|
for (const expression of template.expressions ?? []) {
|
|
18464
|
-
|
|
18465
|
-
if (
|
|
18349
|
+
if (isNodeOfType(expression, "Identifier") && expression.name === itemName) return true;
|
|
18350
|
+
if (isNodeOfType(expression, "MemberExpression") && isNodeOfType(expression.object, "Identifier") && expression.object.name === itemName) return true;
|
|
18466
18351
|
}
|
|
18467
18352
|
return false;
|
|
18468
18353
|
};
|
|
@@ -18475,32 +18360,9 @@ const templateLiteralHasIteratorIdentity = (template, itemNames) => {
|
|
|
18475
18360
|
const isCompositeKeyWithIteratorIdentity = (keyExpression, attributeNode) => {
|
|
18476
18361
|
if (!isNodeOfType(keyExpression, "TemplateLiteral")) return false;
|
|
18477
18362
|
if ((keyExpression.expressions ?? []).length < 2) return false;
|
|
18478
|
-
const
|
|
18479
|
-
if (!
|
|
18480
|
-
return templateLiteralHasIteratorIdentity(keyExpression,
|
|
18481
|
-
};
|
|
18482
|
-
const forLoopTestReadsDataLength = (test) => {
|
|
18483
|
-
let didFindLengthRead = false;
|
|
18484
|
-
walkAst(test, (child) => {
|
|
18485
|
-
if (didFindLengthRead) return false;
|
|
18486
|
-
if (isNodeOfType(child, "MemberExpression") && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
|
|
18487
|
-
didFindLengthRead = true;
|
|
18488
|
-
return false;
|
|
18489
|
-
}
|
|
18490
|
-
});
|
|
18491
|
-
return didFindLengthRead;
|
|
18492
|
-
};
|
|
18493
|
-
const isNumericForLoopCounter = (attributeNode, indexName) => {
|
|
18494
|
-
const binding = findVariableInitializer(attributeNode, indexName);
|
|
18495
|
-
if (!binding) return false;
|
|
18496
|
-
const declarator = binding.bindingIdentifier.parent;
|
|
18497
|
-
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
|
|
18498
|
-
const declaration = declarator.parent;
|
|
18499
|
-
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
18500
|
-
const forStatement = declaration.parent;
|
|
18501
|
-
if (!forStatement || !isNodeOfType(forStatement, "ForStatement") || forStatement.init !== declaration || !declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
|
|
18502
|
-
if (forStatement.test && forLoopTestReadsDataLength(forStatement.test)) return false;
|
|
18503
|
-
return true;
|
|
18363
|
+
const itemName = findIteratorItemName$1(attributeNode);
|
|
18364
|
+
if (!itemName) return false;
|
|
18365
|
+
return templateLiteralHasIteratorIdentity(keyExpression, itemName);
|
|
18504
18366
|
};
|
|
18505
18367
|
const noArrayIndexAsKey = defineRule({
|
|
18506
18368
|
id: "no-array-index-as-key",
|
|
@@ -18512,7 +18374,6 @@ const noArrayIndexAsKey = defineRule({
|
|
|
18512
18374
|
if (!node.value || !isNodeOfType(node.value, "JSXExpressionContainer")) return;
|
|
18513
18375
|
const indexName = extractIndexName(node.value.expression);
|
|
18514
18376
|
if (!indexName) return;
|
|
18515
|
-
if (isNumericForLoopCounter(node, indexName)) return;
|
|
18516
18377
|
if (isInsideStaticPlaceholderMap(node)) return;
|
|
18517
18378
|
if (isInsideStringDerivedMap(node)) return;
|
|
18518
18379
|
if (isCompositeKeyWithIteratorIdentity(node.value.expression, node)) return;
|
|
@@ -20878,7 +20739,7 @@ const isStateMemberExpression = (node) => {
|
|
|
20878
20739
|
};
|
|
20879
20740
|
//#endregion
|
|
20880
20741
|
//#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
|
|
20881
|
-
const MESSAGE$24 = "
|
|
20742
|
+
const MESSAGE$24 = "Your users see stale data because mutating `this.state` by hand never redraws & gets overwritten.";
|
|
20882
20743
|
const shouldIgnoreMutation = (node) => {
|
|
20883
20744
|
let isConstructor = false;
|
|
20884
20745
|
let isInsideCallExpression = false;
|
|
@@ -20972,18 +20833,6 @@ const initializerMarksPlainState = (initializerArgument) => {
|
|
|
20972
20833
|
}
|
|
20973
20834
|
return producesPlainStateValue(unwrapped);
|
|
20974
20835
|
};
|
|
20975
|
-
const collectCallbackRefSetterNames = (componentBody) => {
|
|
20976
|
-
const callbackRefSetterNames = /* @__PURE__ */ new Set();
|
|
20977
|
-
walkAst(componentBody, (node) => {
|
|
20978
|
-
if (!isNodeOfType(node, "JSXAttribute")) return;
|
|
20979
|
-
const attributeName = node.name;
|
|
20980
|
-
if (isNodeOfType(attributeName, "JSXIdentifier") && attributeName.name === "ref" && node.value && isNodeOfType(node.value, "JSXExpressionContainer")) {
|
|
20981
|
-
const expression = stripParenExpression(node.value.expression);
|
|
20982
|
-
if (isNodeOfType(expression, "Identifier")) callbackRefSetterNames.add(expression.name);
|
|
20983
|
-
}
|
|
20984
|
-
});
|
|
20985
|
-
return callbackRefSetterNames;
|
|
20986
|
-
};
|
|
20987
20836
|
const collectFunctionLocalBindings = (functionNode) => {
|
|
20988
20837
|
const localBindings = /* @__PURE__ */ new Set();
|
|
20989
20838
|
if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return localBindings;
|
|
@@ -21026,10 +20875,8 @@ const noDirectStateMutation = defineRule({
|
|
|
21026
20875
|
const bindings = collectUseStateBindings(componentBody);
|
|
21027
20876
|
if (bindings.length === 0) return;
|
|
21028
20877
|
const stateValueToSetter = new Map(bindings.map((binding) => [binding.valueName, binding.setterName]));
|
|
21029
|
-
const callbackRefSetterNames = collectCallbackRefSetterNames(componentBody);
|
|
21030
20878
|
const plainObjectStateValueNames = /* @__PURE__ */ new Set();
|
|
21031
20879
|
for (const binding of bindings) {
|
|
21032
|
-
if (callbackRefSetterNames.has(binding.setterName)) continue;
|
|
21033
20880
|
if (!isNodeOfType(binding.declarator.init, "CallExpression")) continue;
|
|
21034
20881
|
if (initializerMarksPlainState(binding.declarator.init.arguments?.[0])) plainObjectStateValueNames.add(binding.valueName);
|
|
21035
20882
|
}
|
|
@@ -21042,7 +20889,7 @@ const noDirectStateMutation = defineRule({
|
|
|
21042
20889
|
if (currentlyShadowed.has(rootName)) return;
|
|
21043
20890
|
context.report({
|
|
21044
20891
|
node: child,
|
|
21045
|
-
message: `
|
|
20892
|
+
message: `Your screen won't update because you change "${rootName}" in place.`
|
|
21046
20893
|
});
|
|
21047
20894
|
return;
|
|
21048
20895
|
}
|
|
@@ -21058,7 +20905,7 @@ const noDirectStateMutation = defineRule({
|
|
|
21058
20905
|
if (currentlyShadowed.has(rootName)) return;
|
|
21059
20906
|
context.report({
|
|
21060
20907
|
node: child,
|
|
21061
|
-
message: `
|
|
20908
|
+
message: `Your screen won't update because .${methodName}() changes "${rootName}" in place.`
|
|
21062
20909
|
});
|
|
21063
20910
|
}
|
|
21064
20911
|
});
|
|
@@ -22927,16 +22774,11 @@ const NON_DETERMINISTIC_ID_GENERATOR_NAMES = new Set([
|
|
|
22927
22774
|
"ulid",
|
|
22928
22775
|
"createId"
|
|
22929
22776
|
]);
|
|
22930
|
-
const isZeroArgDateConstruction = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "Date" && (node.arguments?.length ?? 0) === 0;
|
|
22931
22777
|
const containsNonDeterministicSource = (root) => {
|
|
22932
22778
|
let found = false;
|
|
22933
22779
|
walkAst(root, (child) => {
|
|
22934
22780
|
if (found) return false;
|
|
22935
22781
|
if (isFunctionLike$1(child)) return false;
|
|
22936
|
-
if (isZeroArgDateConstruction(child)) {
|
|
22937
|
-
found = true;
|
|
22938
|
-
return false;
|
|
22939
|
-
}
|
|
22940
22782
|
if (!isNodeOfType(child, "CallExpression")) return;
|
|
22941
22783
|
const callee = child.callee;
|
|
22942
22784
|
if (isNodeOfType(callee, "Identifier") && NON_DETERMINISTIC_ID_GENERATOR_NAMES.has(callee.name)) {
|
|
@@ -22997,53 +22839,6 @@ const argumentReadsPostMountMeasurement = (argument, effectFn, visitedLocalNames
|
|
|
22997
22839
|
});
|
|
22998
22840
|
return found;
|
|
22999
22841
|
};
|
|
23000
|
-
const isResourceLikeInitializer = (initializer) => {
|
|
23001
|
-
if (isNodeOfType(initializer, "AwaitExpression")) return isResourceLikeInitializer(initializer.argument);
|
|
23002
|
-
return isNodeOfType(initializer, "NewExpression") || isNodeOfType(initializer, "CallExpression");
|
|
23003
|
-
};
|
|
23004
|
-
const collectArgumentSourceLocalNames = (argument, effectFn, sourceLocalNames = /* @__PURE__ */ new Set()) => {
|
|
23005
|
-
walkAst(argument, (child) => {
|
|
23006
|
-
if (!isNodeOfType(child, "Identifier")) return;
|
|
23007
|
-
if (sourceLocalNames.has(child.name)) return;
|
|
23008
|
-
const localInitializer = findEffectLocalInitializer(effectFn, child.name);
|
|
23009
|
-
if (!localInitializer || !isResourceLikeInitializer(localInitializer)) return;
|
|
23010
|
-
sourceLocalNames.add(child.name);
|
|
23011
|
-
collectArgumentSourceLocalNames(localInitializer, effectFn, sourceLocalNames);
|
|
23012
|
-
});
|
|
23013
|
-
return sourceLocalNames;
|
|
23014
|
-
};
|
|
23015
|
-
const isFunctionExpressionLike$1 = (node) => isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression");
|
|
23016
|
-
const findCleanupFunction = (effectFn) => {
|
|
23017
|
-
if (!isNodeOfType(effectFn, "ArrowFunctionExpression") && !isNodeOfType(effectFn, "FunctionExpression")) return null;
|
|
23018
|
-
const body = effectFn.body;
|
|
23019
|
-
if (!isNodeOfType(body, "BlockStatement")) return null;
|
|
23020
|
-
let cleanupFunction = null;
|
|
23021
|
-
walkAst(body, (child) => {
|
|
23022
|
-
if (cleanupFunction) return false;
|
|
23023
|
-
if (isNodeOfType(child, "ReturnStatement")) {
|
|
23024
|
-
if (child.argument && isFunctionExpressionLike$1(child.argument)) cleanupFunction = child.argument;
|
|
23025
|
-
return false;
|
|
23026
|
-
}
|
|
23027
|
-
if (child !== body && isFunctionExpressionLike$1(child)) return false;
|
|
23028
|
-
if (isNodeOfType(child, "FunctionDeclaration")) return false;
|
|
23029
|
-
});
|
|
23030
|
-
return cleanupFunction;
|
|
23031
|
-
};
|
|
23032
|
-
const cleanupDisposesArgumentSource = (argument, effectFn) => {
|
|
23033
|
-
const cleanupFunction = findCleanupFunction(effectFn);
|
|
23034
|
-
if (!cleanupFunction) return false;
|
|
23035
|
-
const sourceLocalNames = collectArgumentSourceLocalNames(argument, effectFn);
|
|
23036
|
-
if (sourceLocalNames.size === 0) return false;
|
|
23037
|
-
let referencesSource = false;
|
|
23038
|
-
walkAst(cleanupFunction, (child) => {
|
|
23039
|
-
if (referencesSource) return false;
|
|
23040
|
-
if (isNodeOfType(child, "Identifier") && sourceLocalNames.has(child.name)) {
|
|
23041
|
-
referencesSource = true;
|
|
23042
|
-
return false;
|
|
23043
|
-
}
|
|
23044
|
-
});
|
|
23045
|
-
return referencesSource;
|
|
23046
|
-
};
|
|
23047
22842
|
const noInitializeState = defineRule({
|
|
23048
22843
|
id: "no-initialize-state",
|
|
23049
22844
|
title: "State initialized from a mount effect",
|
|
@@ -23066,7 +22861,6 @@ const noInitializeState = defineRule({
|
|
|
23066
22861
|
if (!callExpr || !isNodeOfType(callExpr, "CallExpression")) continue;
|
|
23067
22862
|
if (callExpr.arguments?.some((argument) => Boolean(argument) && containsNonDeterministicSource(argument))) continue;
|
|
23068
22863
|
if (callExpr.arguments?.some((argument) => Boolean(argument) && argumentReadsPostMountMeasurement(argument, effectFn))) continue;
|
|
23069
|
-
if (callExpr.arguments?.some((argument) => Boolean(argument) && cleanupDisposesArgumentSource(argument, effectFn))) continue;
|
|
23070
22864
|
const useStateDecl = getUseStateDecl(analysis, ref);
|
|
23071
22865
|
if (!useStateDecl || !isNodeOfType(useStateDecl, "VariableDeclarator")) continue;
|
|
23072
22866
|
if (!isNodeOfType(useStateDecl.id, "ArrayPattern")) continue;
|
|
@@ -24984,18 +24778,6 @@ const KEYBOARD_HANDLER_PROP_NAMES = [
|
|
|
24984
24778
|
"onKeyPress"
|
|
24985
24779
|
];
|
|
24986
24780
|
const isKeyboardOperable = (node) => KEYBOARD_HANDLER_PROP_NAMES.some((propName) => Boolean(hasJsxPropIgnoreCase(node.attributes, propName)));
|
|
24987
|
-
const parseNumericBranch = (expression) => {
|
|
24988
|
-
if (isNodeOfType(expression, "Literal") && typeof expression.value === "number") return expression.value;
|
|
24989
|
-
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "-" && isNodeOfType(expression.argument, "Literal") && typeof expression.argument.value === "number") return -expression.argument.value;
|
|
24990
|
-
return null;
|
|
24991
|
-
};
|
|
24992
|
-
const isRovingTabindexValue = (value) => {
|
|
24993
|
-
if (!isNodeOfType(value, "JSXExpressionContainer")) return false;
|
|
24994
|
-
const expression = value.expression;
|
|
24995
|
-
if (!isNodeOfType(expression, "ConditionalExpression")) return false;
|
|
24996
|
-
if (isNodeOfType(expression.test, "Literal")) return false;
|
|
24997
|
-
return [parseNumericBranch(expression.consequent), parseNumericBranch(expression.alternate)].some((branchValue) => branchValue !== null && branchValue < 0);
|
|
24998
|
-
};
|
|
24999
24781
|
const resolveSettings$14 = (settings) => {
|
|
25000
24782
|
const reactDoctor = settings?.["react-doctor"];
|
|
25001
24783
|
const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noNoninteractiveTabindex ?? {} : {};
|
|
@@ -25019,7 +24801,6 @@ const noNoninteractiveTabindex = defineRule({
|
|
|
25019
24801
|
if (!tabIndex) return;
|
|
25020
24802
|
const tabIndexValue = tabIndex.value;
|
|
25021
24803
|
if (!tabIndexValue) return;
|
|
25022
|
-
if (isRovingTabindexValue(tabIndexValue)) return;
|
|
25023
24804
|
const numeric = parseJsxValue(tabIndexValue);
|
|
25024
24805
|
if (numeric === null) {
|
|
25025
24806
|
if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node)) context.report({
|
|
@@ -26456,7 +26237,7 @@ const noRenderInRender = defineRule({
|
|
|
26456
26237
|
title: "Component rendered by inline function call",
|
|
26457
26238
|
severity: "warn",
|
|
26458
26239
|
tags: ["test-noise"],
|
|
26459
|
-
recommendation: "Make it a named component
|
|
26240
|
+
recommendation: "Make it a named component so React preserves its identity and does not remount its state.",
|
|
26460
26241
|
create: (context) => ({ JSXExpressionContainer(node) {
|
|
26461
26242
|
const expression = node.expression;
|
|
26462
26243
|
if (!isNodeOfType(expression, "CallExpression")) return;
|
|
@@ -26471,7 +26252,7 @@ const noRenderInRender = defineRule({
|
|
|
26471
26252
|
}
|
|
26472
26253
|
context.report({
|
|
26473
26254
|
node: expression,
|
|
26474
|
-
message: `"${calleeName}()"
|
|
26255
|
+
message: `Your users lose state because "${calleeName}()" builds UI from an inline call that React remounts, so pull it into its own component instead.`
|
|
26475
26256
|
});
|
|
26476
26257
|
} })
|
|
26477
26258
|
});
|
|
@@ -26574,8 +26355,8 @@ const countUseStates = (analysis, componentNode) => {
|
|
|
26574
26355
|
for (const ref of getDownstreamRefs(analysis, componentNode)) if (isState(analysis, ref)) stateVariables.add(ref.resolved);
|
|
26575
26356
|
return stateVariables.size;
|
|
26576
26357
|
};
|
|
26577
|
-
const findPropUsedToResetAllState = (analysis, effectFnRefs, depsRefs, useEffectNode
|
|
26578
|
-
const stateSetterRefs = effectFnRefs.filter((ref) =>
|
|
26358
|
+
const findPropUsedToResetAllState = (analysis, effectFnRefs, depsRefs, useEffectNode) => {
|
|
26359
|
+
const stateSetterRefs = effectFnRefs.filter((ref) => isStateSetterCall(analysis, ref));
|
|
26579
26360
|
if (stateSetterRefs.length === 0) return null;
|
|
26580
26361
|
if (!stateSetterRefs.every((ref) => isSetStateToInitialValue(analysis, ref))) return null;
|
|
26581
26362
|
const containing = findContainingNode(analysis, useEffectNode);
|
|
@@ -26598,9 +26379,7 @@ const noResetAllStateOnPropChange = defineRule({
|
|
|
26598
26379
|
if (!effectFnRefs || !depsRefs) return;
|
|
26599
26380
|
const containing = findContainingNode(analysis, node);
|
|
26600
26381
|
if (containing && isCustomHook(containing)) return;
|
|
26601
|
-
|
|
26602
|
-
if (!effectFn) return;
|
|
26603
|
-
if (!findPropUsedToResetAllState(analysis, effectFnRefs, depsRefs, node, effectFn)) return;
|
|
26382
|
+
if (!findPropUsedToResetAllState(analysis, effectFnRefs, depsRefs, node)) return;
|
|
26604
26383
|
context.report({
|
|
26605
26384
|
node,
|
|
26606
26385
|
message: `Your users briefly see stale state when a prop changes because this useEffect clears all state.`
|
|
@@ -27468,7 +27247,6 @@ const noStaticElementInteractions = defineRule({
|
|
|
27468
27247
|
if (!hasNonBlockerHandler) return;
|
|
27469
27248
|
const elementType = getElementType(node, context.settings);
|
|
27470
27249
|
if (!HTML_TAGS.has(elementType)) return;
|
|
27471
|
-
if (elementType === "svg") return;
|
|
27472
27250
|
if (isHiddenFromScreenReader(node, context.settings)) return;
|
|
27473
27251
|
if (isPresentationRole(node)) return;
|
|
27474
27252
|
if (isInteractiveElement(elementType, node)) return;
|
|
@@ -27482,8 +27260,7 @@ const noStaticElementInteractions = defineRule({
|
|
|
27482
27260
|
});
|
|
27483
27261
|
return;
|
|
27484
27262
|
}
|
|
27485
|
-
|
|
27486
|
-
if (isNodeOfType(attributeValue, "JSXExpressionContainer") && isNodeOfType(attributeValue.expression, "Literal") && typeof attributeValue.expression.value === "string") attributeValue = attributeValue.expression;
|
|
27263
|
+
const attributeValue = roleAttribute.value;
|
|
27487
27264
|
if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
|
|
27488
27265
|
const firstRole = attributeValue.value.toLowerCase().trim().split(/\s+/)[0];
|
|
27489
27266
|
if (firstRole && (isInteractiveRole(firstRole) || isNonInteractiveRole(firstRole))) return;
|
|
@@ -30282,7 +30059,7 @@ const containsVnodeFactoryCall = (root) => {
|
|
|
30282
30059
|
};
|
|
30283
30060
|
const isComponentLikeFunction = (functionNode) => {
|
|
30284
30061
|
const bindingName = getFunctionBindingName(functionNode);
|
|
30285
|
-
if (bindingName && (isReactComponentName(bindingName) || HOOK_NAME_PATTERN
|
|
30062
|
+
if (bindingName && (isReactComponentName(bindingName) || HOOK_NAME_PATTERN.test(bindingName))) return true;
|
|
30286
30063
|
const body = "body" in functionNode ? functionNode.body : null;
|
|
30287
30064
|
if (!body || !isAstNode(body)) return false;
|
|
30288
30065
|
return containsJsxElement(body) || containsVnodeFactoryCall(body);
|
|
@@ -31739,7 +31516,7 @@ const queryMutationMissingInvalidation = defineRule({
|
|
|
31739
31516
|
});
|
|
31740
31517
|
if (!hasCacheUpdate) context.report({
|
|
31741
31518
|
node,
|
|
31742
|
-
message: "useMutation with no cache update
|
|
31519
|
+
message: "useMutation with no cache update leaves your users looking at stale data after it runs."
|
|
31743
31520
|
});
|
|
31744
31521
|
} })
|
|
31745
31522
|
});
|
|
@@ -32388,7 +32165,6 @@ const renderingHydrationMismatchTime = defineRule({
|
|
|
32388
32165
|
return { JSXExpressionContainer(node) {
|
|
32389
32166
|
if (isTestlikeFile) return;
|
|
32390
32167
|
if (!node.expression) return;
|
|
32391
|
-
if (isGeneratedImageRenderContext(context, findOpeningElementOfChild(node) ?? node)) return;
|
|
32392
32168
|
const matched = NONDETERMINISTIC_RENDER_PATTERNS.find((pattern) => pattern.matches(node.expression));
|
|
32393
32169
|
if (matched) {
|
|
32394
32170
|
if (hasSuppressHydrationWarningAttribute(findOpeningElementOfChild(node))) return;
|
|
@@ -34204,7 +33980,7 @@ const rnNoDimensionsGet = defineRule({
|
|
|
34204
33980
|
if (binding !== null && !isBindingReactNativeDimensions(node, binding)) return;
|
|
34205
33981
|
if (isMemberProperty(node.callee, "get")) context.report({
|
|
34206
33982
|
node,
|
|
34207
|
-
message: "
|
|
33983
|
+
message: "Your users see a stale layout on rotation or resize because Dimensions.get() does not update."
|
|
34208
33984
|
});
|
|
34209
33985
|
if (isMemberProperty(node.callee, "addEventListener")) context.report({
|
|
34210
33986
|
node,
|
|
@@ -35026,10 +34802,7 @@ const isExpoUiComponentElement = (openingElement, contextNode, componentName) =>
|
|
|
35026
34802
|
};
|
|
35027
34803
|
//#endregion
|
|
35028
34804
|
//#region src/plugin/rules/react-native/rn-no-raw-text.ts
|
|
35029
|
-
const truncateText = (text) => {
|
|
35030
|
-
const collapsedText = text.replace(/\s+/g, " ");
|
|
35031
|
-
return collapsedText.length > 30 ? `${collapsedText.slice(0, 30)}...` : collapsedText;
|
|
35032
|
-
};
|
|
34805
|
+
const truncateText = (text) => text.length > 30 ? `${text.slice(0, 30)}...` : text;
|
|
35033
34806
|
const isRawTextContent = (child) => {
|
|
35034
34807
|
if (isNodeOfType(child, "JSXText")) return Boolean(child.value?.trim());
|
|
35035
34808
|
if (!isNodeOfType(child, "JSXExpressionContainer") || !child.expression) return false;
|
|
@@ -39298,7 +39071,7 @@ const isInsideClassComponent = (node) => {
|
|
|
39298
39071
|
const hasShortCircuitAncestor = (descendant, ancestor) => {
|
|
39299
39072
|
let current = descendant.parent;
|
|
39300
39073
|
while (current && current !== ancestor) {
|
|
39301
|
-
if (isNodeOfType(current, "ConditionalExpression")
|
|
39074
|
+
if (isNodeOfType(current, "ConditionalExpression")) return true;
|
|
39302
39075
|
if (isNodeOfType(current, "LogicalExpression") && (current.operator === "&&" || current.operator === "||" || current.operator === "??") && isWithinRange(descendant, current.right)) return true;
|
|
39303
39076
|
current = current.parent ?? null;
|
|
39304
39077
|
}
|
|
@@ -39424,7 +39197,6 @@ const rulesOfHooks = defineRule({
|
|
|
39424
39197
|
if (parentInfo.isComponentOrHook) isInsideComponentOrHook = true;
|
|
39425
39198
|
}
|
|
39426
39199
|
if (!isInsideComponentOrHook) {
|
|
39427
|
-
if (!enclosing.hasResolvedName) return;
|
|
39428
39200
|
if (isLocalNonHookFunctionCallee(node, context.scopes, settings)) return;
|
|
39429
39201
|
context.report({
|
|
39430
39202
|
node: node.callee,
|
|
@@ -39999,14 +39771,6 @@ const objectExpressionHasCachingConfig = (objectExpression) => (objectExpression
|
|
|
39999
39771
|
const objectExpressionHasSpread = (objectExpression) => (objectExpression.properties ?? []).some((property) => isNodeOfType(property, "SpreadElement"));
|
|
40000
39772
|
const APP_ROUTER_FILE_PATTERN = new RegExp(`/app/(?:[^/]+/)*(?:route|page|layout|template|loading|error|default)\\.${NEXTJS_SOURCE_FILE_EXTENSION_GROUP}$`);
|
|
40001
39773
|
const NON_PROJECT_PATH_PATTERN = /\/(?:node_modules|dist|build|\.next)\//;
|
|
40002
|
-
const REMIX_IMPORT_SOURCE_PATTERN = /^(?:@remix-run\/|@react-router\/|react-router(?:-dom)?$)/;
|
|
40003
|
-
const programImportsRemixRouter = (programNode) => (programNode.body ?? []).some((statement) => isNodeOfType(statement, "ImportDeclaration") && !isTypeOnlyImport(statement) && typeof statement.source?.value === "string" && REMIX_IMPORT_SOURCE_PATTERN.test(statement.source.value));
|
|
40004
|
-
const isImportMetaUrlAssetArgument = (urlArg) => {
|
|
40005
|
-
if (!isNodeOfType(urlArg, "NewExpression")) return false;
|
|
40006
|
-
if (!isNodeOfType(urlArg.callee, "Identifier") || urlArg.callee.name !== "URL") return false;
|
|
40007
|
-
const baseArg = urlArg.arguments?.[1];
|
|
40008
|
-
return isNodeOfType(baseArg, "MemberExpression") && isNodeOfType(baseArg.object, "MetaProperty") && isNodeOfType(baseArg.property, "Identifier") && baseArg.property.name === "url";
|
|
40009
|
-
};
|
|
40010
39774
|
const serverFetchWithoutRevalidate = defineRule({
|
|
40011
39775
|
id: "server-fetch-without-revalidate",
|
|
40012
39776
|
title: "Fetch without revalidate",
|
|
@@ -40026,7 +39790,7 @@ const serverFetchWithoutRevalidate = defineRule({
|
|
|
40026
39790
|
isServerSideFile = false;
|
|
40027
39791
|
return;
|
|
40028
39792
|
}
|
|
40029
|
-
isServerSideFile = !(node.body ?? []).some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === "use client")
|
|
39793
|
+
isServerSideFile = !(node.body ?? []).some((statement) => isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "Literal") && statement.expression.value === "use client");
|
|
40030
39794
|
},
|
|
40031
39795
|
CallExpression(node) {
|
|
40032
39796
|
if (!isServerSideFile) return;
|
|
@@ -40039,7 +39803,6 @@ const serverFetchWithoutRevalidate = defineRule({
|
|
|
40039
39803
|
if (objectExpressionHasSpread(optionsArg)) return;
|
|
40040
39804
|
}
|
|
40041
39805
|
const urlArg = node.arguments?.[0];
|
|
40042
|
-
if (isImportMetaUrlAssetArgument(urlArg)) return;
|
|
40043
39806
|
const urlText = isNodeOfType(urlArg, "Literal") && typeof urlArg.value === "string" ? `"${urlArg.value}"` : "url";
|
|
40044
39807
|
context.report({
|
|
40045
39808
|
node,
|
|
@@ -40268,7 +40031,7 @@ const serverNoMutableModuleState = defineRule({
|
|
|
40268
40031
|
if (node.kind === "let" || node.kind === "var") {
|
|
40269
40032
|
context.report({
|
|
40270
40033
|
node: declarator,
|
|
40271
|
-
message: `Module-scoped ${node.kind} "${variableName}"
|
|
40034
|
+
message: `Module-scoped ${node.kind} "${variableName}" leaks state between your users, since every request shares it.`
|
|
40272
40035
|
});
|
|
40273
40036
|
continue;
|
|
40274
40037
|
}
|
|
@@ -40329,29 +40092,25 @@ const GATE_LEADING_VERBS = new Set([
|
|
|
40329
40092
|
"authorize",
|
|
40330
40093
|
"authenticate"
|
|
40331
40094
|
]);
|
|
40332
|
-
const
|
|
40333
|
-
|
|
40334
|
-
|
|
40335
|
-
|
|
40336
|
-
|
|
40337
|
-
|
|
40338
|
-
|
|
40095
|
+
const isStartedPromiseBinding = (name, statements, beforeIndex) => {
|
|
40096
|
+
for (let index = 0; index < beforeIndex; index++) {
|
|
40097
|
+
const statement = statements[index];
|
|
40098
|
+
if (!isNodeOfType(statement, "VariableDeclaration")) continue;
|
|
40099
|
+
for (const declarator of statement.declarations ?? []) {
|
|
40100
|
+
if (!isNodeOfType(declarator.id, "Identifier")) continue;
|
|
40101
|
+
if (declarator.id.name !== name) continue;
|
|
40102
|
+
if (declarator.init && !isNodeOfType(declarator.init, "AwaitExpression")) return true;
|
|
40103
|
+
}
|
|
40339
40104
|
}
|
|
40340
40105
|
return false;
|
|
40341
40106
|
};
|
|
40342
|
-
const
|
|
40343
|
-
const isRequestScopedCallee = (callee) => {
|
|
40344
|
-
if (!isNodeOfType(callee, "Identifier")) return false;
|
|
40345
|
-
if (REQUEST_SCOPED_IMPORT_SOURCES.some((moduleSource) => getImportedNameFromModule(callee, callee.name, moduleSource) !== null)) return true;
|
|
40346
|
-
return getImportedNameFromModule(callee, callee.name, "next/server") === "connection";
|
|
40347
|
-
};
|
|
40348
|
-
const declarationAwaitsRequestScopedCall = (declaration) => {
|
|
40107
|
+
const declarationAwaitsStartedPromise = (declaration, statements, declarationIndex) => {
|
|
40349
40108
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
40350
40109
|
for (const declarator of declaration.declarations ?? []) {
|
|
40351
40110
|
const init = declarator.init;
|
|
40352
40111
|
if (!isNodeOfType(init, "AwaitExpression")) continue;
|
|
40353
40112
|
const argument = init.argument;
|
|
40354
|
-
if (isNodeOfType(argument, "
|
|
40113
|
+
if (isNodeOfType(argument, "Identifier") && isStartedPromiseBinding(argument.name, statements, declarationIndex)) return true;
|
|
40355
40114
|
}
|
|
40356
40115
|
return false;
|
|
40357
40116
|
};
|
|
@@ -40386,8 +40145,7 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
40386
40145
|
if (!isNodeOfType(nextStatement, "VariableDeclaration")) continue;
|
|
40387
40146
|
if (!declarationStartsWithAwait(nextStatement)) continue;
|
|
40388
40147
|
if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
|
|
40389
|
-
if (
|
|
40390
|
-
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
40148
|
+
if (declarationAwaitsStartedPromise(nextStatement, statements, statementIndex + 1)) continue;
|
|
40391
40149
|
if (declarationAwaitsGate(currentStatement)) continue;
|
|
40392
40150
|
context.report({
|
|
40393
40151
|
node: nextStatement,
|
|
@@ -41340,7 +41098,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
41340
41098
|
if (!isNodeOfType(callParent, "CallExpression")) return false;
|
|
41341
41099
|
if (callParent.callee === functionNode) return false;
|
|
41342
41100
|
if (isNodeOfType(callParent.callee, "MemberExpression") && !callParent.callee.computed && isNodeOfType(callParent.callee.property, "Identifier") && PROMISE_CONTINUATION_METHODS.has(callParent.callee.property.name)) return true;
|
|
41343
|
-
return isNodeOfType(callParent.callee, "Identifier") && HOOK_NAME_PATTERN
|
|
41101
|
+
return isNodeOfType(callParent.callee, "Identifier") && HOOK_NAME_PATTERN.test(callParent.callee.name) && !RENDER_SYNCHRONOUS_HOOK_NAMES.has(callParent.callee.name) && callParent.arguments?.[0] === functionNode;
|
|
41344
41102
|
};
|
|
41345
41103
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && REACT_HANDLER_PROP_PATTERN.test(node.name.name);
|
|
41346
41104
|
const isEventHandlerNamedProperty = (node) => isNodeOfType(node, "Property") && (isNodeOfType(node.key, "Identifier") && typeof node.key.name === "string" && REACT_HANDLER_PROP_PATTERN.test(node.key.name) || isNodeOfType(node.key, "Literal") && typeof node.key.value === "string" && REACT_HANDLER_PROP_PATTERN.test(node.key.value));
|
|
@@ -41368,11 +41126,11 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
41368
41126
|
if (isNodeOfType(parent, "ReturnStatement")) {
|
|
41369
41127
|
const outerFunction = findEnclosingFunction(parent);
|
|
41370
41128
|
const hookName = outerFunction ? getFunctionBindingName(outerFunction) : null;
|
|
41371
|
-
return Boolean(hookName && HOOK_NAME_PATTERN
|
|
41129
|
+
return Boolean(hookName && HOOK_NAME_PATTERN.test(hookName));
|
|
41372
41130
|
}
|
|
41373
41131
|
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === functionNode) {
|
|
41374
41132
|
const hookName = getFunctionBindingName(parent);
|
|
41375
|
-
return Boolean(hookName && HOOK_NAME_PATTERN
|
|
41133
|
+
return Boolean(hookName && HOOK_NAME_PATTERN.test(hookName));
|
|
41376
41134
|
}
|
|
41377
41135
|
return false;
|
|
41378
41136
|
};
|