oxlint-plugin-react-doctor 0.7.2-dev.11e9c87 → 0.7.2-dev.43d766f

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.
Files changed (2) hide show
  1. package/dist/index.js +171 -286
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -595,19 +595,6 @@ const TRIVIAL_INITIALIZER_NAMES = new Set([
595
595
  "parseInt",
596
596
  "parseFloat"
597
597
  ]);
598
- const TRIVIAL_CONSTRUCTOR_NAMES = new Set([
599
- "Date",
600
- "Map",
601
- "Set",
602
- "WeakMap",
603
- "WeakSet",
604
- "WeakRef",
605
- "RegExp",
606
- "Error",
607
- "URL",
608
- "URLSearchParams",
609
- "AbortController"
610
- ]);
611
598
  const TRIVIAL_DERIVATION_CALLEE_NAMES = new Set([
612
599
  "Boolean",
613
600
  "String",
@@ -2248,38 +2235,6 @@ const anchorHasContent = defineRule({
2248
2235
  }
2249
2236
  });
2250
2237
  //#endregion
2251
- //#region src/plugin/utils/get-jsx-prop-static-string-values.ts
2252
- const MAX_CONST_RESOLUTION_HOPS = 4;
2253
- const resolveStaticStringValues = (rawExpression, scopes, remainingHops) => {
2254
- const expression = stripParenExpression(rawExpression);
2255
- if (isNodeOfType(expression, "Literal")) return typeof expression.value === "string" ? [expression.value] : null;
2256
- if (isNodeOfType(expression, "TemplateLiteral")) {
2257
- const staticValue = getStaticTemplateLiteralValue(expression);
2258
- return staticValue === null ? null : [staticValue];
2259
- }
2260
- if (isNodeOfType(expression, "ConditionalExpression")) {
2261
- const consequentValues = resolveStaticStringValues(expression.consequent, scopes, remainingHops);
2262
- if (consequentValues === null) return null;
2263
- const alternateValues = resolveStaticStringValues(expression.alternate, scopes, remainingHops);
2264
- if (alternateValues === null) return null;
2265
- return [...consequentValues, ...alternateValues];
2266
- }
2267
- if (isNodeOfType(expression, "Identifier")) {
2268
- if (remainingHops === 0) return null;
2269
- const symbol = scopes.referenceFor(expression)?.resolvedSymbol;
2270
- if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
2271
- return resolveStaticStringValues(symbol.initializer, scopes, remainingHops - 1);
2272
- }
2273
- return null;
2274
- };
2275
- const getJsxPropStaticStringValues = (attribute, scopes) => {
2276
- const value = attribute.value;
2277
- if (!value) return null;
2278
- if (isNodeOfType(value, "Literal")) return typeof value.value === "string" ? [value.value] : null;
2279
- if (isNodeOfType(value, "JSXExpressionContainer")) return resolveStaticStringValues(value.expression, scopes, MAX_CONST_RESOLUTION_HOPS);
2280
- return null;
2281
- };
2282
- //#endregion
2283
2238
  //#region src/plugin/rules/a11y/anchor-is-valid.ts
2284
2239
  const MESSAGE_MISSING_HREF = "Keyboard users can't reach this link because it has no `href`, so add a real `href` (or use `<button>` for actions).";
2285
2240
  const MESSAGE_INCORRECT_HREF = "Keyboard users can't reach this link because its `href` goes nowhere (`#`, `javascript:`, or empty), so point it at a real destination.";
@@ -2309,11 +2264,28 @@ const isInvalidHref = (value, validHrefs) => {
2309
2264
  if (validHrefs.has(value)) return false;
2310
2265
  return value === "" || value === "#" || value === "javascript:void(0)";
2311
2266
  };
2312
- const isNullishOrFragmentHref = (value) => {
2267
+ const getStaticHrefValue = (value) => {
2268
+ if (isNodeOfType(value, "Literal")) return typeof value.value === "string" ? value.value : null;
2269
+ if (isNodeOfType(value, "JSXExpressionContainer")) {
2270
+ const expression = value.expression;
2271
+ if (isNodeOfType(expression, "Literal") && typeof expression.value === "string") return expression.value;
2272
+ if (isNodeOfType(expression, "TemplateLiteral")) return getStaticTemplateLiteralValue(expression);
2273
+ }
2274
+ return null;
2275
+ };
2276
+ const checkValueIsEmptyOrInvalid = (value, validHrefs) => {
2277
+ if (isNodeOfType(value, "Literal")) return typeof value.value === "string" ? isInvalidHref(value.value, validHrefs) : false;
2313
2278
  if (isNodeOfType(value, "JSXExpressionContainer")) {
2314
2279
  const expression = value.expression;
2315
2280
  if (isNodeOfType(expression, "Identifier") && expression.name === "undefined") return true;
2316
- if (isNodeOfType(expression, "Literal") && expression.value === null) return true;
2281
+ if (isNodeOfType(expression, "Literal")) {
2282
+ if (expression.value === null) return true;
2283
+ if (typeof expression.value === "string") return isInvalidHref(expression.value, validHrefs);
2284
+ }
2285
+ if (isNodeOfType(expression, "TemplateLiteral")) {
2286
+ const staticValue = getStaticTemplateLiteralValue(expression);
2287
+ return staticValue === null ? false : isInvalidHref(staticValue, validHrefs);
2288
+ }
2317
2289
  }
2318
2290
  if (isNodeOfType(value, "JSXFragment")) return true;
2319
2291
  return false;
@@ -2346,10 +2318,9 @@ const anchorIsValid = defineRule({
2346
2318
  });
2347
2319
  return;
2348
2320
  }
2349
- const hrefCandidates = getJsxPropStaticStringValues(hrefAttribute, context.scopes);
2350
- if (hrefCandidates !== null ? hrefCandidates.length > 0 && hrefCandidates.every((candidate) => isInvalidHref(candidate, settings.validHrefs)) : isNullishOrFragmentHref(hrefAttribute.value)) {
2321
+ if (checkValueIsEmptyOrInvalid(hrefAttribute.value, settings.validHrefs)) {
2351
2322
  const hasOnClick = Boolean(hasJsxPropIgnoreCase(node.attributes, "onClick"));
2352
- if (!hasOnClick && hrefCandidates?.every((candidate) => candidate === "#")) return;
2323
+ if (!hasOnClick && getStaticHrefValue(hrefAttribute.value) === "#") return;
2353
2324
  context.report({
2354
2325
  node: node.name,
2355
2326
  message: hasOnClick ? MESSAGE_CANT_BE_ANCHOR : MESSAGE_INCORRECT_HREF
@@ -3405,25 +3376,6 @@ const ariaRole = defineRule({
3405
3376
  if (!roleAttribute) return;
3406
3377
  const elementType = getElementType(node, context.settings);
3407
3378
  if (settings.ignoreNonDOM && !HTML_TAGS.has(elementType)) return;
3408
- const reportFirstInvalidCandidate = (candidates) => {
3409
- for (const candidate of candidates) {
3410
- if (candidate.trim().length === 0) {
3411
- context.report({
3412
- node: roleAttribute,
3413
- message: buildBaseMessage("")
3414
- });
3415
- return;
3416
- }
3417
- const tokens = candidate.split(/\s+/).filter((token) => token.length > 0);
3418
- for (const token of tokens) if (!VALID_ARIA_ROLES.has(token) && !settings.allowedInvalidRoles.includes(token)) {
3419
- context.report({
3420
- node: roleAttribute,
3421
- message: buildBaseMessage(` \`${token}\` is not one.`)
3422
- });
3423
- return;
3424
- }
3425
- }
3426
- };
3427
3379
  const value = roleAttribute.value;
3428
3380
  if (!value) {
3429
3381
  context.report({
@@ -3440,7 +3392,22 @@ const ariaRole = defineRule({
3440
3392
  });
3441
3393
  return;
3442
3394
  }
3443
- reportFirstInvalidCandidate([value.value]);
3395
+ const stringValue = value.value;
3396
+ if (stringValue.trim().length === 0) {
3397
+ context.report({
3398
+ node: roleAttribute,
3399
+ message: buildBaseMessage("")
3400
+ });
3401
+ return;
3402
+ }
3403
+ const tokens = stringValue.split(/\s+/).filter((token) => token.length > 0);
3404
+ for (const token of tokens) if (!VALID_ARIA_ROLES.has(token) && !settings.allowedInvalidRoles.includes(token)) {
3405
+ context.report({
3406
+ node: roleAttribute,
3407
+ message: buildBaseMessage(` \`${token}\` is not one.`)
3408
+ });
3409
+ return;
3410
+ }
3444
3411
  return;
3445
3412
  }
3446
3413
  if (isNodeOfType(value, "JSXExpressionContainer")) {
@@ -3461,11 +3428,6 @@ const ariaRole = defineRule({
3461
3428
  });
3462
3429
  return;
3463
3430
  }
3464
- const resolvedCandidates = getJsxPropStaticStringValues(roleAttribute, context.scopes);
3465
- if (resolvedCandidates !== null) {
3466
- reportFirstInvalidCandidate(resolvedCandidates);
3467
- return;
3468
- }
3469
3431
  return;
3470
3432
  }
3471
3433
  context.report({
@@ -9654,7 +9616,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
9654
9616
  });
9655
9617
  //#endregion
9656
9618
  //#region src/plugin/rules/react-native/expo-no-non-inlined-env.ts
9657
- const EMPTY_VISITORS$6 = {};
9619
+ const EMPTY_VISITORS$5 = {};
9658
9620
  const NODE_OR_BUILD_FILE = /(\.config\.[cm]?[jt]sx?$)|((^|\/)(scripts|tools|tooling|cli|bin)\/)|(\+(api|html)\.[cm]?[jt]sx?$)|(\.server\.[cm]?[jt]sx?$)|(\.(test|spec)\.[cm]?[jt]sx?$)|((^|\/)__tests__\/)|(\.e2e\.[cm]?[jt]sx?$)/;
9659
9621
  const isNonExpoPublicLiteralKey = (key) => isNodeOfType(key, "Literal") && typeof key.value === "string" && !key.value.startsWith("EXPO_PUBLIC_");
9660
9622
  const isProcessEnv = (node) => isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && node.object.name === "process" && isNodeOfType(node.property, "Identifier") && node.property.name === "env";
@@ -9666,7 +9628,7 @@ const expoNoNonInlinedEnv = defineRule({
9666
9628
  recommendation: "Read env vars with static dotted access (`process.env.EXPO_PUBLIC_NAME`). Computed access and destructuring aren't inlined by babel-preset-expo and resolve to `undefined` at runtime.",
9667
9629
  create: (context) => {
9668
9630
  const filename = normalizeFilename(context.filename ?? "");
9669
- if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$6;
9631
+ if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$5;
9670
9632
  return {
9671
9633
  MemberExpression(node) {
9672
9634
  if (!node.computed) return;
@@ -11084,8 +11046,8 @@ const interactiveSupportsFocus = defineRule({
11084
11046
  if (node.attributes.length === 0) return;
11085
11047
  if (hasJsxSpreadAttribute$1(node.attributes)) return;
11086
11048
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
11087
- const roleCandidates = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : null;
11088
- if (roleCandidates === null || roleCandidates.length === 0) return;
11049
+ const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
11050
+ if (!role) return;
11089
11051
  let hasInteractiveHandler = false;
11090
11052
  for (const attribute of node.attributes) {
11091
11053
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -11099,16 +11061,11 @@ const interactiveSupportsFocus = defineRule({
11099
11061
  const elementType = getElementType(node, context.settings);
11100
11062
  if (!HTML_TAGS.has(elementType)) return;
11101
11063
  if (isDisabledElement(node) || isHiddenFromScreenReader(node, context.settings) || isPresentationRole(node)) return;
11064
+ if (COMPOSITE_CONTAINER_ROLES.has(role)) return;
11065
+ if (COMPOSITE_ITEM_ROLES.has(role) && Boolean(hasJsxPropIgnoreCase(node.attributes, "id"))) return;
11102
11066
  const hasTabIndex = Boolean(hasJsxPropIgnoreCase(node.attributes, "tabIndex"));
11103
- const hasId = Boolean(hasJsxPropIgnoreCase(node.attributes, "id"));
11104
- for (const role of roleCandidates) {
11105
- if (COMPOSITE_CONTAINER_ROLES.has(role)) return;
11106
- if (COMPOSITE_ITEM_ROLES.has(role) && hasId) return;
11107
- if (!isInteractiveRole(role) || isInteractiveElement(elementType, node) || isNonInteractiveRole(role) || isNonInteractiveElement(elementType, node) || hasTabIndex) return;
11108
- }
11109
- const isEveryCandidateTabbable = roleCandidates.every((role) => tabbableSet.has(role));
11110
- const roleDisplay = roleCandidates.join("' / '");
11111
- const message = isEveryCandidateTabbable ? buildTabbableMessage(roleDisplay) : buildFocusableMessage(roleDisplay);
11067
+ if (!isInteractiveRole(role) || isInteractiveElement(elementType, node) || isNonInteractiveRole(role) || isNonInteractiveElement(elementType, node) || hasTabIndex) return;
11068
+ const message = tabbableSet.has(role) ? buildTabbableMessage(role) : buildFocusableMessage(role);
11112
11069
  context.report({
11113
11070
  node,
11114
11071
  message
@@ -22520,10 +22477,6 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
22520
22477
  const section = manifest[sectionName];
22521
22478
  return typeof section === "object" && section !== null && Object.keys(section).length > 0;
22522
22479
  });
22523
- const declaresDependency = (manifest, dependencyName) => {
22524
- for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
22525
- return false;
22526
- };
22527
22480
  const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
22528
22481
  const classifyPackagePlatform = (filename) => {
22529
22482
  const manifest = readNearestPackageManifest(filename);
@@ -22540,7 +22493,9 @@ const classifyPackagePlatform = (filename) => {
22540
22493
  return result;
22541
22494
  };
22542
22495
  //#endregion
22543
- //#region src/plugin/utils/is-package-nested-below-project-root.ts
22496
+ //#region src/plugin/utils/is-react-native-file.ts
22497
+ const WEB_FILE_EXTENSION_PATTERN = /\.web\.[cm]?[jt]sx?$/;
22498
+ const NATIVE_FILE_EXTENSION_PATTERN = /\.(?:ios|android|native)\.[cm]?[jt]sx?$/;
22544
22499
  const cachedRealDirectoryByDirectory = /* @__PURE__ */ new Map();
22545
22500
  const resolveRealDirectory = (directory) => {
22546
22501
  const cached = cachedRealDirectoryByDirectory.get(directory);
@@ -22561,10 +22516,6 @@ const isPackageNestedBelowProjectRoot = (packageDirectory, rootDirectory) => {
22561
22516
  const rootPrefix = normalizedRootDirectory.endsWith("/") ? normalizedRootDirectory : `${normalizedRootDirectory}/`;
22562
22517
  return realPackageDirectory.startsWith(rootPrefix);
22563
22518
  };
22564
- //#endregion
22565
- //#region src/plugin/utils/is-react-native-file.ts
22566
- const WEB_FILE_EXTENSION_PATTERN = /\.web\.[cm]?[jt]sx?$/;
22567
- const NATIVE_FILE_EXTENSION_PATTERN = /\.(?:ios|android|native)\.[cm]?[jt]sx?$/;
22568
22519
  const classifyReactNativeFileTarget = (context) => {
22569
22520
  const rawFilename = context.filename;
22570
22521
  if (!rawFilename) return "unknown";
@@ -22981,6 +22932,7 @@ const countStatementSequenceSetStateCalls = (statements, context) => {
22981
22932
  let fallThroughCount = 0;
22982
22933
  let maxTerminatingPathCount = 0;
22983
22934
  for (const statement of statements) {
22935
+ if (isFunctionLike$1(statement)) continue;
22984
22936
  const guardBranch = isGuardWithTerminatingBranch(statement);
22985
22937
  if (guardBranch) {
22986
22938
  maxTerminatingPathCount = Math.max(maxTerminatingPathCount, fallThroughCount + countMaxPathSetStateCalls(guardBranch, context));
@@ -23016,23 +22968,14 @@ const isWholesaleDelegationCall = (callNode, effectCallback) => {
23016
22968
  if (!isNodeOfType(parent, "ExpressionStatement")) return false;
23017
22969
  return parent.parent === effectCallback.body;
23018
22970
  };
23019
- const countFunctionBodySetStateCalls = (functionNode, context) => {
23020
- if (isAsyncFunctionLike(functionNode)) return 0;
23021
- const body = functionNode.body;
23022
- if (!body) return 0;
23023
- return countMaxPathSetStateCalls(body, context);
23024
- };
23025
22971
  const countMaxPathSetStateCalls = (node, context) => {
23026
22972
  if (!node || typeof node !== "object") return 0;
23027
- if (isFunctionLike$1(node)) return 0;
22973
+ if (isAsyncFunctionLike(node)) return 0;
23028
22974
  if (isNodeOfType(node, "BlockStatement") || isNodeOfType(node, "Program")) return countStatementSequenceSetStateCalls(node.body ?? [], context);
23029
22975
  if (isNodeOfType(node, "IfStatement") || isNodeOfType(node, "ConditionalExpression")) {
23030
22976
  const consequent = node.consequent;
23031
22977
  const alternate = node.alternate;
23032
- const testCount = countMaxPathSetStateCalls(node.test, context);
23033
- const thenCount = countMaxPathSetStateCalls(consequent, context);
23034
- const elseCount = alternate ? countMaxPathSetStateCalls(alternate, context) : 0;
23035
- return testCount + Math.max(thenCount, elseCount);
22978
+ return countMaxPathSetStateCalls(consequent, context) + (alternate ? countMaxPathSetStateCalls(alternate, context) : 0);
23036
22979
  }
23037
22980
  if (isNodeOfType(node, "SwitchStatement")) {
23038
22981
  let maxRunSetters = 0;
@@ -23062,31 +23005,28 @@ const countMaxPathSetStateCalls = (node, context) => {
23062
23005
  }
23063
23006
  if (isNodeOfType(node, "CallExpression") && isSetterCall(node) && isNodeOfType(node.callee, "Identifier") && isUseStateSetterInScope(node, node.callee.name)) {
23064
23007
  let nestedSettersInArgs = 0;
23065
- for (const argument of node.arguments ?? []) nestedSettersInArgs += isFunctionLike$1(argument) ? countFunctionBodySetStateCalls(argument, context) : countMaxPathSetStateCalls(argument, context);
23008
+ for (const argument of node.arguments ?? []) nestedSettersInArgs += countMaxPathSetStateCalls(argument, context);
23066
23009
  return 1 + nestedSettersInArgs;
23067
23010
  }
23068
23011
  if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier")) {
23069
23012
  const helperFunction = context.helpersByName.get(node.callee.name);
23070
23013
  if (helperFunction && !context.activeHelpers.has(helperFunction) && isWholesaleDelegationCall(node, context.effectCallback)) {
23071
23014
  context.activeHelpers.add(helperFunction);
23072
- let helperCount = countFunctionBodySetStateCalls(helperFunction, context);
23015
+ let helperCount = countMaxPathSetStateCalls(helperFunction, context);
23073
23016
  context.activeHelpers.delete(helperFunction);
23074
23017
  for (const argument of node.arguments ?? []) helperCount += countMaxPathSetStateCalls(argument, context);
23075
23018
  return helperCount;
23076
23019
  }
23077
23020
  }
23078
- const countChild = (child) => {
23079
- if (isFunctionLike$1(child)) return runsOnEffectDispatch(child) ? countFunctionBodySetStateCalls(child, context) : 0;
23080
- return countMaxPathSetStateCalls(child, context);
23081
- };
23021
+ const shouldWalkChild = (child) => !isFunctionLike$1(child) || runsOnEffectDispatch(child);
23082
23022
  let total = 0;
23083
23023
  const nodeRecord = node;
23084
23024
  for (const key of Object.keys(nodeRecord)) {
23085
23025
  if (key === "parent") continue;
23086
23026
  const child = nodeRecord[key];
23087
23027
  if (Array.isArray(child)) {
23088
- for (const item of child) if (item && typeof item === "object" && "type" in item) total += countChild(item);
23089
- } else if (child && typeof child === "object" && "type" in child) total += countChild(child);
23028
+ for (const item of child) if (item && typeof item === "object" && "type" in item && shouldWalkChild(item)) total += countMaxPathSetStateCalls(item, context);
23029
+ } else if (child && typeof child === "object" && "type" in child && shouldWalkChild(child)) total += countMaxPathSetStateCalls(child, context);
23090
23030
  }
23091
23031
  return total;
23092
23032
  };
@@ -23135,7 +23075,7 @@ const noCascadingSetState = defineRule({
23135
23075
  const callback = getEffectCallback(node);
23136
23076
  if (!callback) return;
23137
23077
  if (isDevOnlyGuardedEffect(callback)) return;
23138
- const setStateCallCount = countFunctionBodySetStateCalls(callback, {
23078
+ const setStateCallCount = countMaxPathSetStateCalls(callback, {
23139
23079
  helpersByName: collectLocalHelperFunctions(findProgramRoot(node) ?? callback),
23140
23080
  activeHelpers: /* @__PURE__ */ new Set(),
23141
23081
  effectCallback: callback
@@ -26353,11 +26293,7 @@ const noEffectEventInDeps = defineRule({
26353
26293
  const initializer = declaratorNode.init;
26354
26294
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
26355
26295
  if (!isHookCall$2(initializer, "useEffectEvent")) return;
26356
- if (isNodeOfType(initializer.callee, "Identifier")) {
26357
- if (isImportedFromNonReactModule(declaratorNode, initializer.callee.name)) return;
26358
- const calleeSymbol = context.scopes.referenceFor(initializer.callee)?.resolvedSymbol;
26359
- if (calleeSymbol && calleeSymbol.kind !== "import") return;
26360
- }
26296
+ if (isNodeOfType(initializer.callee, "Identifier") && isImportedFromNonReactModule(declaratorNode, initializer.callee.name)) return;
26361
26297
  componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
26362
26298
  } });
26363
26299
  return {
@@ -30079,40 +30015,6 @@ const noMutableInDeps = defineRule({
30079
30015
  }
30080
30016
  });
30081
30017
  //#endregion
30082
- //#region src/plugin/utils/is-result-discarded-call.ts
30083
- const isResultDiscardedCall = (callExpression) => {
30084
- let node = callExpression;
30085
- let parent = node.parent;
30086
- while (parent) {
30087
- if (isNodeOfType(parent, "ExpressionStatement")) return true;
30088
- if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
30089
- if (isNodeOfType(parent, "ChainExpression")) {
30090
- node = parent;
30091
- parent = node.parent;
30092
- continue;
30093
- }
30094
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
30095
- node = parent;
30096
- parent = node.parent;
30097
- continue;
30098
- }
30099
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
30100
- node = parent;
30101
- parent = node.parent;
30102
- continue;
30103
- }
30104
- if (isNodeOfType(parent, "SequenceExpression")) {
30105
- const expressions = parent.expressions ?? [];
30106
- if (expressions[expressions.length - 1] !== node) return true;
30107
- node = parent;
30108
- parent = node.parent;
30109
- continue;
30110
- }
30111
- return false;
30112
- }
30113
- return false;
30114
- };
30115
- //#endregion
30116
30018
  //#region src/plugin/rules/state-and-effects/utils/lodash-mutator-call.ts
30117
30019
  const LODASH_MUTATOR_NAMES = new Set([
30118
30020
  "set",
@@ -30210,6 +30112,7 @@ const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
30210
30112
  "reverse",
30211
30113
  "sort"
30212
30114
  ]);
30115
+ const SAME_REFERENCE_COLLECTION_RETURN_METHODS = new Set(["add", "set"]);
30213
30116
  const OBJECT_MUTATION_METHODS = new Set([
30214
30117
  "assign",
30215
30118
  "defineProperties",
@@ -30283,6 +30186,7 @@ const canExpressionReturnOriginalReducerStateReference = (node, state) => {
30283
30186
  const methodName = getStaticMemberPropertyName(unwrappedNode.callee);
30284
30187
  if (methodName === "assign" && isNodeOfType(unwrappedNode.callee.object, "Identifier") && unwrappedNode.callee.object.name === "Object") return isExpressionOriginalReducerStateReference(unwrappedNode.arguments?.[0], state);
30285
30188
  if (methodName && SAME_REFERENCE_ARRAY_RETURN_METHODS.has(methodName) && isExpressionOriginalReducerStateReference(unwrappedNode.callee.object, state)) return true;
30189
+ if (methodName && SAME_REFERENCE_COLLECTION_RETURN_METHODS.has(methodName) && isExpressionOriginalReducerStateReference(unwrappedNode.callee.object, state)) return true;
30286
30190
  }
30287
30191
  }
30288
30192
  if (isNodeOfType(unwrappedNode, "ConditionalExpression")) return canExpressionReturnOriginalReducerStateReference(unwrappedNode.consequent, state) || canExpressionReturnOriginalReducerStateReference(unwrappedNode.alternate, state);
@@ -30321,7 +30225,6 @@ const collectReducerStateMutationsInExpressionOrStatement = (node, state) => {
30321
30225
  if (!isNodeOfType(unwrappedChild.callee, "MemberExpression")) return;
30322
30226
  const methodName = getStaticMemberPropertyName(unwrappedChild.callee);
30323
30227
  if (!methodName || !MUTATING_ARRAY_METHODS.has(methodName) && !MUTATING_COLLECTION_METHODS.has(methodName)) return;
30324
- if (MUTATING_COLLECTION_METHODS.has(methodName) && !MUTATING_ARRAY_METHODS.has(methodName) && !isResultDiscardedCall(unwrappedChild)) return;
30325
30228
  if (isExpressionRootedInMutableReducerStateSource(unwrappedChild.callee.object, state)) mutations.push({ node: unwrappedChild });
30326
30229
  });
30327
30230
  return mutations;
@@ -32033,6 +31936,40 @@ const noPreventDefault = defineRule({
32033
31936
  }
32034
31937
  });
32035
31938
  //#endregion
31939
+ //#region src/plugin/utils/is-result-discarded-call.ts
31940
+ const isResultDiscardedCall = (callExpression) => {
31941
+ let node = callExpression;
31942
+ let parent = node.parent;
31943
+ while (parent) {
31944
+ if (isNodeOfType(parent, "ExpressionStatement")) return true;
31945
+ if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === node) return true;
31946
+ if (isNodeOfType(parent, "ChainExpression")) {
31947
+ node = parent;
31948
+ parent = node.parent;
31949
+ continue;
31950
+ }
31951
+ if (isNodeOfType(parent, "LogicalExpression") && parent.right === node) {
31952
+ node = parent;
31953
+ parent = node.parent;
31954
+ continue;
31955
+ }
31956
+ if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === node || parent.alternate === node)) {
31957
+ node = parent;
31958
+ parent = node.parent;
31959
+ continue;
31960
+ }
31961
+ if (isNodeOfType(parent, "SequenceExpression")) {
31962
+ const expressions = parent.expressions ?? [];
31963
+ if (expressions[expressions.length - 1] !== node) return true;
31964
+ node = parent;
31965
+ parent = node.parent;
31966
+ continue;
31967
+ }
31968
+ return false;
31969
+ }
31970
+ return false;
31971
+ };
31972
+ //#endregion
32036
31973
  //#region src/plugin/rules/state-and-effects/no-prop-callback-in-effect.ts
32037
31974
  const isRefLatchGuardedEffect = (callbackBody) => {
32038
31975
  const refNamesReadInGuards = /* @__PURE__ */ new Set();
@@ -36638,7 +36575,8 @@ const NAMED_EXPORT_MESSAGE = "This file exports non-components, so Fast Refresh
36638
36575
  const ANONYMOUS_MESSAGE = "This component is unnamed, so Fast Refresh can't track it and falls back to a full reload.";
36639
36576
  const EXPORT_ALL_MESSAGE = "`export *` hides what's exported, so Fast Refresh can't safely preserve component state.";
36640
36577
  const REACT_CONTEXT_MESSAGE = "This file exports a context with components, so Fast Refresh can't safely preserve component state.";
36641
- const NAMESPACE_OBJECT_MESSAGE = "This export bundles components inside an object, so Fast Refresh can't track them and falls back to a full reload.";
36578
+ const LOCAL_COMPONENT_MESSAGE = "This component is not exported, so Fast Refresh skips it and local edits can full-reload.";
36579
+ const NO_EXPORT_MESSAGE = "This file exports nothing, so Fast Refresh can't track the component and local edits can full-reload.";
36642
36580
  const DEFAULT_REACT_HOCS = [
36643
36581
  "memo",
36644
36582
  "forwardRef",
@@ -36709,20 +36647,6 @@ const isReactComponentInitializer = (expression, state) => {
36709
36647
  if (isNodeOfType(stripped, "CallExpression") && isHocCallee(stripped.callee, state) && stripped.arguments.length > 0) return true;
36710
36648
  return false;
36711
36649
  };
36712
- const objectExpressionBundlesComponents = (objectExpression, state) => {
36713
- for (const property of objectExpression.properties ?? []) {
36714
- if (!isNodeOfType(property, "Property")) continue;
36715
- const value = skipTsExpression(property.value);
36716
- if (isNodeOfType(value, "Identifier")) {
36717
- if (state.localComponentNames.has(value.name)) return true;
36718
- continue;
36719
- }
36720
- if (!(!property.computed && isNodeOfType(property.key, "Identifier") && isReactComponentName(property.key.name))) continue;
36721
- if (isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) return true;
36722
- if (isNodeOfType(value, "CallExpression") && isHocCallee(value.callee, state) && value.arguments.length > 0) return true;
36723
- }
36724
- return false;
36725
- };
36726
36650
  const classifyExport = (name, reportNode, isFunction, initializer, state) => {
36727
36651
  if (initializer) {
36728
36652
  const expression = skipTsExpression(initializer);
@@ -36753,10 +36677,6 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
36753
36677
  reportNode
36754
36678
  };
36755
36679
  }
36756
- if (isNodeOfType(stripped, "ObjectExpression") && objectExpressionBundlesComponents(stripped, state)) return {
36757
- kind: "namespace-object",
36758
- reportNode
36759
- };
36760
36680
  if (NOT_REACT_COMPONENT_EXPRESSION_TYPES.has(stripped.type)) return {
36761
36681
  kind: "non-component",
36762
36682
  reportNode
@@ -36773,7 +36693,7 @@ const collectRelevantNodes = (programRoot) => {
36773
36693
  walkAst(programRoot, (child) => {
36774
36694
  const childType = child.type;
36775
36695
  if (childType === "ExportAllDeclaration" || childType === "ExportDefaultDeclaration" || childType === "ExportNamedDeclaration") exportNodes.push(child);
36776
- else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator" || childType === "ClassDeclaration") componentCandidates.push(child);
36696
+ else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator") componentCandidates.push(child);
36777
36697
  });
36778
36698
  return {
36779
36699
  exportNodes,
@@ -36818,31 +36738,18 @@ const onlyExportComponents = defineRule({
36818
36738
  category: "Architecture",
36819
36739
  create: (context) => {
36820
36740
  const settings = resolveSettings$6(context.settings);
36741
+ const state = {
36742
+ customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
36743
+ allowExportNames: new Set(settings.allowExportNames),
36744
+ allowConstantExport: settings.allowConstantExport
36745
+ };
36821
36746
  return { Program(node) {
36822
36747
  if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
36823
36748
  const { exportNodes, componentCandidates } = collectRelevantNodes(node);
36824
- const localComponentNames = /* @__PURE__ */ new Set();
36825
- const state = {
36826
- customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
36827
- allowExportNames: new Set(settings.allowExportNames),
36828
- allowConstantExport: settings.allowConstantExport,
36829
- localComponentNames
36830
- };
36831
- for (const child of componentCandidates) {
36832
- if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
36833
- if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
36834
- }
36835
- if (isNodeOfType(child, "ClassDeclaration") && child.id) {
36836
- if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
36837
- }
36838
- if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
36839
- const initializer = child.init;
36840
- if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
36841
- }
36842
- }
36843
36749
  const exports = [];
36844
36750
  let hasReactExport = false;
36845
36751
  let hasAnyExports = false;
36752
+ const localComponents = [];
36846
36753
  const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
36847
36754
  for (const child of exportNodes) {
36848
36755
  if (isNodeOfType(child, "ExportAllDeclaration")) {
@@ -36913,14 +36820,7 @@ const onlyExportComponents = defineRule({
36913
36820
  });
36914
36821
  continue;
36915
36822
  }
36916
- if (isNodeOfType(stripped, "ObjectExpression")) {
36917
- context.report({
36918
- node: stripped,
36919
- message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
36920
- });
36921
- continue;
36922
- }
36923
- if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
36823
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "Literal")) {
36924
36824
  context.report({
36925
36825
  node: stripped,
36926
36826
  message: ANONYMOUS_MESSAGE
@@ -36973,23 +36873,32 @@ const onlyExportComponents = defineRule({
36973
36873
  let entry;
36974
36874
  if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
36975
36875
  else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
36976
- else {
36977
- entry = {
36978
- kind: "non-component",
36979
- reportNode
36980
- };
36981
- if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
36982
- }
36876
+ else entry = {
36877
+ kind: "non-component",
36878
+ reportNode
36879
+ };
36983
36880
  if (isReExportFromSource && entry.kind !== "react-component") continue;
36984
36881
  exports.push(entry);
36985
36882
  }
36986
36883
  }
36987
36884
  }
36988
36885
  for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
36989
- for (const entry of exports) if (entry.kind === "namespace-object") context.report({
36990
- node: entry.reportNode,
36991
- message: NAMESPACE_OBJECT_MESSAGE
36992
- });
36886
+ const isInsideExport = (node) => {
36887
+ let walker = node.parent;
36888
+ while (walker) {
36889
+ if (isNodeOfType(walker, "ExportNamedDeclaration") || isNodeOfType(walker, "ExportDefaultDeclaration") || isNodeOfType(walker, "ExportAllDeclaration")) return true;
36890
+ walker = walker.parent ?? null;
36891
+ }
36892
+ return false;
36893
+ };
36894
+ for (const child of componentCandidates) {
36895
+ if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
36896
+ if (isReactComponentName(child.id.name) && !isInsideExport(child) && !isInsideFunctionScope(child)) localComponents.push(child.id);
36897
+ }
36898
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
36899
+ if (isReactComponentName(child.id.name) && canBeReactFunctionComponent(child.init, state) && !isInsideExport(child) && !isInsideFunctionScope(child)) localComponents.push(child.id);
36900
+ }
36901
+ }
36993
36902
  if (hasAnyExports && hasReactExport) for (const entry of exports) {
36994
36903
  if (entry.kind === "non-component") context.report({
36995
36904
  node: entry.reportNode,
@@ -37000,6 +36909,14 @@ const onlyExportComponents = defineRule({
37000
36909
  message: REACT_CONTEXT_MESSAGE
37001
36910
  });
37002
36911
  }
36912
+ else if (hasAnyExports && !hasReactExport && localComponents.length > 0) for (const local of localComponents) context.report({
36913
+ node: local,
36914
+ message: LOCAL_COMPONENT_MESSAGE
36915
+ });
36916
+ else if (!hasAnyExports && localComponents.length > 0) for (const local of localComponents) context.report({
36917
+ node: local,
36918
+ message: NO_EXPORT_MESSAGE
36919
+ });
37003
36920
  } };
37004
36921
  }
37005
36922
  });
@@ -37797,8 +37714,8 @@ const preferHtmlDialog = defineRule({
37797
37714
  if (!HTML_TAGS.has(tagName)) return;
37798
37715
  const roleAttribute = findJsxAttribute(node.attributes, "role");
37799
37716
  if (roleAttribute) {
37800
- const roleCandidates = getJsxPropStaticStringValues(roleAttribute, context.scopes);
37801
- if (roleCandidates !== null && roleCandidates.length > 0 && roleCandidates.every((candidate) => ROLE_DIALOG_VALUES.has(candidate))) {
37717
+ const roleValue = getJsxPropStringValue(roleAttribute);
37718
+ if (roleValue !== null && ROLE_DIALOG_VALUES.has(roleValue)) {
37802
37719
  if (focusTrapSignals && isElementFocusTrapped(node, focusTrapSignals)) return;
37803
37720
  const ariaModalAttribute = findJsxAttribute(node.attributes, "aria-modal");
37804
37721
  const isModal = ariaModalAttribute ? isAriaModalTrue(ariaModalAttribute) : false;
@@ -40787,7 +40704,6 @@ const rerenderLazyRefInit = defineRule({
40787
40704
  const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
40788
40705
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
40789
40706
  if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
40790
- if (isNewCall && TRIVIAL_CONSTRUCTOR_NAMES.has(calleeName)) return;
40791
40707
  if (isPlainCall && isReactHookName(calleeName)) return;
40792
40708
  const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
40793
40709
  context.report({
@@ -40820,6 +40736,18 @@ const TRIVIAL_DATE_GETTER_NAMES = new Set([
40820
40736
  "getUTCMilliseconds",
40821
40737
  "valueOf"
40822
40738
  ]);
40739
+ const TRIVIAL_CONSTRUCTOR_NAMES = new Set([
40740
+ "Date",
40741
+ "Map",
40742
+ "Set",
40743
+ "WeakMap",
40744
+ "WeakSet",
40745
+ "RegExp",
40746
+ "Error",
40747
+ "URL",
40748
+ "URLSearchParams",
40749
+ "AbortController"
40750
+ ]);
40823
40751
  const EAGER_CALL_RESOLUTION_DEPTH_LIMIT = 4;
40824
40752
  const findEagerInitializerCall = (expression, depth = 0) => {
40825
40753
  if (depth > EAGER_CALL_RESOLUTION_DEPTH_LIMIT) return null;
@@ -41563,7 +41491,7 @@ const rnBottomSheetPreferNative = defineRule({
41563
41491
  });
41564
41492
  //#endregion
41565
41493
  //#region src/plugin/rules/react-native/rn-detox-missing-await.ts
41566
- const EMPTY_VISITORS$5 = {};
41494
+ const EMPTY_VISITORS$4 = {};
41567
41495
  const DETOX_TEST_FILE = /(\.e2e\.[cm]?[jt]sx?$)|((^|\/)e2e\/)/;
41568
41496
  const DETOX_ELEMENT_ACTIONS = new Set([
41569
41497
  "tap",
@@ -41623,7 +41551,7 @@ const rnDetoxMissingAwait = defineRule({
41623
41551
  recommendation: "Prepend `await` to Detox actions, `waitFor(...)` chains, and `expect(element(...))` assertions. They return promises tied to Detox's synchronization, so a missing await runs steps out of order.",
41624
41552
  create: (context) => {
41625
41553
  const filename = normalizeFilename(context.filename ?? "");
41626
- if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$5;
41554
+ if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$4;
41627
41555
  return { ExpressionStatement(node) {
41628
41556
  const expression = node.expression;
41629
41557
  if (!isNodeOfType(expression, "CallExpression")) return;
@@ -42598,7 +42526,7 @@ const isLegacyArchReactNativeFile = (filename) => {
42598
42526
  };
42599
42527
  //#endregion
42600
42528
  //#region src/plugin/rules/react-native/rn-no-legacy-shadow-styles.ts
42601
- const EMPTY_VISITORS$4 = {};
42529
+ const EMPTY_VISITORS$3 = {};
42602
42530
  const reportLegacyShadowProperties = (objectExpression, context) => {
42603
42531
  const legacyShadowPropertyNames = [];
42604
42532
  for (const property of objectExpression.properties ?? []) {
@@ -42621,7 +42549,7 @@ const rnNoLegacyShadowStyles = defineRule({
42621
42549
  severity: "warn",
42622
42550
  recommendation: "Use `boxShadow` for shadows that work on both platforms on the new architecture, instead of platform-specific shadow properties.",
42623
42551
  create: (context) => {
42624
- if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$4;
42552
+ if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$3;
42625
42553
  return {
42626
42554
  JSXAttribute(node) {
42627
42555
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -43542,7 +43470,7 @@ const isExpoManagedFileActive = (context) => {
43542
43470
  };
43543
43471
  //#endregion
43544
43472
  //#region src/plugin/rules/react-native/rn-prefer-expo-image.ts
43545
- const EMPTY_VISITORS$3 = {};
43473
+ const EMPTY_VISITORS$2 = {};
43546
43474
  const BUNDLED_ASSET_SOURCE_PATTERN = /\.(?:png|jpe?g|gif|webp|bmp)$/i;
43547
43475
  const MEMBER_PATH_FAN_OUT_LIMIT = 32;
43548
43476
  const isRequireOfBundledAsset = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "require" && node.arguments?.length === 1 && isNodeOfType(node.arguments[0], "Literal") && typeof node.arguments[0].value === "string" && BUNDLED_ASSET_SOURCE_PATTERN.test(node.arguments[0].value);
@@ -43576,7 +43504,7 @@ const rnPreferExpoImage = defineRule({
43576
43504
  severity: "warn",
43577
43505
  recommendation: "Use `<Image>` from `expo-image` instead of `react-native`. Same props, plus caching, placeholders, and crossfades for faster image loading.",
43578
43506
  create: (context) => {
43579
- if (!isExpoManagedFileActive(context)) return EMPTY_VISITORS$3;
43507
+ if (!isExpoManagedFileActive(context)) return EMPTY_VISITORS$2;
43580
43508
  const flaggedImports = [];
43581
43509
  const assetBindingNames = /* @__PURE__ */ new Set();
43582
43510
  const moduleObjectLiterals = /* @__PURE__ */ new Map();
@@ -44063,7 +43991,7 @@ const rnScrollviewFlexInContentContainer = defineRule({
44063
43991
  });
44064
43992
  //#endregion
44065
43993
  //#region src/plugin/rules/react-native/rn-style-prefer-box-shadow.ts
44066
- const EMPTY_VISITORS$2 = {};
43994
+ const EMPTY_VISITORS$1 = {};
44067
43995
  const IOS_SHADOW_KEYS = new Set([
44068
43996
  "shadowColor",
44069
43997
  "shadowOffset",
@@ -44149,7 +44077,7 @@ const rnStylePreferBoxShadow = defineRule({
44149
44077
  severity: "warn",
44150
44078
  recommendation: "These shadow keys only work on one platform. On RN v7+, use the CSS `boxShadow` string instead, like `boxShadow: \"0 2px 8px rgba(0,0,0,0.1)\"`, which works on both.",
44151
44079
  create: (context) => {
44152
- if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$2;
44080
+ if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$1;
44153
44081
  return {
44154
44082
  JSXAttribute(node) {
44155
44083
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -44246,9 +44174,9 @@ const roleHasRequiredAriaProps = defineRule({
44246
44174
  if (!HTML_TAGS.has(elementType)) return;
44247
44175
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
44248
44176
  if (!roleAttribute) return;
44249
- const roleCandidates = getJsxPropStaticStringValues(roleAttribute, context.scopes);
44250
- if (roleCandidates === null) return;
44251
- const roles = new Set(roleCandidates.flatMap((candidate) => candidate.split(/\s+/).filter((token) => token.length > 0)));
44177
+ const roleValue = getJsxPropStringValue(roleAttribute);
44178
+ if (roleValue === null) return;
44179
+ const roles = roleValue.split(/\s+/).filter((token) => token.length > 0);
44252
44180
  for (const role of roles) {
44253
44181
  const required = ROLE_REQUIRED_PROPS.get(role);
44254
44182
  if (!required) continue;
@@ -47365,9 +47293,7 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
47365
47293
  };
47366
47294
  //#endregion
47367
47295
  //#region src/plugin/rules/a11y/role-supports-aria-props.ts
47368
- const buildMessageDefault = (roles, propName) => {
47369
- return `Screen reader users get no help from \`${propName}\` because role ${roles.map((role) => `\`${role}\``).join(" / ")} ignores it, so remove it or change the role.`;
47370
- };
47296
+ const buildMessageDefault = (role, propName) => `Screen reader users get no help from \`${propName}\` because role \`${role}\` ignores it, so remove it or change the role.`;
47371
47297
  const buildMessageImplicit = (role, propName, elementType) => `Screen reader users get no help from \`${propName}\` because \`${elementType}\` has role \`${role}\`, which ignores it, so remove \`${propName}\` or change the element.`;
47372
47298
  const roleSupportsAriaProps = defineRule({
47373
47299
  id: "role-supports-aria-props",
@@ -47395,21 +47321,17 @@ const roleSupportsAriaProps = defineRule({
47395
47321
  if (!ariaAttributes) return;
47396
47322
  const elementType = getElementType(node, context.settings);
47397
47323
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
47398
- const roleCandidates = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : [getImplicitRole(node, elementType)].filter((role) => role !== null);
47399
- if (roleCandidates === null || roleCandidates.length === 0) return;
47400
- const supportedSets = [];
47401
- for (const role of roleCandidates) {
47402
- if (!VALID_ARIA_ROLES.has(role)) return;
47403
- const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
47404
- if (!supported) return;
47405
- supportedSets.push(supported);
47406
- }
47324
+ const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : getImplicitRole(node, elementType);
47325
+ if (!role) return;
47326
+ if (!VALID_ARIA_ROLES.has(role)) return;
47407
47327
  const isImplicit = !roleAttribute;
47328
+ const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
47329
+ if (!supported) return;
47408
47330
  for (const { attribute, propName } of ariaAttributes) {
47409
- if (supportedSets.some((supported) => supported.has(propName))) continue;
47331
+ if (supported.has(propName)) continue;
47410
47332
  context.report({
47411
47333
  node: attribute,
47412
- message: isImplicit ? buildMessageImplicit(roleCandidates[0], propName, elementType) : buildMessageDefault(roleCandidates, propName)
47334
+ message: isImplicit ? buildMessageImplicit(role, propName, elementType) : buildMessageDefault(role, propName)
47413
47335
  });
47414
47336
  }
47415
47337
  } })
@@ -47761,15 +47683,11 @@ const isUseEffectEventSymbol = (symbol) => {
47761
47683
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
47762
47684
  return getHookNameFromCallee(initializer.callee) === "useEffectEvent";
47763
47685
  };
47764
- const resolvesToLocalNonImportBinding = (identifier, scopes) => {
47765
- const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
47766
- return Boolean(symbol && symbol.kind !== "import");
47767
- };
47768
- const isNonReactEffectEventCallee = (callee, contextNode, scopes) => isNodeOfType(callee, "Identifier") && (isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes));
47769
- const isNonReactEffectEventSymbol = (symbol, contextNode, scopes) => {
47686
+ const isNonReactEffectEventCallee = (callee, contextNode) => isNodeOfType(callee, "Identifier") && isImportedFromNonReactModule(contextNode, callee.name);
47687
+ const isNonReactEffectEventSymbol = (symbol, contextNode) => {
47770
47688
  const initializer = symbol.initializer;
47771
47689
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
47772
- return isNonReactEffectEventCallee(initializer.callee, contextNode, scopes);
47690
+ return isNonReactEffectEventCallee(initializer.callee, contextNode);
47773
47691
  };
47774
47692
  const findEnclosingComponentOrHookFunction = (node) => {
47775
47693
  let current = node.parent;
@@ -47848,7 +47766,7 @@ const rulesOfHooks = defineRule({
47848
47766
  const hookContext = isHookCall$1(node, context.scopes, settings);
47849
47767
  if (!hookContext) return;
47850
47768
  const { hookName } = hookContext;
47851
- if (hookName === "useEffectEvent" && !isUseEffectEventInitializer(node) && !isNonReactEffectEventCallee(node.callee, node, context.scopes)) {
47769
+ if (hookName === "useEffectEvent" && !isUseEffectEventInitializer(node) && !isNonReactEffectEventCallee(node.callee, node)) {
47852
47770
  context.report({
47853
47771
  node: node.callee,
47854
47772
  message: buildEffectEventPassedDownMessage()
@@ -47961,7 +47879,7 @@ const rulesOfHooks = defineRule({
47961
47879
  Identifier(node) {
47962
47880
  const symbol = context.scopes.referenceFor(node)?.resolvedSymbol;
47963
47881
  if (!symbol || !isUseEffectEventSymbol(symbol)) return;
47964
- if (isNonReactEffectEventSymbol(symbol, node, context.scopes)) return;
47882
+ if (isNonReactEffectEventSymbol(symbol, node)) return;
47965
47883
  if (!isSameComponentOrHookScope(symbol, node)) return;
47966
47884
  if (isInsideAllowedEffectEventCallback(node, additionalEffectHooksRegex)) return;
47967
47885
  context.report({
@@ -55533,34 +55451,6 @@ const reactDoctorRules = [
55533
55451
  ];
55534
55452
  const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id, rule.rule]));
55535
55453
  //#endregion
55536
- //#region src/plugin/utils/is-next-file.ts
55537
- const isNextFileActive = (context) => {
55538
- const rawFilename = context.filename;
55539
- if (!rawFilename) return true;
55540
- const filename = normalizeFilename(rawFilename);
55541
- const manifest = readNearestPackageManifest(filename);
55542
- if (!manifest) return true;
55543
- if (declaresDependency(manifest, "next")) return true;
55544
- if (!declaresAnyDependency(manifest)) return true;
55545
- const packageDirectory = findNearestPackageDirectory(filename);
55546
- const rootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
55547
- if (packageDirectory !== null && isPackageNestedBelowProjectRoot(packageDirectory, rootDirectory)) return false;
55548
- return true;
55549
- };
55550
- //#endregion
55551
- //#region src/plugin/utils/wrap-nextjs-rule.ts
55552
- const EMPTY_VISITORS$1 = {};
55553
- const wrapNextjsRule = (rule) => {
55554
- const innerCreate = rule.create.bind(rule);
55555
- return {
55556
- ...rule,
55557
- create: (context) => {
55558
- if (!isNextFileActive(context)) return EMPTY_VISITORS$1;
55559
- return innerCreate(context);
55560
- }
55561
- };
55562
- };
55563
- //#endregion
55564
55454
  //#region src/plugin/utils/wrap-react-native-rule.ts
55565
55455
  const EMPTY_VISITORS = {};
55566
55456
  const wrapReactNativeRule = (rule) => {
@@ -56030,14 +55920,9 @@ const wrapWithSemanticContext = (rule) => ({
56030
55920
  });
56031
55921
  //#endregion
56032
55922
  //#region src/plugin/react-doctor-plugin.ts
56033
- const applyFrameworkGate = (rule) => {
56034
- if (rule.framework === "react-native") return wrapReactNativeRule(rule);
56035
- if (rule.framework === "nextjs") return wrapNextjsRule(rule);
56036
- return rule;
56037
- };
56038
55923
  const applyFrameworkRuleWrappers = (registry) => {
56039
55924
  const wrapped = {};
56040
- for (const [ruleId, rule] of Object.entries(registry)) wrapped[ruleId] = wrapWithSemanticContext(applyFrameworkGate(rule));
55925
+ for (const [ruleId, rule] of Object.entries(registry)) wrapped[ruleId] = wrapWithSemanticContext(rule.framework === "react-native" ? wrapReactNativeRule(rule) : rule);
56041
55926
  return wrapped;
56042
55927
  };
56043
55928
  const plugin = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oxlint-plugin-react-doctor",
3
- "version": "0.7.2-dev.11e9c87",
3
+ "version": "0.7.2-dev.43d766f",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",