oxlint-plugin-react-doctor 0.9.2-dev.3728102 → 0.9.2-dev.3bc63ea
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 +1 -1
- package/dist/index.js +569 -50
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -98,7 +98,7 @@ interface RuleContext extends Omit<BaseRuleContext, "getFilename"> {
|
|
|
98
98
|
}
|
|
99
99
|
//#endregion
|
|
100
100
|
//#region src/plugin/utils/capability.d.ts
|
|
101
|
-
declare const FRAMEWORK_TOKENS: readonly ["nextjs", "vite", "cra", "remix", "gatsby", "expo", "react-native", "tanstack-start", "preact", "unknown"];
|
|
101
|
+
declare const FRAMEWORK_TOKENS: readonly ["nextjs", "astro", "vite", "cra", "remix", "gatsby", "expo", "react-native", "tanstack-start", "preact", "unknown"];
|
|
102
102
|
type FrameworkToken = (typeof FRAMEWORK_TOKENS)[number];
|
|
103
103
|
type Capability = FrameworkToken | "react" | "remotion" | "pure-preact" | "react-native" | "server-actions" | "ssr" | "client-only" | "expo:54" | "nextjs:static-export" | "nextjs:15" | "nextjs:16" | "tailwind" | "tailwind:3.4" | "tailwind:4" | "zod" | "zod:4" | "mobx" | "mobx-react" | "mobx-react-lite" | "mobx-react-binding" | "mobx-react-binding-observer-memo-guard" | "mobx-react-observer-memo-guard" | "mobx-react-lite-observer-memo-guard" | "mobx-state-tree" | "mobx-react-observer" | "zustand" | "typescript" | "react-compiler" | "reanimated" | "reanimated:4" | "tanstack-query" | "valtio" | "i18n" | "styled-components" | "styled-components:6" | "three" | "r3f" | "react-router" | "react-router-framework" | "react-router:6.4" | "react-router:6.7" | "react-router:6.9" | "react-router:6.19" | "react-router:7" | "react-router:7.8" | "react-router:7.9" | "react-router:7.10" | "react-router:7.15" | "react-router:8" | "pre-es2023" | "target-blank-needs-explicit-protection" | "target-blank-needs-noreferrer" | `react:${number}` | `preact:${number}` | `remotion:${number}` | `valtio:${number}` | `mobx:${number}` | `zustand:${number}` | `three:${number}` | `r3f:${number}`;
|
|
104
104
|
type CapabilityQuery = (capability: Capability) => boolean;
|
package/dist/index.js
CHANGED
|
@@ -388,7 +388,7 @@ const SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?)$/i;
|
|
|
388
388
|
const SCRIPT_SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?|py|php)$/i;
|
|
389
389
|
const DATABASE_SOURCE_FILE_PATTERN = /\.(?:[cm]?[jt]sx?|py)$/i;
|
|
390
390
|
const SERVER_CONTEXT_PATTERN = /(?:^|\/)(?:api|backend|server|servers|middleware|route|routes|functions|lambdas|workers)(?:\/|$)|(?:^|\/)[^/]+\.server\.[cm]?[jt]sx?$/i;
|
|
391
|
-
const TEST_CONTEXT_PATTERN = /(?:^|\/)(?:__fixtures__|__mocks__|__tests__|__integration__|fixtures|mocks|test|tests|testdata|test-data|e2e|playwright|cypress|specs?)(?:\/|$)
|
|
391
|
+
const TEST_CONTEXT_PATTERN = /(?:^|\/)(?:__fixtures__|__mocks__|__tests__|__integration__|fixtures|mocks|test|tests|testdata|test-data|e2e|playwright|cypress|specs?)(?:\/|$)|^(?:test|spec)-[^/]+\.[cm]?[jt]sx?$|\.(?:test|spec|e2e|e2e-spec|integration-test|fixture|fixtures|stories|story)\.[cm]?[jt]sx?$|(?:^|\/)(?:playwright|cypress|vitest|jest|karma)[^/]*\.conf(?:ig)?\.[cm]?[jt]s$|(?:^|\/)(?:test_[^/]+|[^/]+_test|conftest)\.py$|\.env\.[^/]*(?:test|e2e)[^/]*$/i;
|
|
392
392
|
const BUILD_CONFIG_FILE_PATTERN = /(?:^|\/)(?:vite|next|nuxt|astro|remix|webpack|rollup|rspack|rsbuild|esbuild|tsup|metro|expo|babel|tailwind|postcss|svelte|farm|parcel|snowpack)[^/]*\.config\.[cm]?[jt]sx?$/i;
|
|
393
393
|
const BUILD_SCRIPT_CONTEXT_PATTERN = /(?:^|\/)scripts(?:\/|$)/i;
|
|
394
394
|
const DEMO_CONTEXT_PATTERN = /(?:^|\/)(?:examples?|tutorials?|demos?|samples?|playgrounds?)(?:\/|$)/i;
|
|
@@ -5887,6 +5887,7 @@ const isInlineFunctionExpression = (node) => Boolean(node && (isNodeOfType(node,
|
|
|
5887
5887
|
//#endregion
|
|
5888
5888
|
//#region src/plugin/rules/js-performance/async-await-in-loop.ts
|
|
5889
5889
|
const LOOP_STATEMENT_TYPES$1 = new Set(LOOP_TYPES);
|
|
5890
|
+
const ORDERED_OUTPUT_INSERTION_METHOD_NAMES = new Set(["push", "unshift"]);
|
|
5890
5891
|
const findFirstAwaitOutsideNestedFunctions = (block, skipNestedLoops = false) => {
|
|
5891
5892
|
let firstAwait = null;
|
|
5892
5893
|
walkAst(block, (child) => {
|
|
@@ -5953,7 +5954,151 @@ const isAwaitingManualPromiseWait = (awaitNode) => {
|
|
|
5953
5954
|
});
|
|
5954
5955
|
return isWaitLike;
|
|
5955
5956
|
};
|
|
5956
|
-
const
|
|
5957
|
+
const getRootObjectIdentifierName = (node) => {
|
|
5958
|
+
let current = node;
|
|
5959
|
+
while (isNodeOfType(current, "MemberExpression")) current = current.object;
|
|
5960
|
+
return isNodeOfType(current, "Identifier") ? current.name : null;
|
|
5961
|
+
};
|
|
5962
|
+
const isScopeWithinFunction = (candidateScope, functionScope) => {
|
|
5963
|
+
let currentScope = candidateScope;
|
|
5964
|
+
while (currentScope) {
|
|
5965
|
+
if (currentScope === functionScope) return true;
|
|
5966
|
+
currentScope = currentScope.parent;
|
|
5967
|
+
}
|
|
5968
|
+
return false;
|
|
5969
|
+
};
|
|
5970
|
+
const isSymbolDirectlyReturned = (symbol, callerFunction) => Boolean(callerFunction) && symbol.references.some((reference) => {
|
|
5971
|
+
const expressionRoot = findTransparentExpressionRoot(reference.identifier);
|
|
5972
|
+
const parent = expressionRoot.parent;
|
|
5973
|
+
return isNodeOfType(parent, "ReturnStatement") && parent.argument === expressionRoot && findEnclosingFunction$1(parent) === callerFunction;
|
|
5974
|
+
});
|
|
5975
|
+
const collectPatternBindingSymbolIds = (pattern, scopes, target) => {
|
|
5976
|
+
if (isNodeOfType(pattern, "Identifier")) {
|
|
5977
|
+
const symbol = scopes.symbolFor(pattern);
|
|
5978
|
+
if (symbol) target.add(symbol.id);
|
|
5979
|
+
return;
|
|
5980
|
+
}
|
|
5981
|
+
if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
5982
|
+
for (const property of pattern.properties ?? []) if (isNodeOfType(property, "Property") && property.value) collectPatternBindingSymbolIds(property.value, scopes, target);
|
|
5983
|
+
else if (isNodeOfType(property, "RestElement") && property.argument) collectPatternBindingSymbolIds(property.argument, scopes, target);
|
|
5984
|
+
return;
|
|
5985
|
+
}
|
|
5986
|
+
if (isNodeOfType(pattern, "ArrayPattern")) {
|
|
5987
|
+
for (const element of pattern.elements ?? []) if (element) collectPatternBindingSymbolIds(element, scopes, target);
|
|
5988
|
+
return;
|
|
5989
|
+
}
|
|
5990
|
+
if (isNodeOfType(pattern, "AssignmentPattern") && pattern.left) collectPatternBindingSymbolIds(pattern.left, scopes, target);
|
|
5991
|
+
};
|
|
5992
|
+
const collectReferencedSymbolIds = (expression, scopes) => {
|
|
5993
|
+
const referencedSymbolIds = /* @__PURE__ */ new Set();
|
|
5994
|
+
walkAst(expression, (child) => {
|
|
5995
|
+
if (child !== expression && isFunctionLike$1(child)) return false;
|
|
5996
|
+
if (!isNodeOfType(child, "Identifier")) return;
|
|
5997
|
+
const symbol = scopes.symbolFor(child);
|
|
5998
|
+
if (symbol) referencedSymbolIds.add(symbol.id);
|
|
5999
|
+
});
|
|
6000
|
+
return referencedSymbolIds;
|
|
6001
|
+
};
|
|
6002
|
+
const collectAwaitDerivedSymbolIds = (block, scopes) => {
|
|
6003
|
+
const awaitDerivedSymbolIds = /* @__PURE__ */ new Set();
|
|
6004
|
+
const bindingDependencies = [];
|
|
6005
|
+
walkAst(block, (child) => {
|
|
6006
|
+
if (child !== block && isFunctionLike$1(child)) return false;
|
|
6007
|
+
if (isNodeOfType(child, "VariableDeclarator") && child.id && child.init) {
|
|
6008
|
+
const declaredSymbolIds = /* @__PURE__ */ new Set();
|
|
6009
|
+
collectPatternBindingSymbolIds(child.id, scopes, declaredSymbolIds);
|
|
6010
|
+
if (containsDirectAwait(child.init)) for (const symbolId of declaredSymbolIds) awaitDerivedSymbolIds.add(symbolId);
|
|
6011
|
+
const referencedSymbolIds = collectReferencedSymbolIds(child.init, scopes);
|
|
6012
|
+
for (const declaredSymbolId of declaredSymbolIds) bindingDependencies.push({
|
|
6013
|
+
declaredSymbolId,
|
|
6014
|
+
referencedSymbolIds
|
|
6015
|
+
});
|
|
6016
|
+
return;
|
|
6017
|
+
}
|
|
6018
|
+
if (isNodeOfType(child, "AssignmentExpression") && child.left) {
|
|
6019
|
+
const assignedSymbolIds = /* @__PURE__ */ new Set();
|
|
6020
|
+
collectPatternBindingSymbolIds(child.left, scopes, assignedSymbolIds);
|
|
6021
|
+
if (containsDirectAwait(child.right)) for (const symbolId of assignedSymbolIds) awaitDerivedSymbolIds.add(symbolId);
|
|
6022
|
+
const referencedSymbolIds = collectReferencedSymbolIds(child.right, scopes);
|
|
6023
|
+
for (const assignedSymbolId of assignedSymbolIds) bindingDependencies.push({
|
|
6024
|
+
declaredSymbolId: assignedSymbolId,
|
|
6025
|
+
referencedSymbolIds
|
|
6026
|
+
});
|
|
6027
|
+
}
|
|
6028
|
+
});
|
|
6029
|
+
let didGrow = true;
|
|
6030
|
+
while (didGrow) {
|
|
6031
|
+
didGrow = false;
|
|
6032
|
+
for (const { declaredSymbolId, referencedSymbolIds } of bindingDependencies) {
|
|
6033
|
+
if (awaitDerivedSymbolIds.has(declaredSymbolId)) continue;
|
|
6034
|
+
for (const referencedSymbolId of referencedSymbolIds) {
|
|
6035
|
+
if (!awaitDerivedSymbolIds.has(referencedSymbolId)) continue;
|
|
6036
|
+
awaitDerivedSymbolIds.add(declaredSymbolId);
|
|
6037
|
+
didGrow = true;
|
|
6038
|
+
break;
|
|
6039
|
+
}
|
|
6040
|
+
}
|
|
6041
|
+
}
|
|
6042
|
+
return awaitDerivedSymbolIds;
|
|
6043
|
+
};
|
|
6044
|
+
const getSimpleParameterIdentifier = (parameter) => {
|
|
6045
|
+
if (isNodeOfType(parameter, "Identifier")) return parameter;
|
|
6046
|
+
if (isNodeOfType(parameter, "AssignmentPattern") && isNodeOfType(parameter.left, "Identifier")) return parameter.left;
|
|
6047
|
+
return null;
|
|
6048
|
+
};
|
|
6049
|
+
const doesAwaitedLocalCallInsertAwaitDerivedOutput = (awaitNode, context) => {
|
|
6050
|
+
if (!isNodeOfType(awaitNode, "AwaitExpression")) return false;
|
|
6051
|
+
const callExpression = awaitNode.argument;
|
|
6052
|
+
if (!isNodeOfType(callExpression, "CallExpression")) return false;
|
|
6053
|
+
const localFunction = resolveStaticLocalCallFunction(callExpression, context.scopes);
|
|
6054
|
+
if (!isFunctionLike$1(localFunction)) return false;
|
|
6055
|
+
const callerFunction = findEnclosingFunction$1(callExpression);
|
|
6056
|
+
const functionScope = context.scopes.ownScopeFor(localFunction);
|
|
6057
|
+
if (!functionScope) return false;
|
|
6058
|
+
const awaitDerivedSymbolIds = collectAwaitDerivedSymbolIds(localFunction.body, context.scopes);
|
|
6059
|
+
const externallyReachableParameterSymbolIds = /* @__PURE__ */ new Set();
|
|
6060
|
+
for (const [parameterIndex, parameter] of localFunction.params.entries()) {
|
|
6061
|
+
const parameterIdentifier = getSimpleParameterIdentifier(parameter);
|
|
6062
|
+
if (!parameterIdentifier) continue;
|
|
6063
|
+
const argument = callExpression.arguments[parameterIndex];
|
|
6064
|
+
if (!isNodeOfType(argument, "Identifier")) continue;
|
|
6065
|
+
const parameterSymbol = context.scopes.symbolFor(parameterIdentifier);
|
|
6066
|
+
const argumentSymbol = context.scopes.symbolFor(argument);
|
|
6067
|
+
if (parameterSymbol && argumentSymbol && (isSymbolDirectlyReturned(argumentSymbol, callerFunction) || argumentSymbol.id === parameterSymbol.id)) externallyReachableParameterSymbolIds.add(parameterSymbol.id);
|
|
6068
|
+
}
|
|
6069
|
+
let doesInsertAwaitDerivedOutput = false;
|
|
6070
|
+
walkAst(localFunction.body, (child) => {
|
|
6071
|
+
if (doesInsertAwaitDerivedOutput) return false;
|
|
6072
|
+
if (child !== localFunction.body && isFunctionLike$1(child)) return false;
|
|
6073
|
+
if (!isNodeOfType(child, "CallExpression")) return;
|
|
6074
|
+
const callee = child.callee;
|
|
6075
|
+
if (!isNodeOfType(callee, "MemberExpression") || callee.computed || !isNodeOfType(callee.property, "Identifier") || !ORDERED_OUTPUT_INSERTION_METHOD_NAMES.has(callee.property.name)) return;
|
|
6076
|
+
let doesMutationConsumeAwaitedValue = false;
|
|
6077
|
+
for (const mutationArgument of child.arguments ?? []) {
|
|
6078
|
+
if (containsDirectAwait(mutationArgument)) {
|
|
6079
|
+
doesMutationConsumeAwaitedValue = true;
|
|
6080
|
+
break;
|
|
6081
|
+
}
|
|
6082
|
+
const referencedSymbolIds = collectReferencedSymbolIds(mutationArgument, context.scopes);
|
|
6083
|
+
for (const referencedSymbolId of referencedSymbolIds) if (awaitDerivedSymbolIds.has(referencedSymbolId)) {
|
|
6084
|
+
doesMutationConsumeAwaitedValue = true;
|
|
6085
|
+
break;
|
|
6086
|
+
}
|
|
6087
|
+
if (doesMutationConsumeAwaitedValue) break;
|
|
6088
|
+
}
|
|
6089
|
+
if (!doesMutationConsumeAwaitedValue) return;
|
|
6090
|
+
let receiverIdentifier = callee.object;
|
|
6091
|
+
while (isNodeOfType(receiverIdentifier, "MemberExpression")) receiverIdentifier = receiverIdentifier.object;
|
|
6092
|
+
if (!isNodeOfType(receiverIdentifier, "Identifier")) return;
|
|
6093
|
+
const receiverSymbol = context.scopes.symbolFor(receiverIdentifier);
|
|
6094
|
+
if (receiverSymbol && (externallyReachableParameterSymbolIds.has(receiverSymbol.id) || !isScopeWithinFunction(receiverSymbol.scope, functionScope) && isSymbolDirectlyReturned(receiverSymbol, callerFunction))) {
|
|
6095
|
+
doesInsertAwaitDerivedOutput = true;
|
|
6096
|
+
return false;
|
|
6097
|
+
}
|
|
6098
|
+
});
|
|
6099
|
+
return doesInsertAwaitDerivedOutput;
|
|
6100
|
+
};
|
|
6101
|
+
const isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode) || doesAwaitedLocalCallInsertAwaitDerivedOutput(awaitNode, context);
|
|
5957
6102
|
const collectPatternIdentifiers = (pattern, target) => {
|
|
5958
6103
|
if (isNodeOfType(pattern, "Identifier")) target.add(pattern.name);
|
|
5959
6104
|
else if (isNodeOfType(pattern, "ObjectPattern")) {
|
|
@@ -6099,11 +6244,6 @@ const loopBodyHasAwaitDependentEarlyExit = (block, loopLabelName) => {
|
|
|
6099
6244
|
});
|
|
6100
6245
|
return hasAwaitDependentExit;
|
|
6101
6246
|
};
|
|
6102
|
-
const getRootObjectIdentifierName = (node) => {
|
|
6103
|
-
let current = node;
|
|
6104
|
-
while (isNodeOfType(current, "MemberExpression")) current = current.object;
|
|
6105
|
-
return isNodeOfType(current, "Identifier") ? current.name : null;
|
|
6106
|
-
};
|
|
6107
6247
|
const MUTATING_ARRAY_METHOD_NAMES$2 = new Set([
|
|
6108
6248
|
...ARRAY_MUTATION_METHOD_NAMES,
|
|
6109
6249
|
"pop",
|
|
@@ -8675,7 +8815,7 @@ const resolveStableOptionsObject = (expression, observedPropertyNames, scopes, r
|
|
|
8675
8815
|
//#endregion
|
|
8676
8816
|
//#region src/plugin/rules/state-and-effects/class-component-missing-component-will-unmount-teardown.ts
|
|
8677
8817
|
const MESSAGE$89 = "This class registers a listener or timer during mount without a matching teardown on every unmount path, so it can keep firing after the component unmounts; release it in `componentWillUnmount`.";
|
|
8678
|
-
const GLOBAL_OBJECT_NAMES$
|
|
8818
|
+
const GLOBAL_OBJECT_NAMES$4 = new Set([
|
|
8679
8819
|
"window",
|
|
8680
8820
|
"globalThis",
|
|
8681
8821
|
"global",
|
|
@@ -8834,14 +8974,14 @@ const getTimerIdentifierAliasName = (identifier, scopes, visitedSymbolIds = /* @
|
|
|
8834
8974
|
const property = symbol.bindingIdentifier.parent;
|
|
8835
8975
|
const objectPattern = property?.parent;
|
|
8836
8976
|
const source = declaration.init ? stripParenExpression(declaration.init) : null;
|
|
8837
|
-
if (isNodeOfType(property, "Property") && isNodeOfType(objectPattern, "ObjectPattern") && isNodeOfType(source, "Identifier") && GLOBAL_OBJECT_NAMES$
|
|
8977
|
+
if (isNodeOfType(property, "Property") && isNodeOfType(objectPattern, "ObjectPattern") && isNodeOfType(source, "Identifier") && GLOBAL_OBJECT_NAMES$4.has(source.name) && scopes.isGlobalReference(source)) return getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
8838
8978
|
return null;
|
|
8839
8979
|
}
|
|
8840
8980
|
const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
|
|
8841
8981
|
if (isNodeOfType(initializer, "Identifier")) return getTimerIdentifierAliasName(initializer, scopes, visitedSymbolIds);
|
|
8842
8982
|
if (!isNodeOfType(initializer, "MemberExpression")) return null;
|
|
8843
8983
|
const receiver = stripParenExpression(initializer.object);
|
|
8844
|
-
return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$
|
|
8984
|
+
return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$4.has(receiver.name) && scopes.isGlobalReference(receiver) ? getStaticPropertyName(initializer) : null;
|
|
8845
8985
|
};
|
|
8846
8986
|
const getTimerCalleeName = (node, scopes) => {
|
|
8847
8987
|
if (!isNodeOfType(node, "CallExpression")) return null;
|
|
@@ -8849,7 +8989,7 @@ const getTimerCalleeName = (node, scopes) => {
|
|
|
8849
8989
|
if (getBareCalleeName(node) && isNodeOfType(callee, "Identifier")) return getTimerIdentifierAliasName(callee, scopes);
|
|
8850
8990
|
if (!isNodeOfType(callee, "MemberExpression")) return null;
|
|
8851
8991
|
const receiver = stripParenExpression(callee.object);
|
|
8852
|
-
if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$
|
|
8992
|
+
if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$4.has(receiver.name) || findVariableInitializer(receiver, receiver.name)) return null;
|
|
8853
8993
|
return getStaticPropertyName(callee);
|
|
8854
8994
|
};
|
|
8855
8995
|
const getClassMemberName$1 = (member) => {
|
|
@@ -10045,6 +10185,12 @@ const FOCUS_FORWARDING_METHOD_NAMES = new Set([
|
|
|
10045
10185
|
"preventDefault",
|
|
10046
10186
|
"stopImmediatePropagation"
|
|
10047
10187
|
]);
|
|
10188
|
+
const DOM_QUERY_METHOD_NAMES = new Set(["getElementById", "querySelector"]);
|
|
10189
|
+
const GLOBAL_OBJECT_NAMES$3 = new Set([
|
|
10190
|
+
"global",
|
|
10191
|
+
"globalThis",
|
|
10192
|
+
"window"
|
|
10193
|
+
]);
|
|
10048
10194
|
const isFocusForwardingCall = (node) => {
|
|
10049
10195
|
if (!node) return false;
|
|
10050
10196
|
const inner = isNodeOfType(node, "ChainExpression") ? node.expression : node;
|
|
@@ -10054,6 +10200,12 @@ const isFocusForwardingCall = (node) => {
|
|
|
10054
10200
|
if (!isNodeOfType(callee.property, "Identifier")) return false;
|
|
10055
10201
|
return FOCUS_FORWARDING_METHOD_NAMES.has(callee.property.name);
|
|
10056
10202
|
};
|
|
10203
|
+
const isGlobalDocumentExpression = (expression, scopes) => {
|
|
10204
|
+
const candidate = stripParenExpression(expression);
|
|
10205
|
+
if (isNodeOfType(candidate, "Identifier")) return candidate.name === "document" && scopes.isGlobalReference(candidate);
|
|
10206
|
+
if (!isNodeOfType(candidate, "MemberExpression") || !isNodeOfType(candidate.object, "Identifier") || !isNodeOfType(candidate.property, "Identifier") || candidate.property.name !== "document") return false;
|
|
10207
|
+
return GLOBAL_OBJECT_NAMES$3.has(candidate.object.name) && scopes.isGlobalReference(candidate.object);
|
|
10208
|
+
};
|
|
10057
10209
|
const isFocusForwardingFunctionBody = (body) => {
|
|
10058
10210
|
if (!body) return false;
|
|
10059
10211
|
if (isFocusForwardingCall(body)) return true;
|
|
@@ -10073,19 +10225,70 @@ const resolveHandlerFunction$2 = (attribute) => {
|
|
|
10073
10225
|
return resolveHandlerFunctionExpression(attribute.value.expression);
|
|
10074
10226
|
};
|
|
10075
10227
|
const resolveHandlerFunctionExpression = (handlerExpression) => {
|
|
10076
|
-
let expression = handlerExpression;
|
|
10228
|
+
let expression = stripParenExpression(handlerExpression);
|
|
10077
10229
|
if (isNodeOfType(expression, "Identifier")) {
|
|
10078
10230
|
const binding = findVariableInitializer(expression, expression.name);
|
|
10079
10231
|
if (!binding?.initializer) return null;
|
|
10080
|
-
expression = binding.initializer;
|
|
10232
|
+
expression = stripParenExpression(binding.initializer);
|
|
10081
10233
|
}
|
|
10082
10234
|
if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "FunctionDeclaration")) return expression;
|
|
10083
10235
|
return null;
|
|
10084
10236
|
};
|
|
10085
|
-
const
|
|
10237
|
+
const isEmptyReturn = (statement) => isNodeOfType(statement, "ReturnStatement") && statement.argument === null;
|
|
10238
|
+
const isClosestEarlyReturn = (statement) => {
|
|
10239
|
+
if (!isNodeOfType(statement, "IfStatement") || statement.alternate) return false;
|
|
10240
|
+
const consequent = statement.consequent;
|
|
10241
|
+
if (!(isNodeOfType(consequent, "BlockStatement") ? consequent.body.length === 1 && isEmptyReturn(consequent.body[0]) : isEmptyReturn(consequent))) return false;
|
|
10242
|
+
const test = stripParenExpression(statement.test);
|
|
10243
|
+
const call = isNodeOfType(test, "ChainExpression") ? test.expression : test;
|
|
10244
|
+
if (!isNodeOfType(call, "CallExpression") || call.arguments.length !== 1) return false;
|
|
10245
|
+
const callee = stripParenExpression(call.callee);
|
|
10246
|
+
const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : null;
|
|
10247
|
+
return isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "closest" && isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.object, "Identifier") && isNodeOfType(receiver.property, "Identifier") && receiver.property.name === "target" && isNodeOfType(call.arguments[0], "Literal") && typeof call.arguments[0].value === "string";
|
|
10248
|
+
};
|
|
10249
|
+
const isStaticSelectorExpression = (expression) => {
|
|
10250
|
+
const candidate = stripParenExpression(expression);
|
|
10251
|
+
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "Literal")) return true;
|
|
10252
|
+
return isNodeOfType(candidate, "TemplateLiteral") && candidate.expressions.every((innerExpression) => isStaticSelectorExpression(innerExpression));
|
|
10253
|
+
};
|
|
10254
|
+
const getDomQueryVariableName = (statement, scopes) => {
|
|
10255
|
+
if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const" || statement.declarations.length !== 1) return null;
|
|
10256
|
+
const declaration = statement.declarations[0];
|
|
10257
|
+
if (!declaration || !isNodeOfType(declaration.id, "Identifier") || !declaration.init) return null;
|
|
10258
|
+
const initializer = stripParenExpression(declaration.init);
|
|
10259
|
+
if (!isNodeOfType(initializer, "CallExpression") || initializer.arguments.length !== 1 || isNodeOfType(initializer.arguments[0], "SpreadElement") || !isStaticSelectorExpression(initializer.arguments[0])) return null;
|
|
10260
|
+
const callee = stripParenExpression(initializer.callee);
|
|
10261
|
+
if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || !DOM_QUERY_METHOD_NAMES.has(callee.property.name) || !isGlobalDocumentExpression(callee.object, scopes)) return null;
|
|
10262
|
+
return declaration.id.name;
|
|
10263
|
+
};
|
|
10264
|
+
const isFocusCallOnVariable = (statement, variableName) => {
|
|
10265
|
+
if (!isNodeOfType(statement, "ExpressionStatement")) return false;
|
|
10266
|
+
const expression = stripParenExpression(statement.expression);
|
|
10267
|
+
if (!isNodeOfType(expression, "CallExpression")) return false;
|
|
10268
|
+
const callee = stripParenExpression(expression.callee);
|
|
10269
|
+
if (!isNodeOfType(callee, "MemberExpression")) return false;
|
|
10270
|
+
const receiver = stripParenExpression(callee.object);
|
|
10271
|
+
return isNodeOfType(receiver, "Identifier") && receiver.name === variableName && isNodeOfType(callee.property, "Identifier") && callee.property.name === "focus" && expression.arguments.length === 0;
|
|
10272
|
+
};
|
|
10273
|
+
const isConditionalFocusForwardingHandler = (attribute, scopes) => {
|
|
10274
|
+
if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
|
|
10275
|
+
const expression = stripParenExpression(attribute.value.expression);
|
|
10276
|
+
if (!isNodeOfType(expression, "ConditionalExpression")) return false;
|
|
10277
|
+
const consequent = stripParenExpression(expression.consequent);
|
|
10278
|
+
const alternate = stripParenExpression(expression.alternate);
|
|
10279
|
+
const nullishBranch = isNullishExpression$2(consequent) ? consequent : alternate;
|
|
10280
|
+
if (!isNullishExpression$2(nullishBranch) || isNodeOfType(nullishBranch, "Identifier") && !scopes.isGlobalReference(nullishBranch)) return false;
|
|
10281
|
+
const handlerFunction = resolveHandlerFunctionExpression(nullishBranch === consequent ? alternate : consequent);
|
|
10282
|
+
if (!handlerFunction || !isNodeOfType(handlerFunction.body, "BlockStatement")) return false;
|
|
10283
|
+
const [guard, query, focus, ...rest] = handlerFunction.body.body;
|
|
10284
|
+
if (!guard || !query || !focus || rest.length > 0 || !isClosestEarlyReturn(guard)) return false;
|
|
10285
|
+
const queryVariableName = getDomQueryVariableName(query, scopes);
|
|
10286
|
+
return Boolean(queryVariableName && isFocusCallOnVariable(focus, queryVariableName));
|
|
10287
|
+
};
|
|
10288
|
+
const isFocusForwardingHandler = (attribute, scopes) => {
|
|
10289
|
+
if (isConditionalFocusForwardingHandler(attribute, scopes)) return true;
|
|
10086
10290
|
const handlerFunction = resolveHandlerFunction$2(attribute);
|
|
10087
|
-
|
|
10088
|
-
return isFocusForwardingFunctionBody(handlerFunction.body ?? null);
|
|
10291
|
+
return Boolean(handlerFunction && isFocusForwardingFunctionBody(handlerFunction.body ?? null));
|
|
10089
10292
|
};
|
|
10090
10293
|
const COMPOSITE_ITEM_ROLES$1 = new Set([
|
|
10091
10294
|
"option",
|
|
@@ -10155,7 +10358,7 @@ const clickEventsHaveKeyEvents = defineRule({
|
|
|
10155
10358
|
const spreadOnClickExpression = CLICK_HANDLERS.map((name) => spreadEventValues.get(name.toLowerCase())).find((expression) => expression !== void 0);
|
|
10156
10359
|
if (!onClick && !spreadOnClickExpression) return;
|
|
10157
10360
|
if (onClick && isPureEventBlockerHandler(onClick)) return;
|
|
10158
|
-
if (onClick && isFocusForwardingHandler(onClick)) return;
|
|
10361
|
+
if (onClick && isFocusForwardingHandler(onClick, context.scopes)) return;
|
|
10159
10362
|
const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
|
|
10160
10363
|
if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
|
|
10161
10364
|
if (hasCompositeItemRole(node)) return;
|
|
@@ -30471,7 +30674,8 @@ const STRING_TYPED_PROPERTY_NAMES = new Set([
|
|
|
30471
30674
|
"code",
|
|
30472
30675
|
"label",
|
|
30473
30676
|
"slug",
|
|
30474
|
-
"prefix"
|
|
30677
|
+
"prefix",
|
|
30678
|
+
"__html"
|
|
30475
30679
|
]);
|
|
30476
30680
|
const STRING_TYPED_IDENTIFIER_SUFFIXES = [
|
|
30477
30681
|
"Text",
|
|
@@ -30589,13 +30793,25 @@ const STRING_TYPED_IDENTIFIER_NAMES = new Set([
|
|
|
30589
30793
|
"title"
|
|
30590
30794
|
]);
|
|
30591
30795
|
const STRING_RETURNING_CALLEE_PREFIX_PATTERN = /^(?:normalize|format|stringify|serialize)/;
|
|
30796
|
+
const FRESH_ARRAY_METHOD_NAMES$2 = new Set([
|
|
30797
|
+
"concat",
|
|
30798
|
+
"filter",
|
|
30799
|
+
"flat",
|
|
30800
|
+
"flatMap",
|
|
30801
|
+
"map",
|
|
30802
|
+
"slice",
|
|
30803
|
+
"split"
|
|
30804
|
+
]);
|
|
30592
30805
|
const isLikelyStringReceiver = (receiver) => {
|
|
30593
30806
|
if (!receiver) return false;
|
|
30807
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
30808
|
+
if (unwrappedReceiver !== receiver) return isLikelyStringReceiver(unwrappedReceiver);
|
|
30594
30809
|
if (isNodeOfType(receiver, "Literal") && typeof receiver.value === "string") return true;
|
|
30595
30810
|
if (isNodeOfType(receiver, "TemplateLiteral")) return true;
|
|
30596
30811
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && receiver.callee.name === "String") return true;
|
|
30597
30812
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && STRING_RETURNING_METHODS.has(receiver.callee.property.name)) return true;
|
|
30598
30813
|
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "Identifier") && STRING_RETURNING_CALLEE_PREFIX_PATTERN.test(receiver.callee.name)) return true;
|
|
30814
|
+
if (isNodeOfType(receiver, "CallExpression") && isNodeOfType(receiver.callee, "MemberExpression") && isNodeOfType(receiver.callee.property, "Identifier") && (receiver.callee.property.name === "concat" || receiver.callee.property.name === "slice") && isLikelyStringReceiver(receiver.callee.object)) return true;
|
|
30599
30815
|
if (isNodeOfType(receiver, "MemberExpression") && isNodeOfType(receiver.property, "Identifier")) {
|
|
30600
30816
|
if (STRING_TYPED_PROPERTY_NAMES.has(receiver.property.name)) return true;
|
|
30601
30817
|
}
|
|
@@ -30613,8 +30829,46 @@ const isLikelyStringReceiver = (receiver) => {
|
|
|
30613
30829
|
}
|
|
30614
30830
|
if (isNodeOfType(receiver, "BinaryExpression") && receiver.operator === "+") return isLikelyStringReceiver(receiver.left) || isLikelyStringReceiver(receiver.right);
|
|
30615
30831
|
if (isNodeOfType(receiver, "ConditionalExpression")) return isLikelyStringReceiver(receiver.consequent) && isLikelyStringReceiver(receiver.alternate);
|
|
30832
|
+
if (isNodeOfType(receiver, "LogicalExpression")) return isLikelyStringReceiver(receiver.left) && isLikelyStringReceiver(receiver.right);
|
|
30616
30833
|
return false;
|
|
30617
30834
|
};
|
|
30835
|
+
const isFreshArrayReceiver = (receiver) => {
|
|
30836
|
+
const unwrappedReceiver = stripParenExpression(receiver);
|
|
30837
|
+
if (unwrappedReceiver !== receiver) return isFreshArrayReceiver(unwrappedReceiver);
|
|
30838
|
+
if (!isNodeOfType(receiver, "CallExpression") || !isNodeOfType(receiver.callee, "MemberExpression") || !isNodeOfType(receiver.callee.property, "Identifier")) return false;
|
|
30839
|
+
if (!FRESH_ARRAY_METHOD_NAMES$2.has(receiver.callee.property.name)) return false;
|
|
30840
|
+
if (receiver.callee.property.name === "split") return isLikelyStringReceiver(receiver.callee.object);
|
|
30841
|
+
const sourceReceiver = stripParenExpression(receiver.callee.object);
|
|
30842
|
+
return isKnownNativeArrayReceiver(sourceReceiver) || isFreshArrayReceiver(sourceReceiver);
|
|
30843
|
+
};
|
|
30844
|
+
const isSmallRestHelperOmissionList = (node) => {
|
|
30845
|
+
if (!node) return false;
|
|
30846
|
+
const candidate = stripParenExpression(node);
|
|
30847
|
+
if (!isNodeOfType(candidate, "ArrayExpression")) return false;
|
|
30848
|
+
const elements = candidate.elements ?? [];
|
|
30849
|
+
return elements.length <= 8 && elements.every((element) => element === null || !isNodeOfType(element, "SpreadElement"));
|
|
30850
|
+
};
|
|
30851
|
+
const isTypeScriptRestHelperLookup = (lookupCall, receiver, scopes) => {
|
|
30852
|
+
if (!isNodeOfType(receiver, "Identifier")) return false;
|
|
30853
|
+
const enclosingFunction = findEnclosingFunction$1(lookupCall);
|
|
30854
|
+
if (!isNodeOfType(enclosingFunction, "FunctionExpression") || !isNodeOfType(enclosingFunction.params?.[1], "Identifier") || enclosingFunction.params[1].name !== receiver.name) return false;
|
|
30855
|
+
let bindingIdentifier = null;
|
|
30856
|
+
let ancestor = enclosingFunction.parent;
|
|
30857
|
+
while (ancestor && !isFunctionLike$1(ancestor)) {
|
|
30858
|
+
if (isNodeOfType(ancestor, "VariableDeclarator") && isNodeOfType(ancestor.id, "Identifier") && ancestor.id.name === "__rest") {
|
|
30859
|
+
bindingIdentifier = ancestor.id;
|
|
30860
|
+
break;
|
|
30861
|
+
}
|
|
30862
|
+
ancestor = ancestor.parent;
|
|
30863
|
+
}
|
|
30864
|
+
if (!bindingIdentifier) return false;
|
|
30865
|
+
const helperSymbol = scopes.symbolFor(bindingIdentifier);
|
|
30866
|
+
if (!helperSymbol || helperSymbol.references.length === 0) return false;
|
|
30867
|
+
return helperSymbol.references.every((reference) => {
|
|
30868
|
+
const callExpression = reference.identifier.parent;
|
|
30869
|
+
return isNodeOfType(callExpression, "CallExpression") && callExpression.callee === reference.identifier && isSmallRestHelperOmissionList(callExpression.arguments?.[1]);
|
|
30870
|
+
});
|
|
30871
|
+
};
|
|
30618
30872
|
const INDEX_LIKE_IDENTIFIER_NAMES = new Set([
|
|
30619
30873
|
"i",
|
|
30620
30874
|
"j",
|
|
@@ -31211,6 +31465,8 @@ const jsSetMapLookups = defineRule({
|
|
|
31211
31465
|
const query = node.arguments[0];
|
|
31212
31466
|
if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
|
|
31213
31467
|
if (isLikelyStringReceiver(receiver)) return;
|
|
31468
|
+
if (isFreshArrayReceiver(receiver)) return;
|
|
31469
|
+
if (isTypeScriptRestHelperLookup(node, receiver, context.scopes)) return;
|
|
31214
31470
|
if (isSmallInlineLiteralArray(receiver)) return;
|
|
31215
31471
|
if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
|
|
31216
31472
|
if (isSmallFixedListMember(receiver)) return;
|
|
@@ -46652,6 +46908,101 @@ const isLoopCounterDeclarator = (declarator, referenceNode, indexName) => {
|
|
|
46652
46908
|
}
|
|
46653
46909
|
return isBindingReassignedOrMutated(referenceNode, indexName);
|
|
46654
46910
|
};
|
|
46911
|
+
const findEnclosingWhileLoop = (node) => {
|
|
46912
|
+
let current = node.parent;
|
|
46913
|
+
while (current) {
|
|
46914
|
+
if (isNodeOfType(current, "WhileStatement") || isNodeOfType(current, "DoWhileStatement")) return current;
|
|
46915
|
+
if (isFunctionLike$1(current) || isNodeOfType(current, "Program")) return null;
|
|
46916
|
+
current = current.parent;
|
|
46917
|
+
}
|
|
46918
|
+
return null;
|
|
46919
|
+
};
|
|
46920
|
+
const isStaticMemberChain = (expression) => {
|
|
46921
|
+
const candidate = stripParenExpression(expression);
|
|
46922
|
+
if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "ThisExpression")) return true;
|
|
46923
|
+
return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isNodeOfType(candidate.property, "Identifier") && isStaticMemberChain(candidate.object));
|
|
46924
|
+
};
|
|
46925
|
+
const findLengthBoundCollections = (expression) => {
|
|
46926
|
+
const collections = [];
|
|
46927
|
+
walkAst(expression, (child) => {
|
|
46928
|
+
if (isNodeOfType(child, "MemberExpression") && !child.computed && isStaticMemberChain(child.object) && isNodeOfType(child.property, "Identifier") && child.property.name === "length") collections.push(child.object);
|
|
46929
|
+
});
|
|
46930
|
+
return collections;
|
|
46931
|
+
};
|
|
46932
|
+
const areSameBoundStaticMemberChains = (first, second) => isStaticMemberChain(first) && isStaticMemberChain(second) && areExpressionsStructurallyEqual(first, second, { areIdentifiersEqual: (firstIdentifier, secondIdentifier) => {
|
|
46933
|
+
if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
|
|
46934
|
+
const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
|
|
46935
|
+
const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
|
|
46936
|
+
return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
|
|
46937
|
+
} });
|
|
46938
|
+
const RELATIONAL_BOUND_OPERATORS = new Set([
|
|
46939
|
+
"<",
|
|
46940
|
+
"<=",
|
|
46941
|
+
">",
|
|
46942
|
+
">="
|
|
46943
|
+
]);
|
|
46944
|
+
const loopTestBoundsCounterByLength = (expression, indexName, bindingIdentifier) => {
|
|
46945
|
+
const readsCounter = (candidate) => {
|
|
46946
|
+
let didReadCounter = false;
|
|
46947
|
+
walkAst(candidate, (child) => {
|
|
46948
|
+
if (didReadCounter) return false;
|
|
46949
|
+
if (isNodeOfType(child, "Identifier") && child.name === indexName && findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
|
|
46950
|
+
didReadCounter = true;
|
|
46951
|
+
return false;
|
|
46952
|
+
}
|
|
46953
|
+
});
|
|
46954
|
+
return didReadCounter;
|
|
46955
|
+
};
|
|
46956
|
+
const readsLength = (candidate) => {
|
|
46957
|
+
let didReadLength = false;
|
|
46958
|
+
walkAst(candidate, (child) => {
|
|
46959
|
+
if (didReadLength) return false;
|
|
46960
|
+
if (isNodeOfType(child, "MemberExpression") && !child.computed && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
|
|
46961
|
+
didReadLength = true;
|
|
46962
|
+
return false;
|
|
46963
|
+
}
|
|
46964
|
+
});
|
|
46965
|
+
return didReadLength;
|
|
46966
|
+
};
|
|
46967
|
+
let didFindLengthBound = false;
|
|
46968
|
+
walkAst(expression, (child) => {
|
|
46969
|
+
if (didFindLengthBound) return false;
|
|
46970
|
+
if (!isNodeOfType(child, "BinaryExpression") || !RELATIONAL_BOUND_OPERATORS.has(child.operator)) return;
|
|
46971
|
+
if (readsCounter(child.left) && readsLength(child.right) || readsCounter(child.right) && readsLength(child.left)) {
|
|
46972
|
+
didFindLengthBound = true;
|
|
46973
|
+
return false;
|
|
46974
|
+
}
|
|
46975
|
+
});
|
|
46976
|
+
return didFindLengthBound;
|
|
46977
|
+
};
|
|
46978
|
+
const isDataIndexedWhileLoopCounter = (referenceNode, bindingIdentifier, indexName) => {
|
|
46979
|
+
if (!INDEX_PARAMETER_NAMES.has(indexName)) return false;
|
|
46980
|
+
const loop = findEnclosingWhileLoop(referenceNode);
|
|
46981
|
+
if (!loop) return false;
|
|
46982
|
+
let doesTestReadCounter = false;
|
|
46983
|
+
walkAst(loop.test, (child) => {
|
|
46984
|
+
if (doesTestReadCounter) return false;
|
|
46985
|
+
if (!isNodeOfType(child, "Identifier") || child.name !== indexName) return;
|
|
46986
|
+
if (findVariableInitializer(child, indexName)?.bindingIdentifier === bindingIdentifier) {
|
|
46987
|
+
doesTestReadCounter = true;
|
|
46988
|
+
return false;
|
|
46989
|
+
}
|
|
46990
|
+
});
|
|
46991
|
+
if (!doesTestReadCounter) return false;
|
|
46992
|
+
const lengthBoundCollections = findLengthBoundCollections(loop.test);
|
|
46993
|
+
if (lengthBoundCollections.length === 0) return false;
|
|
46994
|
+
let didFindIndexedCollectionRead = false;
|
|
46995
|
+
walkAst(loop.body, (child) => {
|
|
46996
|
+
if (didFindIndexedCollectionRead) return false;
|
|
46997
|
+
if (isFunctionLike$1(child)) return false;
|
|
46998
|
+
if (!isNodeOfType(child, "MemberExpression") || !child.computed || !isNodeOfType(child.property, "Identifier") || child.property.name !== indexName) return;
|
|
46999
|
+
if (findVariableInitializer(child.property, indexName)?.bindingIdentifier === bindingIdentifier && lengthBoundCollections.some((collection) => areSameBoundStaticMemberChains(collection, child.object))) {
|
|
47000
|
+
didFindIndexedCollectionRead = true;
|
|
47001
|
+
return false;
|
|
47002
|
+
}
|
|
47003
|
+
});
|
|
47004
|
+
return didFindIndexedCollectionRead;
|
|
47005
|
+
};
|
|
46655
47006
|
/**
|
|
46656
47007
|
* Resolves whether an identifier is PROVABLY the positional array
|
|
46657
47008
|
* index, by classifying its binding. Per the official rule prompt,
|
|
@@ -46702,6 +47053,12 @@ const resolvePositionalIndexBinding = (identifierNode, depth) => {
|
|
|
46702
47053
|
}
|
|
46703
47054
|
const declarator = binding.bindingIdentifier.parent;
|
|
46704
47055
|
if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declarator.id === binding.bindingIdentifier && declarator.init) {
|
|
47056
|
+
if (isDataIndexedWhileLoopCounter(identifierNode, binding.bindingIdentifier, identifierNode.name)) return {
|
|
47057
|
+
iteratorCall: null,
|
|
47058
|
+
bindingFunction: null,
|
|
47059
|
+
indexParameterPosition: null,
|
|
47060
|
+
isDataIndexedLoopCounter: true
|
|
47061
|
+
};
|
|
46705
47062
|
const initializer = stripParenExpression(declarator.init);
|
|
46706
47063
|
if (isNodeOfType(initializer, "Literal") && typeof initializer.value === "number") {
|
|
46707
47064
|
if (!INDEX_PARAMETER_NAMES.has(identifierNode.name)) return null;
|
|
@@ -46736,6 +47093,101 @@ const iteratorCallExemptsIndexKey = (iteratorCall) => {
|
|
|
46736
47093
|
const receiver = iteratorCall.callee.object;
|
|
46737
47094
|
return isStaticPlaceholderReceiver(receiver) || isFixedMemoReceiver(receiver) || isStaticDefaultLiteralReceiver(receiver) || isStringDerivedReceiver(receiver);
|
|
46738
47095
|
};
|
|
47096
|
+
const isReactNamespaceIdentifier = (node) => {
|
|
47097
|
+
if (!isNodeOfType(node, "Identifier")) return false;
|
|
47098
|
+
const importBinding = getImportBindingForName(node, node.name);
|
|
47099
|
+
const visibleBinding = findVariableInitializer(node, node.name);
|
|
47100
|
+
if (!importBinding) return node.name === "React" && !visibleBinding;
|
|
47101
|
+
return visibleBinding?.initializer?.type.startsWith("Import") === true && importBinding.source === "react" && (importBinding.isNamespace || importBinding.exportedName === "default");
|
|
47102
|
+
};
|
|
47103
|
+
const isReactChildrenObject = (node) => {
|
|
47104
|
+
const candidate = stripParenExpression(node);
|
|
47105
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
47106
|
+
const importBinding = getImportBindingForName(candidate, candidate.name);
|
|
47107
|
+
const visibleBinding = findVariableInitializer(candidate, candidate.name);
|
|
47108
|
+
return Boolean(visibleBinding?.initializer?.type === "ImportSpecifier" && importBinding?.source === "react" && importBinding.exportedName === "Children");
|
|
47109
|
+
}
|
|
47110
|
+
return Boolean(isNodeOfType(candidate, "MemberExpression") && !candidate.computed && isReactNamespaceIdentifier(candidate.object) && isNodeOfType(candidate.property, "Identifier") && candidate.property.name === "Children");
|
|
47111
|
+
};
|
|
47112
|
+
const isReactChildrenToArrayCall = (node) => {
|
|
47113
|
+
const candidate = stripParenExpression(node);
|
|
47114
|
+
if (!isNodeOfType(candidate, "CallExpression") || !isNodeOfType(candidate.callee, "MemberExpression") || candidate.callee.computed || !isReactChildrenObject(candidate.callee.object) || !isNodeOfType(candidate.callee.property, "Identifier") || candidate.callee.property.name !== "toArray") return false;
|
|
47115
|
+
const normalizedValueNode = candidate.arguments?.[0];
|
|
47116
|
+
const normalizedValue = normalizedValueNode ? stripParenExpression(normalizedValueNode) : null;
|
|
47117
|
+
if (!normalizedValue || !isNodeOfType(normalizedValue, "Identifier") || normalizedValue.name !== "children") return false;
|
|
47118
|
+
const normalizedBinding = findVariableInitializer(normalizedValue, normalizedValue.name);
|
|
47119
|
+
return Boolean(normalizedBinding && findEnclosingParameter(normalizedBinding.bindingIdentifier));
|
|
47120
|
+
};
|
|
47121
|
+
const isSameIdentifier = (first, second) => {
|
|
47122
|
+
const firstIdentifier = stripParenExpression(first);
|
|
47123
|
+
const secondIdentifier = stripParenExpression(second);
|
|
47124
|
+
if (!isNodeOfType(firstIdentifier, "Identifier") || !isNodeOfType(secondIdentifier, "Identifier") || firstIdentifier.name !== secondIdentifier.name) return false;
|
|
47125
|
+
const firstBinding = findVariableInitializer(firstIdentifier, firstIdentifier.name);
|
|
47126
|
+
const secondBinding = findVariableInitializer(secondIdentifier, secondIdentifier.name);
|
|
47127
|
+
return firstBinding?.bindingIdentifier === secondBinding?.bindingIdentifier;
|
|
47128
|
+
};
|
|
47129
|
+
const isReactChildrenArrayNormalization = (node) => {
|
|
47130
|
+
const candidate = stripParenExpression(node);
|
|
47131
|
+
if (!isNodeOfType(candidate, "ConditionalExpression")) return false;
|
|
47132
|
+
const test = stripParenExpression(candidate.test);
|
|
47133
|
+
if (!isNodeOfType(test, "CallExpression") || !isNodeOfType(test.callee, "MemberExpression") || test.callee.computed || !isNodeOfType(test.callee.object, "Identifier") || test.callee.object.name !== "Array" || findVariableInitializer(test.callee.object, "Array") || !isNodeOfType(test.callee.property, "Identifier") || test.callee.property.name !== "isArray") return false;
|
|
47134
|
+
const testedValueNode = test.arguments?.[0];
|
|
47135
|
+
if (!testedValueNode) return false;
|
|
47136
|
+
const testedValue = stripParenExpression(testedValueNode);
|
|
47137
|
+
if (!isNodeOfType(testedValue, "Identifier")) return false;
|
|
47138
|
+
const testedBinding = findVariableInitializer(testedValue, testedValue.name);
|
|
47139
|
+
if (testedValue.name !== "children" || !testedBinding || !findEnclosingParameter(testedBinding.bindingIdentifier)) return false;
|
|
47140
|
+
const branchWrapsTestedValue = (branch) => {
|
|
47141
|
+
const unwrappedBranch = stripParenExpression(branch);
|
|
47142
|
+
return isNodeOfType(unwrappedBranch, "ArrayExpression") && unwrappedBranch.elements?.length === 1 && Boolean(unwrappedBranch.elements[0] && isSameIdentifier(unwrappedBranch.elements[0], testedValue));
|
|
47143
|
+
};
|
|
47144
|
+
return isSameIdentifier(candidate.consequent, testedValue) && branchWrapsTestedValue(candidate.alternate) || isSameIdentifier(candidate.alternate, testedValue) && branchWrapsTestedValue(candidate.consequent);
|
|
47145
|
+
};
|
|
47146
|
+
const isMutatedEmptyArrayBinding = (identifierNode, depth) => {
|
|
47147
|
+
const binding = findVariableInitializer(identifierNode, identifierNode.name);
|
|
47148
|
+
const initializer = binding?.initializer ? stripParenExpression(binding.initializer) : null;
|
|
47149
|
+
if (!binding || !initializer || !isNodeOfType(initializer, "ArrayExpression") || initializer.elements?.length !== 0) return false;
|
|
47150
|
+
const program = findProgramRoot(identifierNode);
|
|
47151
|
+
if (!program) return false;
|
|
47152
|
+
let didFindReactChildPush = false;
|
|
47153
|
+
walkAst(program, (child) => {
|
|
47154
|
+
if (didFindReactChildPush) return false;
|
|
47155
|
+
if (!isNodeOfType(child, "CallExpression") || !isNodeOfType(child.callee, "MemberExpression") || child.callee.computed || !isNodeOfType(child.callee.object, "Identifier") || child.callee.object.name !== identifierNode.name || !isNodeOfType(child.callee.property, "Identifier") || child.callee.property.name !== "push") return;
|
|
47156
|
+
if (findVariableInitializer(child.callee.object, identifierNode.name)?.bindingIdentifier === binding.bindingIdentifier && (child.arguments ?? []).some((argument) => {
|
|
47157
|
+
const candidate = stripParenExpression(argument);
|
|
47158
|
+
if (!(isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "JSXElement") || isNodeOfType(candidate, "JSXFragment"))) return false;
|
|
47159
|
+
let doesCarryReactChild = false;
|
|
47160
|
+
walkAst(candidate, (argumentChild) => {
|
|
47161
|
+
if (doesCarryReactChild) return false;
|
|
47162
|
+
if (!isNodeOfType(argumentChild, "Identifier")) return;
|
|
47163
|
+
const declarator = findVariableInitializer(argumentChild, argumentChild.name)?.bindingIdentifier.parent;
|
|
47164
|
+
const declaration = declarator?.parent;
|
|
47165
|
+
const forOfStatement = declaration?.parent;
|
|
47166
|
+
if (declarator && isNodeOfType(declarator, "VariableDeclarator") && declaration && isNodeOfType(declaration, "VariableDeclaration") && forOfStatement && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isDynamicReactChildrenExpression(forOfStatement.right, depth + 1)) {
|
|
47167
|
+
doesCarryReactChild = true;
|
|
47168
|
+
return false;
|
|
47169
|
+
}
|
|
47170
|
+
});
|
|
47171
|
+
return doesCarryReactChild;
|
|
47172
|
+
})) {
|
|
47173
|
+
didFindReactChildPush = true;
|
|
47174
|
+
return false;
|
|
47175
|
+
}
|
|
47176
|
+
});
|
|
47177
|
+
return didFindReactChildPush;
|
|
47178
|
+
};
|
|
47179
|
+
const isDynamicReactChildrenExpression = (expression, depth) => {
|
|
47180
|
+
if (depth > TYPE_RESOLUTION_DEPTH_LIMIT$2) return false;
|
|
47181
|
+
const candidate = stripParenExpression(expression);
|
|
47182
|
+
if (isReactChildrenToArrayCall(candidate) || isReactChildrenArrayNormalization(candidate)) return true;
|
|
47183
|
+
if (isNodeOfType(candidate, "Identifier")) {
|
|
47184
|
+
if (isMutatedEmptyArrayBinding(candidate, depth)) return true;
|
|
47185
|
+
const binding = findVariableInitializer(candidate, candidate.name);
|
|
47186
|
+
return Boolean(binding?.initializer && isDynamicReactChildrenExpression(binding.initializer, depth + 1));
|
|
47187
|
+
}
|
|
47188
|
+
if (isNodeOfType(candidate, "CallExpression") && isNodeOfType(candidate.callee, "MemberExpression") && !candidate.callee.computed && isNodeOfType(candidate.callee.property, "Identifier") && candidate.callee.property.name === "filter") return isDynamicReactChildrenExpression(candidate.callee.object, depth + 1);
|
|
47189
|
+
return false;
|
|
47190
|
+
};
|
|
46739
47191
|
const resolveKeyTemplateLiteral = (expression) => {
|
|
46740
47192
|
const node = stripParenExpression(expression);
|
|
46741
47193
|
if (isNodeOfType(node, "TemplateLiteral")) return node;
|
|
@@ -46788,28 +47240,19 @@ const findBareItemNamesReferencedByTemplate = (template, itemNames) => {
|
|
|
46788
47240
|
}
|
|
46789
47241
|
return referencedItemNames;
|
|
46790
47242
|
};
|
|
46791
|
-
const
|
|
46792
|
-
let didFindLengthRead = false;
|
|
46793
|
-
walkAst(test, (child) => {
|
|
46794
|
-
if (didFindLengthRead) return false;
|
|
46795
|
-
if (isNodeOfType(child, "MemberExpression") && isNodeOfType(child.property, "Identifier") && child.property.name === "length") {
|
|
46796
|
-
didFindLengthRead = true;
|
|
46797
|
-
return false;
|
|
46798
|
-
}
|
|
46799
|
-
});
|
|
46800
|
-
return didFindLengthRead;
|
|
46801
|
-
};
|
|
46802
|
-
const isNumericForLoopCounter = (attributeNode, indexName) => {
|
|
47243
|
+
const isNumericPlaceholderLoopCounter = (attributeNode, indexName) => {
|
|
46803
47244
|
const binding = findVariableInitializer(attributeNode, indexName);
|
|
46804
47245
|
if (!binding) return false;
|
|
46805
47246
|
const declarator = binding.bindingIdentifier.parent;
|
|
46806
47247
|
if (!declarator || !isNodeOfType(declarator, "VariableDeclarator")) return false;
|
|
46807
47248
|
const declaration = declarator.parent;
|
|
46808
47249
|
if (!declaration || !isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
47250
|
+
if (!declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
|
|
46809
47251
|
const forStatement = declaration.parent;
|
|
46810
|
-
if (
|
|
46811
|
-
|
|
46812
|
-
return
|
|
47252
|
+
if (forStatement && isNodeOfType(forStatement, "ForStatement") && forStatement.init === declaration) return !(forStatement.test && loopTestBoundsCounterByLength(forStatement.test, indexName, binding.bindingIdentifier));
|
|
47253
|
+
const whileLoop = findEnclosingWhileLoop(attributeNode);
|
|
47254
|
+
if (!whileLoop) return false;
|
|
47255
|
+
return !loopTestBoundsCounterByLength(whileLoop.test, indexName, binding.bindingIdentifier);
|
|
46813
47256
|
};
|
|
46814
47257
|
const EMPTY_NAME_SET$1 = /* @__PURE__ */ new Set();
|
|
46815
47258
|
const findIteratorItemNamesOfBinding = (binding) => {
|
|
@@ -46843,11 +47286,11 @@ const collectDerivedRowContentNames = (bindingFunction, itemNames) => {
|
|
|
46843
47286
|
* observable harm; anything stateful — form controls, media, custom
|
|
46844
47287
|
* components, unknown calls — keeps the diagnostic.
|
|
46845
47288
|
*/
|
|
46846
|
-
const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) => {
|
|
47289
|
+
const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames, areBareItemsDynamicReactChildren) => {
|
|
46847
47290
|
const jsxElement = openingElement.parent;
|
|
46848
47291
|
if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
46849
47292
|
const children = jsxElement.children ?? [];
|
|
46850
|
-
const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : new Set([...derivedNames, ...itemNames]);
|
|
47293
|
+
const bareIdentifierNames = children.some((child) => isNodeOfType(child, "JSXElement")) ? derivedNames : areBareItemsDynamicReactChildren ? derivedNames : new Set([...derivedNames, ...itemNames]);
|
|
46851
47294
|
return children.some((child) => containsStatefulDescendant(child, {
|
|
46852
47295
|
memberRootNames: itemNames,
|
|
46853
47296
|
allowAnyMemberRead: true,
|
|
@@ -46855,6 +47298,15 @@ const fragmentHasStatefulChildren = (openingElement, itemNames, derivedNames) =>
|
|
|
46855
47298
|
callCalleeRootNames: itemNames
|
|
46856
47299
|
}));
|
|
46857
47300
|
};
|
|
47301
|
+
const elementHasDirectItemChild = (openingElement, itemNames) => {
|
|
47302
|
+
const jsxElement = openingElement.parent;
|
|
47303
|
+
if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement")) return false;
|
|
47304
|
+
return (jsxElement.children ?? []).some((child) => {
|
|
47305
|
+
if (!isNodeOfType(child, "JSXExpressionContainer")) return false;
|
|
47306
|
+
const expression = stripParenExpression(child.expression);
|
|
47307
|
+
return isNodeOfType(expression, "Identifier") && itemNames.has(expression.name);
|
|
47308
|
+
});
|
|
47309
|
+
};
|
|
46858
47310
|
const callbackFiltersRows = (bindingFunction) => {
|
|
46859
47311
|
if (!bindingFunction) return false;
|
|
46860
47312
|
let didFindNullReturn = false;
|
|
@@ -46906,19 +47358,21 @@ const noArrayIndexAsKey = defineRule({
|
|
|
46906
47358
|
const indexUse = findPositionalIndexUse(node.value.expression, 0);
|
|
46907
47359
|
if (!indexUse) return;
|
|
46908
47360
|
const indexName = indexUse.identifier.name;
|
|
46909
|
-
if (
|
|
47361
|
+
if (isNumericPlaceholderLoopCounter(node, indexName)) return;
|
|
46910
47362
|
if (indexUse.binding.iteratorCall && iteratorCallExemptsIndexKey(indexUse.binding.iteratorCall)) return;
|
|
46911
47363
|
const keyTemplate = resolveKeyTemplateLiteral(node.value.expression);
|
|
46912
47364
|
if (keyTemplate && templateHasOuterMemberIdentity(keyTemplate, indexUse.binding.bindingFunction)) return;
|
|
46913
|
-
if (hasAriaHiddenAncestor(node)) return;
|
|
47365
|
+
if (hasAriaHiddenAncestor(node) && !indexUse.binding.isDataIndexedLoopCounter) return;
|
|
46914
47366
|
const itemNames = findIteratorItemNamesOfBinding(indexUse.binding);
|
|
46915
47367
|
const derivedNames = collectDerivedRowContentNames(indexUse.binding.bindingFunction, itemNames);
|
|
47368
|
+
const iteratorCallee = indexUse.binding.iteratorCall?.callee;
|
|
47369
|
+
const hasDynamicReactChildren = Boolean(iteratorCallee && isNodeOfType(iteratorCallee, "MemberExpression") && isDynamicReactChildrenExpression(iteratorCallee.object, 0));
|
|
46916
47370
|
const openingElement = node.parent;
|
|
46917
47371
|
if (openingElement && isNodeOfType(openingElement, "JSXOpeningElement")) {
|
|
46918
47372
|
const elementName = openingElement.name;
|
|
46919
47373
|
if (isNodeOfType(elementName, "JSXIdentifier")) {
|
|
46920
47374
|
if (elementName.name === "Fragment") {
|
|
46921
|
-
if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames)) return;
|
|
47375
|
+
if (!fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
|
|
46922
47376
|
} else if (PURE_SVG_PRIMITIVE_TAGS.has(elementName.name)) {
|
|
46923
47377
|
if (!callbackFiltersRows(indexUse.binding.bindingFunction)) return;
|
|
46924
47378
|
} else if (STATELESS_HTML_LEAF_TAGS.has(elementName.name)) {
|
|
@@ -46926,14 +47380,14 @@ const noArrayIndexAsKey = defineRule({
|
|
|
46926
47380
|
if (jsxElement && isNodeOfType(jsxElement, "JSXElement")) {
|
|
46927
47381
|
const isInlineTextRun = INLINE_TEXT_LEAF_TAGS.has(elementName.name);
|
|
46928
47382
|
const primitiveItemNames = keyTemplate ? findBareItemNamesReferencedByTemplate(keyTemplate, itemNames) : EMPTY_NAME_SET$1;
|
|
46929
|
-
if (!containsStatefulDescendant(jsxElement, {
|
|
47383
|
+
if (!(hasDynamicReactChildren && elementHasDirectItemChild(openingElement, itemNames) || containsStatefulDescendant(jsxElement, {
|
|
46930
47384
|
memberRootNames: isInlineTextRun ? itemNames : EMPTY_NAME_SET$1,
|
|
46931
47385
|
bareIdentifierNames: primitiveItemNames.size > 0 ? new Set([...derivedNames, ...primitiveItemNames]) : derivedNames
|
|
46932
|
-
})) return;
|
|
47386
|
+
}))) return;
|
|
46933
47387
|
}
|
|
46934
47388
|
}
|
|
46935
47389
|
}
|
|
46936
|
-
if (isNodeOfType(elementName, "JSXMemberExpression") && isNodeOfType(elementName.object, "JSXIdentifier") && isNodeOfType(elementName.property, "JSXIdentifier") && elementName.object.name === "React" && elementName.property.name === "Fragment" && !fragmentHasStatefulChildren(openingElement, itemNames, derivedNames)) return;
|
|
47390
|
+
if (isNodeOfType(elementName, "JSXMemberExpression") && isNodeOfType(elementName.object, "JSXIdentifier") && isNodeOfType(elementName.property, "JSXIdentifier") && elementName.object.name === "React" && elementName.property.name === "Fragment" && !fragmentHasStatefulChildren(openingElement, itemNames, derivedNames, hasDynamicReactChildren)) return;
|
|
46937
47391
|
}
|
|
46938
47392
|
context.report({
|
|
46939
47393
|
node,
|
|
@@ -88308,6 +88762,11 @@ const noUndersizedIconButton = defineRule({
|
|
|
88308
88762
|
//#region src/plugin/rules/correctness/no-unescaped-dynamic-string-in-regexp.ts
|
|
88309
88763
|
const TEST_CONTEXT_FILE_PATTERN = /(\.test\.|\.spec\.|__tests__|(^|\/)(test|tests|e2e|cypress|playwright)\/)/;
|
|
88310
88764
|
const SEARCH_TERM_NAME_PATTERN = /search|query|highlight|filter|term(?!in)|keyword/i;
|
|
88765
|
+
const REGEX_SOURCE_WORDS = [
|
|
88766
|
+
"pattern",
|
|
88767
|
+
"regex",
|
|
88768
|
+
"regexp"
|
|
88769
|
+
];
|
|
88311
88770
|
const ESCAPE_HELPER_NAME_PATTERN = /escape.*reg|safe.*reg/i;
|
|
88312
88771
|
const SANITIZED_NAME_PATTERN = /escap|sanitiz/i;
|
|
88313
88772
|
const INITIALIZER_RESOLUTION_HOPS = 2;
|
|
@@ -88461,14 +88920,70 @@ const isTypePositionIdentifier = (identifier) => {
|
|
|
88461
88920
|
}
|
|
88462
88921
|
return false;
|
|
88463
88922
|
};
|
|
88464
|
-
const
|
|
88465
|
-
const
|
|
88923
|
+
const identifierNameHasPathSegmentSemantics = (identifierName) => {
|
|
88924
|
+
const identifierWords = identifierName.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_]+/).map((word) => word.toLowerCase());
|
|
88925
|
+
if (identifierWords.some((word) => REGEX_SOURCE_WORDS.includes(word))) return false;
|
|
88926
|
+
return identifierWords.some((word) => [
|
|
88927
|
+
"path",
|
|
88928
|
+
"folder",
|
|
88929
|
+
"directory",
|
|
88930
|
+
"root"
|
|
88931
|
+
].includes(word)) || identifierWords.includes("top") && identifierWords.includes("level");
|
|
88932
|
+
};
|
|
88933
|
+
const flattenPatternParts = (expression) => {
|
|
88934
|
+
const inner = stripParenExpression(expression);
|
|
88935
|
+
const staticValue = literalStringValue(inner);
|
|
88936
|
+
if (staticValue !== null) return [staticValue];
|
|
88937
|
+
if (isNodeOfType(inner, "Identifier")) return [inner];
|
|
88938
|
+
if (isNodeOfType(inner, "TemplateLiteral")) {
|
|
88939
|
+
const parts = [];
|
|
88940
|
+
for (let index = 0; index < inner.quasis.length; index += 1) {
|
|
88941
|
+
const quasi = inner.quasis[index];
|
|
88942
|
+
if (quasi) parts.push(quasi.value.cooked ?? quasi.value.raw);
|
|
88943
|
+
const templateExpression = inner.expressions[index];
|
|
88944
|
+
if (templateExpression) parts.push(stripParenExpression(templateExpression));
|
|
88945
|
+
}
|
|
88946
|
+
return parts;
|
|
88947
|
+
}
|
|
88948
|
+
if (isNodeOfType(inner, "BinaryExpression") && inner.operator === "+") {
|
|
88949
|
+
const leftParts = flattenPatternParts(inner.left);
|
|
88950
|
+
const rightParts = flattenPatternParts(inner.right);
|
|
88951
|
+
return leftParts && rightParts ? [...leftParts, ...rightParts] : null;
|
|
88952
|
+
}
|
|
88953
|
+
if (isNodeOfType(inner, "CallExpression") && isNodeOfType(inner.callee, "MemberExpression") && getStaticPropertyName(inner.callee) === "concat") {
|
|
88954
|
+
const receiverParts = flattenPatternParts(inner.callee.object);
|
|
88955
|
+
if (!receiverParts) return null;
|
|
88956
|
+
const parts = [...receiverParts];
|
|
88957
|
+
for (const argument of inner.arguments) {
|
|
88958
|
+
if (isNodeOfType(argument, "SpreadElement")) return null;
|
|
88959
|
+
const argumentParts = flattenPatternParts(argument);
|
|
88960
|
+
if (!argumentParts) return null;
|
|
88961
|
+
parts.push(...argumentParts);
|
|
88962
|
+
}
|
|
88963
|
+
return parts;
|
|
88964
|
+
}
|
|
88965
|
+
return null;
|
|
88966
|
+
};
|
|
88967
|
+
const isIdentifierAnAnchoredPathSegment = (argument, identifier) => {
|
|
88968
|
+
if (!identifierNameHasPathSegmentSemantics(identifier.name)) return false;
|
|
88969
|
+
const parts = flattenPatternParts(argument);
|
|
88970
|
+
if (!parts) return false;
|
|
88971
|
+
const identifierPartIndex = parts.findIndex((part) => part === identifier);
|
|
88972
|
+
if (identifierPartIndex < 0) return false;
|
|
88973
|
+
const precedingParts = parts.slice(0, identifierPartIndex);
|
|
88974
|
+
if (precedingParts.some((part) => typeof part !== "string")) return false;
|
|
88975
|
+
const staticPrefix = precedingParts.join("");
|
|
88976
|
+
const followingPart = parts[identifierPartIndex + 1];
|
|
88977
|
+
return staticPrefix === "^" && typeof followingPart === "string" && followingPart.startsWith("/");
|
|
88978
|
+
};
|
|
88979
|
+
const collectRawDynamicLiteralIdentifiers = (argument) => {
|
|
88980
|
+
const rawDynamicLiteralIdentifiers = [];
|
|
88466
88981
|
walkAst(argument, (child) => {
|
|
88467
88982
|
if (isEscapingCall(child) || isRegexSourceAccess(child)) return false;
|
|
88468
88983
|
if (isLiteralReturningGetterCall(child)) return false;
|
|
88469
|
-
if (isNodeOfType(child, "Identifier") && SEARCH_TERM_NAME_PATTERN.test(child.name) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child))
|
|
88984
|
+
if (isNodeOfType(child, "Identifier") && (SEARCH_TERM_NAME_PATTERN.test(child.name) || isIdentifierAnAnchoredPathSegment(argument, child)) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child)) rawDynamicLiteralIdentifiers.push(child);
|
|
88470
88985
|
});
|
|
88471
|
-
return
|
|
88986
|
+
return rawDynamicLiteralIdentifiers;
|
|
88472
88987
|
};
|
|
88473
88988
|
const collectLeafIdentifiers = (node) => {
|
|
88474
88989
|
const leafIdentifiers = [];
|
|
@@ -88479,8 +88994,11 @@ const collectLeafIdentifiers = (node) => {
|
|
|
88479
88994
|
return leafIdentifiers;
|
|
88480
88995
|
};
|
|
88481
88996
|
const compositeInitializerResolvesEscaped = (strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames) => {
|
|
88997
|
+
if (isNodeOfType(strippedInitializer, "ConditionalExpression")) return [strippedInitializer.consequent, strippedInitializer.alternate].every((branch) => initializerLooksEscaped(branch, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
|
|
88998
|
+
if (isNodeOfType(strippedInitializer, "BinaryExpression") || isNodeOfType(strippedInitializer, "LogicalExpression")) return [strippedInitializer.left, strippedInitializer.right].every((operand) => initializerLooksEscaped(operand, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
|
|
88999
|
+
if (isNodeOfType(strippedInitializer, "TemplateLiteral")) return strippedInitializer.expressions.every((expression) => initializerLooksEscaped(expression, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
|
|
88482
89000
|
let didResolveAnyLeafEscaped = false;
|
|
88483
|
-
for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
|
|
89001
|
+
for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
|
|
88484
89002
|
else if (SEARCH_TERM_NAME_PATTERN.test(leafIdentifier.name)) return false;
|
|
88485
89003
|
return didResolveAnyLeafEscaped;
|
|
88486
89004
|
};
|
|
@@ -88492,7 +89010,7 @@ const initializerLooksEscaped = (initializer, remainingHops, scopes, regexpObjec
|
|
|
88492
89010
|
if (isNodeOfType(strippedInitializer, "CallExpression") && (isEscapingCall(strippedInitializer) || calleeBindingBodyEscapes(strippedInitializer))) return true;
|
|
88493
89011
|
if (remainingHops > 0) {
|
|
88494
89012
|
if (isNodeOfType(strippedInitializer, "Identifier")) return identifierResolvesToEscapedValue(strippedInitializer, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
|
|
88495
|
-
return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops
|
|
89013
|
+
return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
|
|
88496
89014
|
}
|
|
88497
89015
|
return false;
|
|
88498
89016
|
};
|
|
@@ -88684,7 +89202,7 @@ const noUnescapedDynamicStringInRegexp = defineRule({
|
|
|
88684
89202
|
severity: "warn",
|
|
88685
89203
|
category: "Correctness",
|
|
88686
89204
|
tags: ["test-noise"],
|
|
88687
|
-
recommendation: "A search
|
|
89205
|
+
recommendation: "A dynamic literal string such as a search term or path segment dropped straight into `new RegExp(...)` lets its regex metacharacters act as operators, so values containing `.` or `(` over-match or throw. Escape the value with an `escapeRegExp` helper before constructing the pattern.",
|
|
88688
89206
|
create: (context) => {
|
|
88689
89207
|
if (TEST_CONTEXT_FILE_PATTERN.test(context.filename ?? "")) return {};
|
|
88690
89208
|
let regexpObjectIndex = null;
|
|
@@ -88701,10 +89219,10 @@ const noUnescapedDynamicStringInRegexp = defineRule({
|
|
|
88701
89219
|
regexpObjectIndex = buildRegExpObjectIndex(programRoot, context.scopes);
|
|
88702
89220
|
}
|
|
88703
89221
|
const currentRegExpObjectIndex = regexpObjectIndex;
|
|
88704
|
-
if (!
|
|
89222
|
+
if (!collectRawDynamicLiteralIdentifiers(firstArgument).some((identifier) => !identifierResolvesToEscapedValue(identifier, INITIALIZER_RESOLUTION_HOPS, context.scopes, currentRegExpObjectIndex.regexpObjectSymbolIds, currentRegExpObjectIndex.globalRegExpObjectNames) && !isShapeTestedByDominatingGuard(node, identifier, context.scopes) && !isParameterFedOnlyMetacharacterFreeLiterals(identifier, context.scopes))) return;
|
|
88705
89223
|
context.report({
|
|
88706
89224
|
node,
|
|
88707
|
-
message: "This builds a `RegExp` from a dynamic
|
|
89225
|
+
message: "This builds a `RegExp` from a dynamic literal string without escaping it, so regex metacharacters in the value act as operators and over-match or throw. Escape the value with an `escapeRegExp` helper first."
|
|
88708
89226
|
});
|
|
88709
89227
|
};
|
|
88710
89228
|
return {
|
|
@@ -140049,6 +140567,7 @@ const shouldReadSecurityScanContent = (relativePath, isGeneratedBundle) => isGen
|
|
|
140049
140567
|
//#region src/plugin/utils/capability.ts
|
|
140050
140568
|
const FRAMEWORK_TOKENS = [
|
|
140051
140569
|
"nextjs",
|
|
140570
|
+
"astro",
|
|
140052
140571
|
"vite",
|
|
140053
140572
|
"cra",
|
|
140054
140573
|
"remix",
|