oxlint-plugin-react-doctor 0.9.2-dev.86add14 → 0.9.2-dev.b10cd4c
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 +74 -10
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -98,7 +98,7 @@ interface RuleContext extends Omit<BaseRuleContext, "getFilename"> {
|
|
|
98
98
|
}
|
|
99
99
|
//#endregion
|
|
100
100
|
//#region src/plugin/utils/capability.d.ts
|
|
101
|
-
declare const FRAMEWORK_TOKENS: readonly ["nextjs", "vite", "cra", "remix", "gatsby", "expo", "react-native", "tanstack-start", "preact", "unknown"];
|
|
101
|
+
declare const FRAMEWORK_TOKENS: readonly ["nextjs", "astro", "vite", "cra", "remix", "gatsby", "expo", "react-native", "tanstack-start", "preact", "unknown"];
|
|
102
102
|
type FrameworkToken = (typeof FRAMEWORK_TOKENS)[number];
|
|
103
103
|
type Capability = FrameworkToken | "react" | "remotion" | "pure-preact" | "react-native" | "server-actions" | "ssr" | "client-only" | "expo:54" | "nextjs:static-export" | "nextjs:15" | "nextjs:16" | "tailwind" | "tailwind:3.4" | "tailwind:4" | "zod" | "zod:4" | "mobx" | "mobx-react" | "mobx-react-lite" | "mobx-react-binding" | "mobx-react-binding-observer-memo-guard" | "mobx-react-observer-memo-guard" | "mobx-react-lite-observer-memo-guard" | "mobx-state-tree" | "mobx-react-observer" | "zustand" | "typescript" | "react-compiler" | "reanimated" | "reanimated:4" | "tanstack-query" | "valtio" | "i18n" | "styled-components" | "styled-components:6" | "three" | "r3f" | "react-router" | "react-router-framework" | "react-router:6.4" | "react-router:6.7" | "react-router:6.9" | "react-router:6.19" | "react-router:7" | "react-router:7.8" | "react-router:7.9" | "react-router:7.10" | "react-router:7.15" | "react-router:8" | "pre-es2023" | "target-blank-needs-explicit-protection" | "target-blank-needs-noreferrer" | `react:${number}` | `preact:${number}` | `remotion:${number}` | `valtio:${number}` | `mobx:${number}` | `zustand:${number}` | `three:${number}` | `r3f:${number}`;
|
|
104
104
|
type CapabilityQuery = (capability: Capability) => boolean;
|
package/dist/index.js
CHANGED
|
@@ -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;
|
|
@@ -140049,6 +140112,7 @@ const shouldReadSecurityScanContent = (relativePath, isGeneratedBundle) => isGen
|
|
|
140049
140112
|
//#region src/plugin/utils/capability.ts
|
|
140050
140113
|
const FRAMEWORK_TOKENS = [
|
|
140051
140114
|
"nextjs",
|
|
140115
|
+
"astro",
|
|
140052
140116
|
"vite",
|
|
140053
140117
|
"cra",
|
|
140054
140118
|
"remix",
|