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 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;
@@ -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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.9.2-dev.86add14",
3
+ "version": "0.9.2-dev.b10cd4c",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",