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 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?)(?:\/|$)|\.(?: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;
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 isIntentionallySequentialAwait = (awaitNode, context) => isAwaitingPossiblyMutatedMemberCall(awaitNode, context) || isAwaitingSleepLikeCall(awaitNode, context) || isAwaitingPromiseConcurrencyCall(awaitNode) || isAwaitingManualPromiseWait(awaitNode);
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$3 = new Set([
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$3.has(source.name) && scopes.isGlobalReference(source)) return getStaticPropertyKeyName(property, { allowComputedString: true });
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$3.has(receiver.name) && scopes.isGlobalReference(receiver) ? getStaticPropertyName(initializer) : null;
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$3.has(receiver.name) || findVariableInitializer(receiver, receiver.name)) return null;
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 isFocusForwardingHandler = (attribute) => {
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
- if (!handlerFunction) return false;
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 forLoopTestReadsDataLength = (test) => {
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 (!forStatement || !isNodeOfType(forStatement, "ForStatement") || forStatement.init !== declaration || !declarator.init || !isNodeOfType(declarator.init, "Literal") || typeof declarator.init.value !== "number") return false;
46811
- if (forStatement.test && forLoopTestReadsDataLength(forStatement.test)) return false;
46812
- return true;
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 (isNumericForLoopCounter(node, indexName)) return;
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 collectRawSearchTermIdentifiers = (argument) => {
88465
- const rawSearchTermIdentifiers = [];
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)) rawSearchTermIdentifiers.push(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 rawSearchTermIdentifiers;
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 - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
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/filter/highlight term dropped straight into `new RegExp(...)` lets its regex metacharacters act as operators, so a user typing `.` or `(` over-matches or throws. Escape the value with an `escapeRegExp` helper before constructing the pattern.",
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 (!collectRawSearchTermIdentifiers(firstArgument).some((identifier) => !identifierResolvesToEscapedValue(identifier, INITIALIZER_RESOLUTION_HOPS, context.scopes, currentRegExpObjectIndex.regexpObjectSymbolIds, currentRegExpObjectIndex.globalRegExpObjectNames) && !isShapeTestedByDominatingGuard(node, identifier, context.scopes) && !isParameterFedOnlyMetacharacterFreeLiterals(identifier, context.scopes))) return;
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 search/filter term 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."
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.9.2-dev.3728102",
3
+ "version": "0.9.2-dev.3bc63ea",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",