oxlint-plugin-react-doctor 0.7.2-dev.b97a92f → 0.7.2-dev.cb8f726

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 +251 -135
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -595,6 +595,19 @@ 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
+ ]);
598
611
  const TRIVIAL_DERIVATION_CALLEE_NAMES = new Set([
599
612
  "Boolean",
600
613
  "String",
@@ -2235,6 +2248,38 @@ const anchorHasContent = defineRule({
2235
2248
  }
2236
2249
  });
2237
2250
  //#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
2238
2283
  //#region src/plugin/rules/a11y/anchor-is-valid.ts
2239
2284
  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).";
2240
2285
  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.";
@@ -2264,28 +2309,11 @@ const isInvalidHref = (value, validHrefs) => {
2264
2309
  if (validHrefs.has(value)) return false;
2265
2310
  return value === "" || value === "#" || value === "javascript:void(0)";
2266
2311
  };
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;
2312
+ const isNullishOrFragmentHref = (value) => {
2278
2313
  if (isNodeOfType(value, "JSXExpressionContainer")) {
2279
2314
  const expression = value.expression;
2280
2315
  if (isNodeOfType(expression, "Identifier") && expression.name === "undefined") 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
- }
2316
+ if (isNodeOfType(expression, "Literal") && expression.value === null) return true;
2289
2317
  }
2290
2318
  if (isNodeOfType(value, "JSXFragment")) return true;
2291
2319
  return false;
@@ -2318,9 +2346,10 @@ const anchorIsValid = defineRule({
2318
2346
  });
2319
2347
  return;
2320
2348
  }
2321
- if (checkValueIsEmptyOrInvalid(hrefAttribute.value, settings.validHrefs)) {
2349
+ const hrefCandidates = getJsxPropStaticStringValues(hrefAttribute, context.scopes);
2350
+ if (hrefCandidates !== null ? hrefCandidates.length > 0 && hrefCandidates.every((candidate) => isInvalidHref(candidate, settings.validHrefs)) : isNullishOrFragmentHref(hrefAttribute.value)) {
2322
2351
  const hasOnClick = Boolean(hasJsxPropIgnoreCase(node.attributes, "onClick"));
2323
- if (!hasOnClick && getStaticHrefValue(hrefAttribute.value) === "#") return;
2352
+ if (!hasOnClick && hrefCandidates?.every((candidate) => candidate === "#")) return;
2324
2353
  context.report({
2325
2354
  node: node.name,
2326
2355
  message: hasOnClick ? MESSAGE_CANT_BE_ANCHOR : MESSAGE_INCORRECT_HREF
@@ -3376,6 +3405,25 @@ const ariaRole = defineRule({
3376
3405
  if (!roleAttribute) return;
3377
3406
  const elementType = getElementType(node, context.settings);
3378
3407
  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
+ };
3379
3427
  const value = roleAttribute.value;
3380
3428
  if (!value) {
3381
3429
  context.report({
@@ -3392,22 +3440,7 @@ const ariaRole = defineRule({
3392
3440
  });
3393
3441
  return;
3394
3442
  }
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
- }
3443
+ reportFirstInvalidCandidate([value.value]);
3411
3444
  return;
3412
3445
  }
3413
3446
  if (isNodeOfType(value, "JSXExpressionContainer")) {
@@ -3428,6 +3461,11 @@ const ariaRole = defineRule({
3428
3461
  });
3429
3462
  return;
3430
3463
  }
3464
+ const resolvedCandidates = getJsxPropStaticStringValues(roleAttribute, context.scopes);
3465
+ if (resolvedCandidates !== null) {
3466
+ reportFirstInvalidCandidate(resolvedCandidates);
3467
+ return;
3468
+ }
3431
3469
  return;
3432
3470
  }
3433
3471
  context.report({
@@ -9616,7 +9654,7 @@ If the missing value is recreated every render, move it inside the hook or stabi
9616
9654
  });
9617
9655
  //#endregion
9618
9656
  //#region src/plugin/rules/react-native/expo-no-non-inlined-env.ts
9619
- const EMPTY_VISITORS$5 = {};
9657
+ const EMPTY_VISITORS$6 = {};
9620
9658
  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?$)/;
9621
9659
  const isNonExpoPublicLiteralKey = (key) => isNodeOfType(key, "Literal") && typeof key.value === "string" && !key.value.startsWith("EXPO_PUBLIC_");
9622
9660
  const isProcessEnv = (node) => isNodeOfType(node, "MemberExpression") && !node.computed && isNodeOfType(node.object, "Identifier") && node.object.name === "process" && isNodeOfType(node.property, "Identifier") && node.property.name === "env";
@@ -9628,7 +9666,7 @@ const expoNoNonInlinedEnv = defineRule({
9628
9666
  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.",
9629
9667
  create: (context) => {
9630
9668
  const filename = normalizeFilename(context.filename ?? "");
9631
- if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$5;
9669
+ if (filename && NODE_OR_BUILD_FILE.test(filename)) return EMPTY_VISITORS$6;
9632
9670
  return {
9633
9671
  MemberExpression(node) {
9634
9672
  if (!node.computed) return;
@@ -11046,8 +11084,8 @@ const interactiveSupportsFocus = defineRule({
11046
11084
  if (node.attributes.length === 0) return;
11047
11085
  if (hasJsxSpreadAttribute$1(node.attributes)) return;
11048
11086
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
11049
- const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : null;
11050
- if (!role) return;
11087
+ const roleCandidates = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : null;
11088
+ if (roleCandidates === null || roleCandidates.length === 0) return;
11051
11089
  let hasInteractiveHandler = false;
11052
11090
  for (const attribute of node.attributes) {
11053
11091
  if (!isNodeOfType(attribute, "JSXAttribute")) continue;
@@ -11061,11 +11099,16 @@ const interactiveSupportsFocus = defineRule({
11061
11099
  const elementType = getElementType(node, context.settings);
11062
11100
  if (!HTML_TAGS.has(elementType)) return;
11063
11101
  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;
11066
11102
  const hasTabIndex = Boolean(hasJsxPropIgnoreCase(node.attributes, "tabIndex"));
11067
- if (!isInteractiveRole(role) || isInteractiveElement(elementType, node) || isNonInteractiveRole(role) || isNonInteractiveElement(elementType, node) || hasTabIndex) return;
11068
- const message = tabbableSet.has(role) ? buildTabbableMessage(role) : buildFocusableMessage(role);
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);
11069
11112
  context.report({
11070
11113
  node,
11071
11114
  message
@@ -22477,6 +22520,10 @@ const declaresAnyDependency = (manifest) => DEPENDENCY_SECTION_NAMES.some((secti
22477
22520
  const section = manifest[sectionName];
22478
22521
  return typeof section === "object" && section !== null && Object.keys(section).length > 0;
22479
22522
  });
22523
+ const declaresDependency = (manifest, dependencyName) => {
22524
+ for (const declaredName of iterateDependencyNames(manifest)) if (declaredName === dependencyName) return true;
22525
+ return false;
22526
+ };
22480
22527
  const cachedPlatformByManifest = /* @__PURE__ */ new WeakMap();
22481
22528
  const classifyPackagePlatform = (filename) => {
22482
22529
  const manifest = readNearestPackageManifest(filename);
@@ -22493,9 +22540,7 @@ const classifyPackagePlatform = (filename) => {
22493
22540
  return result;
22494
22541
  };
22495
22542
  //#endregion
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?$/;
22543
+ //#region src/plugin/utils/is-package-nested-below-project-root.ts
22499
22544
  const cachedRealDirectoryByDirectory = /* @__PURE__ */ new Map();
22500
22545
  const resolveRealDirectory = (directory) => {
22501
22546
  const cached = cachedRealDirectoryByDirectory.get(directory);
@@ -22516,6 +22561,10 @@ const isPackageNestedBelowProjectRoot = (packageDirectory, rootDirectory) => {
22516
22561
  const rootPrefix = normalizedRootDirectory.endsWith("/") ? normalizedRootDirectory : `${normalizedRootDirectory}/`;
22517
22562
  return realPackageDirectory.startsWith(rootPrefix);
22518
22563
  };
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?$/;
22519
22568
  const classifyReactNativeFileTarget = (context) => {
22520
22569
  const rawFilename = context.filename;
22521
22570
  if (!rawFilename) return "unknown";
@@ -22932,7 +22981,6 @@ const countStatementSequenceSetStateCalls = (statements, context) => {
22932
22981
  let fallThroughCount = 0;
22933
22982
  let maxTerminatingPathCount = 0;
22934
22983
  for (const statement of statements) {
22935
- if (isFunctionLike$1(statement)) continue;
22936
22984
  const guardBranch = isGuardWithTerminatingBranch(statement);
22937
22985
  if (guardBranch) {
22938
22986
  maxTerminatingPathCount = Math.max(maxTerminatingPathCount, fallThroughCount + countMaxPathSetStateCalls(guardBranch, context));
@@ -22968,14 +23016,23 @@ const isWholesaleDelegationCall = (callNode, effectCallback) => {
22968
23016
  if (!isNodeOfType(parent, "ExpressionStatement")) return false;
22969
23017
  return parent.parent === effectCallback.body;
22970
23018
  };
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
+ };
22971
23025
  const countMaxPathSetStateCalls = (node, context) => {
22972
23026
  if (!node || typeof node !== "object") return 0;
22973
- if (isAsyncFunctionLike(node)) return 0;
23027
+ if (isFunctionLike$1(node)) return 0;
22974
23028
  if (isNodeOfType(node, "BlockStatement") || isNodeOfType(node, "Program")) return countStatementSequenceSetStateCalls(node.body ?? [], context);
22975
23029
  if (isNodeOfType(node, "IfStatement") || isNodeOfType(node, "ConditionalExpression")) {
22976
23030
  const consequent = node.consequent;
22977
23031
  const alternate = node.alternate;
22978
- return countMaxPathSetStateCalls(consequent, context) + (alternate ? countMaxPathSetStateCalls(alternate, context) : 0);
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);
22979
23036
  }
22980
23037
  if (isNodeOfType(node, "SwitchStatement")) {
22981
23038
  let maxRunSetters = 0;
@@ -23005,28 +23062,31 @@ const countMaxPathSetStateCalls = (node, context) => {
23005
23062
  }
23006
23063
  if (isNodeOfType(node, "CallExpression") && isSetterCall(node) && isNodeOfType(node.callee, "Identifier") && isUseStateSetterInScope(node, node.callee.name)) {
23007
23064
  let nestedSettersInArgs = 0;
23008
- for (const argument of node.arguments ?? []) nestedSettersInArgs += countMaxPathSetStateCalls(argument, context);
23065
+ for (const argument of node.arguments ?? []) nestedSettersInArgs += isFunctionLike$1(argument) ? countFunctionBodySetStateCalls(argument, context) : countMaxPathSetStateCalls(argument, context);
23009
23066
  return 1 + nestedSettersInArgs;
23010
23067
  }
23011
23068
  if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier")) {
23012
23069
  const helperFunction = context.helpersByName.get(node.callee.name);
23013
23070
  if (helperFunction && !context.activeHelpers.has(helperFunction) && isWholesaleDelegationCall(node, context.effectCallback)) {
23014
23071
  context.activeHelpers.add(helperFunction);
23015
- let helperCount = countMaxPathSetStateCalls(helperFunction, context);
23072
+ let helperCount = countFunctionBodySetStateCalls(helperFunction, context);
23016
23073
  context.activeHelpers.delete(helperFunction);
23017
23074
  for (const argument of node.arguments ?? []) helperCount += countMaxPathSetStateCalls(argument, context);
23018
23075
  return helperCount;
23019
23076
  }
23020
23077
  }
23021
- const shouldWalkChild = (child) => !isFunctionLike$1(child) || runsOnEffectDispatch(child);
23078
+ const countChild = (child) => {
23079
+ if (isFunctionLike$1(child)) return runsOnEffectDispatch(child) ? countFunctionBodySetStateCalls(child, context) : 0;
23080
+ return countMaxPathSetStateCalls(child, context);
23081
+ };
23022
23082
  let total = 0;
23023
23083
  const nodeRecord = node;
23024
23084
  for (const key of Object.keys(nodeRecord)) {
23025
23085
  if (key === "parent") continue;
23026
23086
  const child = nodeRecord[key];
23027
23087
  if (Array.isArray(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);
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);
23030
23090
  }
23031
23091
  return total;
23032
23092
  };
@@ -23075,7 +23135,7 @@ const noCascadingSetState = defineRule({
23075
23135
  const callback = getEffectCallback(node);
23076
23136
  if (!callback) return;
23077
23137
  if (isDevOnlyGuardedEffect(callback)) return;
23078
- const setStateCallCount = countMaxPathSetStateCalls(callback, {
23138
+ const setStateCallCount = countFunctionBodySetStateCalls(callback, {
23079
23139
  helpersByName: collectLocalHelperFunctions(findProgramRoot(node) ?? callback),
23080
23140
  activeHelpers: /* @__PURE__ */ new Set(),
23081
23141
  effectCallback: callback
@@ -26293,7 +26353,11 @@ const noEffectEventInDeps = defineRule({
26293
26353
  const initializer = declaratorNode.init;
26294
26354
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return;
26295
26355
  if (!isHookCall$2(initializer, "useEffectEvent")) return;
26296
- if (isNodeOfType(initializer.callee, "Identifier") && isImportedFromNonReactModule(declaratorNode, initializer.callee.name)) 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
+ }
26297
26361
  componentBindings.addBindingToCurrentFrame(declaratorNode.id.name);
26298
26362
  } });
26299
26363
  return {
@@ -36575,8 +36639,7 @@ const NAMED_EXPORT_MESSAGE = "This file exports non-components, so Fast Refresh
36575
36639
  const ANONYMOUS_MESSAGE = "This component is unnamed, so Fast Refresh can't track it and falls back to a full reload.";
36576
36640
  const EXPORT_ALL_MESSAGE = "`export *` hides what's exported, so Fast Refresh can't safely preserve component state.";
36577
36641
  const REACT_CONTEXT_MESSAGE = "This file exports a context with components, so Fast Refresh can't safely preserve component state.";
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
+ 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.";
36580
36643
  const DEFAULT_REACT_HOCS = [
36581
36644
  "memo",
36582
36645
  "forwardRef",
@@ -36647,6 +36710,20 @@ const isReactComponentInitializer = (expression, state) => {
36647
36710
  if (isNodeOfType(stripped, "CallExpression") && isHocCallee(stripped.callee, state) && stripped.arguments.length > 0) return true;
36648
36711
  return false;
36649
36712
  };
36713
+ const objectExpressionBundlesComponents = (objectExpression, state) => {
36714
+ for (const property of objectExpression.properties ?? []) {
36715
+ if (!isNodeOfType(property, "Property")) continue;
36716
+ const value = skipTsExpression(property.value);
36717
+ if (isNodeOfType(value, "Identifier")) {
36718
+ if (state.localComponentNames.has(value.name)) return true;
36719
+ continue;
36720
+ }
36721
+ if (!(!property.computed && isNodeOfType(property.key, "Identifier") && isReactComponentName(property.key.name))) continue;
36722
+ if (isNodeOfType(value, "ArrowFunctionExpression") || isNodeOfType(value, "FunctionExpression")) return true;
36723
+ if (isNodeOfType(value, "CallExpression") && isHocCallee(value.callee, state) && value.arguments.length > 0) return true;
36724
+ }
36725
+ return false;
36726
+ };
36650
36727
  const classifyExport = (name, reportNode, isFunction, initializer, state) => {
36651
36728
  if (initializer) {
36652
36729
  const expression = skipTsExpression(initializer);
@@ -36677,6 +36754,10 @@ const classifyExport = (name, reportNode, isFunction, initializer, state) => {
36677
36754
  reportNode
36678
36755
  };
36679
36756
  }
36757
+ if (isNodeOfType(stripped, "ObjectExpression") && objectExpressionBundlesComponents(stripped, state)) return {
36758
+ kind: "namespace-object",
36759
+ reportNode
36760
+ };
36680
36761
  if (NOT_REACT_COMPONENT_EXPRESSION_TYPES.has(stripped.type)) return {
36681
36762
  kind: "non-component",
36682
36763
  reportNode
@@ -36693,7 +36774,7 @@ const collectRelevantNodes = (programRoot) => {
36693
36774
  walkAst(programRoot, (child) => {
36694
36775
  const childType = child.type;
36695
36776
  if (childType === "ExportAllDeclaration" || childType === "ExportDefaultDeclaration" || childType === "ExportNamedDeclaration") exportNodes.push(child);
36696
- else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator") componentCandidates.push(child);
36777
+ else if (childType === "FunctionDeclaration" || childType === "VariableDeclarator" || childType === "ClassDeclaration") componentCandidates.push(child);
36697
36778
  });
36698
36779
  return {
36699
36780
  exportNodes,
@@ -36738,18 +36819,31 @@ const onlyExportComponents = defineRule({
36738
36819
  category: "Architecture",
36739
36820
  create: (context) => {
36740
36821
  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
- };
36746
36822
  return { Program(node) {
36747
36823
  if (!isFileNameAllowed(normalizeFilename(context.filename ?? ""), settings.checkJS)) return;
36748
36824
  const { exportNodes, componentCandidates } = collectRelevantNodes(node);
36825
+ const localComponentNames = /* @__PURE__ */ new Set();
36826
+ const state = {
36827
+ customHocs: new Set([...DEFAULT_REACT_HOCS, ...settings.customHOCs]),
36828
+ allowExportNames: new Set(settings.allowExportNames),
36829
+ allowConstantExport: settings.allowConstantExport,
36830
+ localComponentNames
36831
+ };
36832
+ for (const child of componentCandidates) {
36833
+ if (isNodeOfType(child, "FunctionDeclaration") && child.id) {
36834
+ if (isReactComponentName(child.id.name) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
36835
+ }
36836
+ if (isNodeOfType(child, "ClassDeclaration") && child.id) {
36837
+ if (isReactComponentName(child.id.name) && isEs6Component(child) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
36838
+ }
36839
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier")) {
36840
+ const initializer = child.init;
36841
+ if (isReactComponentName(child.id.name) && (canBeReactFunctionComponent(initializer, state) || (initializer ? isEs6Component(skipTsExpression(initializer)) : false)) && !isInsideFunctionScope(child)) localComponentNames.add(child.id.name);
36842
+ }
36843
+ }
36749
36844
  const exports = [];
36750
36845
  let hasReactExport = false;
36751
36846
  let hasAnyExports = false;
36752
- const localComponents = [];
36753
36847
  const isExportedNodeIds = /* @__PURE__ */ new WeakSet();
36754
36848
  for (const child of exportNodes) {
36755
36849
  if (isNodeOfType(child, "ExportAllDeclaration")) {
@@ -36820,7 +36914,14 @@ const onlyExportComponents = defineRule({
36820
36914
  });
36821
36915
  continue;
36822
36916
  }
36823
- if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "ObjectExpression") || isNodeOfType(stripped, "Literal")) {
36917
+ if (isNodeOfType(stripped, "ObjectExpression")) {
36918
+ context.report({
36919
+ node: stripped,
36920
+ message: objectExpressionBundlesComponents(stripped, state) ? NAMESPACE_OBJECT_MESSAGE : ANONYMOUS_MESSAGE
36921
+ });
36922
+ continue;
36923
+ }
36924
+ if (isNodeOfType(stripped, "ArrowFunctionExpression") || isNodeOfType(stripped, "Literal")) {
36824
36925
  context.report({
36825
36926
  node: stripped,
36826
36927
  message: ANONYMOUS_MESSAGE
@@ -36873,32 +36974,23 @@ const onlyExportComponents = defineRule({
36873
36974
  let entry;
36874
36975
  if (exportedName === "default" && localName) entry = classifyExport(localName, reportNode, false, null, state);
36875
36976
  else if (exportedName) entry = classifyExport(exportedName, reportNode, false, null, state);
36876
- else entry = {
36877
- kind: "non-component",
36878
- reportNode
36879
- };
36977
+ else {
36978
+ entry = {
36979
+ kind: "non-component",
36980
+ reportNode
36981
+ };
36982
+ if (localName && isReactComponentName(localName)) exports.push({ kind: "react-component" });
36983
+ }
36880
36984
  if (isReExportFromSource && entry.kind !== "react-component") continue;
36881
36985
  exports.push(entry);
36882
36986
  }
36883
36987
  }
36884
36988
  }
36885
36989
  for (const entry of exports) if (entry.kind === "react-component") hasReactExport = true;
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
- }
36990
+ for (const entry of exports) if (entry.kind === "namespace-object") context.report({
36991
+ node: entry.reportNode,
36992
+ message: NAMESPACE_OBJECT_MESSAGE
36993
+ });
36902
36994
  if (hasAnyExports && hasReactExport) for (const entry of exports) {
36903
36995
  if (entry.kind === "non-component") context.report({
36904
36996
  node: entry.reportNode,
@@ -36909,14 +37001,6 @@ const onlyExportComponents = defineRule({
36909
37001
  message: REACT_CONTEXT_MESSAGE
36910
37002
  });
36911
37003
  }
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
- });
36920
37004
  } };
36921
37005
  }
36922
37006
  });
@@ -37714,8 +37798,8 @@ const preferHtmlDialog = defineRule({
37714
37798
  if (!HTML_TAGS.has(tagName)) return;
37715
37799
  const roleAttribute = findJsxAttribute(node.attributes, "role");
37716
37800
  if (roleAttribute) {
37717
- const roleValue = getJsxPropStringValue(roleAttribute);
37718
- if (roleValue !== null && ROLE_DIALOG_VALUES.has(roleValue)) {
37801
+ const roleCandidates = getJsxPropStaticStringValues(roleAttribute, context.scopes);
37802
+ if (roleCandidates !== null && roleCandidates.length > 0 && roleCandidates.every((candidate) => ROLE_DIALOG_VALUES.has(candidate))) {
37719
37803
  if (focusTrapSignals && isElementFocusTrapped(node, focusTrapSignals)) return;
37720
37804
  const ariaModalAttribute = findJsxAttribute(node.attributes, "aria-modal");
37721
37805
  const isModal = ariaModalAttribute ? isAriaModalTrue(ariaModalAttribute) : false;
@@ -40704,6 +40788,7 @@ const rerenderLazyRefInit = defineRule({
40704
40788
  const memberPropertyName = isNodeOfType(callee, "MemberExpression") && (isNodeOfType(callee.property, "Identifier") || isNodeOfType(callee.property, "PrivateIdentifier")) ? callee.property.name : null;
40705
40789
  const calleeName = isNodeOfType(callee, "Identifier") ? callee.name : memberPropertyName ?? "fn";
40706
40790
  if (TRIVIAL_INITIALIZER_NAMES.has(calleeName)) return;
40791
+ if (isNewCall && TRIVIAL_CONSTRUCTOR_NAMES.has(calleeName)) return;
40707
40792
  if (isPlainCall && isReactHookName(calleeName)) return;
40708
40793
  const callShape = isNewCall ? `new ${calleeName}()` : `${calleeName}()`;
40709
40794
  context.report({
@@ -40736,18 +40821,6 @@ const TRIVIAL_DATE_GETTER_NAMES = new Set([
40736
40821
  "getUTCMilliseconds",
40737
40822
  "valueOf"
40738
40823
  ]);
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
- ]);
40751
40824
  const EAGER_CALL_RESOLUTION_DEPTH_LIMIT = 4;
40752
40825
  const findEagerInitializerCall = (expression, depth = 0) => {
40753
40826
  if (depth > EAGER_CALL_RESOLUTION_DEPTH_LIMIT) return null;
@@ -41491,7 +41564,7 @@ const rnBottomSheetPreferNative = defineRule({
41491
41564
  });
41492
41565
  //#endregion
41493
41566
  //#region src/plugin/rules/react-native/rn-detox-missing-await.ts
41494
- const EMPTY_VISITORS$4 = {};
41567
+ const EMPTY_VISITORS$5 = {};
41495
41568
  const DETOX_TEST_FILE = /(\.e2e\.[cm]?[jt]sx?$)|((^|\/)e2e\/)/;
41496
41569
  const DETOX_ELEMENT_ACTIONS = new Set([
41497
41570
  "tap",
@@ -41551,7 +41624,7 @@ const rnDetoxMissingAwait = defineRule({
41551
41624
  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.",
41552
41625
  create: (context) => {
41553
41626
  const filename = normalizeFilename(context.filename ?? "");
41554
- if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$4;
41627
+ if (!filename || !DETOX_TEST_FILE.test(filename)) return EMPTY_VISITORS$5;
41555
41628
  return { ExpressionStatement(node) {
41556
41629
  const expression = node.expression;
41557
41630
  if (!isNodeOfType(expression, "CallExpression")) return;
@@ -42526,7 +42599,7 @@ const isLegacyArchReactNativeFile = (filename) => {
42526
42599
  };
42527
42600
  //#endregion
42528
42601
  //#region src/plugin/rules/react-native/rn-no-legacy-shadow-styles.ts
42529
- const EMPTY_VISITORS$3 = {};
42602
+ const EMPTY_VISITORS$4 = {};
42530
42603
  const reportLegacyShadowProperties = (objectExpression, context) => {
42531
42604
  const legacyShadowPropertyNames = [];
42532
42605
  for (const property of objectExpression.properties ?? []) {
@@ -42549,7 +42622,7 @@ const rnNoLegacyShadowStyles = defineRule({
42549
42622
  severity: "warn",
42550
42623
  recommendation: "Use `boxShadow` for shadows that work on both platforms on the new architecture, instead of platform-specific shadow properties.",
42551
42624
  create: (context) => {
42552
- if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$3;
42625
+ if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$4;
42553
42626
  return {
42554
42627
  JSXAttribute(node) {
42555
42628
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -43470,7 +43543,7 @@ const isExpoManagedFileActive = (context) => {
43470
43543
  };
43471
43544
  //#endregion
43472
43545
  //#region src/plugin/rules/react-native/rn-prefer-expo-image.ts
43473
- const EMPTY_VISITORS$2 = {};
43546
+ const EMPTY_VISITORS$3 = {};
43474
43547
  const BUNDLED_ASSET_SOURCE_PATTERN = /\.(?:png|jpe?g|gif|webp|bmp)$/i;
43475
43548
  const MEMBER_PATH_FAN_OUT_LIMIT = 32;
43476
43549
  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);
@@ -43504,7 +43577,7 @@ const rnPreferExpoImage = defineRule({
43504
43577
  severity: "warn",
43505
43578
  recommendation: "Use `<Image>` from `expo-image` instead of `react-native`. Same props, plus caching, placeholders, and crossfades for faster image loading.",
43506
43579
  create: (context) => {
43507
- if (!isExpoManagedFileActive(context)) return EMPTY_VISITORS$2;
43580
+ if (!isExpoManagedFileActive(context)) return EMPTY_VISITORS$3;
43508
43581
  const flaggedImports = [];
43509
43582
  const assetBindingNames = /* @__PURE__ */ new Set();
43510
43583
  const moduleObjectLiterals = /* @__PURE__ */ new Map();
@@ -43991,7 +44064,7 @@ const rnScrollviewFlexInContentContainer = defineRule({
43991
44064
  });
43992
44065
  //#endregion
43993
44066
  //#region src/plugin/rules/react-native/rn-style-prefer-box-shadow.ts
43994
- const EMPTY_VISITORS$1 = {};
44067
+ const EMPTY_VISITORS$2 = {};
43995
44068
  const IOS_SHADOW_KEYS = new Set([
43996
44069
  "shadowColor",
43997
44070
  "shadowOffset",
@@ -44077,7 +44150,7 @@ const rnStylePreferBoxShadow = defineRule({
44077
44150
  severity: "warn",
44078
44151
  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.",
44079
44152
  create: (context) => {
44080
- if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$1;
44153
+ if (context.filename && isLegacyArchReactNativeFile(normalizeFilename(context.filename))) return EMPTY_VISITORS$2;
44081
44154
  return {
44082
44155
  JSXAttribute(node) {
44083
44156
  if (!isNodeOfType(node.name, "JSXIdentifier")) return;
@@ -44174,9 +44247,9 @@ const roleHasRequiredAriaProps = defineRule({
44174
44247
  if (!HTML_TAGS.has(elementType)) return;
44175
44248
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
44176
44249
  if (!roleAttribute) return;
44177
- const roleValue = getJsxPropStringValue(roleAttribute);
44178
- if (roleValue === null) return;
44179
- const roles = roleValue.split(/\s+/).filter((token) => token.length > 0);
44250
+ const roleCandidates = getJsxPropStaticStringValues(roleAttribute, context.scopes);
44251
+ if (roleCandidates === null) return;
44252
+ const roles = new Set(roleCandidates.flatMap((candidate) => candidate.split(/\s+/).filter((token) => token.length > 0)));
44180
44253
  for (const role of roles) {
44181
44254
  const required = ROLE_REQUIRED_PROPS.get(role);
44182
44255
  if (!required) continue;
@@ -47293,7 +47366,9 @@ const ROLE_SUPPORTS_ARIA_PROPS = {
47293
47366
  };
47294
47367
  //#endregion
47295
47368
  //#region src/plugin/rules/a11y/role-supports-aria-props.ts
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.`;
47369
+ const buildMessageDefault = (roles, propName) => {
47370
+ 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.`;
47371
+ };
47297
47372
  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.`;
47298
47373
  const roleSupportsAriaProps = defineRule({
47299
47374
  id: "role-supports-aria-props",
@@ -47321,17 +47396,21 @@ const roleSupportsAriaProps = defineRule({
47321
47396
  if (!ariaAttributes) return;
47322
47397
  const elementType = getElementType(node, context.settings);
47323
47398
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
47324
- const role = roleAttribute ? getJsxPropStringValue(roleAttribute) : getImplicitRole(node, elementType);
47325
- if (!role) return;
47326
- if (!VALID_ARIA_ROLES.has(role)) return;
47399
+ const roleCandidates = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : [getImplicitRole(node, elementType)].filter((role) => role !== null);
47400
+ if (roleCandidates === null || roleCandidates.length === 0) return;
47401
+ const supportedSets = [];
47402
+ for (const role of roleCandidates) {
47403
+ if (!VALID_ARIA_ROLES.has(role)) return;
47404
+ const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
47405
+ if (!supported) return;
47406
+ supportedSets.push(supported);
47407
+ }
47327
47408
  const isImplicit = !roleAttribute;
47328
- const supported = ROLE_SUPPORTS_ARIA_PROPS[role];
47329
- if (!supported) return;
47330
47409
  for (const { attribute, propName } of ariaAttributes) {
47331
- if (supported.has(propName)) continue;
47410
+ if (supportedSets.some((supported) => supported.has(propName))) continue;
47332
47411
  context.report({
47333
47412
  node: attribute,
47334
- message: isImplicit ? buildMessageImplicit(role, propName, elementType) : buildMessageDefault(role, propName)
47413
+ message: isImplicit ? buildMessageImplicit(roleCandidates[0], propName, elementType) : buildMessageDefault(roleCandidates, propName)
47335
47414
  });
47336
47415
  }
47337
47416
  } })
@@ -47683,11 +47762,15 @@ const isUseEffectEventSymbol = (symbol) => {
47683
47762
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
47684
47763
  return getHookNameFromCallee(initializer.callee) === "useEffectEvent";
47685
47764
  };
47686
- const isNonReactEffectEventCallee = (callee, contextNode) => isNodeOfType(callee, "Identifier") && isImportedFromNonReactModule(contextNode, callee.name);
47687
- const isNonReactEffectEventSymbol = (symbol, contextNode) => {
47765
+ const resolvesToLocalNonImportBinding = (identifier, scopes) => {
47766
+ const symbol = scopes.referenceFor(identifier)?.resolvedSymbol;
47767
+ return Boolean(symbol && symbol.kind !== "import");
47768
+ };
47769
+ const isNonReactEffectEventCallee = (callee, contextNode, scopes) => isNodeOfType(callee, "Identifier") && (isImportedFromNonReactModule(contextNode, callee.name) || resolvesToLocalNonImportBinding(callee, scopes));
47770
+ const isNonReactEffectEventSymbol = (symbol, contextNode, scopes) => {
47688
47771
  const initializer = symbol.initializer;
47689
47772
  if (!initializer || !isNodeOfType(initializer, "CallExpression")) return false;
47690
- return isNonReactEffectEventCallee(initializer.callee, contextNode);
47773
+ return isNonReactEffectEventCallee(initializer.callee, contextNode, scopes);
47691
47774
  };
47692
47775
  const findEnclosingComponentOrHookFunction = (node) => {
47693
47776
  let current = node.parent;
@@ -47766,7 +47849,7 @@ const rulesOfHooks = defineRule({
47766
47849
  const hookContext = isHookCall$1(node, context.scopes, settings);
47767
47850
  if (!hookContext) return;
47768
47851
  const { hookName } = hookContext;
47769
- if (hookName === "useEffectEvent" && !isUseEffectEventInitializer(node) && !isNonReactEffectEventCallee(node.callee, node)) {
47852
+ if (hookName === "useEffectEvent" && !isUseEffectEventInitializer(node) && !isNonReactEffectEventCallee(node.callee, node, context.scopes)) {
47770
47853
  context.report({
47771
47854
  node: node.callee,
47772
47855
  message: buildEffectEventPassedDownMessage()
@@ -47879,7 +47962,7 @@ const rulesOfHooks = defineRule({
47879
47962
  Identifier(node) {
47880
47963
  const symbol = context.scopes.referenceFor(node)?.resolvedSymbol;
47881
47964
  if (!symbol || !isUseEffectEventSymbol(symbol)) return;
47882
- if (isNonReactEffectEventSymbol(symbol, node)) return;
47965
+ if (isNonReactEffectEventSymbol(symbol, node, context.scopes)) return;
47883
47966
  if (!isSameComponentOrHookScope(symbol, node)) return;
47884
47967
  if (isInsideAllowedEffectEventCallback(node, additionalEffectHooksRegex)) return;
47885
47968
  context.report({
@@ -55451,6 +55534,34 @@ const reactDoctorRules = [
55451
55534
  ];
55452
55535
  const ruleRegistry = Object.fromEntries(reactDoctorRules.map((rule) => [rule.id, rule.rule]));
55453
55536
  //#endregion
55537
+ //#region src/plugin/utils/is-next-file.ts
55538
+ const isNextFileActive = (context) => {
55539
+ const rawFilename = context.filename;
55540
+ if (!rawFilename) return true;
55541
+ const filename = normalizeFilename(rawFilename);
55542
+ const manifest = readNearestPackageManifest(filename);
55543
+ if (!manifest) return true;
55544
+ if (declaresDependency(manifest, "next")) return true;
55545
+ if (!declaresAnyDependency(manifest)) return true;
55546
+ const packageDirectory = findNearestPackageDirectory(filename);
55547
+ const rootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
55548
+ if (packageDirectory !== null && isPackageNestedBelowProjectRoot(packageDirectory, rootDirectory)) return false;
55549
+ return true;
55550
+ };
55551
+ //#endregion
55552
+ //#region src/plugin/utils/wrap-nextjs-rule.ts
55553
+ const EMPTY_VISITORS$1 = {};
55554
+ const wrapNextjsRule = (rule) => {
55555
+ const innerCreate = rule.create.bind(rule);
55556
+ return {
55557
+ ...rule,
55558
+ create: (context) => {
55559
+ if (!isNextFileActive(context)) return EMPTY_VISITORS$1;
55560
+ return innerCreate(context);
55561
+ }
55562
+ };
55563
+ };
55564
+ //#endregion
55454
55565
  //#region src/plugin/utils/wrap-react-native-rule.ts
55455
55566
  const EMPTY_VISITORS = {};
55456
55567
  const wrapReactNativeRule = (rule) => {
@@ -55920,9 +56031,14 @@ const wrapWithSemanticContext = (rule) => ({
55920
56031
  });
55921
56032
  //#endregion
55922
56033
  //#region src/plugin/react-doctor-plugin.ts
56034
+ const applyFrameworkGate = (rule) => {
56035
+ if (rule.framework === "react-native") return wrapReactNativeRule(rule);
56036
+ if (rule.framework === "nextjs") return wrapNextjsRule(rule);
56037
+ return rule;
56038
+ };
55923
56039
  const applyFrameworkRuleWrappers = (registry) => {
55924
56040
  const wrapped = {};
55925
- for (const [ruleId, rule] of Object.entries(registry)) wrapped[ruleId] = wrapWithSemanticContext(rule.framework === "react-native" ? wrapReactNativeRule(rule) : rule);
56041
+ for (const [ruleId, rule] of Object.entries(registry)) wrapped[ruleId] = wrapWithSemanticContext(applyFrameworkGate(rule));
55926
56042
  return wrapped;
55927
56043
  };
55928
56044
  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.b97a92f",
3
+ "version": "0.7.2-dev.cb8f726",
4
4
  "description": "React Doctor rules for oxlint.",
5
5
  "keywords": [
6
6
  "accessibility",