oxlint-plugin-react-doctor 0.9.2-dev.3f3197f → 0.9.2-dev.444e177

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
@@ -8675,7 +8675,7 @@ const resolveStableOptionsObject = (expression, observedPropertyNames, scopes, r
8675
8675
  //#endregion
8676
8676
  //#region src/plugin/rules/state-and-effects/class-component-missing-component-will-unmount-teardown.ts
8677
8677
  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([
8678
+ const GLOBAL_OBJECT_NAMES$4 = new Set([
8679
8679
  "window",
8680
8680
  "globalThis",
8681
8681
  "global",
@@ -8834,14 +8834,14 @@ const getTimerIdentifierAliasName = (identifier, scopes, visitedSymbolIds = /* @
8834
8834
  const property = symbol.bindingIdentifier.parent;
8835
8835
  const objectPattern = property?.parent;
8836
8836
  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 });
8837
+ 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
8838
  return null;
8839
8839
  }
8840
8840
  const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
8841
8841
  if (isNodeOfType(initializer, "Identifier")) return getTimerIdentifierAliasName(initializer, scopes, visitedSymbolIds);
8842
8842
  if (!isNodeOfType(initializer, "MemberExpression")) return null;
8843
8843
  const receiver = stripParenExpression(initializer.object);
8844
- return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$3.has(receiver.name) && scopes.isGlobalReference(receiver) ? getStaticPropertyName(initializer) : null;
8844
+ return isNodeOfType(receiver, "Identifier") && GLOBAL_OBJECT_NAMES$4.has(receiver.name) && scopes.isGlobalReference(receiver) ? getStaticPropertyName(initializer) : null;
8845
8845
  };
8846
8846
  const getTimerCalleeName = (node, scopes) => {
8847
8847
  if (!isNodeOfType(node, "CallExpression")) return null;
@@ -8849,7 +8849,7 @@ const getTimerCalleeName = (node, scopes) => {
8849
8849
  if (getBareCalleeName(node) && isNodeOfType(callee, "Identifier")) return getTimerIdentifierAliasName(callee, scopes);
8850
8850
  if (!isNodeOfType(callee, "MemberExpression")) return null;
8851
8851
  const receiver = stripParenExpression(callee.object);
8852
- if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$3.has(receiver.name) || findVariableInitializer(receiver, receiver.name)) return null;
8852
+ if (!isNodeOfType(receiver, "Identifier") || !GLOBAL_OBJECT_NAMES$4.has(receiver.name) || findVariableInitializer(receiver, receiver.name)) return null;
8853
8853
  return getStaticPropertyName(callee);
8854
8854
  };
8855
8855
  const getClassMemberName$1 = (member) => {
@@ -10045,6 +10045,12 @@ const FOCUS_FORWARDING_METHOD_NAMES = new Set([
10045
10045
  "preventDefault",
10046
10046
  "stopImmediatePropagation"
10047
10047
  ]);
10048
+ const DOM_QUERY_METHOD_NAMES = new Set(["getElementById", "querySelector"]);
10049
+ const GLOBAL_OBJECT_NAMES$3 = new Set([
10050
+ "global",
10051
+ "globalThis",
10052
+ "window"
10053
+ ]);
10048
10054
  const isFocusForwardingCall = (node) => {
10049
10055
  if (!node) return false;
10050
10056
  const inner = isNodeOfType(node, "ChainExpression") ? node.expression : node;
@@ -10054,6 +10060,12 @@ const isFocusForwardingCall = (node) => {
10054
10060
  if (!isNodeOfType(callee.property, "Identifier")) return false;
10055
10061
  return FOCUS_FORWARDING_METHOD_NAMES.has(callee.property.name);
10056
10062
  };
10063
+ const isGlobalDocumentExpression = (expression, scopes) => {
10064
+ const candidate = stripParenExpression(expression);
10065
+ if (isNodeOfType(candidate, "Identifier")) return candidate.name === "document" && scopes.isGlobalReference(candidate);
10066
+ if (!isNodeOfType(candidate, "MemberExpression") || !isNodeOfType(candidate.object, "Identifier") || !isNodeOfType(candidate.property, "Identifier") || candidate.property.name !== "document") return false;
10067
+ return GLOBAL_OBJECT_NAMES$3.has(candidate.object.name) && scopes.isGlobalReference(candidate.object);
10068
+ };
10057
10069
  const isFocusForwardingFunctionBody = (body) => {
10058
10070
  if (!body) return false;
10059
10071
  if (isFocusForwardingCall(body)) return true;
@@ -10073,19 +10085,70 @@ const resolveHandlerFunction$2 = (attribute) => {
10073
10085
  return resolveHandlerFunctionExpression(attribute.value.expression);
10074
10086
  };
10075
10087
  const resolveHandlerFunctionExpression = (handlerExpression) => {
10076
- let expression = handlerExpression;
10088
+ let expression = stripParenExpression(handlerExpression);
10077
10089
  if (isNodeOfType(expression, "Identifier")) {
10078
10090
  const binding = findVariableInitializer(expression, expression.name);
10079
10091
  if (!binding?.initializer) return null;
10080
- expression = binding.initializer;
10092
+ expression = stripParenExpression(binding.initializer);
10081
10093
  }
10082
10094
  if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "FunctionDeclaration")) return expression;
10083
10095
  return null;
10084
10096
  };
10085
- const isFocusForwardingHandler = (attribute) => {
10097
+ const isEmptyReturn = (statement) => isNodeOfType(statement, "ReturnStatement") && statement.argument === null;
10098
+ const isClosestEarlyReturn = (statement) => {
10099
+ if (!isNodeOfType(statement, "IfStatement") || statement.alternate) return false;
10100
+ const consequent = statement.consequent;
10101
+ if (!(isNodeOfType(consequent, "BlockStatement") ? consequent.body.length === 1 && isEmptyReturn(consequent.body[0]) : isEmptyReturn(consequent))) return false;
10102
+ const test = stripParenExpression(statement.test);
10103
+ const call = isNodeOfType(test, "ChainExpression") ? test.expression : test;
10104
+ if (!isNodeOfType(call, "CallExpression") || call.arguments.length !== 1) return false;
10105
+ const callee = stripParenExpression(call.callee);
10106
+ const receiver = isNodeOfType(callee, "MemberExpression") ? stripParenExpression(callee.object) : null;
10107
+ 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";
10108
+ };
10109
+ const isStaticSelectorExpression = (expression) => {
10110
+ const candidate = stripParenExpression(expression);
10111
+ if (isNodeOfType(candidate, "Identifier") || isNodeOfType(candidate, "Literal")) return true;
10112
+ return isNodeOfType(candidate, "TemplateLiteral") && candidate.expressions.every((innerExpression) => isStaticSelectorExpression(innerExpression));
10113
+ };
10114
+ const getDomQueryVariableName = (statement, scopes) => {
10115
+ if (!isNodeOfType(statement, "VariableDeclaration") || statement.kind !== "const" || statement.declarations.length !== 1) return null;
10116
+ const declaration = statement.declarations[0];
10117
+ if (!declaration || !isNodeOfType(declaration.id, "Identifier") || !declaration.init) return null;
10118
+ const initializer = stripParenExpression(declaration.init);
10119
+ if (!isNodeOfType(initializer, "CallExpression") || initializer.arguments.length !== 1 || isNodeOfType(initializer.arguments[0], "SpreadElement") || !isStaticSelectorExpression(initializer.arguments[0])) return null;
10120
+ const callee = stripParenExpression(initializer.callee);
10121
+ if (!isNodeOfType(callee, "MemberExpression") || !isNodeOfType(callee.property, "Identifier") || !DOM_QUERY_METHOD_NAMES.has(callee.property.name) || !isGlobalDocumentExpression(callee.object, scopes)) return null;
10122
+ return declaration.id.name;
10123
+ };
10124
+ const isFocusCallOnVariable = (statement, variableName) => {
10125
+ if (!isNodeOfType(statement, "ExpressionStatement")) return false;
10126
+ const expression = stripParenExpression(statement.expression);
10127
+ if (!isNodeOfType(expression, "CallExpression")) return false;
10128
+ const callee = stripParenExpression(expression.callee);
10129
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
10130
+ const receiver = stripParenExpression(callee.object);
10131
+ return isNodeOfType(receiver, "Identifier") && receiver.name === variableName && isNodeOfType(callee.property, "Identifier") && callee.property.name === "focus" && expression.arguments.length === 0;
10132
+ };
10133
+ const isConditionalFocusForwardingHandler = (attribute, scopes) => {
10134
+ if (!attribute.value || !isNodeOfType(attribute.value, "JSXExpressionContainer")) return false;
10135
+ const expression = stripParenExpression(attribute.value.expression);
10136
+ if (!isNodeOfType(expression, "ConditionalExpression")) return false;
10137
+ const consequent = stripParenExpression(expression.consequent);
10138
+ const alternate = stripParenExpression(expression.alternate);
10139
+ const nullishBranch = isNullishExpression$2(consequent) ? consequent : alternate;
10140
+ if (!isNullishExpression$2(nullishBranch) || isNodeOfType(nullishBranch, "Identifier") && !scopes.isGlobalReference(nullishBranch)) return false;
10141
+ const handlerFunction = resolveHandlerFunctionExpression(nullishBranch === consequent ? alternate : consequent);
10142
+ if (!handlerFunction || !isNodeOfType(handlerFunction.body, "BlockStatement")) return false;
10143
+ const [guard, query, focus, ...rest] = handlerFunction.body.body;
10144
+ if (!guard || !query || !focus || rest.length > 0 || !isClosestEarlyReturn(guard)) return false;
10145
+ const queryVariableName = getDomQueryVariableName(query, scopes);
10146
+ return Boolean(queryVariableName && isFocusCallOnVariable(focus, queryVariableName));
10147
+ };
10148
+ const isFocusForwardingHandler = (attribute, scopes) => {
10149
+ if (isConditionalFocusForwardingHandler(attribute, scopes)) return true;
10086
10150
  const handlerFunction = resolveHandlerFunction$2(attribute);
10087
- if (!handlerFunction) return false;
10088
- return isFocusForwardingFunctionBody(handlerFunction.body ?? null);
10151
+ return Boolean(handlerFunction && isFocusForwardingFunctionBody(handlerFunction.body ?? null));
10089
10152
  };
10090
10153
  const COMPOSITE_ITEM_ROLES$1 = new Set([
10091
10154
  "option",
@@ -10155,7 +10218,7 @@ const clickEventsHaveKeyEvents = defineRule({
10155
10218
  const spreadOnClickExpression = CLICK_HANDLERS.map((name) => spreadEventValues.get(name.toLowerCase())).find((expression) => expression !== void 0);
10156
10219
  if (!onClick && !spreadOnClickExpression) return;
10157
10220
  if (onClick && isPureEventBlockerHandler(onClick)) return;
10158
- if (onClick && isFocusForwardingHandler(onClick)) return;
10221
+ if (onClick && isFocusForwardingHandler(onClick, context.scopes)) return;
10159
10222
  const spreadHandlerFunction = spreadOnClickExpression ? resolveHandlerFunctionExpression(spreadOnClickExpression) : null;
10160
10223
  if (spreadHandlerFunction && (isFocusForwardingFunctionBody(spreadHandlerFunction.body ?? null) || containsBackdropDismissComparison(spreadHandlerFunction.body ?? null))) return;
10161
10224
  if (hasCompositeItemRole(node)) return;
@@ -88308,6 +88371,11 @@ const noUndersizedIconButton = defineRule({
88308
88371
  //#region src/plugin/rules/correctness/no-unescaped-dynamic-string-in-regexp.ts
88309
88372
  const TEST_CONTEXT_FILE_PATTERN = /(\.test\.|\.spec\.|__tests__|(^|\/)(test|tests|e2e|cypress|playwright)\/)/;
88310
88373
  const SEARCH_TERM_NAME_PATTERN = /search|query|highlight|filter|term(?!in)|keyword/i;
88374
+ const REGEX_SOURCE_WORDS = [
88375
+ "pattern",
88376
+ "regex",
88377
+ "regexp"
88378
+ ];
88311
88379
  const ESCAPE_HELPER_NAME_PATTERN = /escape.*reg|safe.*reg/i;
88312
88380
  const SANITIZED_NAME_PATTERN = /escap|sanitiz/i;
88313
88381
  const INITIALIZER_RESOLUTION_HOPS = 2;
@@ -88461,14 +88529,70 @@ const isTypePositionIdentifier = (identifier) => {
88461
88529
  }
88462
88530
  return false;
88463
88531
  };
88464
- const collectRawSearchTermIdentifiers = (argument) => {
88465
- const rawSearchTermIdentifiers = [];
88532
+ const identifierNameHasPathSegmentSemantics = (identifierName) => {
88533
+ const identifierWords = identifierName.replaceAll(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[\s_]+/).map((word) => word.toLowerCase());
88534
+ if (identifierWords.some((word) => REGEX_SOURCE_WORDS.includes(word))) return false;
88535
+ return identifierWords.some((word) => [
88536
+ "path",
88537
+ "folder",
88538
+ "directory",
88539
+ "root"
88540
+ ].includes(word)) || identifierWords.includes("top") && identifierWords.includes("level");
88541
+ };
88542
+ const flattenPatternParts = (expression) => {
88543
+ const inner = stripParenExpression(expression);
88544
+ const staticValue = literalStringValue(inner);
88545
+ if (staticValue !== null) return [staticValue];
88546
+ if (isNodeOfType(inner, "Identifier")) return [inner];
88547
+ if (isNodeOfType(inner, "TemplateLiteral")) {
88548
+ const parts = [];
88549
+ for (let index = 0; index < inner.quasis.length; index += 1) {
88550
+ const quasi = inner.quasis[index];
88551
+ if (quasi) parts.push(quasi.value.cooked ?? quasi.value.raw);
88552
+ const templateExpression = inner.expressions[index];
88553
+ if (templateExpression) parts.push(stripParenExpression(templateExpression));
88554
+ }
88555
+ return parts;
88556
+ }
88557
+ if (isNodeOfType(inner, "BinaryExpression") && inner.operator === "+") {
88558
+ const leftParts = flattenPatternParts(inner.left);
88559
+ const rightParts = flattenPatternParts(inner.right);
88560
+ return leftParts && rightParts ? [...leftParts, ...rightParts] : null;
88561
+ }
88562
+ if (isNodeOfType(inner, "CallExpression") && isNodeOfType(inner.callee, "MemberExpression") && getStaticPropertyName(inner.callee) === "concat") {
88563
+ const receiverParts = flattenPatternParts(inner.callee.object);
88564
+ if (!receiverParts) return null;
88565
+ const parts = [...receiverParts];
88566
+ for (const argument of inner.arguments) {
88567
+ if (isNodeOfType(argument, "SpreadElement")) return null;
88568
+ const argumentParts = flattenPatternParts(argument);
88569
+ if (!argumentParts) return null;
88570
+ parts.push(...argumentParts);
88571
+ }
88572
+ return parts;
88573
+ }
88574
+ return null;
88575
+ };
88576
+ const isIdentifierAnAnchoredPathSegment = (argument, identifier) => {
88577
+ if (!identifierNameHasPathSegmentSemantics(identifier.name)) return false;
88578
+ const parts = flattenPatternParts(argument);
88579
+ if (!parts) return false;
88580
+ const identifierPartIndex = parts.findIndex((part) => part === identifier);
88581
+ if (identifierPartIndex < 0) return false;
88582
+ const precedingParts = parts.slice(0, identifierPartIndex);
88583
+ if (precedingParts.some((part) => typeof part !== "string")) return false;
88584
+ const staticPrefix = precedingParts.join("");
88585
+ const followingPart = parts[identifierPartIndex + 1];
88586
+ return staticPrefix === "^" && typeof followingPart === "string" && followingPart.startsWith("/");
88587
+ };
88588
+ const collectRawDynamicLiteralIdentifiers = (argument) => {
88589
+ const rawDynamicLiteralIdentifiers = [];
88466
88590
  walkAst(argument, (child) => {
88467
88591
  if (isEscapingCall(child) || isRegexSourceAccess(child)) return false;
88468
88592
  if (isLiteralReturningGetterCall(child)) return false;
88469
- if (isNodeOfType(child, "Identifier") && SEARCH_TERM_NAME_PATTERN.test(child.name) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child)) rawSearchTermIdentifiers.push(child);
88593
+ if (isNodeOfType(child, "Identifier") && (SEARCH_TERM_NAME_PATTERN.test(child.name) || isIdentifierAnAnchoredPathSegment(argument, child)) && !isPropertyNamePosition(child) && !isTypePositionIdentifier(child)) rawDynamicLiteralIdentifiers.push(child);
88470
88594
  });
88471
- return rawSearchTermIdentifiers;
88595
+ return rawDynamicLiteralIdentifiers;
88472
88596
  };
88473
88597
  const collectLeafIdentifiers = (node) => {
88474
88598
  const leafIdentifiers = [];
@@ -88479,8 +88603,11 @@ const collectLeafIdentifiers = (node) => {
88479
88603
  return leafIdentifiers;
88480
88604
  };
88481
88605
  const compositeInitializerResolvesEscaped = (strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames) => {
88606
+ if (isNodeOfType(strippedInitializer, "ConditionalExpression")) return [strippedInitializer.consequent, strippedInitializer.alternate].every((branch) => initializerLooksEscaped(branch, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
88607
+ if (isNodeOfType(strippedInitializer, "BinaryExpression") || isNodeOfType(strippedInitializer, "LogicalExpression")) return [strippedInitializer.left, strippedInitializer.right].every((operand) => initializerLooksEscaped(operand, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
88608
+ if (isNodeOfType(strippedInitializer, "TemplateLiteral")) return strippedInitializer.expressions.every((expression) => initializerLooksEscaped(expression, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames));
88482
88609
  let didResolveAnyLeafEscaped = false;
88483
- for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
88610
+ for (const leafIdentifier of collectLeafIdentifiers(strippedInitializer)) if (identifierResolvesToEscapedValue(leafIdentifier, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames)) didResolveAnyLeafEscaped = true;
88484
88611
  else if (SEARCH_TERM_NAME_PATTERN.test(leafIdentifier.name)) return false;
88485
88612
  return didResolveAnyLeafEscaped;
88486
88613
  };
@@ -88492,7 +88619,7 @@ const initializerLooksEscaped = (initializer, remainingHops, scopes, regexpObjec
88492
88619
  if (isNodeOfType(strippedInitializer, "CallExpression") && (isEscapingCall(strippedInitializer) || calleeBindingBodyEscapes(strippedInitializer))) return true;
88493
88620
  if (remainingHops > 0) {
88494
88621
  if (isNodeOfType(strippedInitializer, "Identifier")) return identifierResolvesToEscapedValue(strippedInitializer, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
88495
- return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops - 1, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
88622
+ return compositeInitializerResolvesEscaped(strippedInitializer, remainingHops, scopes, regexpObjectSymbolIds, globalRegExpObjectNames);
88496
88623
  }
88497
88624
  return false;
88498
88625
  };
@@ -88684,7 +88811,7 @@ const noUnescapedDynamicStringInRegexp = defineRule({
88684
88811
  severity: "warn",
88685
88812
  category: "Correctness",
88686
88813
  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.",
88814
+ 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
88815
  create: (context) => {
88689
88816
  if (TEST_CONTEXT_FILE_PATTERN.test(context.filename ?? "")) return {};
88690
88817
  let regexpObjectIndex = null;
@@ -88701,10 +88828,10 @@ const noUnescapedDynamicStringInRegexp = defineRule({
88701
88828
  regexpObjectIndex = buildRegExpObjectIndex(programRoot, context.scopes);
88702
88829
  }
88703
88830
  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;
88831
+ 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
88832
  context.report({
88706
88833
  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."
88834
+ 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
88835
  });
88709
88836
  };
88710
88837
  return {
@@ -140049,6 +140176,7 @@ const shouldReadSecurityScanContent = (relativePath, isGeneratedBundle) => isGen
140049
140176
  //#region src/plugin/utils/capability.ts
140050
140177
  const FRAMEWORK_TOKENS = [
140051
140178
  "nextjs",
140179
+ "astro",
140052
140180
  "vite",
140053
140181
  "cra",
140054
140182
  "remix",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.9.2-dev.3f3197f",
3
+ "version": "0.9.2-dev.444e177",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",
@@ -46,7 +46,7 @@
46
46
  "@typescript-eslint/types": "^8.59.3",
47
47
  "eslint-scope": "^9.1.2",
48
48
  "eslint-visitor-keys": "^5.0.1",
49
- "oxc-parser": "^0.141.0"
49
+ "oxc-parser": "^0.142.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@types/node": "^25.6.0"