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 +1 -1
- package/dist/index.js +147 -19
- package/package.json +2 -2
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
|
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
|
-
|
|
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
|
|
88465
|
-
const
|
|
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))
|
|
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
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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.
|
|
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.
|
|
49
|
+
"oxc-parser": "^0.142.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@types/node": "^25.6.0"
|