oxlint-plugin-react-doctor 0.7.7-dev.fbc804e → 0.7.7

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 (3) hide show
  1. package/dist/index.d.ts +0 -46
  2. package/dist/index.js +413 -1320
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,13 +7,6 @@ import { analyze } from "eslint-scope";
7
7
  //#region src/plugin/utils/is-node-of-type.ts
8
8
  const isNodeOfType = (node, type) => node !== null && typeof node === "object" && "type" in node && node.type === type;
9
9
  //#endregion
10
- //#region src/plugin/utils/is-type-only-import.ts
11
- const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
12
- if (!specifiers || specifiers.length === 0) return false;
13
- return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
14
- };
15
- const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
16
- //#endregion
17
10
  //#region src/plugin/utils/non-react-jsx-dialect.ts
18
11
  const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
19
12
  "solid-js",
@@ -28,33 +21,17 @@ const NON_REACT_JSX_DIALECT_PACKAGES = new Set([
28
21
  "vidode"
29
22
  ]);
30
23
  const NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES = ["solid-js", "@builder.io/qwik"];
31
- const REACT_JSX_DIALECT_PACKAGE_PREFIXES = [
32
- "react",
33
- "react-dom",
34
- "preact"
35
- ];
36
24
  const startsWithAny = (source, prefixes) => prefixes.some((prefix) => source === prefix || source.startsWith(`${prefix}/`));
37
- const collectJsxRuntimeImports = (program) => {
38
- let hasNonReactRuntime = false;
39
- let hasReactRuntime = false;
25
+ const fileImportsNonReactJsxDialect = (program) => {
40
26
  for (const statement of program.body) {
41
27
  if (!isNodeOfType(statement, "ImportDeclaration")) continue;
42
- const importDeclaration = statement;
43
- if (isTypeOnlyImport(importDeclaration)) continue;
44
- const source = importDeclaration.source;
28
+ const source = statement.source;
45
29
  const value = source && typeof source.value === "string" ? source.value : null;
46
30
  if (!value) continue;
47
- if (NON_REACT_JSX_DIALECT_PACKAGES.has(value) || startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasNonReactRuntime = true;
48
- if (startsWithAny(value, REACT_JSX_DIALECT_PACKAGE_PREFIXES)) hasReactRuntime = true;
31
+ if (NON_REACT_JSX_DIALECT_PACKAGES.has(value)) return true;
32
+ if (startsWithAny(value, NON_REACT_JSX_DIALECT_PACKAGE_PREFIXES)) return true;
49
33
  }
50
- return {
51
- hasNonReactRuntime,
52
- hasReactRuntime
53
- };
54
- };
55
- const fileImportsNonReactJsxDialect = (program) => {
56
- const runtimeImports = collectJsxRuntimeImports(program);
57
- return runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
34
+ return false;
58
35
  };
59
36
  const jsxAttributeIsNonReactDialectMarker = (openingNode) => {
60
37
  for (const attribute of openingNode.attributes) {
@@ -267,7 +244,6 @@ const VISITOR_NODE_NAME_PATTERN = /^[A-Z]/;
267
244
  const wrapCreateForReactJsxOnly = (create) => ((context) => {
268
245
  const innerVisitors = create(context);
269
246
  let fileIsNonReactJsx = false;
270
- let fileImportsReactRuntime = false;
271
247
  const wrappedVisitors = {};
272
248
  for (const [key, visitor] of Object.entries(innerVisitors)) {
273
249
  if (typeof visitor !== "function") {
@@ -280,16 +256,14 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
280
256
  }
281
257
  if (key === "Program") {
282
258
  wrappedVisitors.Program = (node) => {
283
- const runtimeImports = collectJsxRuntimeImports(node);
284
- fileImportsReactRuntime = runtimeImports.hasReactRuntime;
285
- fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
259
+ fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
286
260
  visitor(node);
287
261
  };
288
262
  continue;
289
263
  }
290
264
  if (key === "JSXOpeningElement") {
291
265
  wrappedVisitors.JSXOpeningElement = (node) => {
292
- if (!fileImportsReactRuntime && !fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
266
+ if (!fileIsNonReactJsx && jsxAttributeIsNonReactDialectMarker(node)) fileIsNonReactJsx = true;
293
267
  if (fileIsNonReactJsx) return;
294
268
  visitor(node);
295
269
  };
@@ -301,9 +275,7 @@ const wrapCreateForReactJsxOnly = (create) => ((context) => {
301
275
  };
302
276
  }
303
277
  if (!("Program" in wrappedVisitors)) wrappedVisitors.Program = (node) => {
304
- const runtimeImports = collectJsxRuntimeImports(node);
305
- fileImportsReactRuntime = runtimeImports.hasReactRuntime;
306
- fileIsNonReactJsx = runtimeImports.hasNonReactRuntime && !runtimeImports.hasReactRuntime;
278
+ fileIsNonReactJsx = fileImportsNonReactJsxDialect(node);
307
279
  };
308
280
  return wrappedVisitors;
309
281
  });
@@ -2340,7 +2312,7 @@ const anchorAmbiguousText = defineRule({
2340
2312
  });
2341
2313
  //#endregion
2342
2314
  //#region src/plugin/rules/a11y/anchor-has-content.ts
2343
- const MESSAGE$62 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2315
+ const MESSAGE$61 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2344
2316
  const isTransComponentsTemplate = (node) => {
2345
2317
  let current = node.parent;
2346
2318
  while (current) {
@@ -2376,7 +2348,7 @@ const anchorHasContent = defineRule({
2376
2348
  if (isTransComponentsTemplate(node)) return;
2377
2349
  context.report({
2378
2350
  node: opening.name,
2379
- message: MESSAGE$62
2351
+ message: MESSAGE$61
2380
2352
  });
2381
2353
  } };
2382
2354
  }
@@ -2808,7 +2780,7 @@ const parseJsxValue = (value) => {
2808
2780
  };
2809
2781
  //#endregion
2810
2782
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
2811
- const MESSAGE$61 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2783
+ const MESSAGE$60 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2812
2784
  const mayBeContentEditable = (node) => {
2813
2785
  const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
2814
2786
  if (!attribute) return false;
@@ -2839,7 +2811,7 @@ const ariaActivedescendantHasTabindex = defineRule({
2839
2811
  if (tabIndexValue === null || tabIndexValue >= -1) return;
2840
2812
  context.report({
2841
2813
  node: node.name,
2842
- message: MESSAGE$61
2814
+ message: MESSAGE$60
2843
2815
  });
2844
2816
  return;
2845
2817
  }
@@ -2847,7 +2819,7 @@ const ariaActivedescendantHasTabindex = defineRule({
2847
2819
  if (mayBeContentEditable(node)) return;
2848
2820
  context.report({
2849
2821
  node: node.name,
2850
- message: MESSAGE$61
2822
+ message: MESSAGE$60
2851
2823
  });
2852
2824
  } })
2853
2825
  });
@@ -5252,7 +5224,7 @@ const asyncParallel = defineRule({
5252
5224
  });
5253
5225
  //#endregion
5254
5226
  //#region src/plugin/rules/security/auth-token-in-web-storage.ts
5255
- const MESSAGE$60 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
5227
+ const MESSAGE$59 = "Storing an auth token in `localStorage`/`sessionStorage` exposes it to any XSS on the page: JavaScript can read web storage and exfiltrate the token. Keep tokens in an `HttpOnly`, `Secure`, `SameSite` cookie instead.";
5256
5228
  const STORAGE_NAMES = new Set(["localStorage", "sessionStorage"]);
5257
5229
  const STORAGE_GLOBALS = new Set([
5258
5230
  "window",
@@ -5317,7 +5289,7 @@ const authTokenInWebStorage = defineRule({
5317
5289
  if (keyString === null || !isAuthCredentialKey(keyString)) return;
5318
5290
  context.report({
5319
5291
  node,
5320
- message: MESSAGE$60
5292
+ message: MESSAGE$59
5321
5293
  });
5322
5294
  },
5323
5295
  AssignmentExpression(node) {
@@ -5328,7 +5300,7 @@ const authTokenInWebStorage = defineRule({
5328
5300
  if (!propertyName || !isAuthCredentialKey(propertyName)) return;
5329
5301
  context.report({
5330
5302
  node: target,
5331
- message: MESSAGE$60
5303
+ message: MESSAGE$59
5332
5304
  });
5333
5305
  }
5334
5306
  }))
@@ -5467,7 +5439,7 @@ const CI_INSTALL_NEAR_SECRET_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b
5467
5439
  const INSTALL_COMMAND_PATTERN = /(?:npm|pnpm|yarn|bun)\s+(?:install|ci)\b/i;
5468
5440
  const SECRET_REFERENCE_PATTERN = /\bsecrets\.[A-Z0-9_]+/;
5469
5441
  const IGNORE_SCRIPTS_FLAG_PATTERN = /--ignore-scripts\b/;
5470
- const MESSAGE$59 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
5442
+ const MESSAGE$58 = "The build or install pipeline can execute package lifecycle code while CI secrets may be present.";
5471
5443
  const isWorkflowPath = (relativePath) => /(?:^|\/)\.github\/workflows\/[^/]+\.ya?ml$/i.test(relativePath);
5472
5444
  const scanWorkflowContent = (content) => {
5473
5445
  const lines = content.split("\n");
@@ -5512,7 +5484,7 @@ const scanWorkflowContent = (content) => {
5512
5484
  const installLineOffset = Math.max(step.lines.findIndex((stepLine) => INSTALL_COMMAND_PATTERN.test(stepLine)), 0);
5513
5485
  const installColumnIndex = step.lines[installLineOffset].search(INSTALL_COMMAND_PATTERN);
5514
5486
  return [{
5515
- message: MESSAGE$59,
5487
+ message: MESSAGE$58,
5516
5488
  line: step.startLineIndex + installLineOffset + 1,
5517
5489
  column: (installColumnIndex === -1 ? 0 : installColumnIndex) + 1
5518
5490
  }];
@@ -5522,7 +5494,7 @@ const scanWorkflowContent = (content) => {
5522
5494
  const scanNonWorkflowConfig = scanByPattern({
5523
5495
  shouldScan: (file) => isConfigOrCiPath(file.relativePath) && !file.relativePath.endsWith("package.json") && !isWorkflowPath(file.relativePath),
5524
5496
  pattern: CI_INSTALL_NEAR_SECRET_PATTERN,
5525
- message: MESSAGE$59
5497
+ message: MESSAGE$58
5526
5498
  });
5527
5499
  const scan = (file) => {
5528
5500
  if (isWorkflowPath(file.relativePath)) return scanWorkflowContent(file.content);
@@ -6019,17 +5991,15 @@ const checkedRequiresOnchangeOrReadonly = defineRule({
6019
5991
  //#endregion
6020
5992
  //#region src/plugin/utils/get-static-property-key-name.ts
6021
5993
  const getStaticPropertyKeyName = (node, options = {}) => {
6022
- if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition") && !isNodeOfType(node, "MemberExpression")) return null;
6023
- const key = isNodeOfType(node, "MemberExpression") ? node.property : node.key;
5994
+ if (!isNodeOfType(node, "Property") && !isNodeOfType(node, "MethodDefinition")) return null;
6024
5995
  if (node.computed) {
6025
- if (options.allowComputedString && isNodeOfType(key, "Literal") && typeof key.value === "string") return key.value;
6026
- if (options.allowComputedString && isNodeOfType(key, "TemplateLiteral") && key.expressions.length === 0) return key.quasis[0]?.value.cooked ?? key.quasis[0]?.value.raw ?? null;
5996
+ if (options.allowComputedString && isNodeOfType(node.key, "Literal") && typeof node.key.value === "string") return node.key.value;
6027
5997
  return null;
6028
5998
  }
6029
- if (isNodeOfType(key, "Identifier")) return key.name;
6030
- if (isNodeOfType(key, "Literal")) {
6031
- if (typeof key.value === "string") return key.value;
6032
- if (options.stringifyNonStringLiterals) return String(key.value);
5999
+ if (isNodeOfType(node.key, "Identifier")) return node.key.name;
6000
+ if (isNodeOfType(node.key, "Literal")) {
6001
+ if (typeof node.key.value === "string") return node.key.value;
6002
+ if (options.stringifyNonStringLiterals) return String(node.key.value);
6033
6003
  }
6034
6004
  return null;
6035
6005
  };
@@ -6291,7 +6261,7 @@ const isPureEventBlockerHandler = (attribute) => {
6291
6261
  };
6292
6262
  //#endregion
6293
6263
  //#region src/plugin/rules/a11y/click-events-have-key-events.ts
6294
- const MESSAGE$58 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
6264
+ const MESSAGE$57 = "Keyboard users can't trigger this click handler because there's no keyboard one, so add `onKeyUp`, `onKeyDown`, or `onKeyPress`.";
6295
6265
  const KEY_HANDLERS = [
6296
6266
  "onKeyUp",
6297
6267
  "onKeyDown",
@@ -6502,7 +6472,7 @@ const clickEventsHaveKeyEvents = defineRule({
6502
6472
  if (KEY_HANDLERS.some((handler) => hasJsxPropIgnoreCase(node.attributes, handler) || spreadEventValues.has(handler.toLowerCase()))) return;
6503
6473
  context.report({
6504
6474
  node: node.name,
6505
- message: MESSAGE$58
6475
+ message: MESSAGE$57
6506
6476
  });
6507
6477
  } };
6508
6478
  }
@@ -8755,7 +8725,7 @@ const getClassNameLiteral = (classAttribute) => {
8755
8725
  };
8756
8726
  //#endregion
8757
8727
  //#region src/plugin/rules/a11y/control-has-associated-label.ts
8758
- const MESSAGE$57 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
8728
+ const MESSAGE$56 = "Blind users can't tell what this control does because screen readers find no label, so add visible text, `aria-label`, or `aria-labelledby`.";
8759
8729
  const NON_OPERABLE_ELEMENTS = new Set([
8760
8730
  "td",
8761
8731
  "th",
@@ -9193,7 +9163,7 @@ const controlHasAssociatedLabel = defineRule({
9193
9163
  if (candidate.enclosingBindingName !== null && labelEmbeddedNames.has(candidate.enclosingBindingName)) continue;
9194
9164
  context.report({
9195
9165
  node: candidate.opening,
9196
- message: MESSAGE$57
9166
+ message: MESSAGE$56
9197
9167
  });
9198
9168
  }
9199
9169
  }
@@ -9954,7 +9924,7 @@ const noVagueButtonLabel = defineRule({
9954
9924
  });
9955
9925
  //#endregion
9956
9926
  //#region src/plugin/rules/a11y/dialog-has-accessible-name.ts
9957
- const MESSAGE$56 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
9927
+ const MESSAGE$55 = "This dialog has no accessible name, so screen readers announce it as just “dialog.” Add `aria-label` or point `aria-labelledby` at its heading.";
9958
9928
  const DIALOG_ROLES = new Set(["dialog", "alertdialog"]);
9959
9929
  const NAME_PROVIDING_ATTRIBUTES = [
9960
9930
  "aria-label",
@@ -9979,7 +9949,7 @@ const dialogHasAccessibleName = defineRule({
9979
9949
  if (NAME_PROVIDING_ATTRIBUTES.some((attribute) => hasJsxPropIgnoreCase(node.attributes, attribute))) return;
9980
9950
  context.report({
9981
9951
  node: node.name,
9982
- message: MESSAGE$56
9952
+ message: MESSAGE$55
9983
9953
  });
9984
9954
  } };
9985
9955
  }
@@ -10019,7 +9989,7 @@ const isEs6Component = (node) => {
10019
9989
  };
10020
9990
  //#endregion
10021
9991
  //#region src/plugin/rules/react-builtins/display-name.ts
10022
- const MESSAGE$55 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
9992
+ const MESSAGE$54 = "This component shows up as Anonymous in React DevTools because it has no `displayName`.";
10023
9993
  const DEFAULT_ADDITIONAL_HOCS = [
10024
9994
  "observer",
10025
9995
  "lazy",
@@ -10212,7 +10182,7 @@ const displayName = defineRule({
10212
10182
  const reportAt = (node) => {
10213
10183
  context.report({
10214
10184
  node,
10215
- message: MESSAGE$55
10185
+ message: MESSAGE$54
10216
10186
  });
10217
10187
  };
10218
10188
  return {
@@ -11033,14 +11003,6 @@ const PROMISE_CHAIN_METHOD_NAMES$1 = new Set([
11033
11003
  "catch",
11034
11004
  "finally"
11035
11005
  ]);
11036
- const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
11037
- const getPromiseChainCallForCallback = (candidate) => {
11038
- let callbackContainer = candidate.parent;
11039
- while (callbackContainer && TRANSPARENT_EXPRESSION_WRAPPER_TYPES.has(callbackContainer.type)) callbackContainer = callbackContainer.parent;
11040
- if (!isNodeOfType(callbackContainer, "CallExpression")) return null;
11041
- if (!callbackContainer.arguments?.some((argument) => stripParenExpression(argument) === candidate)) return null;
11042
- return isPromiseChainCall(stripParenExpression(callbackContainer.callee)) ? callbackContainer : null;
11043
- };
11044
11006
  const collectEffectInvokedFunctions = (effectCallback) => {
11045
11007
  const invokedFunctions = new Set([effectCallback]);
11046
11008
  const localFunctionBindings = /* @__PURE__ */ new Map();
@@ -11052,6 +11014,7 @@ const collectEffectInvokedFunctions = (effectCallback) => {
11052
11014
  invokedFunctions.add(strippedCandidate);
11053
11015
  pendingFunctions.push(strippedCandidate);
11054
11016
  };
11017
+ const isPromiseChainCall = (callee) => isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && PROMISE_CHAIN_METHOD_NAMES$1.has(callee.property.name) && isNodeOfType(stripParenExpression(callee.object), "CallExpression");
11055
11018
  while (pendingFunctions.length > 0) {
11056
11019
  const currentFunction = pendingFunctions.pop();
11057
11020
  if (!currentFunction) break;
@@ -11259,50 +11222,6 @@ const isCleanupReturningSubscribeLikeCallExpression = (node) => {
11259
11222
  return true;
11260
11223
  };
11261
11224
  //#endregion
11262
- //#region src/plugin/utils/is-node-reachable-within-function.ts
11263
- const isInsideStaticallyUnreachableBranch = (node) => {
11264
- let child = node;
11265
- let parent = node.parent;
11266
- while (parent) {
11267
- if (isNodeOfType(parent, "IfStatement") && isNodeOfType(parent.test, "Literal")) {
11268
- if (parent.test.value === false && parent.consequent === child) return true;
11269
- if (parent.test.value === true && parent.alternate === child) return true;
11270
- }
11271
- if (isNodeOfType(parent, "ConditionalExpression") && isNodeOfType(parent.test, "Literal")) {
11272
- if (parent.test.value === false && parent.consequent === child) return true;
11273
- if (parent.test.value === true && parent.alternate === child) return true;
11274
- }
11275
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) {
11276
- if (isNodeOfType(parent.left, "Literal") && (parent.operator === "&&" && !parent.left.value || parent.operator === "||" && Boolean(parent.left.value))) return true;
11277
- }
11278
- child = parent;
11279
- parent = parent.parent;
11280
- }
11281
- return false;
11282
- };
11283
- const isNodeReachableWithinFunction = (node, context) => {
11284
- if (isInsideStaticallyUnreachableBranch(node)) return false;
11285
- const owner = context.cfg.enclosingFunction(node);
11286
- if (!owner) return true;
11287
- const functionCfg = context.cfg.cfgFor(owner);
11288
- if (!functionCfg) return true;
11289
- const targetBlock = functionCfg.blockOf(node);
11290
- if (!targetBlock) return true;
11291
- const visitedBlocks = new Set([functionCfg.entry]);
11292
- const pendingBlocks = [functionCfg.entry];
11293
- while (pendingBlocks.length > 0) {
11294
- const currentBlock = pendingBlocks.pop();
11295
- if (!currentBlock) break;
11296
- if (currentBlock === targetBlock) return true;
11297
- for (const edge of currentBlock.successors) {
11298
- if (visitedBlocks.has(edge.to)) continue;
11299
- visitedBlocks.add(edge.to);
11300
- pendingBlocks.push(edge.to);
11301
- }
11302
- }
11303
- return false;
11304
- };
11305
- //#endregion
11306
11225
  //#region src/plugin/rules/state-and-effects/effect-needs-cleanup.ts
11307
11226
  const OBSERVER_REGISTRATION_METHOD_NAME = "observe";
11308
11227
  const CLEANUP_EFFECT_HOOK_NAMES = new Set([...EFFECT_HOOK_NAMES$1, "useInsertionEffect"]);
@@ -11431,6 +11350,27 @@ const findSubscribeLikeUsages = (callback, context) => {
11431
11350
  });
11432
11351
  return usages.filter((usage) => isNodeReachableWithinFunction(usage.node, context));
11433
11352
  };
11353
+ const isNodeReachableWithinFunction = (node, context) => {
11354
+ const owner = context.cfg.enclosingFunction(node);
11355
+ if (!owner) return true;
11356
+ const functionCfg = context.cfg.cfgFor(owner);
11357
+ if (!functionCfg) return true;
11358
+ const targetBlock = functionCfg.blockOf(node);
11359
+ if (!targetBlock) return true;
11360
+ const visitedBlocks = new Set([functionCfg.entry]);
11361
+ const pendingBlocks = [functionCfg.entry];
11362
+ while (pendingBlocks.length > 0) {
11363
+ const currentBlock = pendingBlocks.pop();
11364
+ if (!currentBlock) break;
11365
+ if (currentBlock === targetBlock) return true;
11366
+ for (const edge of currentBlock.successors) {
11367
+ if (visitedBlocks.has(edge.to)) continue;
11368
+ visitedBlocks.add(edge.to);
11369
+ pendingBlocks.push(edge.to);
11370
+ }
11371
+ }
11372
+ return false;
11373
+ };
11434
11374
  const doMatchingNodesCoverEveryPathAfterUsage = (usageNode, matchingNodes, context) => {
11435
11375
  let pathAnchor = usageNode;
11436
11376
  let pathOwner = findEnclosingFunction(pathAnchor);
@@ -11904,94 +11844,6 @@ const hasStableUnmountCleanupForUsage = (callback, usage, context) => {
11904
11844
  return didFindUnmountCleanup;
11905
11845
  };
11906
11846
  const hasSplitLifecycleCleanup = (callback, usage, context) => usage.handleKey !== null && hasRerunReleaseBeforeUsage(callback, usage, context) && hasStableUnmountCleanupForUsage(callback, usage, context);
11907
- const collectBlockingBooleanStates = (expression, blockedExpressionValue, context) => {
11908
- const unwrappedExpression = stripParenExpression(expression);
11909
- if (isNodeOfType(unwrappedExpression, "UnaryExpression") && unwrappedExpression.operator === "!") return collectBlockingBooleanStates(unwrappedExpression.argument, !blockedExpressionValue, context);
11910
- if (isNodeOfType(unwrappedExpression, "LogicalExpression")) {
11911
- if (!(unwrappedExpression.operator === "||" && blockedExpressionValue || unwrappedExpression.operator === "&&" && !blockedExpressionValue)) return [];
11912
- return [...collectBlockingBooleanStates(unwrappedExpression.left, blockedExpressionValue, context), ...collectBlockingBooleanStates(unwrappedExpression.right, blockedExpressionValue, context)];
11913
- }
11914
- if (isNodeOfType(unwrappedExpression, "BinaryExpression") && [
11915
- "===",
11916
- "==",
11917
- "!==",
11918
- "!="
11919
- ].includes(unwrappedExpression.operator)) {
11920
- const leftValue = readStaticBoolean(unwrappedExpression.left);
11921
- const rightValue = readStaticBoolean(unwrappedExpression.right);
11922
- const booleanValue = leftValue ?? rightValue;
11923
- const comparedKey = resolveExpressionKey(leftValue === null ? unwrappedExpression.left : unwrappedExpression.right, context);
11924
- if (booleanValue === null || comparedKey === null) return [];
11925
- return [{
11926
- key: comparedKey,
11927
- value: (unwrappedExpression.operator === "===" || unwrappedExpression.operator === "==") === blockedExpressionValue ? booleanValue : !booleanValue
11928
- }];
11929
- }
11930
- const expressionKey = resolveExpressionKey(unwrappedExpression, context);
11931
- return expressionKey === null ? [] : [{
11932
- key: expressionKey,
11933
- value: blockedExpressionValue
11934
- }];
11935
- };
11936
- const isDirectEarlyReturnConsequent = (ifStatement) => {
11937
- if (!isNodeOfType(ifStatement, "IfStatement") || ifStatement.alternate) return false;
11938
- if (isNodeOfType(ifStatement.consequent, "ReturnStatement")) return true;
11939
- return isNodeOfType(ifStatement.consequent, "BlockStatement") && ifStatement.consequent.body.length === 1 && isNodeOfType(ifStatement.consequent.body[0], "ReturnStatement");
11940
- };
11941
- const collectDeferredUsageGuardStates = (callback, usageNode, context) => {
11942
- if (!isFunctionLike$2(callback) || callback.async) return [];
11943
- const guardStates = [];
11944
- walkAst(callback.body, (child) => {
11945
- if (child !== callback.body && isFunctionLike$2(child)) return false;
11946
- if (isNodeOfType(child, "IfStatement") && isDirectEarlyReturnConsequent(child) && doMatchingNodesCoverEveryPathBeforeUsage(usageNode, [child], callback, context)) guardStates.push(...collectBlockingBooleanStates(child.test, true, context));
11947
- });
11948
- let descendant = usageNode;
11949
- let ancestor = descendant.parent;
11950
- while (ancestor && ancestor !== callback) {
11951
- if (isNodeOfType(ancestor, "IfStatement") && ancestor.consequent === descendant) guardStates.push(...collectBlockingBooleanStates(ancestor.test, false, context));
11952
- descendant = ancestor;
11953
- ancestor = ancestor.parent;
11954
- }
11955
- return guardStates;
11956
- };
11957
- const cleanupReturnInvalidatesGuard = (cleanupReturn, guardState, context) => {
11958
- if (!isNodeOfType(cleanupReturn, "ReturnStatement") || !cleanupReturn.argument) return false;
11959
- const cleanupFunction = resolveStableValue(cleanupReturn.argument, context);
11960
- if (!cleanupFunction || !isFunctionLike$2(cleanupFunction) || cleanupFunction.async) return false;
11961
- let didInvalidateGuard = false;
11962
- walkAst(cleanupFunction.body, (child) => {
11963
- if (didInvalidateGuard) return false;
11964
- if (child !== cleanupFunction.body && isFunctionLike$2(child)) return false;
11965
- if (isNodeOfType(child, "AssignmentExpression") && child.operator === "=" && resolveExpressionKey(child.left, context) === guardState.key && readStaticBoolean(child.right) === guardState.value && context.cfg.isUnconditionalFromEntry(child)) {
11966
- didInvalidateGuard = true;
11967
- return false;
11968
- }
11969
- });
11970
- return didInvalidateGuard;
11971
- };
11972
- const deferredUsageWritesGuardBeforeUsage = (callback, usageNode, guardState, context) => {
11973
- const usageStart = getRangeStart(usageNode);
11974
- if (!isFunctionLike$2(callback) || usageStart === null) return true;
11975
- let didWriteGuard = false;
11976
- walkAst(callback.body, (child) => {
11977
- if (didWriteGuard) return false;
11978
- if (child !== callback.body && isFunctionLike$2(child)) return false;
11979
- const childStart = getRangeStart(child);
11980
- if (childStart === null || childStart >= usageStart) return;
11981
- const writtenExpression = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : null;
11982
- if (writtenExpression && resolveExpressionKey(writtenExpression, context) === guardState.key) {
11983
- didWriteGuard = true;
11984
- return false;
11985
- }
11986
- });
11987
- return didWriteGuard;
11988
- };
11989
- const hasGuardedDeferredCleanup = (callback, usage, cleanupReturns, context) => {
11990
- const usageFunction = findEnclosingFunction(usage.node);
11991
- const promiseChainCall = usageFunction ? getPromiseChainCallForCallback(usageFunction) : null;
11992
- if (usage.handleKey === null || !usageFunction || usageFunction === callback || !promiseChainCall || !collectEffectInvokedFunctions(callback).has(usageFunction) || !doMatchingNodesCoverEveryPathAfterUsage(promiseChainCall, cleanupReturns, context)) return false;
11993
- return collectDeferredUsageGuardStates(usageFunction, usage.node, context).some((guardState) => !deferredUsageWritesGuardBeforeUsage(usageFunction, usage.node, guardState, context) && cleanupReturns.every((cleanupReturn) => cleanupReturnInvalidatesGuard(cleanupReturn, guardState, context)));
11994
- };
11995
11847
  const effectHasCleanupForUsage = (callback, usage, context) => {
11996
11848
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return false;
11997
11849
  if (callback.async) return false;
@@ -12031,7 +11883,6 @@ const effectHasCleanupForUsage = (callback, usage, context) => {
12031
11883
  if (!cleanupFunction || !isFunctionLike$2(cleanupFunction)) return;
12032
11884
  if (doesCleanupFunctionReleaseUsage(cleanupFunction, usage, context)) matchingCleanupReturns.push(child);
12033
11885
  });
12034
- if (hasGuardedDeferredCleanup(callback, usage, matchingCleanupReturns, context)) return true;
12035
11886
  return doMatchingNodesCoverEveryPathAfterUsage(resolveCleanupPathAnchor(usage.node, callback, context), matchingCleanupReturns, context);
12036
11887
  };
12037
11888
  const findFirstUsageWithoutCleanup = (callback, usages, context) => {
@@ -14775,7 +14626,7 @@ const forbidElements = defineRule({
14775
14626
  });
14776
14627
  //#endregion
14777
14628
  //#region src/plugin/rules/react-builtins/forward-ref-uses-ref.ts
14778
- const MESSAGE$54 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
14629
+ const MESSAGE$53 = "The parent can't reach this component's node because the `forwardRef` wrapper ignores `ref`.";
14779
14630
  const forwardRefUsesRef = defineRule({
14780
14631
  id: "forward-ref-uses-ref",
14781
14632
  title: "forwardRef without ref parameter",
@@ -14798,7 +14649,7 @@ const forwardRefUsesRef = defineRule({
14798
14649
  if (isNodeOfType(onlyParam, "RestElement")) return;
14799
14650
  context.report({
14800
14651
  node: inner,
14801
- message: MESSAGE$54
14652
+ message: MESSAGE$53
14802
14653
  });
14803
14654
  } })
14804
14655
  });
@@ -14839,7 +14690,7 @@ const gitProviderUrlInjectionRisk = defineRule({
14839
14690
  });
14840
14691
  //#endregion
14841
14692
  //#region src/plugin/rules/a11y/heading-has-content.ts
14842
- const MESSAGE$53 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
14693
+ const MESSAGE$52 = "Blind users can't use this heading to navigate because screen readers skip it empty, so add text, `aria-label`, or `aria-labelledby`.";
14843
14694
  const DEFAULT_HEADING_TAGS = [
14844
14695
  "h1",
14845
14696
  "h2",
@@ -14873,7 +14724,7 @@ const headingHasContent = defineRule({
14873
14724
  for (const attribute of ["aria-label", "aria-labelledby"]) if (hasJsxPropIgnoreCase(node.attributes, attribute)) return;
14874
14725
  context.report({
14875
14726
  node,
14876
- message: MESSAGE$53
14727
+ message: MESSAGE$52
14877
14728
  });
14878
14729
  } };
14879
14730
  }
@@ -15037,7 +14888,7 @@ const hooksNoNanInDeps = defineRule({
15037
14888
  });
15038
14889
  //#endregion
15039
14890
  //#region src/plugin/rules/a11y/html-has-lang.ts
15040
- const MESSAGE$52 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
14891
+ const MESSAGE$51 = "Screen readers may mispronounce this page because it doesn't declare a language, so add a `lang` attribute like `en`.";
15041
14892
  const resolveSettings$39 = (settings) => {
15042
14893
  const reactDoctor = settings?.["react-doctor"];
15043
14894
  return { htmlTags: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.htmlHasLang ?? {} : {}).htmlTags ?? ["html"] };
@@ -15084,13 +14935,13 @@ const htmlHasLang = defineRule({
15084
14935
  if (!lang) {
15085
14936
  context.report({
15086
14937
  node: node.name,
15087
- message: MESSAGE$52
14938
+ message: MESSAGE$51
15088
14939
  });
15089
14940
  return;
15090
14941
  }
15091
14942
  if (evaluateLang(lang.value) === "empty") context.report({
15092
14943
  node: lang,
15093
- message: MESSAGE$52
14944
+ message: MESSAGE$51
15094
14945
  });
15095
14946
  } };
15096
14947
  }
@@ -15330,7 +15181,7 @@ const htmlNoNestedInteractive = defineRule({
15330
15181
  });
15331
15182
  //#endregion
15332
15183
  //#region src/plugin/rules/a11y/iframe-has-title.ts
15333
- const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
15184
+ const MESSAGE$50 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
15334
15185
  const evaluateTitleValue = (value) => {
15335
15186
  if (!value) return "missing";
15336
15187
  if (isNodeOfType(value, "Literal")) {
@@ -15370,14 +15221,14 @@ const iframeHasTitle = defineRule({
15370
15221
  if (!titleAttr) {
15371
15222
  if (hasSpread || tag === "iframe") context.report({
15372
15223
  node: node.name,
15373
- message: MESSAGE$51
15224
+ message: MESSAGE$50
15374
15225
  });
15375
15226
  return;
15376
15227
  }
15377
15228
  const verdict = evaluateTitleValue(titleAttr.value);
15378
15229
  if (verdict === "missing" || verdict === "empty") context.report({
15379
15230
  node: titleAttr,
15380
- message: MESSAGE$51
15231
+ message: MESSAGE$50
15381
15232
  });
15382
15233
  } })
15383
15234
  });
@@ -15502,7 +15353,7 @@ const iframeMissingSandbox = defineRule({
15502
15353
  });
15503
15354
  //#endregion
15504
15355
  //#region src/plugin/rules/a11y/img-redundant-alt.ts
15505
- const MESSAGE$50 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
15356
+ const MESSAGE$49 = "Screen reader users hear \"image\" or \"photo\" twice because they already announce it, so describe what the image shows instead.";
15506
15357
  const DEFAULT_COMPONENTS = ["img"];
15507
15358
  const DEFAULT_REDUNDANT_WORDS = [
15508
15359
  "image",
@@ -15567,7 +15418,7 @@ const imgRedundantAlt = defineRule({
15567
15418
  if (!altAttribute) return;
15568
15419
  if (altValueRedundant(altAttribute, settings.words)) context.report({
15569
15420
  node: altAttribute,
15570
- message: MESSAGE$50
15421
+ message: MESSAGE$49
15571
15422
  });
15572
15423
  } };
15573
15424
  }
@@ -17350,7 +17201,7 @@ const GLOBAL_BUILTIN_NAMES = new Set([
17350
17201
  ]);
17351
17202
  const STRING_PROTOTYPE_PATH = "String.prototype";
17352
17203
  const REGEXP_PROTOTYPE_PATH = "RegExp.prototype";
17353
- const getStaticStringValue$1 = (argument) => {
17204
+ const getStaticStringValue = (argument) => {
17354
17205
  if (!argument) return null;
17355
17206
  const unwrappedArgument = stripParenExpression(argument);
17356
17207
  if (isNodeOfType(unwrappedArgument, "Literal") && typeof unwrappedArgument.value === "string") return unwrappedArgument.value;
@@ -17358,7 +17209,7 @@ const getStaticStringValue$1 = (argument) => {
17358
17209
  return null;
17359
17210
  };
17360
17211
  const getEffectiveRegExpFlags = (patternArgument, flagsArgument) => {
17361
- if (flagsArgument) return getStaticStringValue$1(flagsArgument);
17212
+ if (flagsArgument) return getStaticStringValue(flagsArgument);
17362
17213
  if (!patternArgument) return "";
17363
17214
  const unwrappedPattern = stripParenExpression(patternArgument);
17364
17215
  if (isNodeOfType(unwrappedPattern, "Literal") && unwrappedPattern.value instanceof RegExp) return unwrappedPattern.value.flags;
@@ -17508,7 +17359,7 @@ const getCallRegExpHazard = (node, context, symbolCache) => {
17508
17359
  const mutationHazard = targetPath === "global" ? "globalRegExpReplaced" : "replaceAllIntegrityLost";
17509
17360
  const guardedPropertyName = targetPath === "global" ? "RegExp" : "replaceAll";
17510
17361
  if (isSinglePropertyMutation) {
17511
- const propertyName = getStaticStringValue$1(node.arguments?.[1]);
17362
+ const propertyName = getStaticStringValue(node.arguments?.[1]);
17512
17363
  return propertyName === null || propertyName === guardedPropertyName ? mutationHazard : "none";
17513
17364
  }
17514
17365
  if (!isPropertyCollectionMutation) return "none";
@@ -17537,7 +17388,7 @@ const getHoistableRegExpConstructionKind = (node, context) => {
17537
17388
  if (!STATEFUL_REGEXP_FLAGS_PATTERN.test(effectiveFlags)) return "stateless";
17538
17389
  return isSafeStatefulReplaceAllSearch(node, effectiveFlags, context) ? "statefulReplaceAll" : null;
17539
17390
  };
17540
- const MESSAGE$49 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
17391
+ const MESSAGE$48 = "`new RegExp()` rebuilds the pattern on every loop pass. Move it to a constant outside the loop.";
17541
17392
  const jsHoistRegexp = defineRule({
17542
17393
  id: "js-hoist-regexp",
17543
17394
  title: "RegExp built inside a loop",
@@ -17554,7 +17405,7 @@ const jsHoistRegexp = defineRule({
17554
17405
  if (constructionKind === "statefulReplaceAll" && cachedEnvironmentHazard === "replaceAllIntegrityLost") return;
17555
17406
  context.report({
17556
17407
  node,
17557
- message: MESSAGE$49
17408
+ message: MESSAGE$48
17558
17409
  });
17559
17410
  };
17560
17411
  return createLoopAwareVisitors({
@@ -18482,26 +18333,6 @@ const MEMBERSHIP_COMPARISON_OPERATORS = new Set([
18482
18333
  ]);
18483
18334
  const isNegativeOneLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "UnaryExpression") && expression.operator === "-" && isNodeOfType(expression.argument, "Literal") && expression.argument.value === 1;
18484
18335
  const isZeroLiteral = (expression) => Boolean(expression) && isNodeOfType(expression, "Literal") && expression.value === 0;
18485
- const isToIntegerOrInfinityZero = (value) => Number.isNaN(value) || Math.trunc(value) === 0;
18486
- const isZeroFromIndex = (expression) => {
18487
- if (!expression) return false;
18488
- const strippedExpression = stripParenExpression(expression);
18489
- if (isNodeOfType(strippedExpression, "Literal")) {
18490
- if (typeof strippedExpression.value === "number") return isToIntegerOrInfinityZero(strippedExpression.value);
18491
- if (typeof strippedExpression.value === "string") return isToIntegerOrInfinityZero(Number(strippedExpression.value));
18492
- return strippedExpression.value === null || strippedExpression.value === false;
18493
- }
18494
- if (isNodeOfType(strippedExpression, "UnaryExpression") && strippedExpression.operator === "void") return true;
18495
- if (isNodeOfType(strippedExpression, "UnaryExpression") && (strippedExpression.operator === "-" || strippedExpression.operator === "+") && isNodeOfType(strippedExpression.argument, "Literal") && typeof strippedExpression.argument.value === "number") return isToIntegerOrInfinityZero(strippedExpression.operator === "-" ? -strippedExpression.argument.value : strippedExpression.argument.value);
18496
- if (isNodeOfType(strippedExpression, "Identifier")) return (strippedExpression.name === "undefined" || strippedExpression.name === "NaN") && findVariableInitializer(strippedExpression, strippedExpression.name) === null;
18497
- return isNodeOfType(strippedExpression, "MemberExpression") && !strippedExpression.computed && isNodeOfType(strippedExpression.object, "Identifier") && strippedExpression.object.name === "Number" && isNodeOfType(strippedExpression.property, "Identifier") && strippedExpression.property.name === "NaN" && findVariableInitializer(strippedExpression.object, strippedExpression.object.name) === null;
18498
- };
18499
- const hasSemanticsPreservingIncludesArguments = (node) => {
18500
- if (node.arguments.length === 1) return !isNodeOfType(node.arguments[0], "SpreadElement");
18501
- if (node.arguments.length !== 2) return false;
18502
- if (isNodeOfType(node.arguments[0], "SpreadElement") || isNodeOfType(node.arguments[1], "SpreadElement")) return false;
18503
- return isZeroFromIndex(node.arguments[1]);
18504
- };
18505
18336
  const PARENT_WRAPPER_TYPES = new Set([
18506
18337
  "ParenthesizedExpression",
18507
18338
  "ChainExpression",
@@ -18522,306 +18353,6 @@ const isIndexOfResultUsedAsMembershipTest = (node) => {
18522
18353
  if (isNegativeOneLiteral(otherOperand)) return true;
18523
18354
  return isZeroLiteral(otherOperand) && (parent.operator === ">=" || parent.operator === "<");
18524
18355
  };
18525
- const getTypeAnnotation = (node) => {
18526
- if (!node || !("typeAnnotation" in node)) return null;
18527
- const annotation = node.typeAnnotation;
18528
- if (!annotation || !isNodeOfType(annotation, "TSTypeAnnotation")) return null;
18529
- return annotation.typeAnnotation;
18530
- };
18531
- const getDeclaredPropertyType = (members, propertyName) => {
18532
- for (const member of members) if (isNodeOfType(member, "TSPropertySignature") && isNodeOfType(member.key, "Identifier") && member.key.name === propertyName) return getTypeAnnotation(member);
18533
- return null;
18534
- };
18535
- const getArrayElementType = (typeNode) => {
18536
- if (!typeNode) return null;
18537
- if (isNodeOfType(typeNode, "TSArrayType")) return typeNode.elementType;
18538
- if (isNodeOfType(typeNode, "TSTypeReference") && isNodeOfType(typeNode.typeName, "Identifier") && (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray")) return typeNode.typeArguments?.params?.[0] ?? null;
18539
- if (isNodeOfType(typeNode, "TSUnionType")) {
18540
- const arrayElementTypes = typeNode.types.map(getArrayElementType).filter(Boolean);
18541
- return arrayElementTypes.length === 1 ? arrayElementTypes[0] : null;
18542
- }
18543
- return null;
18544
- };
18545
- const getDestructuredDeclaredType = (identifier) => {
18546
- const binding = findVariableInitializer(identifier, identifier.name);
18547
- if (!binding) return null;
18548
- const property = binding.bindingIdentifier.parent;
18549
- const objectPattern = property?.parent;
18550
- if (!isNodeOfType(property, "Property") || !isNodeOfType(property.key, "Identifier") || !isNodeOfType(objectPattern, "ObjectPattern")) return null;
18551
- const propsType = getTypeAnnotation(objectPattern);
18552
- if (!propsType) return null;
18553
- if (isNodeOfType(propsType, "TSTypeLiteral")) return getDeclaredPropertyType(propsType.members ?? [], property.key.name);
18554
- if (!isNodeOfType(propsType, "TSTypeReference") || !isNodeOfType(propsType.typeName, "Identifier")) return null;
18555
- const program = findProgramRoot(identifier);
18556
- if (!program) return null;
18557
- for (const statement of program.body) {
18558
- const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
18559
- if (isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name) return getDeclaredPropertyType(declaration.body.body, property.key.name);
18560
- if (isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === propsType.typeName.name && isNodeOfType(declaration.typeAnnotation, "TSTypeLiteral")) return getDeclaredPropertyType(declaration.typeAnnotation.members ?? [], property.key.name);
18561
- }
18562
- return null;
18563
- };
18564
- const getIdentifierDeclaredType = (identifier, visitedBindingIdentifiers = /* @__PURE__ */ new Set()) => {
18565
- const binding = findVariableInitializer(identifier, identifier.name);
18566
- if (!binding || visitedBindingIdentifiers.has(binding.bindingIdentifier)) return null;
18567
- visitedBindingIdentifiers.add(binding.bindingIdentifier);
18568
- const directType = getTypeAnnotation(binding.bindingIdentifier);
18569
- if (directType) return directType;
18570
- const initializer = binding.initializer;
18571
- if (isNodeOfType(initializer, "TSAsExpression") || isNodeOfType(initializer, "TSTypeAssertion") || isNodeOfType(initializer, "TSSatisfiesExpression")) return initializer.typeAnnotation;
18572
- const destructuredType = getDestructuredDeclaredType(identifier);
18573
- if (destructuredType) return destructuredType;
18574
- const declarator = binding.bindingIdentifier.parent;
18575
- const declaration = declarator?.parent;
18576
- const forOfStatement = declaration?.parent;
18577
- if (isNodeOfType(declarator, "VariableDeclarator") && isNodeOfType(declaration, "VariableDeclaration") && isNodeOfType(forOfStatement, "ForOfStatement") && forOfStatement.left === declaration && isNodeOfType(forOfStatement.right, "Identifier")) return getArrayElementType(getIdentifierDeclaredType(forOfStatement.right, visitedBindingIdentifiers));
18578
- return null;
18579
- };
18580
- const isNativeIterationIndex = (identifier) => {
18581
- const binding = findVariableInitializer(identifier, identifier.name);
18582
- if (!binding) return false;
18583
- const callback = binding.bindingIdentifier.parent;
18584
- if (!isInlineFunctionExpression(callback)) return false;
18585
- const callbackCall = callback.parent;
18586
- if (!isNodeOfType(callbackCall, "CallExpression") || !isIterationCallbackCall(callbackCall) || !isNodeOfType(callbackCall.callee, "MemberExpression") || !isNodeOfType(callbackCall.callee.property, "Identifier")) return false;
18587
- const indexParameterPosition = callbackCall.callee.property.name === "reduce" || callbackCall.callee.property.name === "reduceRight" ? 2 : 1;
18588
- return callback.params?.[indexParameterPosition] === binding.bindingIdentifier;
18589
- };
18590
- const hasSameIdentifierBinding = (leftIdentifier, rightIdentifier) => {
18591
- if (leftIdentifier.name !== rightIdentifier.name) return false;
18592
- const leftBinding = findVariableInitializer(leftIdentifier, leftIdentifier.name);
18593
- const rightBinding = findVariableInitializer(rightIdentifier, rightIdentifier.name);
18594
- return Boolean(leftBinding && rightBinding && leftBinding.bindingIdentifier === rightBinding.bindingIdentifier);
18595
- };
18596
- const NON_NAN_RELATIONAL_OPERATORS = new Set([
18597
- "<",
18598
- "<=",
18599
- ">",
18600
- ">="
18601
- ]);
18602
- const testProvesIdentifierIsNotNaN = (test, identifier) => {
18603
- if (!test) return false;
18604
- const strippedTest = stripParenExpression(test);
18605
- if (isNodeOfType(strippedTest, "LogicalExpression") && strippedTest.operator === "&&") return testProvesIdentifierIsNotNaN(strippedTest.left, identifier) || testProvesIdentifierIsNotNaN(strippedTest.right, identifier);
18606
- if (!isNodeOfType(strippedTest, "BinaryExpression") || !NON_NAN_RELATIONAL_OPERATORS.has(strippedTest.operator)) return false;
18607
- return isNodeOfType(strippedTest.left, "Identifier") && hasSameIdentifierBinding(strippedTest.left, identifier) || isNodeOfType(strippedTest.right, "Identifier") && hasSameIdentifierBinding(strippedTest.right, identifier);
18608
- };
18609
- const writeTargetContainsIdentifierBinding = (writeTarget, identifier) => {
18610
- let containsBinding = false;
18611
- walkAst(writeTarget, (child) => {
18612
- if (isNodeOfType(child, "Identifier") && hasSameIdentifierBinding(child, identifier)) {
18613
- containsBinding = true;
18614
- return false;
18615
- }
18616
- });
18617
- return containsBinding;
18618
- };
18619
- const hasWriteBeforeQuery = (body, identifier) => {
18620
- const queryStart = getRangeStart(identifier);
18621
- if (queryStart === null) return true;
18622
- let hasEarlierWrite = false;
18623
- walkAst(body, (child) => {
18624
- if (hasEarlierWrite) return false;
18625
- const childStart = getRangeStart(child);
18626
- if (childStart !== null && childStart >= queryStart) return false;
18627
- if (child !== body && isFunctionLike$2(child)) return false;
18628
- const writeTarget = isNodeOfType(child, "AssignmentExpression") ? child.left : isNodeOfType(child, "UpdateExpression") ? child.argument : isNodeOfType(child, "ForInStatement") || isNodeOfType(child, "ForOfStatement") ? child.left : null;
18629
- if (writeTarget && writeTargetContainsIdentifierBinding(writeTarget, identifier)) {
18630
- hasEarlierWrite = true;
18631
- return false;
18632
- }
18633
- });
18634
- return hasEarlierWrite;
18635
- };
18636
- const isProtectedByRelationalLoopGuard = (identifier) => {
18637
- let descendant = identifier;
18638
- let ancestor = identifier.parent;
18639
- while (ancestor) {
18640
- if (isFunctionLike$2(ancestor)) return false;
18641
- if ((isNodeOfType(ancestor, "ForStatement") || isNodeOfType(ancestor, "WhileStatement")) && ancestor.body === descendant && testProvesIdentifierIsNotNaN(ancestor.test, identifier)) return !hasWriteBeforeQuery(ancestor.body, identifier);
18642
- descendant = ancestor;
18643
- ancestor = ancestor.parent;
18644
- }
18645
- return false;
18646
- };
18647
- const isKnownSafeIndexOfQuery = (query) => {
18648
- if (!query) return false;
18649
- const strippedQuery = stripParenExpression(query);
18650
- if (isNodeOfType(strippedQuery, "Literal")) return typeof strippedQuery.value !== "number" || Number.isFinite(strippedQuery.value);
18651
- if (!isNodeOfType(strippedQuery, "Identifier")) return false;
18652
- if (isNativeIterationIndex(strippedQuery)) return true;
18653
- return isProtectedByRelationalLoopGuard(strippedQuery);
18654
- };
18655
- const findSameFileTypeAlias = (reference, typeName) => {
18656
- const program = findProgramRoot(reference);
18657
- if (!program) return null;
18658
- for (const statement of program.body) {
18659
- const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
18660
- if (declaration && isNodeOfType(declaration, "TSTypeAliasDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === typeName) return declaration;
18661
- }
18662
- return null;
18663
- };
18664
- const hasDeclaredMembershipMethod = (members, methodName) => members.some((member) => (isNodeOfType(member, "TSMethodSignature") || isNodeOfType(member, "TSPropertySignature")) && isNodeOfType(member.key, "Identifier") && member.key.name === methodName);
18665
- const isKnownUserlandMembershipReceiver = (receiver, methodName) => {
18666
- if (!isNodeOfType(receiver, "Identifier")) return false;
18667
- const declaredType = getIdentifierDeclaredType(receiver);
18668
- if (!declaredType) return false;
18669
- if (isNodeOfType(declaredType, "TSTypeLiteral")) return hasDeclaredMembershipMethod(declaredType.members ?? [], methodName);
18670
- if (!isNodeOfType(declaredType, "TSTypeReference") || !isNodeOfType(declaredType.typeName, "Identifier")) return false;
18671
- const typeAlias = findSameFileTypeAlias(receiver, declaredType.typeName.name);
18672
- if (typeAlias && isNodeOfType(typeAlias.typeAnnotation, "TSTypeLiteral")) return hasDeclaredMembershipMethod(typeAlias.typeAnnotation.members ?? [], methodName);
18673
- const program = findProgramRoot(receiver);
18674
- if (!program) return false;
18675
- for (const statement of program.body) {
18676
- const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
18677
- if (declaration && isNodeOfType(declaration, "TSInterfaceDeclaration") && isNodeOfType(declaration.id, "Identifier") && declaration.id.name === declaredType.typeName.name) return hasDeclaredMembershipMethod(declaration.body.body, methodName);
18678
- }
18679
- return false;
18680
- };
18681
- const findTypeParameter = (reference, typeName) => {
18682
- let ancestor = reference.parent;
18683
- while (ancestor) {
18684
- if (isFunctionLike$2(ancestor) || isNodeOfType(ancestor, "ClassDeclaration") || isNodeOfType(ancestor, "ClassExpression")) {
18685
- const matchingTypeParameter = ancestor.typeParameters?.params?.find((typeParameter) => isNodeOfType(typeParameter, "TSTypeParameter") && isNodeOfType(typeParameter.name, "Identifier") && typeParameter.name.name === typeName);
18686
- if (matchingTypeParameter && isNodeOfType(matchingTypeParameter, "TSTypeParameter")) return matchingTypeParameter;
18687
- }
18688
- ancestor = ancestor.parent;
18689
- }
18690
- return null;
18691
- };
18692
- const POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES = new Set(["NonNullable", "PropertyKey"]);
18693
- const buildTypeAliasArguments = (typeAlias, typeReference, inheritedArguments) => {
18694
- const typeArguments = new Map(inheritedArguments);
18695
- for (const [index, typeParameter] of (typeAlias.typeParameters?.params ?? []).entries()) {
18696
- if (!isNodeOfType(typeParameter, "TSTypeParameter")) continue;
18697
- if (!isNodeOfType(typeParameter.name, "Identifier")) continue;
18698
- const argument = typeReference.typeArguments?.params?.[index] ?? typeParameter.default;
18699
- if (argument) typeArguments.set(typeParameter.name.name, argument);
18700
- }
18701
- return typeArguments;
18702
- };
18703
- const typeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
18704
- if (!typeNode) return false;
18705
- if (isNodeOfType(typeNode, "TSNumberKeyword") || isNodeOfType(typeNode, "TSAnyKeyword") || isNodeOfType(typeNode, "TSUnknownKeyword")) return true;
18706
- if (isNodeOfType(typeNode, "TSStringKeyword") || isNodeOfType(typeNode, "TSBooleanKeyword") || isNodeOfType(typeNode, "TSBigIntKeyword") || isNodeOfType(typeNode, "TSSymbolKeyword") || isNodeOfType(typeNode, "TSNullKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword") || isNodeOfType(typeNode, "TSObjectKeyword") || isNodeOfType(typeNode, "TSLiteralType") || isNodeOfType(typeNode, "TSFunctionType")) return false;
18707
- if (isNodeOfType(typeNode, "TSTypeLiteral")) return (typeNode.members?.length ?? 0) === 0;
18708
- if (isNodeOfType(typeNode, "TSTypeOperator") && typeNode.operator === "keyof") return false;
18709
- if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => typeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
18710
- if (isNodeOfType(typeNode, "TSOptionalType") || isNodeOfType(typeNode, "TSRestType")) return typeCanHaveSameValueZeroDifference(typeNode.typeAnnotation, reference, activeTypeNodes, typeArguments);
18711
- if (isNodeOfType(typeNode, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
18712
- if (!isNodeOfType(typeNode, "TSTypeReference")) return true;
18713
- if (!isNodeOfType(typeNode.typeName, "Identifier")) return false;
18714
- const substitutedType = typeArguments.get(typeNode.typeName.name);
18715
- if (substitutedType) {
18716
- const remainingArguments = new Map(typeArguments);
18717
- remainingArguments.delete(typeNode.typeName.name);
18718
- return typeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
18719
- }
18720
- const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
18721
- if (typeParameter) {
18722
- if (!typeParameter.constraint || activeTypeNodes.has(typeParameter)) return true;
18723
- activeTypeNodes.add(typeParameter);
18724
- const constraintCanDiffer = typeCanHaveSameValueZeroDifference(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
18725
- activeTypeNodes.delete(typeParameter);
18726
- return constraintCanDiffer;
18727
- }
18728
- const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
18729
- if (!typeAlias) return POSSIBLY_NUMERIC_TYPE_REFERENCE_NAMES.has(typeNode.typeName.name) || /(?:number|numeric)/i.test(typeNode.typeName.name);
18730
- if (activeTypeNodes.has(typeAlias)) return true;
18731
- activeTypeNodes.add(typeAlias);
18732
- const canDiffer = typeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
18733
- activeTypeNodes.delete(typeAlias);
18734
- return canDiffer;
18735
- };
18736
- const arrayTypeCanHaveSameValueZeroDifference = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
18737
- if (!typeNode) return false;
18738
- if (isNodeOfType(typeNode, "TSArrayType")) return typeCanHaveSameValueZeroDifference(typeNode.elementType, reference, activeTypeNodes, typeArguments);
18739
- if (isNodeOfType(typeNode, "TSTupleType")) return typeNode.elementTypes.some((elementType) => {
18740
- if (isNodeOfType(elementType, "TSRestType")) return arrayTypeCanHaveSameValueZeroDifference(elementType.typeAnnotation, reference, activeTypeNodes, typeArguments);
18741
- if (isNodeOfType(elementType, "TSNamedTupleMember")) return typeCanHaveSameValueZeroDifference(elementType.elementType, reference, activeTypeNodes, typeArguments);
18742
- return typeCanHaveSameValueZeroDifference(elementType, reference, activeTypeNodes, typeArguments);
18743
- });
18744
- if (isNodeOfType(typeNode, "TSTypeOperator")) return arrayTypeCanHaveSameValueZeroDifference(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
18745
- if (isNodeOfType(typeNode, "TSUnionType") || isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => arrayTypeCanHaveSameValueZeroDifference(memberType, reference, activeTypeNodes, typeArguments));
18746
- if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
18747
- const substitutedType = typeArguments.get(typeNode.typeName.name);
18748
- if (substitutedType) {
18749
- const remainingArguments = new Map(typeArguments);
18750
- remainingArguments.delete(typeNode.typeName.name);
18751
- return arrayTypeCanHaveSameValueZeroDifference(substitutedType, reference, activeTypeNodes, remainingArguments);
18752
- }
18753
- if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return typeCanHaveSameValueZeroDifference(typeNode.typeArguments?.params?.[0] ?? null, reference, activeTypeNodes, typeArguments);
18754
- const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
18755
- if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
18756
- activeTypeNodes.add(typeAlias);
18757
- const canDiffer = arrayTypeCanHaveSameValueZeroDifference(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
18758
- activeTypeNodes.delete(typeAlias);
18759
- return canDiffer;
18760
- };
18761
- const isKnownArrayType = (typeNode, reference, activeTypeNodes, typeArguments = /* @__PURE__ */ new Map()) => {
18762
- if (!typeNode) return false;
18763
- if (isNodeOfType(typeNode, "TSArrayType") || isNodeOfType(typeNode, "TSTupleType")) return true;
18764
- if (isNodeOfType(typeNode, "TSTypeOperator")) return isKnownArrayType(typeNode.typeAnnotation ?? null, reference, activeTypeNodes, typeArguments);
18765
- if (isNodeOfType(typeNode, "TSUnionType")) {
18766
- const nonNullishTypes = typeNode.types.filter((memberType) => !isNodeOfType(memberType, "TSNullKeyword") && !isNodeOfType(memberType, "TSUndefinedKeyword"));
18767
- return nonNullishTypes.length > 0 && nonNullishTypes.every((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
18768
- }
18769
- if (isNodeOfType(typeNode, "TSIntersectionType")) return typeNode.types.some((memberType) => isKnownArrayType(memberType, reference, activeTypeNodes, typeArguments));
18770
- if (!isNodeOfType(typeNode, "TSTypeReference") || !isNodeOfType(typeNode.typeName, "Identifier")) return false;
18771
- const substitutedType = typeArguments.get(typeNode.typeName.name);
18772
- if (substitutedType) {
18773
- const remainingArguments = new Map(typeArguments);
18774
- remainingArguments.delete(typeNode.typeName.name);
18775
- return isKnownArrayType(substitutedType, reference, activeTypeNodes, remainingArguments);
18776
- }
18777
- if (typeNode.typeName.name === "Array" || typeNode.typeName.name === "ReadonlyArray") return true;
18778
- const typeParameter = findTypeParameter(reference, typeNode.typeName.name);
18779
- if (typeParameter?.constraint) {
18780
- if (activeTypeNodes.has(typeParameter)) return false;
18781
- activeTypeNodes.add(typeParameter);
18782
- const isConstraintArray = isKnownArrayType(typeParameter.constraint, reference, activeTypeNodes, typeArguments);
18783
- activeTypeNodes.delete(typeParameter);
18784
- return isConstraintArray;
18785
- }
18786
- const typeAlias = findSameFileTypeAlias(reference, typeNode.typeName.name);
18787
- if (!typeAlias || activeTypeNodes.has(typeAlias)) return false;
18788
- activeTypeNodes.add(typeAlias);
18789
- const isArray = isKnownArrayType(typeAlias.typeAnnotation, reference, activeTypeNodes, buildTypeAliasArguments(typeAlias, typeNode, typeArguments));
18790
- activeTypeNodes.delete(typeAlias);
18791
- return isArray;
18792
- };
18793
- const isKnownNativeArrayReceiver = (receiver) => {
18794
- if (isNodeOfType(receiver, "ArrayExpression")) return true;
18795
- if (isNodeOfType(receiver, "Identifier") && isKnownArrayType(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set())) return true;
18796
- const initializer = getResolvedInitializer(receiver)?.initializer;
18797
- if (!initializer) return false;
18798
- const strippedInitializer = stripParenExpression(initializer);
18799
- if (isNodeOfType(strippedInitializer, "ArrayExpression")) return true;
18800
- if (isNodeOfType(strippedInitializer, "NewExpression") && isNodeOfType(strippedInitializer.callee, "Identifier") && strippedInitializer.callee.name === "Array" && findVariableInitializer(strippedInitializer.callee, strippedInitializer.callee.name) === null) return true;
18801
- return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
18802
- };
18803
- const isKnownDenseArrayReceiver = (receiver) => {
18804
- const initializer = isNodeOfType(receiver, "Identifier") ? getResolvedInitializer(receiver)?.initializer : receiver;
18805
- if (!initializer) return false;
18806
- const strippedInitializer = stripParenExpression(initializer);
18807
- if (isNodeOfType(strippedInitializer, "ArrayExpression")) return (strippedInitializer.elements ?? []).every((element) => element !== null && !isNodeOfType(element, "SpreadElement"));
18808
- return isNodeOfType(strippedInitializer, "CallExpression") && isNodeOfType(strippedInitializer.callee, "MemberExpression") && isNodeOfType(strippedInitializer.callee.object, "Identifier") && strippedInitializer.callee.object.name === "Array" && isNodeOfType(strippedInitializer.callee.property, "Identifier") && (strippedInitializer.callee.property.name === "from" || strippedInitializer.callee.property.name === "of") && findVariableInitializer(strippedInitializer.callee.object, strippedInitializer.callee.object.name) === null;
18809
- };
18810
- const isKnownUnsafeIndexOfQuery = (query, receiver) => {
18811
- if (!query) return true;
18812
- const strippedQuery = stripParenExpression(query);
18813
- if (isNodeOfType(strippedQuery, "Identifier")) {
18814
- if (strippedQuery.name === "undefined" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return !isKnownDenseArrayReceiver(receiver);
18815
- if (strippedQuery.name === "NaN" && findVariableInitializer(strippedQuery, strippedQuery.name) === null) return true;
18816
- return typeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(strippedQuery), strippedQuery, /* @__PURE__ */ new Set());
18817
- }
18818
- if (isNodeOfType(strippedQuery, "MemberExpression")) return !strippedQuery.computed && isNodeOfType(strippedQuery.object, "Identifier") && strippedQuery.object.name === "Number" && isNodeOfType(strippedQuery.property, "Identifier") && strippedQuery.property.name === "NaN" && findVariableInitializer(strippedQuery.object, strippedQuery.object.name) === null;
18819
- return true;
18820
- };
18821
- const isKnownUnsafeIndexOfReceiver = (receiver) => {
18822
- if (!isNodeOfType(receiver, "Identifier")) return false;
18823
- return arrayTypeCanHaveSameValueZeroDifference(getIdentifierDeclaredType(receiver), receiver, /* @__PURE__ */ new Set());
18824
- };
18825
18356
  const ITERATION_CALLBACK_METHOD_NAMES = new Set([
18826
18357
  "forEach",
18827
18358
  "map",
@@ -18950,22 +18481,16 @@ const jsSetMapLookups = defineRule({
18950
18481
  if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
18951
18482
  const methodName = node.callee.property.name;
18952
18483
  if (methodName !== "includes" && methodName !== "indexOf") return;
18953
- if (methodName === "includes" && !hasSemanticsPreservingIncludesArguments(node)) return;
18954
- if (methodName === "indexOf" && (node.arguments.length !== 1 || isNodeOfType(node.arguments[0], "SpreadElement") || !isIndexOfResultUsedAsMembershipTest(node))) return;
18484
+ if (methodName === "indexOf" && !isIndexOfResultUsedAsMembershipTest(node)) return;
18955
18485
  const rawReceiver = node.callee.object;
18956
18486
  if (!rawReceiver) return;
18957
18487
  const receiver = stripParenExpression(rawReceiver);
18958
- const isKnownNativeArray = isKnownNativeArrayReceiver(receiver);
18959
- if (isKnownUserlandMembershipReceiver(receiver, methodName)) return;
18960
- if (methodName === "includes" && node.arguments.length === 2 && !isKnownNativeArray) return;
18961
- const query = node.arguments[0];
18962
- if (methodName === "indexOf" && !isKnownSafeIndexOfQuery(query) && (isKnownUnsafeIndexOfQuery(query, receiver) || isKnownUnsafeIndexOfReceiver(receiver))) return;
18963
18488
  if (isLikelyStringReceiver(receiver)) return;
18964
18489
  if (isSmallInlineLiteralArray(receiver)) return;
18965
18490
  if (isScreamingSnakeCaseConstantReceiver(receiver)) return;
18966
18491
  if (isSmallFixedListMember(receiver)) return;
18967
- if (isSubstringSearchLiteral(query)) return;
18968
- if (isIndexedArrayElementWithStringArgument(receiver, query)) return;
18492
+ if (isSubstringSearchLiteral(node.arguments?.[0])) return;
18493
+ if (isIndexedArrayElementWithStringArgument(receiver, node.arguments?.[0])) return;
18969
18494
  const resolvedInitializer = getResolvedInitializer(receiver);
18970
18495
  if (resolvedInitializer) {
18971
18496
  if (isLikelyStringReceiver(resolvedInitializer.initializer)) return;
@@ -20174,7 +19699,7 @@ const jsxMaxDepth = defineRule({
20174
19699
  });
20175
19700
  //#endregion
20176
19701
  //#region src/plugin/rules/react-builtins/jsx-no-comment-textnodes.ts
20177
- const MESSAGE$48 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
19702
+ const MESSAGE$47 = "Your users see this comment as text on the page because `//` & `/*` aren't hidden in JSX.";
20178
19703
  const LITERAL_TEXT_TAGS = new Set([
20179
19704
  "code",
20180
19705
  "pre",
@@ -20244,7 +19769,7 @@ const jsxNoCommentTextnodes = defineRule({
20244
19769
  if (isDeliberateStyledCommentToken(node)) return;
20245
19770
  context.report({
20246
19771
  node,
20247
- message: MESSAGE$48
19772
+ message: MESSAGE$47
20248
19773
  });
20249
19774
  } })
20250
19775
  });
@@ -20275,7 +19800,7 @@ const isInsideFunctionScope = (node) => {
20275
19800
  };
20276
19801
  //#endregion
20277
19802
  //#region src/plugin/rules/react-builtins/jsx-no-constructed-context-values.ts
20278
- const MESSAGE$47 = "Every reader of this context redraws on each render because you build its `value` inline.";
19803
+ const MESSAGE$46 = "Every reader of this context redraws on each render because you build its `value` inline.";
20279
19804
  const CONTEXT_MODULES$1 = [
20280
19805
  "react",
20281
19806
  "use-context-selector",
@@ -20373,7 +19898,7 @@ const jsxNoConstructedContextValues = defineRule({
20373
19898
  if (!isConstructedValue(innerExpression)) continue;
20374
19899
  context.report({
20375
19900
  node: attribute,
20376
- message: MESSAGE$47
19901
+ message: MESSAGE$46
20377
19902
  });
20378
19903
  }
20379
19904
  }
@@ -21228,7 +20753,7 @@ const DATA_ARRAY_PROP_SUFFIXES = [
21228
20753
  ];
21229
20754
  //#endregion
21230
20755
  //#region src/plugin/rules/react-builtins/jsx-no-new-array-as-prop.ts
21231
- const MESSAGE$46 = "This child redraws every render because the prop gets a brand new array each time.";
20756
+ const MESSAGE$45 = "This child redraws every render because the prop gets a brand new array each time.";
21232
20757
  const isDataArrayPropName = (propName) => {
21233
20758
  if (DATA_ARRAY_PROP_NAMES.has(propName)) return true;
21234
20759
  for (const suffix of DATA_ARRAY_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -21315,7 +20840,7 @@ const jsxNoNewArrayAsProp = defineRule({
21315
20840
  if (!isArrayProducingExpression(expressionNode) && !followsRenderLocalArrayBinding(expressionNode, node)) return;
21316
20841
  context.report({
21317
20842
  node,
21318
- message: MESSAGE$46
20843
+ message: MESSAGE$45
21319
20844
  });
21320
20845
  }
21321
20846
  };
@@ -21573,7 +21098,7 @@ const SAFE_RECEIVER_NAMES = new Set([
21573
21098
  ]);
21574
21099
  //#endregion
21575
21100
  //#region src/plugin/rules/react-builtins/jsx-no-new-function-as-prop.ts
21576
- const MESSAGE$45 = "This child redraws every render because the prop gets a brand new function each time.";
21101
+ const MESSAGE$44 = "This child redraws every render because the prop gets a brand new function each time.";
21577
21102
  const isAccessorPredicateName = (propName) => {
21578
21103
  for (const prefix of ACCESSOR_PREDICATE_PREFIXES) {
21579
21104
  if (propName.length <= prefix.length) continue;
@@ -21779,7 +21304,7 @@ const jsxNoNewFunctionAsProp = defineRule({
21779
21304
  if (!isFunctionProducingExpression(expressionNode) && !followsRenderLocalFunctionBinding(expressionNode, node)) return;
21780
21305
  context.report({
21781
21306
  node,
21782
- message: MESSAGE$45
21307
+ message: MESSAGE$44
21783
21308
  });
21784
21309
  }
21785
21310
  };
@@ -21999,7 +21524,7 @@ const CONFIG_OBJECT_PROP_SUFFIXES = [
21999
21524
  ];
22000
21525
  //#endregion
22001
21526
  //#region src/plugin/rules/react-builtins/jsx-no-new-object-as-prop.ts
22002
- const MESSAGE$44 = "This child redraws every render because the prop gets a brand new object each time.";
21527
+ const MESSAGE$43 = "This child redraws every render because the prop gets a brand new object each time.";
22003
21528
  const isConfigObjectPropName = (propName) => {
22004
21529
  if (CONFIG_OBJECT_PROP_NAMES.has(propName)) return true;
22005
21530
  for (const suffix of CONFIG_OBJECT_PROP_SUFFIXES) if (propName.length > suffix.length && propName.endsWith(suffix)) return true;
@@ -22088,7 +21613,7 @@ const jsxNoNewObjectAsProp = defineRule({
22088
21613
  if (!isObjectProducingExpression(expressionNode) && !followsRenderLocalObjectBinding(expressionNode, node)) return;
22089
21614
  context.report({
22090
21615
  node,
22091
- message: MESSAGE$44
21616
+ message: MESSAGE$43
22092
21617
  });
22093
21618
  }
22094
21619
  };
@@ -22096,7 +21621,7 @@ const jsxNoNewObjectAsProp = defineRule({
22096
21621
  });
22097
21622
  //#endregion
22098
21623
  //#region src/plugin/rules/react-builtins/jsx-no-script-url.ts
22099
- const MESSAGE$43 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
21624
+ const MESSAGE$42 = "A `javascript:` URL is an XSS hole that runs injected input as code.";
22100
21625
  const JAVASCRIPT_URL_PATTERN = /^[\u0000-\u001F ]*j[\r\n\t]*a[\r\n\t]*v[\r\n\t]*a[\r\n\t]*s[\r\n\t]*c[\r\n\t]*r[\r\n\t]*i[\r\n\t]*p[\r\n\t]*t[\r\n\t]*:/i;
22101
21626
  const resolveSettings$29 = (settings) => {
22102
21627
  const reactDoctor = settings?.["react-doctor"];
@@ -22134,7 +21659,7 @@ const jsxNoScriptUrl = defineRule({
22134
21659
  if (!value || !isNodeOfType(value, "Literal") || typeof value.value !== "string") continue;
22135
21660
  if (JAVASCRIPT_URL_PATTERN.test(value.value)) context.report({
22136
21661
  node: attribute,
22137
- message: MESSAGE$43
21662
+ message: MESSAGE$42
22138
21663
  });
22139
21664
  }
22140
21665
  } };
@@ -22754,7 +22279,7 @@ const jsxPropsNoSpreadMulti = defineRule({
22754
22279
  });
22755
22280
  //#endregion
22756
22281
  //#region src/plugin/rules/react-builtins/jsx-props-no-spreading.ts
22757
- const MESSAGE$42 = "You can't tell what props reach this element when you spread them.";
22282
+ const MESSAGE$41 = "You can't tell what props reach this element when you spread them.";
22758
22283
  const resolveSettings$25 = (settings) => {
22759
22284
  const reactDoctor = settings?.["react-doctor"];
22760
22285
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.jsxPropsNoSpreading ?? {} : {};
@@ -22797,7 +22322,7 @@ const jsxPropsNoSpreading = defineRule({
22797
22322
  }
22798
22323
  context.report({
22799
22324
  node: attribute,
22800
- message: MESSAGE$42
22325
+ message: MESSAGE$41
22801
22326
  });
22802
22327
  didReportInFile = true;
22803
22328
  return;
@@ -23073,7 +22598,7 @@ const labelHasAssociatedControl = defineRule({
23073
22598
  });
23074
22599
  //#endregion
23075
22600
  //#region src/plugin/rules/a11y/lang.ts
23076
- const MESSAGE$41 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
22601
+ const MESSAGE$40 = "Screen readers can't pick the right voice because this `lang` isn't a real language code, so use a valid one like `en` or `en-US`.";
23077
22602
  const COMMON_LANGUAGE_PRIMARY_TAGS = new Set([
23078
22603
  "aa",
23079
22604
  "ab",
@@ -23553,7 +23078,7 @@ const lang = defineRule({
23553
23078
  if (expression.type === "Identifier" && expression.name === "undefined" || expression.type === "Literal" && expression.value === null) {
23554
23079
  context.report({
23555
23080
  node: langAttr,
23556
- message: MESSAGE$41
23081
+ message: MESSAGE$40
23557
23082
  });
23558
23083
  return;
23559
23084
  }
@@ -23562,7 +23087,7 @@ const lang = defineRule({
23562
23087
  if (value === null) return;
23563
23088
  if (!isValidLangTag(value)) context.report({
23564
23089
  node: langAttr,
23565
- message: MESSAGE$41
23090
+ message: MESSAGE$40
23566
23091
  });
23567
23092
  } })
23568
23093
  });
@@ -23613,7 +23138,7 @@ const mdxSsrExecutionRisk = defineRule({
23613
23138
  });
23614
23139
  //#endregion
23615
23140
  //#region src/plugin/rules/a11y/media-has-caption.ts
23616
- const MESSAGE$40 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
23141
+ const MESSAGE$39 = "Deaf and hard-of-hearing users need captions for this media. Add a `<track kind=\"captions\">` inside the `<audio>` or `<video>`.";
23617
23142
  const DEFAULT_AUDIO = ["audio"];
23618
23143
  const DEFAULT_VIDEO = ["video"];
23619
23144
  const DEFAULT_TRACK = ["track"];
@@ -23702,7 +23227,7 @@ const mediaHasCaption = defineRule({
23702
23227
  if (!parent || !isNodeOfType(parent, "JSXElement")) {
23703
23228
  context.report({
23704
23229
  node: node.name,
23705
- message: MESSAGE$40
23230
+ message: MESSAGE$39
23706
23231
  });
23707
23232
  return;
23708
23233
  }
@@ -23721,7 +23246,7 @@ const mediaHasCaption = defineRule({
23721
23246
  return kindValue.value.toLowerCase() === "captions";
23722
23247
  })) context.report({
23723
23248
  node: node.name,
23724
- message: MESSAGE$40
23249
+ message: MESSAGE$39
23725
23250
  });
23726
23251
  } };
23727
23252
  }
@@ -25264,7 +24789,7 @@ const nextjsNoVercelOgImport = defineRule({
25264
24789
  });
25265
24790
  //#endregion
25266
24791
  //#region src/plugin/rules/a11y/no-access-key.ts
25267
- const MESSAGE$39 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
24792
+ const MESSAGE$38 = "Screen reader users can lose their shortcuts because `accessKey` clashes with them, so remove it.";
25268
24793
  const isUndefinedIdentifier = (expression) => isNodeOfType(expression, "Identifier") && expression.name === "undefined";
25269
24794
  const noAccessKey = defineRule({
25270
24795
  id: "no-access-key",
@@ -25283,7 +24808,7 @@ const noAccessKey = defineRule({
25283
24808
  if (isNodeOfType(attributeValue, "Literal") && typeof attributeValue.value === "string") {
25284
24809
  context.report({
25285
24810
  node: accessKey,
25286
- message: MESSAGE$39
24811
+ message: MESSAGE$38
25287
24812
  });
25288
24813
  return;
25289
24814
  }
@@ -25293,7 +24818,7 @@ const noAccessKey = defineRule({
25293
24818
  if (isUndefinedIdentifier(expression)) return;
25294
24819
  context.report({
25295
24820
  node: accessKey,
25296
- message: MESSAGE$39
24821
+ message: MESSAGE$38
25297
24822
  });
25298
24823
  }
25299
24824
  } };
@@ -25598,11 +25123,8 @@ const unwrapUseCallback = (node) => {
25598
25123
  const callee = node.callee;
25599
25124
  return isNodeOfType(callee, "Identifier") && callee.name === "useCallback" || isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && callee.property.name === "useCallback" ? node.arguments?.[0] : node;
25600
25125
  };
25601
- const hasParameterDefinition = (ref) => Boolean(ref.resolved?.defs.some((definition) => definition.type === "Parameter"));
25602
25126
  const resolveToFunction = (ref) => {
25603
- const definition = ref.resolved?.defs[0];
25604
- if (!definition || hasParameterDefinition(ref)) return null;
25605
- const definitionNode = definition.node;
25127
+ const definitionNode = ref.resolved?.defs[0]?.node;
25606
25128
  if (!definitionNode) return null;
25607
25129
  if (isFunctionLike$2(definitionNode)) return definitionNode;
25608
25130
  if (isNodeOfType(definitionNode, "VariableDeclarator")) {
@@ -26012,7 +25534,7 @@ const isCleanupReturnArgument = (analysis, node) => {
26012
25534
  if (isNodeOfType(node, "MemberExpression")) return true;
26013
25535
  if (isNodeOfType(node, "Identifier")) {
26014
25536
  const ref = getRef(analysis, node);
26015
- if (ref && (hasParameterDefinition(ref) || resolveToFunction(ref))) return true;
25537
+ if (ref && resolveToFunction(ref)) return true;
26016
25538
  }
26017
25539
  if (isNodeOfType(node, "ConditionalExpression")) return isCleanupReturnArgument(analysis, node.consequent) || isCleanupReturnArgument(analysis, node.alternate);
26018
25540
  return false;
@@ -26484,7 +26006,7 @@ const resolveWrappedCallable = (analysis, node) => {
26484
26006
  if (isNodeOfType(candidate, "Identifier")) {
26485
26007
  const reference = getRef(analysis, candidate);
26486
26008
  if (!reference) return null;
26487
- if (reference.resolved?.defs.some((definition) => definition.type === "ImportBinding")) return null;
26009
+ if (reference.resolved?.defs.some((definition) => definition.type === "Parameter" || definition.type === "ImportBinding")) return null;
26488
26010
  const resolved = resolveToFunction(reference);
26489
26011
  if (resolved) return resolved;
26490
26012
  const definitionNode = reference.resolved?.defs[0]?.node;
@@ -27148,7 +26670,7 @@ const noAdjustStateOnPropChange = defineRule({
27148
26670
  });
27149
26671
  //#endregion
27150
26672
  //#region src/plugin/rules/a11y/no-aria-hidden-on-focusable.ts
27151
- const MESSAGE$38 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
26673
+ const MESSAGE$37 = "Screen reader users tab to this focusable element but hear nothing because `aria-hidden` skips it, so remove `aria-hidden` or stop it being focusable.";
27152
26674
  const ALWAYS_FOCUSABLE_TAGS = new Set([
27153
26675
  "button",
27154
26676
  "embed",
@@ -27260,7 +26782,7 @@ const noAriaHiddenOnFocusable = defineRule({
27260
26782
  const isImplicitlyFocusable = isNativelyFocusable(tag, node);
27261
26783
  if (isExplicitlyFocusable || isImplicitlyFocusable) context.report({
27262
26784
  node: ariaHidden,
27263
- message: MESSAGE$38
26785
+ message: MESSAGE$37
27264
26786
  });
27265
26787
  } })
27266
26788
  });
@@ -28182,7 +27704,7 @@ const isAllLiteralArrayExpression = (node) => {
28182
27704
  };
28183
27705
  //#endregion
28184
27706
  //#region src/plugin/rules/react-builtins/no-array-index-key.ts
28185
- const MESSAGE$37 = "Your users can see & submit the wrong data when this list reorders.";
27707
+ const MESSAGE$36 = "Your users can see & submit the wrong data when this list reorders.";
28186
27708
  const SECOND_INDEX_METHODS = new Set([
28187
27709
  "every",
28188
27710
  "filter",
@@ -28301,14 +27823,14 @@ const noArrayIndexKey = defineRule({
28301
27823
  if (propName !== "key") continue;
28302
27824
  if (expressionUsesIndex(property.value, indexBinding.name)) context.report({
28303
27825
  node: property,
28304
- message: MESSAGE$37
27826
+ message: MESSAGE$36
28305
27827
  });
28306
27828
  }
28307
27829
  } })
28308
27830
  });
28309
27831
  //#endregion
28310
27832
  //#region src/plugin/rules/state-and-effects/no-async-effect-callback.ts
28311
- const MESSAGE$36 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
27833
+ const MESSAGE$35 = "The `useEffect` callback is `async`, so it returns a Promise instead of a cleanup function. React calls that Promise as cleanup (a no-op) and the effect can race on unmount. Put the async work in an inner function and call it.";
28312
27834
  const noAsyncEffectCallback = defineRule({
28313
27835
  id: "no-async-effect-callback",
28314
27836
  title: "Async effect callback",
@@ -28326,13 +27848,13 @@ const noAsyncEffectCallback = defineRule({
28326
27848
  if (!callback.async) return;
28327
27849
  context.report({
28328
27850
  node: callback,
28329
- message: MESSAGE$36
27851
+ message: MESSAGE$35
28330
27852
  });
28331
27853
  } })
28332
27854
  });
28333
27855
  //#endregion
28334
27856
  //#region src/plugin/rules/a11y/no-autofocus.ts
28335
- const MESSAGE$35 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
27857
+ const MESSAGE$34 = "`autoFocus` moves focus on load, which can disrupt screen reader and keyboard users. Remove it and let users choose where to focus.";
28336
27858
  const resolveSettings$21 = (settings) => {
28337
27859
  const reactDoctor = settings?.["react-doctor"];
28338
27860
  return { ignoreNonDOM: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noAutofocus ?? {} : {}).ignoreNonDOM ?? true };
@@ -28429,7 +27951,7 @@ const noAutofocus = defineRule({
28429
27951
  if (isConditionallyRendered(node)) return;
28430
27952
  context.report({
28431
27953
  node: autoFocusAttribute,
28432
- message: MESSAGE$35
27954
+ message: MESSAGE$34
28433
27955
  });
28434
27956
  } };
28435
27957
  }
@@ -29402,7 +28924,7 @@ const noChainStateUpdates = defineRule({
29402
28924
  });
29403
28925
  //#endregion
29404
28926
  //#region src/plugin/rules/react-builtins/no-children-prop.ts
29405
- const MESSAGE$34 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
28927
+ const MESSAGE$33 = "A `children` prop can override or hide nested children, so the component may render different content than the JSX shows.";
29406
28928
  const noChildrenProp = defineRule({
29407
28929
  id: "no-children-prop",
29408
28930
  title: "Children passed as a prop",
@@ -29414,7 +28936,7 @@ const noChildrenProp = defineRule({
29414
28936
  if (node.name.name !== "children") return;
29415
28937
  context.report({
29416
28938
  node: node.name,
29417
- message: MESSAGE$34
28939
+ message: MESSAGE$33
29418
28940
  });
29419
28941
  },
29420
28942
  CallExpression(node) {
@@ -29427,7 +28949,7 @@ const noChildrenProp = defineRule({
29427
28949
  const propertyKey = property.key;
29428
28950
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "children" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "children") context.report({
29429
28951
  node: propertyKey,
29430
- message: MESSAGE$34
28952
+ message: MESSAGE$33
29431
28953
  });
29432
28954
  }
29433
28955
  }
@@ -29435,7 +28957,7 @@ const noChildrenProp = defineRule({
29435
28957
  });
29436
28958
  //#endregion
29437
28959
  //#region src/plugin/rules/react-builtins/no-clone-element.ts
29438
- const MESSAGE$33 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
28960
+ const MESSAGE$32 = "`React.cloneElement` couples the parent to the child's prop shape, so child prop changes can silently break injected behavior.";
29439
28961
  const noCloneElement = defineRule({
29440
28962
  id: "no-clone-element",
29441
28963
  title: "cloneElement makes child props fragile",
@@ -29448,7 +28970,7 @@ const noCloneElement = defineRule({
29448
28970
  if (isNodeOfType(callee, "Identifier") && callee.name === "cloneElement") {
29449
28971
  if (isImportedFromModule(node, "cloneElement", "react")) context.report({
29450
28972
  node: callee,
29451
- message: MESSAGE$33
28973
+ message: MESSAGE$32
29452
28974
  });
29453
28975
  return;
29454
28976
  }
@@ -29461,7 +28983,7 @@ const noCloneElement = defineRule({
29461
28983
  if (!isImportedFromModule(node, callee.object.name, "react")) return;
29462
28984
  context.report({
29463
28985
  node: callee,
29464
- message: MESSAGE$33
28986
+ message: MESSAGE$32
29465
28987
  });
29466
28988
  }
29467
28989
  } })
@@ -29481,7 +29003,7 @@ const getCallMethodName = (callee) => {
29481
29003
  };
29482
29004
  //#endregion
29483
29005
  //#region src/plugin/rules/state-and-effects/no-create-context-in-render.ts
29484
- const MESSAGE$32 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
29006
+ const MESSAGE$31 = "createContext() builds a new context every render, so every consumer gets cut off & resets.";
29485
29007
  const CONTEXT_MODULES = [
29486
29008
  "react",
29487
29009
  "use-context-selector",
@@ -29529,13 +29051,13 @@ const noCreateContextInRender = defineRule({
29529
29051
  if (!componentOrHookName) return;
29530
29052
  context.report({
29531
29053
  node,
29532
- message: `${MESSAGE$32} (called inside "${componentOrHookName}")`
29054
+ message: `${MESSAGE$31} (called inside "${componentOrHookName}")`
29533
29055
  });
29534
29056
  } })
29535
29057
  });
29536
29058
  //#endregion
29537
29059
  //#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
29538
- const MESSAGE$31 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
29060
+ const MESSAGE$30 = "`createRef()` in a function component allocates a brand-new ref on every render, so it never holds a value between renders. Use the `useRef()` hook instead.";
29539
29061
  const isUseMemoCallbackArgument = (functionNode) => {
29540
29062
  const parent = functionNode.parent;
29541
29063
  if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
@@ -29565,7 +29087,7 @@ const noCreateRefInFunctionComponent = defineRule({
29565
29087
  if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
29566
29088
  context.report({
29567
29089
  node,
29568
- message: MESSAGE$31
29090
+ message: MESSAGE$30
29569
29091
  });
29570
29092
  } })
29571
29093
  });
@@ -29705,7 +29227,7 @@ const noCreateStoreInRender = defineRule({
29705
29227
  });
29706
29228
  //#endregion
29707
29229
  //#region src/plugin/rules/react-builtins/no-danger.ts
29708
- const MESSAGE$30 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
29230
+ const MESSAGE$29 = "`dangerouslySetInnerHTML` is an XSS hole that runs attacker-controlled HTML in your users' browsers.";
29709
29231
  const noDanger = defineRule({
29710
29232
  id: "no-danger",
29711
29233
  title: "Raw HTML injection can run unsafe markup",
@@ -29719,7 +29241,7 @@ const noDanger = defineRule({
29719
29241
  if (!propAttribute) return;
29720
29242
  context.report({
29721
29243
  node: propAttribute.name,
29722
- message: MESSAGE$30
29244
+ message: MESSAGE$29
29723
29245
  });
29724
29246
  },
29725
29247
  CallExpression(node) {
@@ -29731,7 +29253,7 @@ const noDanger = defineRule({
29731
29253
  const propertyKey = property.key;
29732
29254
  if (isNodeOfType(propertyKey, "Identifier") && propertyKey.name === "dangerouslySetInnerHTML" || isNodeOfType(propertyKey, "Literal") && propertyKey.value === "dangerouslySetInnerHTML") context.report({
29733
29255
  node: propertyKey,
29734
- message: MESSAGE$30
29256
+ message: MESSAGE$29
29735
29257
  });
29736
29258
  }
29737
29259
  }
@@ -29754,7 +29276,7 @@ const isMeaningfulJsxChild = (child) => {
29754
29276
  };
29755
29277
  //#endregion
29756
29278
  //#region src/plugin/rules/react-builtins/no-danger-with-children.ts
29757
- const MESSAGE$29 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
29279
+ const MESSAGE$28 = "React throws an error when you set both children & `dangerouslySetInnerHTML`.";
29758
29280
  const mergePropsShape = (target, source) => {
29759
29281
  target.hasDangerously ||= source.hasDangerously;
29760
29282
  target.hasChildren ||= source.hasChildren;
@@ -29829,7 +29351,7 @@ const noDangerWithChildren = defineRule({
29829
29351
  if (!hasChildrenProp && !hasNestedChildren) return;
29830
29352
  if (hasJsxPropIgnoreCase(opening.attributes, "dangerouslySetInnerHTML") || spreadPropsShape.hasDangerously) context.report({
29831
29353
  node: opening,
29832
- message: MESSAGE$29
29354
+ message: MESSAGE$28
29833
29355
  });
29834
29356
  },
29835
29357
  CallExpression(node) {
@@ -29842,7 +29364,7 @@ const noDangerWithChildren = defineRule({
29842
29364
  const positionalChildren = node.arguments.slice(2);
29843
29365
  if (positionalChildren.length > 1 || positionalChildren.some((argument) => !isNullishExpression(argument)) || propsShape.hasChildren) context.report({
29844
29366
  node,
29845
- message: MESSAGE$29
29367
+ message: MESSAGE$28
29846
29368
  });
29847
29369
  }
29848
29370
  })
@@ -30807,7 +30329,7 @@ const isSetStateCallInLifecycle = (setStateCall, lifecycleNames, options = {}) =
30807
30329
  //#endregion
30808
30330
  //#region src/plugin/rules/react-builtins/no-did-mount-set-state.ts
30809
30331
  const LIFECYCLE_NAMES$2 = new Set(["componentDidMount"]);
30810
- const MESSAGE$28 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
30332
+ const MESSAGE$27 = "Your users see an extra render right after mount when you call `setState` in `componentDidMount`.";
30811
30333
  const getNodeStart = (node) => "start" in node && typeof node.start === "number" ? node.start : -1;
30812
30334
  const getEnclosingLifecycleFunction = (setStateCall) => {
30813
30335
  let ancestor = setStateCall.parent;
@@ -30935,7 +30457,7 @@ const noDidMountSetState = defineRule({
30935
30457
  }
30936
30458
  context.report({
30937
30459
  node: node.callee,
30938
- message: MESSAGE$28
30460
+ message: MESSAGE$27
30939
30461
  });
30940
30462
  } };
30941
30463
  }
@@ -30943,7 +30465,7 @@ const noDidMountSetState = defineRule({
30943
30465
  //#endregion
30944
30466
  //#region src/plugin/rules/react-builtins/no-did-update-set-state.ts
30945
30467
  const LIFECYCLE_NAMES$1 = new Set(["componentDidUpdate"]);
30946
- const MESSAGE$27 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
30468
+ const MESSAGE$26 = "Calling setState in componentDidUpdate can trigger another update immediately, loop forever, and freeze the component.";
30947
30469
  const EQUALITY_OPERATORS = new Set([
30948
30470
  "==",
30949
30471
  "===",
@@ -31117,7 +30639,7 @@ const noDidUpdateSetState = defineRule({
31117
30639
  if (isInsideDiffGuard(node)) return;
31118
30640
  context.report({
31119
30641
  node: node.callee,
31120
- message: MESSAGE$27
30642
+ message: MESSAGE$26
31121
30643
  });
31122
30644
  } };
31123
30645
  }
@@ -31140,7 +30662,7 @@ const isStateMemberExpression = (node) => {
31140
30662
  };
31141
30663
  //#endregion
31142
30664
  //#region src/plugin/rules/react-builtins/no-direct-mutation-state.ts
31143
- const MESSAGE$26 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
30665
+ const MESSAGE$25 = "Mutating `this.state` by hand never triggers a redraw on its own & a later setState can overwrite it, so use `this.setState` instead.";
31144
30666
  const shouldIgnoreMutation = (node) => {
31145
30667
  let isConstructor = false;
31146
30668
  let isInsideCallExpression = false;
@@ -31162,7 +30684,7 @@ const reportIfStateMutation = (context, reportNode, target) => {
31162
30684
  if (shouldIgnoreMutation(reportNode)) return;
31163
30685
  context.report({
31164
30686
  node: reportNode,
31165
- message: MESSAGE$26
30687
+ message: MESSAGE$25
31166
30688
  });
31167
30689
  };
31168
30690
  const noDirectMutationState = defineRule({
@@ -31513,7 +31035,7 @@ const noDocumentStartViewTransition = defineRule({
31513
31035
  });
31514
31036
  //#endregion
31515
31037
  //#region src/plugin/rules/js-performance/no-document-write.ts
31516
- const MESSAGE$25 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
31038
+ const MESSAGE$24 = "`document.write()` blocks parsing, is ignored (or wipes the page) after load, and is flagged by browsers as a performance anti-pattern. Build DOM nodes or set `innerHTML`/`textContent` on a target element instead.";
31517
31039
  const WRITE_METHODS = new Set(["write", "writeln"]);
31518
31040
  const isGlobalDocumentReference = (node, context) => node.name === "document" && context.scopes.isGlobalReference(node);
31519
31041
  const noDocumentWrite = defineRule({
@@ -31530,7 +31052,7 @@ const noDocumentWrite = defineRule({
31530
31052
  if (methodName === null || !WRITE_METHODS.has(methodName)) return;
31531
31053
  context.report({
31532
31054
  node,
31533
- message: MESSAGE$25
31055
+ message: MESSAGE$24
31534
31056
  });
31535
31057
  } })
31536
31058
  });
@@ -33748,7 +33270,7 @@ const ALLOWED_NAMESPACES = new Set([
33748
33270
  "ReactDOM",
33749
33271
  "ReactDom"
33750
33272
  ]);
33751
- const MESSAGE$24 = "`findDOMNode` crashes your app in React 19 because it was removed.";
33273
+ const MESSAGE$23 = "`findDOMNode` crashes your app in React 19 because it was removed.";
33752
33274
  const noFindDomNode = defineRule({
33753
33275
  id: "no-find-dom-node",
33754
33276
  title: "findDOMNode breaks component encapsulation",
@@ -33759,7 +33281,7 @@ const noFindDomNode = defineRule({
33759
33281
  if (isNodeOfType(callee, "Identifier") && callee.name === "findDOMNode") {
33760
33282
  if (isImportedFromModule(node, callee.name, "react-dom")) context.report({
33761
33283
  node: callee,
33762
- message: MESSAGE$24
33284
+ message: MESSAGE$23
33763
33285
  });
33764
33286
  return;
33765
33287
  }
@@ -33770,7 +33292,7 @@ const noFindDomNode = defineRule({
33770
33292
  if (callee.property.name !== "findDOMNode") return;
33771
33293
  context.report({
33772
33294
  node: callee.property,
33773
- message: MESSAGE$24
33295
+ message: MESSAGE$23
33774
33296
  });
33775
33297
  }
33776
33298
  } })
@@ -33979,6 +33501,13 @@ const isPublishedLibraryPackage = (filename) => {
33979
33501
  return manifest.private !== true && typeof manifest.peerDependencies === "object" && manifest.peerDependencies !== null && "react" in manifest.peerDependencies;
33980
33502
  };
33981
33503
  //#endregion
33504
+ //#region src/plugin/utils/is-type-only-import.ts
33505
+ const isEverySpecifierInlineType = (specifiers, specifierType, kindField) => {
33506
+ if (!specifiers || specifiers.length === 0) return false;
33507
+ return specifiers.every((specifier) => isNodeOfType(specifier, specifierType) && specifier[kindField] === "type");
33508
+ };
33509
+ const isTypeOnlyImport = (node) => node.importKind === "type" || isEverySpecifierInlineType(node.specifiers, "ImportSpecifier", "importKind");
33510
+ //#endregion
33982
33511
  //#region src/plugin/rules/bundle-size/no-full-lodash-import.ts
33983
33512
  const isLibraryDevPage = (filename) => {
33984
33513
  if (!filename) return false;
@@ -34677,7 +34206,7 @@ const noHydrationBranchOnBrowserGlobal = defineRule({
34677
34206
  });
34678
34207
  //#endregion
34679
34208
  //#region src/plugin/rules/performance/no-img-lazy-with-high-fetchpriority.ts
34680
- const MESSAGE$23 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
34209
+ const MESSAGE$22 = "`<img loading=\"lazy\">` defers the request while `fetchPriority=\"high\"` asks the browser to rush it, so the two directives contradict each other. Drop one: keep `fetchPriority=\"high\"` (and eager loading) for an LCP image, or `loading=\"lazy\"` for a below-the-fold one.";
34681
34210
  const noImgLazyWithHighFetchpriority = defineRule({
34682
34211
  id: "no-img-lazy-with-high-fetchpriority",
34683
34212
  title: "Lazy image with high fetchPriority",
@@ -34691,7 +34220,7 @@ const noImgLazyWithHighFetchpriority = defineRule({
34691
34220
  if (!fetchPriorityAttribute || getJsxPropStringValue(fetchPriorityAttribute)?.toLowerCase() !== "high") return;
34692
34221
  context.report({
34693
34222
  node: node.name,
34694
- message: MESSAGE$23
34223
+ message: MESSAGE$22
34695
34224
  });
34696
34225
  } })
34697
34226
  });
@@ -34880,7 +34409,7 @@ const noImpureStateUpdater = defineRule({
34880
34409
  });
34881
34410
  //#endregion
34882
34411
  //#region src/plugin/rules/correctness/no-indeterminate-attribute.ts
34883
- const MESSAGE$22 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
34412
+ const MESSAGE$21 = "The `indeterminate` HTML attribute does not set a checkbox's visual state. Assign the `HTMLInputElement.indeterminate` DOM property instead.";
34884
34413
  const REACT_USE_REF_OPTIONS = {
34885
34414
  allowGlobalReactNamespace: false,
34886
34415
  allowUnboundBareCalls: false
@@ -34981,7 +34510,7 @@ const noIndeterminateAttribute = defineRule({
34981
34510
  if (!inputTypeValues || !inputTypeValues.every((inputTypeValue) => inputTypeValue.toLowerCase() === "checkbox")) return;
34982
34511
  context.report({
34983
34512
  node: indeterminateAttribute,
34984
- message: MESSAGE$22
34513
+ message: MESSAGE$21
34985
34514
  });
34986
34515
  },
34987
34516
  CallExpression(node) {
@@ -34990,7 +34519,7 @@ const noIndeterminateAttribute = defineRule({
34990
34519
  if (!isProvenHtmlInputElement(receiver, context.scopes)) return;
34991
34520
  context.report({
34992
34521
  node,
34993
- message: MESSAGE$22
34522
+ message: MESSAGE$21
34994
34523
  });
34995
34524
  }
34996
34525
  };
@@ -35278,7 +34807,7 @@ const noIsMounted = defineRule({
35278
34807
  });
35279
34808
  //#endregion
35280
34809
  //#region src/plugin/rules/js-performance/no-json-parse-stringify-clone.ts
35281
- const MESSAGE$21 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
34810
+ const MESSAGE$20 = "`JSON.parse(JSON.stringify(x))` deep-clones by re-serializing: it is slow on large objects and silently drops `undefined`, functions, `Date`/`Map`/`Set`, and cyclic references. Use `structuredClone(x)`.";
35282
34811
  const isJsonMethodCall = (node, method) => {
35283
34812
  if (!isNodeOfType(node, "CallExpression")) return false;
35284
34813
  const callee = node.callee;
@@ -35342,13 +34871,13 @@ const noJsonParseStringifyClone = defineRule({
35342
34871
  if (isCatchParameterRoundTrip(firstArgument)) return;
35343
34872
  context.report({
35344
34873
  node,
35345
- message: MESSAGE$21
34874
+ message: MESSAGE$20
35346
34875
  });
35347
34876
  } })
35348
34877
  });
35349
34878
  //#endregion
35350
34879
  //#region src/plugin/rules/correctness/no-jsx-element-type.ts
35351
- const MESSAGE$20 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
34880
+ const MESSAGE$19 = "`JSX.Element` is too narrow: it excludes `null`, strings, numbers, and fragments that components commonly return. Use `React.ReactNode` instead.";
35352
34881
  const isJsxElementTypeReference = (node) => {
35353
34882
  if (!isNodeOfType(node, "TSTypeReference")) return false;
35354
34883
  const typeName = node.typeName;
@@ -35402,7 +34931,7 @@ const noJsxElementType = defineRule({
35402
34931
  if (isJsxImported) return;
35403
34932
  for (const typeAnnotation of flaggedAnnotations) context.report({
35404
34933
  node: typeAnnotation,
35405
- message: MESSAGE$20
34934
+ message: MESSAGE$19
35406
34935
  });
35407
34936
  }
35408
34937
  };
@@ -35634,221 +35163,6 @@ const noLegacyClassLifecycles = defineRule({
35634
35163
  }
35635
35164
  });
35636
35165
  //#endregion
35637
- //#region src/plugin/utils/is-proven-react-class-component.ts
35638
- const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
35639
- const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
35640
- const expression = stripParenExpression(node);
35641
- if (isNodeOfType(expression, "MemberExpression")) {
35642
- const propertyName = getStaticPropertyName(expression);
35643
- const receiver = stripParenExpression(expression.object);
35644
- return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
35645
- }
35646
- if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
35647
- if (!isNodeOfType(expression, "Identifier")) return false;
35648
- const symbol = scopes.symbolFor(expression);
35649
- if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
35650
- visitedSymbolIds.add(symbol.id);
35651
- if (isImportedFromReact(symbol)) {
35652
- const importedName = getImportedName(symbol.declarationNode);
35653
- return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
35654
- }
35655
- if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
35656
- return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
35657
- };
35658
- const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
35659
- if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
35660
- visitedClassNodes.add(classNode);
35661
- return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
35662
- };
35663
- //#endregion
35664
- //#region src/plugin/utils/function-contains-proven-react-hook-call.ts
35665
- const functionContainsProvenReactHookCall = (functionNode, scopes) => {
35666
- if (!isFunctionLike$2(functionNode)) return false;
35667
- let containsReactHookCall = false;
35668
- walkAst(functionNode.body, (node) => {
35669
- if (containsReactHookCall) return false;
35670
- if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
35671
- if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
35672
- containsReactHookCall = true;
35673
- return false;
35674
- }
35675
- });
35676
- return containsReactHookCall;
35677
- };
35678
- //#endregion
35679
- //#region src/plugin/utils/function-returns-props-children.ts
35680
- const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
35681
- if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
35682
- const firstParameter = stripParenExpression(functionNode.params[0]);
35683
- const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
35684
- const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
35685
- const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
35686
- if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
35687
- for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
35688
- const propertyValue = stripParenExpression(property.value);
35689
- const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
35690
- if (!isNodeOfType(childrenBinding, "Identifier")) continue;
35691
- const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
35692
- if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
35693
- }
35694
- }
35695
- return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
35696
- const candidate = stripParenExpression(expression);
35697
- if (isNodeOfType(candidate, "Identifier")) {
35698
- const symbol = scopes.symbolFor(candidate);
35699
- return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
35700
- }
35701
- if (!isNodeOfType(candidate, "MemberExpression")) return false;
35702
- if (getStaticPropertyName(candidate) !== "children") return false;
35703
- const receiver = stripParenExpression(candidate.object);
35704
- if (!isNodeOfType(receiver, "Identifier")) return false;
35705
- const receiverSymbol = scopes.symbolFor(receiver);
35706
- if (!receiverSymbol || !propsParameterSymbol) return false;
35707
- return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
35708
- }, controlFlow);
35709
- };
35710
- //#endregion
35711
- //#region src/plugin/utils/function-returns-only-null.ts
35712
- const isNullExpression = (expression) => {
35713
- const candidate = stripParenExpression(expression);
35714
- return isNodeOfType(candidate, "Literal") && candidate.value === null;
35715
- };
35716
- const functionReturnsOnlyNull = (functionNode) => {
35717
- if (!isFunctionLike$2(functionNode)) return false;
35718
- if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
35719
- const returnStatements = collectFunctionReturnStatements(functionNode);
35720
- return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
35721
- };
35722
- //#endregion
35723
- //#region src/plugin/utils/is-proven-styled-component-expression.ts
35724
- const findFactoryRoot = (node) => {
35725
- const candidate = stripParenExpression(node);
35726
- if (isNodeOfType(candidate, "Identifier")) return candidate;
35727
- if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
35728
- if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
35729
- return null;
35730
- };
35731
- const findFactoryPropertyName = (node) => {
35732
- const candidate = stripParenExpression(node);
35733
- if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
35734
- if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
35735
- return null;
35736
- };
35737
- const isProvenStyledComponentExpression = (expression, scopes) => {
35738
- const candidate = stripParenExpression(expression);
35739
- if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
35740
- const factoryRoot = findFactoryRoot(candidate.tag);
35741
- if (!factoryRoot) return false;
35742
- const factoryPropertyName = findFactoryPropertyName(candidate.tag);
35743
- if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
35744
- const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
35745
- if (!symbol || symbol.kind !== "import") return false;
35746
- if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
35747
- const importDeclaration = symbol.declarationNode.parent;
35748
- return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
35749
- };
35750
- //#endregion
35751
- //#region src/plugin/utils/is-proven-react-component-symbol.ts
35752
- const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
35753
- const LEGACY_REACT_COMPONENT_FACTORY_NAMES = new Set(["createClass", "createReactClass"]);
35754
- const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
35755
- const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
35756
- const candidate = stripParenExpression(expression);
35757
- if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
35758
- if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
35759
- if (isProvenStyledComponentExpression(candidate, scopes)) return true;
35760
- if (isNodeOfType(candidate, "Identifier")) {
35761
- const symbol = scopes.symbolFor(candidate);
35762
- if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
35763
- visitedSymbolIds.add(symbol.id);
35764
- if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
35765
- if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
35766
- return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
35767
- }
35768
- if (!isNodeOfType(candidate, "CallExpression")) return false;
35769
- if (!hasStableCallTarget(candidate, scopes)) return false;
35770
- const factoryCallee = stripParenExpression(candidate.callee);
35771
- if (isNodeOfType(factoryCallee, "Identifier") && isDefaultImportFromModule(factoryCallee, factoryCallee.name, "create-react-class") || isReactApiCall(candidate, LEGACY_REACT_COMPONENT_FACTORY_NAMES, scopes)) return true;
35772
- if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
35773
- const wrappedComponent = candidate.arguments[0];
35774
- return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
35775
- }
35776
- if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
35777
- const factory = candidate.arguments[0];
35778
- if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
35779
- const unwrappedFactory = stripParenExpression(factory);
35780
- if (!isInlineFunctionExpression(unwrappedFactory)) return false;
35781
- if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
35782
- const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
35783
- const returnedExpression = returnStatements[0]?.argument;
35784
- return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
35785
- };
35786
- const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
35787
- const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
35788
- for (const candidateSymbol of candidateSymbols) {
35789
- if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
35790
- if (isComponentDeclaration(candidateSymbol.declarationNode)) {
35791
- if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
35792
- continue;
35793
- }
35794
- const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
35795
- if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
35796
- if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
35797
- continue;
35798
- }
35799
- if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
35800
- if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
35801
- continue;
35802
- }
35803
- if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
35804
- }
35805
- return false;
35806
- };
35807
- //#endregion
35808
- //#region src/plugin/utils/symbol-has-react-component-type-annotation.ts
35809
- const REACT_COMPONENT_TYPE_NAMES = new Set([
35810
- "ComponentClass",
35811
- "ComponentType",
35812
- "FC",
35813
- "FunctionComponent"
35814
- ]);
35815
- const findVisibleSymbol = (identifier, scopes) => {
35816
- if (!isNodeOfType(identifier, "Identifier")) return null;
35817
- let scope = scopes.scopeFor(identifier);
35818
- while (true) {
35819
- const symbol = scope.symbolsByName.get(identifier.name);
35820
- if (symbol) return symbol;
35821
- if (!scope.parent) return null;
35822
- scope = scope.parent;
35823
- }
35824
- };
35825
- const isReactNamespaceType = (identifier, scopes) => {
35826
- const symbol = findVisibleSymbol(identifier, scopes);
35827
- return Boolean(symbol && isImportedFromReact(symbol) && (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier")));
35828
- };
35829
- const isReactComponentType = (typeNode, scopes, visitedSymbolIds) => {
35830
- if (!typeNode) return false;
35831
- if (isNodeOfType(typeNode, "TSTypeAnnotation")) return isReactComponentType(typeNode.typeAnnotation, scopes, visitedSymbolIds);
35832
- if (isNodeOfType(typeNode, "TSIntersectionType")) return (typeNode.types ?? []).some((member) => isReactComponentType(member, scopes, visitedSymbolIds));
35833
- if (!isNodeOfType(typeNode, "TSTypeReference")) return false;
35834
- const typeName = typeNode.typeName;
35835
- if (isNodeOfType(typeName, "TSQualifiedName")) return isNodeOfType(typeName.left, "Identifier") && isNodeOfType(typeName.right, "Identifier") && REACT_COMPONENT_TYPE_NAMES.has(typeName.right.name) && isReactNamespaceType(typeName.left, scopes);
35836
- if (!isNodeOfType(typeName, "Identifier")) return false;
35837
- const typeSymbol = findVisibleSymbol(typeName, scopes);
35838
- if (!typeSymbol || visitedSymbolIds.has(typeSymbol.id)) return false;
35839
- if (isImportedFromReact(typeSymbol)) {
35840
- const importedName = getImportedName(typeSymbol.declarationNode);
35841
- return Boolean(importedName && REACT_COMPONENT_TYPE_NAMES.has(importedName));
35842
- }
35843
- if (!isNodeOfType(typeSymbol.declarationNode, "TSTypeAliasDeclaration")) return false;
35844
- visitedSymbolIds.add(typeSymbol.id);
35845
- return isReactComponentType(typeSymbol.declarationNode.typeAnnotation, scopes, visitedSymbolIds);
35846
- };
35847
- const symbolHasReactComponentTypeAnnotation = (symbol, scopes) => {
35848
- const bindingIdentifier = symbol.bindingIdentifier;
35849
- return isNodeOfType(bindingIdentifier, "Identifier") && isReactComponentType(bindingIdentifier.typeAnnotation, scopes, /* @__PURE__ */ new Set());
35850
- };
35851
- //#endregion
35852
35166
  //#region src/plugin/rules/architecture/no-legacy-context-api.ts
35853
35167
  const LEGACY_CONTEXT_NAMES = new Set([
35854
35168
  "childContextTypes",
@@ -35859,6 +35173,15 @@ const buildLegacyContextMessage = (memberName) => {
35859
35173
  if (memberName === "childContextTypes" || memberName === "getChildContext") return `${memberName} uses the old context API that React 19 removes, so your provider stops passing data. Switch to \`createContext\` with \`<MyContext.Provider value={...}>\` & read it with \`useContext()\`, moving every consumer together.`;
35860
35174
  return "contextTypes uses the old context API that React 19 removes, so your component stops receiving context. Use `static contextType = MyContext` or `useContext()` in a function component, & update the provider too.";
35861
35175
  };
35176
+ const isInsideClassBody = (node) => {
35177
+ let current = node.parent;
35178
+ while (current) {
35179
+ if (isNodeOfType(current, "ClassBody")) return true;
35180
+ if (isFunctionLike$2(current)) return false;
35181
+ current = current.parent;
35182
+ }
35183
+ return false;
35184
+ };
35862
35185
  const noLegacyContextApi = defineRule({
35863
35186
  id: "no-legacy-context-api",
35864
35187
  title: "Legacy context API",
@@ -35873,7 +35196,6 @@ const noLegacyContextApi = defineRule({
35873
35196
  if (!isNodeOfType(memberNode, "MethodDefinition") && !isNodeOfType(memberNode, "PropertyDefinition")) return;
35874
35197
  if (!isNodeOfType(memberNode.key, "Identifier")) return;
35875
35198
  if (!LEGACY_CONTEXT_NAMES.has(memberNode.key.name)) return;
35876
- if (memberNode.key.name === "getChildContext" ? memberNode.static : !memberNode.static) return;
35877
35199
  context.report({
35878
35200
  node: memberNode.key,
35879
35201
  message: buildLegacyContextMessage(memberNode.key.name)
@@ -35881,8 +35203,6 @@ const noLegacyContextApi = defineRule({
35881
35203
  };
35882
35204
  return {
35883
35205
  ClassBody(node) {
35884
- const classNode = node.parent;
35885
- if (!classNode || !isProvenReactClassComponent(classNode, context.scopes)) return;
35886
35206
  for (const member of node.body ?? []) checkMember(member);
35887
35207
  },
35888
35208
  AssignmentExpression(node) {
@@ -35892,11 +35212,9 @@ const noLegacyContextApi = defineRule({
35892
35212
  if (left.computed) return;
35893
35213
  if (!isNodeOfType(left.property, "Identifier")) return;
35894
35214
  if (!LEGACY_CONTEXT_NAMES.has(left.property.name)) return;
35895
- if (left.property.name === "getChildContext") return;
35896
- const component = stripParenExpression(left.object);
35897
- if (!isNodeOfType(component, "Identifier")) return;
35898
- const symbol = context.scopes.symbolFor(component);
35899
- if (!symbol || !isProvenReactComponentSymbol(symbol, context.scopes, context.cfg, component) && (hasSymbolWriteBefore(symbol, component, context.scopes) || !symbolHasReactComponentTypeAnnotation(symbol, context.scopes))) return;
35215
+ if (!isNodeOfType(left.object, "Identifier")) return;
35216
+ if (!isUppercaseName(left.object.name)) return;
35217
+ if (isInsideClassBody(node)) return;
35900
35218
  context.report({
35901
35219
  node: left,
35902
35220
  message: buildLegacyContextMessage(left.property.name)
@@ -36718,7 +36036,7 @@ const noMoment = defineRule({
36718
36036
  });
36719
36037
  //#endregion
36720
36038
  //#region src/plugin/rules/react-builtins/no-multi-comp.ts
36721
- const MESSAGE$19 = "This file declares several components, so each component is harder to find, test, and change.";
36039
+ const MESSAGE$18 = "This file declares several components, so each component is harder to find, test, and change.";
36722
36040
  const resolveSettings$16 = (settings) => {
36723
36041
  const reactDoctor = settings?.["react-doctor"];
36724
36042
  return { ignoreStateless: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noMultiComp ?? {} : {}).ignoreStateless ?? false };
@@ -37077,7 +36395,7 @@ const noMultiComp = defineRule({
37077
36395
  if (isSmallFeatureModule || isLargeFeatureModule || isVeryLargeFeatureModule) return;
37078
36396
  for (const component of flagged.slice(1)) context.report({
37079
36397
  node: component.reportNode,
37080
- message: MESSAGE$19
36398
+ message: MESSAGE$18
37081
36399
  });
37082
36400
  } };
37083
36401
  }
@@ -37290,7 +36608,7 @@ const resolveReducerFunction = (node, currentFilename) => {
37290
36608
  };
37291
36609
  //#endregion
37292
36610
  //#region src/plugin/rules/state-and-effects/no-mutating-reducer-state.ts
37293
- const MESSAGE$18 = "This reducer changes state in place, so your update is silently skipped.";
36611
+ const MESSAGE$17 = "This reducer changes state in place, so your update is silently skipped.";
37294
36612
  const SAME_REFERENCE_ARRAY_RETURN_METHODS = new Set([
37295
36613
  "copyWithin",
37296
36614
  "fill",
@@ -37499,7 +36817,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
37499
36817
  reportedNodes.add(options.crossFileConsumerCallSite);
37500
36818
  context.report({
37501
36819
  node: options.crossFileConsumerCallSite,
37502
- message: `${MESSAGE$18} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
36820
+ message: `${MESSAGE$17} (mutation in imported reducer at \`${options.crossFileSourceDisplay}\`)`
37503
36821
  });
37504
36822
  return;
37505
36823
  }
@@ -37508,7 +36826,7 @@ const analyzeReactUseReducerFunctionForStateMutation = (context, functionNode, r
37508
36826
  reportedNodes.add(mutation.node);
37509
36827
  context.report({
37510
36828
  node: mutation.node,
37511
- message: MESSAGE$18
36829
+ message: MESSAGE$17
37512
36830
  });
37513
36831
  }
37514
36832
  };
@@ -37915,7 +37233,7 @@ const noNoninteractiveElementToInteractiveRole = defineRule({
37915
37233
  });
37916
37234
  //#endregion
37917
37235
  //#region src/plugin/rules/a11y/no-noninteractive-tabindex.ts
37918
- const MESSAGE$17 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
37236
+ const MESSAGE$16 = "Keyboard users get stuck focusing this element they can't act on because `tabIndex` makes it tabbable, so remove it.";
37919
37237
  const KEYBOARD_HANDLER_PROP_NAMES = [
37920
37238
  "onKeyDown",
37921
37239
  "onKeyUp",
@@ -38034,7 +37352,7 @@ const noNoninteractiveTabindex = defineRule({
38034
37352
  if (numeric === null) {
38035
37353
  if (isNodeOfType(tabIndexValue, "JSXExpressionContainer") && !settings.allowExpressionValues && !isKeyboardOperable(node) && !isFocusOperable(node) && !hasJsxSpreadAttribute$1(node.attributes)) context.report({
38036
37354
  node: tabIndex,
38037
- message: MESSAGE$17
37355
+ message: MESSAGE$16
38038
37356
  });
38039
37357
  return;
38040
37358
  }
@@ -38056,7 +37374,7 @@ const noNoninteractiveTabindex = defineRule({
38056
37374
  if (!roleAttribute) {
38057
37375
  context.report({
38058
37376
  node: tabIndex,
38059
- message: MESSAGE$17
37377
+ message: MESSAGE$16
38060
37378
  });
38061
37379
  return;
38062
37380
  }
@@ -38070,7 +37388,7 @@ const noNoninteractiveTabindex = defineRule({
38070
37388
  }
38071
37389
  context.report({
38072
37390
  node: tabIndex,
38073
- message: MESSAGE$17
37391
+ message: MESSAGE$16
38074
37392
  });
38075
37393
  } };
38076
37394
  }
@@ -38497,6 +37815,8 @@ const getWrapperHookWrappedFunction = (initializer) => {
38497
37815
  if (!wrapped || !isFunctionLike$2(wrapped)) return null;
38498
37816
  return wrapped;
38499
37817
  };
37818
+ const hasParameterDef = (ref) => Boolean(ref.resolved?.defs.some((def) => def.type === "Parameter"));
37819
+ const resolvesToFunctionBinding = (ref) => !hasParameterDef(ref) && Boolean(resolveToFunction(ref));
38500
37820
  const HANDLER_NAMED_PROP_PATTERN = /^(on|handle)[A-Z]/;
38501
37821
  const wrappedFunctionNotifiesParent = (analysis, wrappedFunction) => getDownstreamRefs(analysis, wrappedFunction).some((innerRef) => {
38502
37822
  if (!isProp(analysis, innerRef)) return false;
@@ -38817,7 +38137,7 @@ const noPassDataToParent = defineRule({
38817
38137
  if (isConstant(argRef)) return false;
38818
38138
  if (isParentWiredHookResultRef(analysis, argRef)) return false;
38819
38139
  if (isParentWiredHookCalleeRef(analysis, argRef)) return false;
38820
- if (resolveToFunction(argRef)) return false;
38140
+ if (resolvesToFunctionBinding(argRef)) return false;
38821
38141
  const argIdentifier = argRef.identifier;
38822
38142
  if (isImportBindingRef(argRef) && !isCalleePosition(argIdentifier)) return false;
38823
38143
  if (isNodeOfType(argIdentifier, "Identifier") && argIdentifier.name === "undefined") return false;
@@ -39489,6 +38809,174 @@ const noPropCallbackInRender = defineRule({
39489
38809
  } })
39490
38810
  });
39491
38811
  //#endregion
38812
+ //#region src/plugin/utils/is-proven-react-class-component.ts
38813
+ const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
38814
+ const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
38815
+ const expression = stripParenExpression(node);
38816
+ if (isNodeOfType(expression, "MemberExpression")) {
38817
+ const propertyName = getStaticPropertyName(expression);
38818
+ const receiver = stripParenExpression(expression.object);
38819
+ return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
38820
+ }
38821
+ if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
38822
+ if (!isNodeOfType(expression, "Identifier")) return false;
38823
+ const symbol = scopes.symbolFor(expression);
38824
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
38825
+ visitedSymbolIds.add(symbol.id);
38826
+ if (isImportedFromReact(symbol)) {
38827
+ const importedName = getImportedName(symbol.declarationNode);
38828
+ return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
38829
+ }
38830
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
38831
+ return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
38832
+ };
38833
+ const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38834
+ if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
38835
+ visitedClassNodes.add(classNode);
38836
+ return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
38837
+ };
38838
+ //#endregion
38839
+ //#region src/plugin/utils/function-contains-proven-react-hook-call.ts
38840
+ const functionContainsProvenReactHookCall = (functionNode, scopes) => {
38841
+ if (!isFunctionLike$2(functionNode)) return false;
38842
+ let containsReactHookCall = false;
38843
+ walkAst(functionNode.body, (node) => {
38844
+ if (containsReactHookCall) return false;
38845
+ if (node !== functionNode.body && (isFunctionLike$2(node) || isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression"))) return false;
38846
+ if (isNodeOfType(node, "CallExpression") && isReactApiCall(node, BUILTIN_HOOK_NAMES, scopes, { resolveNamedAliases: true })) {
38847
+ containsReactHookCall = true;
38848
+ return false;
38849
+ }
38850
+ });
38851
+ return containsReactHookCall;
38852
+ };
38853
+ //#endregion
38854
+ //#region src/plugin/utils/function-returns-props-children.ts
38855
+ const functionReturnsPropsChildren = (functionNode, scopes, controlFlow) => {
38856
+ if (!isFunctionLike$2(functionNode) || functionNode.params.length === 0) return false;
38857
+ const firstParameter = stripParenExpression(functionNode.params[0]);
38858
+ const firstParameterPattern = isNodeOfType(firstParameter, "AssignmentPattern") ? stripParenExpression(firstParameter.left) : firstParameter;
38859
+ const propsParameterSymbol = isNodeOfType(firstParameterPattern, "Identifier") ? scopes.symbolFor(firstParameterPattern) : null;
38860
+ const childrenBindingSymbolIds = /* @__PURE__ */ new Set();
38861
+ if (isNodeOfType(firstParameterPattern, "ObjectPattern")) {
38862
+ for (const property of firstParameterPattern.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children") {
38863
+ const propertyValue = stripParenExpression(property.value);
38864
+ const childrenBinding = isNodeOfType(propertyValue, "AssignmentPattern") ? stripParenExpression(propertyValue.left) : propertyValue;
38865
+ if (!isNodeOfType(childrenBinding, "Identifier")) continue;
38866
+ const childrenBindingSymbol = scopes.symbolFor(childrenBinding);
38867
+ if (childrenBindingSymbol) childrenBindingSymbolIds.add(childrenBindingSymbol.id);
38868
+ }
38869
+ }
38870
+ return functionReturnsMatchingExpression(functionNode, scopes, (expression) => {
38871
+ const candidate = stripParenExpression(expression);
38872
+ if (isNodeOfType(candidate, "Identifier")) {
38873
+ const symbol = scopes.symbolFor(candidate);
38874
+ return Boolean(symbol && childrenBindingSymbolIds.has(symbol.id) && !hasSymbolWriteBefore(symbol, candidate, scopes));
38875
+ }
38876
+ if (!isNodeOfType(candidate, "MemberExpression")) return false;
38877
+ if (getStaticPropertyName(candidate) !== "children") return false;
38878
+ const receiver = stripParenExpression(candidate.object);
38879
+ if (!isNodeOfType(receiver, "Identifier")) return false;
38880
+ const receiverSymbol = scopes.symbolFor(receiver);
38881
+ if (!receiverSymbol || !propsParameterSymbol) return false;
38882
+ return receiverSymbol.id === propsParameterSymbol.id && !hasSymbolWriteBefore(receiverSymbol, candidate, scopes) && !hasStaticPropertyWriteBefore(receiver, "children", candidate, scopes);
38883
+ }, controlFlow);
38884
+ };
38885
+ //#endregion
38886
+ //#region src/plugin/utils/function-returns-only-null.ts
38887
+ const isNullExpression = (expression) => {
38888
+ const candidate = stripParenExpression(expression);
38889
+ return isNodeOfType(candidate, "Literal") && candidate.value === null;
38890
+ };
38891
+ const functionReturnsOnlyNull = (functionNode) => {
38892
+ if (!isFunctionLike$2(functionNode)) return false;
38893
+ if (!isNodeOfType(functionNode.body, "BlockStatement")) return isNullExpression(functionNode.body);
38894
+ const returnStatements = collectFunctionReturnStatements(functionNode);
38895
+ return returnStatements.length > 0 && returnStatements.every((returnStatement) => Boolean(returnStatement.argument && isNullExpression(returnStatement.argument)));
38896
+ };
38897
+ //#endregion
38898
+ //#region src/plugin/utils/is-proven-styled-component-expression.ts
38899
+ const findFactoryRoot = (node) => {
38900
+ const candidate = stripParenExpression(node);
38901
+ if (isNodeOfType(candidate, "Identifier")) return candidate;
38902
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryRoot(candidate.object);
38903
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryRoot(candidate.callee);
38904
+ return null;
38905
+ };
38906
+ const findFactoryPropertyName = (node) => {
38907
+ const candidate = stripParenExpression(node);
38908
+ if (isNodeOfType(candidate, "MemberExpression")) return findFactoryPropertyName(candidate.object) ?? getStaticPropertyName(candidate);
38909
+ if (isNodeOfType(candidate, "CallExpression")) return findFactoryPropertyName(candidate.callee);
38910
+ return null;
38911
+ };
38912
+ const isProvenStyledComponentExpression = (expression, scopes) => {
38913
+ const candidate = stripParenExpression(expression);
38914
+ if (!isNodeOfType(candidate, "TaggedTemplateExpression")) return false;
38915
+ const factoryRoot = findFactoryRoot(candidate.tag);
38916
+ if (!factoryRoot) return false;
38917
+ const factoryPropertyName = findFactoryPropertyName(candidate.tag);
38918
+ if (factoryPropertyName && hasStaticPropertyWriteBefore(factoryRoot, factoryPropertyName, candidate, scopes)) return false;
38919
+ const symbol = resolveConstIdentifierAlias(factoryRoot, scopes);
38920
+ if (!symbol || symbol.kind !== "import") return false;
38921
+ if (!(isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "styled")) return false;
38922
+ const importDeclaration = symbol.declarationNode.parent;
38923
+ return Boolean(importDeclaration && isNodeOfType(importDeclaration, "ImportDeclaration") && importDeclaration.source.value === "styled-components");
38924
+ };
38925
+ //#endregion
38926
+ //#region src/plugin/utils/is-proven-react-component-symbol.ts
38927
+ const REACT_COMPONENT_HOC_NAMES = new Set(["memo", "forwardRef"]);
38928
+ const functionHasComponentEvidence = (functionNode, scopes, controlFlow) => functionContainsReactRenderOutput(functionNode, scopes, controlFlow) || functionReturnsPropsChildren(functionNode, scopes, controlFlow) || functionContainsProvenReactHookCall(functionNode, scopes) && functionReturnsOnlyNull(functionNode);
38929
+ const isProvenReactComponentExpression = (expression, scopes, controlFlow, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
38930
+ const candidate = stripParenExpression(expression);
38931
+ if (isInlineFunctionExpression(candidate)) return functionHasComponentEvidence(candidate, scopes, controlFlow);
38932
+ if (isNodeOfType(candidate, "ClassExpression")) return isProvenReactClassComponent(candidate, scopes);
38933
+ if (isProvenStyledComponentExpression(candidate, scopes)) return true;
38934
+ if (isNodeOfType(candidate, "Identifier")) {
38935
+ const symbol = scopes.symbolFor(candidate);
38936
+ if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, candidate, scopes)) return false;
38937
+ visitedSymbolIds.add(symbol.id);
38938
+ if (isNodeOfType(symbol.declarationNode, "FunctionDeclaration")) return functionHasComponentEvidence(symbol.declarationNode, scopes, controlFlow);
38939
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes);
38940
+ return Boolean(symbol.initializer && isProvenReactComponentExpression(symbol.initializer, scopes, controlFlow, visitedSymbolIds));
38941
+ }
38942
+ if (!isNodeOfType(candidate, "CallExpression")) return false;
38943
+ if (!hasStableCallTarget(candidate, scopes)) return false;
38944
+ if (isReactApiCall(candidate, REACT_COMPONENT_HOC_NAMES, scopes, { resolveNamedAliases: true })) {
38945
+ const wrappedComponent = candidate.arguments[0];
38946
+ return Boolean(wrappedComponent && !isNodeOfType(wrappedComponent, "SpreadElement") && isProvenReactComponentExpression(wrappedComponent, scopes, controlFlow, visitedSymbolIds));
38947
+ }
38948
+ if (!isReactApiCall(candidate, "useMemo", scopes, { resolveNamedAliases: true })) return false;
38949
+ const factory = candidate.arguments[0];
38950
+ if (!factory || isNodeOfType(factory, "SpreadElement")) return false;
38951
+ const unwrappedFactory = stripParenExpression(factory);
38952
+ if (!isInlineFunctionExpression(unwrappedFactory)) return false;
38953
+ if (!isNodeOfType(unwrappedFactory.body, "BlockStatement")) return isProvenReactComponentExpression(unwrappedFactory.body, scopes, controlFlow, visitedSymbolIds);
38954
+ const returnStatements = collectFunctionReturnStatements(unwrappedFactory);
38955
+ const returnedExpression = returnStatements[0]?.argument;
38956
+ return Boolean(returnStatements.length === 1 && returnedExpression && isProvenReactComponentExpression(returnedExpression, scopes, controlFlow, visitedSymbolIds));
38957
+ };
38958
+ const isProvenReactComponentSymbol = (symbol, scopes, controlFlow, componentReference) => {
38959
+ const candidateSymbols = symbol.kind === "ts-module" ? symbol.scope.symbols.filter((candidateSymbol) => candidateSymbol.name === symbol.name && candidateSymbol.kind !== "ts-module") : [symbol];
38960
+ for (const candidateSymbol of candidateSymbols) {
38961
+ if (hasSymbolWriteBefore(candidateSymbol, componentReference, scopes)) continue;
38962
+ if (isComponentDeclaration(candidateSymbol.declarationNode)) {
38963
+ if (functionHasComponentEvidence(candidateSymbol.declarationNode, scopes, controlFlow)) return true;
38964
+ continue;
38965
+ }
38966
+ const initializer = candidateSymbol.initializer ? stripParenExpression(candidateSymbol.initializer) : null;
38967
+ if (isNodeOfType(candidateSymbol.declarationNode, "VariableDeclarator") && isNodeOfType(candidateSymbol.declarationNode.id, "Identifier") && isUppercaseName(candidateSymbol.declarationNode.id.name) && initializer) {
38968
+ if (isProvenReactComponentExpression(initializer, scopes, controlFlow)) return true;
38969
+ continue;
38970
+ }
38971
+ if (isNodeOfType(candidateSymbol.declarationNode, "ClassDeclaration") || isNodeOfType(candidateSymbol.declarationNode, "ClassExpression")) {
38972
+ if (isProvenReactClassComponent(candidateSymbol.declarationNode, scopes)) return true;
38973
+ continue;
38974
+ }
38975
+ if (initializer && isNodeOfType(initializer, "ClassExpression") && isProvenReactClassComponent(initializer, scopes)) return true;
38976
+ }
38977
+ return false;
38978
+ };
38979
+ //#endregion
39492
38980
  //#region src/plugin/rules/architecture/no-prop-types.ts
39493
38981
  const PROP_TYPES_PROPERTY = "propTypes";
39494
38982
  const isPropTypesKey = (key, computed) => {
@@ -39696,7 +39184,7 @@ const noRandomKey = defineRule({
39696
39184
  });
39697
39185
  //#endregion
39698
39186
  //#region src/plugin/rules/react-builtins/no-react-children.ts
39699
- const MESSAGE$16 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
39187
+ const MESSAGE$15 = "`React.Children` traversal depends on the runtime child shape, so wrapping or unwrapping a child can silently change what gets visited.";
39700
39188
  const isChildrenIdentifier = (node, contextNode) => {
39701
39189
  if (!isNodeOfType(node, "Identifier") || node.name !== "Children") return false;
39702
39190
  return isImportedFromModule(contextNode, "Children", "react");
@@ -39722,13 +39210,13 @@ const noReactChildren = defineRule({
39722
39210
  if (isChildrenIdentifier(memberObject, node)) {
39723
39211
  context.report({
39724
39212
  node: calleeOuter,
39725
- message: MESSAGE$16
39213
+ message: MESSAGE$15
39726
39214
  });
39727
39215
  return;
39728
39216
  }
39729
39217
  if (isReactNamespaceMember(memberObject, node)) context.report({
39730
39218
  node: calleeOuter,
39731
- message: MESSAGE$16
39219
+ message: MESSAGE$15
39732
39220
  });
39733
39221
  } })
39734
39222
  });
@@ -40409,7 +39897,7 @@ const noRenderPropChildren = defineRule({
40409
39897
  });
40410
39898
  //#endregion
40411
39899
  //#region src/plugin/rules/react-builtins/no-render-return-value.ts
40412
- const MESSAGE$15 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
39900
+ const MESSAGE$14 = "Your app breaks in React 19 because `ReactDOM.render` returns nothing there.";
40413
39901
  const isReactDomRenderCall = (node) => isGlobalMethodCall(node, "ReactDOM", "render");
40414
39902
  const isUsedAsReturnValue = (parent) => {
40415
39903
  if (!parent) return false;
@@ -40427,7 +39915,7 @@ const noRenderReturnValue = defineRule({
40427
39915
  if (!isUsedAsReturnValue(node.parent)) return;
40428
39916
  context.report({
40429
39917
  node: node.callee,
40430
- message: MESSAGE$15
39918
+ message: MESSAGE$14
40431
39919
  });
40432
39920
  } })
40433
39921
  });
@@ -41236,7 +40724,7 @@ const noSelfUpdatingEffect = defineRule({
41236
40724
  });
41237
40725
  //#endregion
41238
40726
  //#region src/plugin/rules/react-builtins/no-set-state.ts
41239
- const MESSAGE$14 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
40727
+ const MESSAGE$13 = "`this.setState` keeps local class state in a project that forbids it, so state ownership becomes harder to reason about.";
41240
40728
  const noSetState = defineRule({
41241
40729
  id: "no-set-state",
41242
40730
  title: "Local class state forbidden",
@@ -41251,7 +40739,7 @@ const noSetState = defineRule({
41251
40739
  if (!getParentComponent(node)) return;
41252
40740
  context.report({
41253
40741
  node: node.callee,
41254
- message: MESSAGE$14
40742
+ message: MESSAGE$13
41255
40743
  });
41256
40744
  } })
41257
40745
  });
@@ -41621,7 +41109,7 @@ const isAbstractRole = (openingElement, settings) => {
41621
41109
  };
41622
41110
  //#endregion
41623
41111
  //#region src/plugin/rules/a11y/no-static-element-interactions.ts
41624
- const MESSAGE$13 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
41112
+ const MESSAGE$12 = "Screen reader users can't tell this click handler is interactive because it has no `role`, so add a `role` or use a button or link.";
41625
41113
  const DEFAULT_HANDLERS = [
41626
41114
  "onClick",
41627
41115
  "onMouseDown",
@@ -41736,7 +41224,7 @@ const noStaticElementInteractions = defineRule({
41736
41224
  if (!roleAttribute || !roleAttribute.value) {
41737
41225
  context.report({
41738
41226
  node: node.name,
41739
- message: MESSAGE$13
41227
+ message: MESSAGE$12
41740
41228
  });
41741
41229
  return;
41742
41230
  }
@@ -41746,7 +41234,7 @@ const noStaticElementInteractions = defineRule({
41746
41234
  if (isRecognizedRoleString(attributeValue.value)) return;
41747
41235
  context.report({
41748
41236
  node: node.name,
41749
- message: MESSAGE$13
41237
+ message: MESSAGE$12
41750
41238
  });
41751
41239
  return;
41752
41240
  }
@@ -41754,7 +41242,7 @@ const noStaticElementInteractions = defineRule({
41754
41242
  if (!settings.allowExpressionValues) {
41755
41243
  context.report({
41756
41244
  node: node.name,
41757
- message: MESSAGE$13
41245
+ message: MESSAGE$12
41758
41246
  });
41759
41247
  return;
41760
41248
  }
@@ -41762,7 +41250,7 @@ const noStaticElementInteractions = defineRule({
41762
41250
  if (isStaticNullishExpression(expression)) {
41763
41251
  context.report({
41764
41252
  node: node.name,
41765
- message: MESSAGE$13
41253
+ message: MESSAGE$12
41766
41254
  });
41767
41255
  return;
41768
41256
  }
@@ -41772,7 +41260,7 @@ const noStaticElementInteractions = defineRule({
41772
41260
  if (branches.some((branch) => branch.role !== null && isRecognizedRoleString(branch.role))) return;
41773
41261
  context.report({
41774
41262
  node: node.name,
41775
- message: MESSAGE$13
41263
+ message: MESSAGE$12
41776
41264
  });
41777
41265
  return;
41778
41266
  }
@@ -41780,7 +41268,7 @@ const noStaticElementInteractions = defineRule({
41780
41268
  }
41781
41269
  context.report({
41782
41270
  node: node.name,
41783
- message: MESSAGE$13
41271
+ message: MESSAGE$12
41784
41272
  });
41785
41273
  } };
41786
41274
  }
@@ -41886,7 +41374,7 @@ const noStringRefs = defineRule({
41886
41374
  });
41887
41375
  //#endregion
41888
41376
  //#region src/plugin/rules/js-performance/no-sync-xhr.ts
41889
- const MESSAGE$12 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
41377
+ const MESSAGE$11 = "A synchronous `XMLHttpRequest` (`.open(method, url, false)`) freezes the main thread until the request finishes, blocking all rendering and input. Use `fetch()` or an async XHR (`open(method, url, true)`).";
41890
41378
  const isFalseLiteral = (node) => isNodeOfType(node, "Literal") && node.value === false;
41891
41379
  const PUBLIC_ASSET_PATH_PATTERN = /(?:^|\/)public\//i;
41892
41380
  const noSyncXhr = defineRule({
@@ -41907,7 +41395,7 @@ const noSyncXhr = defineRule({
41907
41395
  if (!asyncArgument || !isFalseLiteral(stripParenExpression(asyncArgument))) return;
41908
41396
  context.report({
41909
41397
  node,
41910
- message: MESSAGE$12
41398
+ message: MESSAGE$11
41911
41399
  });
41912
41400
  };
41913
41401
  return {
@@ -41932,7 +41420,7 @@ const noSyncXhr = defineRule({
41932
41420
  });
41933
41421
  //#endregion
41934
41422
  //#region src/plugin/rules/react-builtins/no-this-in-sfc.ts
41935
- const MESSAGE$11 = "This value is `undefined` because function components have no `this`.";
41423
+ const MESSAGE$10 = "This value is `undefined` because function components have no `this`.";
41936
41424
  const isInsideClassMethod = (node, customClassFactoryNames) => {
41937
41425
  let ancestor = node.parent;
41938
41426
  while (ancestor) {
@@ -42020,7 +41508,7 @@ const noThisInSfc = defineRule({
42020
41508
  if (functionHasOwnThisMemberWrite(enclosingFunction)) return;
42021
41509
  context.report({
42022
41510
  node,
42023
- message: MESSAGE$11
41511
+ message: MESSAGE$10
42024
41512
  });
42025
41513
  } };
42026
41514
  }
@@ -43901,7 +43389,6 @@ const noUnstableNestedComponents = defineRule({
43901
43389
  severity: "warn",
43902
43390
  recommendation: "Move nested components to module scope so React does not remount them and lose state on every render.",
43903
43391
  category: "Performance",
43904
- tags: ["react-jsx-only"],
43905
43392
  create: (context) => {
43906
43393
  const settings = resolveSettings$8(context.settings);
43907
43394
  const renderPropRegex = compileGlob(settings.propNamePattern);
@@ -44168,7 +43655,7 @@ const noWideLetterSpacing = defineRule({
44168
43655
  //#endregion
44169
43656
  //#region src/plugin/rules/react-builtins/no-will-update-set-state.ts
44170
43657
  const LIFECYCLE_NAMES = new Set(["componentWillUpdate", "UNSAFE_componentWillUpdate"]);
44171
- const MESSAGE$10 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
43658
+ const MESSAGE$9 = "Calling setState in componentWillUpdate can trigger another update immediately, loop forever, and freeze the component.";
44172
43659
  const resolveSettings$7 = (settings) => {
44173
43660
  const reactDoctor = settings?.["react-doctor"];
44174
43661
  return { mode: (typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.noWillUpdateSetState ?? {} : {}).mode ?? "allowed" };
@@ -44202,7 +43689,7 @@ const noWillUpdateSetState = defineRule({
44202
43689
  if (!isSetStateCallInLifecycle(node, activeLifecycleNames, { disallowInNestedFunctions: mode === "disallow-in-func" })) return;
44203
43690
  context.report({
44204
43691
  node: node.callee,
44205
- message: MESSAGE$10
43692
+ message: MESSAGE$9
44206
43693
  });
44207
43694
  } };
44208
43695
  }
@@ -45198,7 +44685,7 @@ const preactNoRenderArguments = defineRule({
45198
44685
  });
45199
44686
  //#endregion
45200
44687
  //#region src/plugin/rules/preact/preact-prefer-ondblclick.ts
45201
- const MESSAGE$9 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
44688
+ const MESSAGE$8 = "Your users get no response from `onDoubleClick` in Preact core, where it never fires, so use `onDblClick` instead, which matches the DOM event name.";
45202
44689
  const preactPreferOndblclick = defineRule({
45203
44690
  id: "preact-prefer-ondblclick",
45204
44691
  title: "onDoubleClick instead of onDblClick",
@@ -45213,7 +44700,7 @@ const preactPreferOndblclick = defineRule({
45213
44700
  if (!onDoubleClickAttribute) return;
45214
44701
  context.report({
45215
44702
  node: onDoubleClickAttribute,
45216
- message: MESSAGE$9
44703
+ message: MESSAGE$8
45217
44704
  });
45218
44705
  } })
45219
44706
  });
@@ -45464,7 +44951,7 @@ const preferExplicitVariants = defineRule({
45464
44951
  });
45465
44952
  //#endregion
45466
44953
  //#region src/plugin/rules/react-builtins/prefer-function-component.ts
45467
- const MESSAGE$8 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
44954
+ const MESSAGE$7 = "This class component keeps behavior in lifecycle methods, so state and effects are harder to follow than in a hook-based function component.";
45468
44955
  const resolveSettings$4 = (settings) => {
45469
44956
  const reactDoctor = settings?.["react-doctor"];
45470
44957
  const ruleSettings = typeof reactDoctor === "object" && reactDoctor !== null ? reactDoctor.preferFunctionComponent ?? {} : {};
@@ -45503,7 +44990,7 @@ const preferFunctionComponent = defineRule({
45503
44990
  const reportNode = node.id ?? node;
45504
44991
  context.report({
45505
44992
  node: reportNode,
45506
- message: MESSAGE$8
44993
+ message: MESSAGE$7
45507
44994
  });
45508
44995
  };
45509
44996
  return {
@@ -47081,258 +46568,7 @@ const queryMutationMissingInvalidation = defineRule({
47081
46568
  }
47082
46569
  });
47083
46570
  //#endregion
47084
- //#region src/plugin/utils/is-node-conditionally-executed.ts
47085
- const isNodeConditionallyExecuted = (node, boundary) => {
47086
- let child = node;
47087
- let parent = child.parent ?? null;
47088
- while (parent && parent !== boundary) {
47089
- if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === child || parent.alternate === child)) return true;
47090
- if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) return true;
47091
- if (isNodeOfType(parent, "AssignmentPattern") && parent.right === child) return true;
47092
- child = parent;
47093
- parent = child.parent ?? null;
47094
- }
47095
- return false;
47096
- };
47097
- //#endregion
47098
- //#region src/plugin/rules/tanstack-query/utils/resolve-tanstack-query-hook-name.ts
47099
- const resolveTanstackNamespaceHookName = (memberExpression, contextNode, scopes) => {
47100
- const hookName = getStaticPropertyKeyName(memberExpression, { allowComputedString: true });
47101
- const namespaceObject = stripParenExpression(memberExpression.object);
47102
- if (!hookName || !TANSTACK_QUERY_HOOKS.has(hookName)) return null;
47103
- if (!isNodeOfType(namespaceObject, "Identifier")) return null;
47104
- const resolvedNamespaceSymbol = resolveConstIdentifierAlias(namespaceObject, scopes);
47105
- if (resolvedNamespaceSymbol?.kind !== "import") return null;
47106
- const namespaceBinding = getImportBindingForName(contextNode, resolvedNamespaceSymbol.name);
47107
- return namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source) ? hookName : null;
47108
- };
47109
- const resolveTanstackQueryHookName = (callExpression, scopes) => {
47110
- const callee = stripParenExpression(callExpression.callee);
47111
- if (isNodeOfType(callee, "Identifier")) {
47112
- const resolvedSymbol = resolveConstIdentifierAlias(callee, scopes);
47113
- if (!resolvedSymbol) return null;
47114
- if (resolvedSymbol.kind === "const" && resolvedSymbol.initializer) {
47115
- const initializer = stripParenExpression(resolvedSymbol.initializer);
47116
- return isNodeOfType(initializer, "MemberExpression") ? resolveTanstackNamespaceHookName(initializer, callExpression, scopes) : null;
47117
- }
47118
- if (resolvedSymbol.kind !== "import") return null;
47119
- const importBinding = getImportBindingForName(callExpression, resolvedSymbol.name);
47120
- if (importBinding === null) return null;
47121
- if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
47122
- return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
47123
- }
47124
- if (isNodeOfType(callee, "MemberExpression")) return resolveTanstackNamespaceHookName(callee, callExpression, scopes);
47125
- return null;
47126
- };
47127
- const resolveTanstackQueryHookNameFromInitializer = (initializer, scopes) => {
47128
- const unwrappedInitializer = stripParenExpression(initializer);
47129
- if (isNodeOfType(unwrappedInitializer, "CallExpression")) return resolveTanstackQueryHookName(unwrappedInitializer, scopes);
47130
- if (!isNodeOfType(unwrappedInitializer, "Identifier")) return null;
47131
- const resolvedSymbol = resolveConstIdentifierAlias(unwrappedInitializer, scopes);
47132
- if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
47133
- const resolvedInitializer = stripParenExpression(resolvedSymbol.initializer);
47134
- if (!isNodeOfType(resolvedInitializer, "CallExpression")) return null;
47135
- return resolveTanstackQueryHookName(resolvedInitializer, scopes);
47136
- };
47137
- //#endregion
47138
46571
  //#region src/plugin/rules/tanstack-query/query-no-query-in-effect.ts
47139
- const isTanstackQueryResult = (expression, context) => Boolean(resolveTanstackQueryHookNameFromInitializer(expression, context.scopes));
47140
- const isStaticRefetchMember = (memberExpression) => getStaticPropertyKeyName(memberExpression, { allowComputedString: true }) === "refetch";
47141
- const resolveCalledFunction = (callee, context) => {
47142
- const unwrappedCallee = stripParenExpression(callee);
47143
- if (isFunctionLike$2(unwrappedCallee)) return unwrappedCallee;
47144
- if (!isNodeOfType(unwrappedCallee, "Identifier")) return null;
47145
- const symbol = resolveConstIdentifierAlias(unwrappedCallee, context.scopes);
47146
- if (!symbol) return null;
47147
- const candidate = symbol.kind === "function" ? symbol.declarationNode : symbol.initializer;
47148
- return candidate && isFunctionLike$2(candidate) ? candidate : null;
47149
- };
47150
- const hasSuspensionBefore = (functionNode, boundary, context) => {
47151
- if (!isFunctionLike$2(functionNode)) return true;
47152
- if (functionNode.generator) return true;
47153
- const boundaryStart = getRangeStart(boundary);
47154
- if (boundaryStart === null) return true;
47155
- let hasSuspension = false;
47156
- walkAst(functionNode, (node) => {
47157
- if (node !== functionNode && isFunctionLike$2(node)) return false;
47158
- if (!isNodeOfType(node, "AwaitExpression") || !isNodeReachableWithinFunction(node, context)) return;
47159
- const suspensionStart = getRangeStart(node);
47160
- if (suspensionStart !== null && suspensionStart < boundaryStart) {
47161
- hasSuspension = true;
47162
- return false;
47163
- }
47164
- });
47165
- return hasSuspension;
47166
- };
47167
- const isFunctionAncestor = (ancestor, functionNode) => {
47168
- let enclosingFunction = findEnclosingFunction(functionNode);
47169
- while (enclosingFunction) {
47170
- if (enclosingFunction === ancestor) return true;
47171
- enclosingFunction = findEnclosingFunction(enclosingFunction);
47172
- }
47173
- return false;
47174
- };
47175
- const isUnconditionallyExecuted = (node, functionNode, context) => context.cfg.isUnconditionalFromEntry(node) && !isNodeConditionallyExecuted(node, functionNode) && !(isNodeOfType(node, "MemberExpression") && isNodeOfType(node.parent, "AssignmentExpression") && node.parent.left === node && (node.parent.operator === "&&=" || node.parent.operator === "||=" || node.parent.operator === "??="));
47176
- const functionInvokesTarget = (callerFunction, targetFunction, context, visitedFunctions, canCrossSuspension = false) => {
47177
- if (visitedFunctions.has(callerFunction)) return false;
47178
- visitedFunctions.add(callerFunction);
47179
- let invokesTarget = false;
47180
- walkAst(callerFunction, (node) => {
47181
- if (node !== callerFunction && isFunctionLike$2(node)) return false;
47182
- if (!isNodeOfType(node, "CallExpression")) return;
47183
- if (!isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context) || !canCrossSuspension && hasSuspensionBefore(callerFunction, node, context)) return;
47184
- const calledFunction = resolveCalledFunction(node.callee, context);
47185
- if (calledFunction === targetFunction || calledFunction && functionInvokesTarget(calledFunction, targetFunction, context, visitedFunctions, canCrossSuspension)) {
47186
- invokesTarget = true;
47187
- return false;
47188
- }
47189
- });
47190
- return invokesTarget;
47191
- };
47192
- const isFunctionInvokedBefore = (invokedFunction, boundary, context) => {
47193
- const boundaryFunction = findEnclosingFunction(boundary);
47194
- const boundaryStart = getRangeStart(boundary);
47195
- if (!boundaryFunction || boundaryStart === null) return false;
47196
- let isInvokedBefore = false;
47197
- walkAst(boundaryFunction, (node) => {
47198
- if (node !== boundaryFunction && isFunctionLike$2(node)) return false;
47199
- if (!isNodeOfType(node, "CallExpression")) return;
47200
- const callStart = getRangeStart(node);
47201
- if (callStart === null || callStart >= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, boundaryFunction, context) || hasSuspensionBefore(boundaryFunction, node, context)) return;
47202
- const calledFunction = resolveCalledFunction(node.callee, context);
47203
- if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set())) {
47204
- isInvokedBefore = true;
47205
- return false;
47206
- }
47207
- });
47208
- return isInvokedBefore;
47209
- };
47210
- const isFunctionInvokedAfter = (invokedFunction, boundary, callerFunction, context) => {
47211
- const boundaryStart = getRangeStart(boundary);
47212
- if (boundaryStart === null) return false;
47213
- let isInvokedAfter = false;
47214
- walkAst(callerFunction, (node) => {
47215
- if (node !== callerFunction && isFunctionLike$2(node)) return false;
47216
- if (!isNodeOfType(node, "CallExpression")) return;
47217
- const callStart = getRangeStart(node);
47218
- if (callStart === null || callStart <= boundaryStart || !isNodeReachableWithinFunction(node, context) || !isUnconditionallyExecuted(node, callerFunction, context)) return;
47219
- const calledFunction = resolveCalledFunction(node.callee, context);
47220
- if (calledFunction === invokedFunction || calledFunction && functionInvokesTarget(calledFunction, invokedFunction, context, /* @__PURE__ */ new Set(), true)) {
47221
- isInvokedAfter = true;
47222
- return false;
47223
- }
47224
- });
47225
- return isInvokedAfter;
47226
- };
47227
- const isWriteExecutedBefore = (writeNode, boundary, context, deferredExecutionFunction) => {
47228
- const writeStart = getRangeStart(writeNode);
47229
- const boundaryStart = getRangeStart(boundary);
47230
- const writeFunction = findEnclosingFunction(writeNode);
47231
- const writeExecutionBoundary = writeFunction ?? findProgramRoot(writeNode);
47232
- if (writeStart === null || boundaryStart === null || !writeExecutionBoundary || !isNodeReachableWithinFunction(writeNode, context) || !isUnconditionallyExecuted(writeNode, writeExecutionBoundary, context)) return false;
47233
- const boundaryFunction = findEnclosingFunction(boundary);
47234
- const renderFunction = deferredExecutionFunction ? findEnclosingFunction(deferredExecutionFunction) : null;
47235
- if (writeFunction === boundaryFunction) return writeStart < boundaryStart;
47236
- if (!writeFunction) return writeStart < boundaryStart;
47237
- if (boundaryFunction && isFunctionAncestor(writeFunction, boundaryFunction)) {
47238
- if (Boolean(renderFunction && (writeFunction === renderFunction || isFunctionAncestor(writeFunction, renderFunction)))) return true;
47239
- if (deferredExecutionFunction && boundaryFunction !== deferredExecutionFunction) {
47240
- if (hasSuspensionBefore(boundaryFunction, boundary, context) && isFunctionInvokedBefore(boundaryFunction, writeNode, context)) return true;
47241
- return isFunctionInvokedAfter(boundaryFunction, writeNode, writeFunction, context);
47242
- }
47243
- return writeStart < boundaryStart;
47244
- }
47245
- if (renderFunction && isFunctionAncestor(renderFunction, writeFunction) && !hasSuspensionBefore(writeFunction, writeNode, context) && functionInvokesTarget(renderFunction, writeFunction, context, /* @__PURE__ */ new Set())) return true;
47246
- return !hasSuspensionBefore(writeFunction, writeNode, context) && isFunctionInvokedBefore(writeFunction, boundary, context);
47247
- };
47248
- const getStaticStringValue = (node) => {
47249
- const unwrappedNode = stripParenExpression(node);
47250
- if (isNodeOfType(unwrappedNode, "Literal") && typeof unwrappedNode.value === "string") return unwrappedNode.value;
47251
- if (isNodeOfType(unwrappedNode, "TemplateLiteral") && unwrappedNode.expressions.length === 0) return getStaticTemplateLiteralValue(unwrappedNode);
47252
- return null;
47253
- };
47254
- const isSameRefetchMember = (target, candidate, context) => {
47255
- const unwrappedTarget = stripParenExpression(target);
47256
- const unwrappedCandidate = stripParenExpression(candidate);
47257
- if (!isNodeOfType(unwrappedTarget, "Identifier") || !isNodeOfType(unwrappedCandidate, "MemberExpression") || !isStaticRefetchMember(unwrappedCandidate)) return false;
47258
- const candidateTarget = stripParenExpression(unwrappedCandidate.object);
47259
- if (!isNodeOfType(candidateTarget, "Identifier")) return false;
47260
- const targetSymbol = resolveConstIdentifierAlias(unwrappedTarget, context.scopes);
47261
- const candidateSymbol = resolveConstIdentifierAlias(candidateTarget, context.scopes);
47262
- return Boolean(targetSymbol && candidateSymbol?.id === targetSymbol.id);
47263
- };
47264
- const getRefetchMutationTarget = (node, context) => {
47265
- if (isNodeOfType(node, "MemberExpression") && isStaticRefetchMember(node)) {
47266
- const parent = node.parent;
47267
- if (isNodeOfType(parent, "AssignmentExpression") && parent.left === node && isSameRefetchMember(node.object, parent.right, context)) return null;
47268
- return isNodeOfType(parent, "AssignmentExpression") && parent.left === node || isNodeOfType(parent, "UpdateExpression") && parent.argument === node || isNodeOfType(parent, "UnaryExpression") && parent.operator === "delete" ? node.object : null;
47269
- }
47270
- if (!isNodeOfType(node, "CallExpression")) return null;
47271
- const callee = stripParenExpression(node.callee);
47272
- if (!isNodeOfType(callee, "MemberExpression")) return null;
47273
- const receiver = stripParenExpression(callee.object);
47274
- if (!isNodeOfType(receiver, "Identifier") || receiver.name !== "Object" || context.scopes.symbolFor(receiver)) return null;
47275
- const methodName = getStaticPropertyKeyName(callee, { allowComputedString: true });
47276
- const target = node.arguments[0];
47277
- if (!target || isNodeOfType(target, "SpreadElement")) return null;
47278
- if (methodName === "defineProperty") {
47279
- const propertyKey = node.arguments[1];
47280
- if (!propertyKey || getStaticStringValue(propertyKey) !== "refetch") return null;
47281
- const descriptor = node.arguments[2];
47282
- if (isNodeOfType(descriptor, "ObjectExpression")) {
47283
- const valueProperty = descriptor.properties.find((property) => isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "value");
47284
- if (isNodeOfType(valueProperty, "Property") && isSameRefetchMember(target, valueProperty.value, context)) return null;
47285
- }
47286
- return target;
47287
- }
47288
- if (methodName !== "assign") return null;
47289
- let finalRefetchValue = null;
47290
- for (const source of node.arguments.slice(1)) {
47291
- if (!isNodeOfType(source, "ObjectExpression")) continue;
47292
- for (const property of source.properties) if (isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "refetch") finalRefetchValue = property.value;
47293
- }
47294
- if (!finalRefetchValue || isSameRefetchMember(target, finalRefetchValue, context)) return null;
47295
- return target;
47296
- };
47297
- const hasRefetchMemberWriteBefore = (expression, boundary, context, deferredExecutionFunction) => {
47298
- const unwrappedExpression = stripParenExpression(expression);
47299
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
47300
- const resultSymbol = resolveConstIdentifierAlias(unwrappedExpression, context.scopes);
47301
- if (!resultSymbol) return false;
47302
- const program = findProgramRoot(expression);
47303
- if (!program) return true;
47304
- if (getRangeStart(boundary) === null) return true;
47305
- let hasWrite = false;
47306
- walkAst(program, (node) => {
47307
- const mutationTarget = getRefetchMutationTarget(node, context);
47308
- if (!mutationTarget) return;
47309
- if (!isWriteExecutedBefore(node, boundary, context, deferredExecutionFunction)) return;
47310
- const object = stripParenExpression(mutationTarget);
47311
- if (!isNodeOfType(object, "Identifier")) return;
47312
- if (resolveConstIdentifierAlias(object, context.scopes)?.id === resultSymbol.id) {
47313
- hasWrite = true;
47314
- return false;
47315
- }
47316
- });
47317
- return hasWrite;
47318
- };
47319
- const isTanstackRefetchExpression = (expression, context, deferredExecutionFunction, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
47320
- const unwrappedExpression = stripParenExpression(expression);
47321
- if (isNodeOfType(unwrappedExpression, "MemberExpression")) return isStaticRefetchMember(unwrappedExpression) && isTanstackQueryResult(unwrappedExpression.object, context) && !hasRefetchMemberWriteBefore(unwrappedExpression.object, unwrappedExpression, context, deferredExecutionFunction);
47322
- if (!isNodeOfType(unwrappedExpression, "Identifier")) return false;
47323
- const symbol = context.scopes.symbolFor(unwrappedExpression);
47324
- if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || symbol.references.some((reference) => reference.flag !== "read") || !isNodeOfType(symbol.declarationNode, "VariableDeclarator")) return false;
47325
- visitedSymbolIds.add(symbol.id);
47326
- const bindingProperty = symbol.bindingIdentifier.parent;
47327
- if (isNodeOfType(bindingProperty, "Property") && getStaticPropertyKeyName(bindingProperty, { allowComputedString: true }) === "refetch") {
47328
- const initializer = symbol.declarationNode.init;
47329
- return Boolean(initializer && isTanstackQueryResult(initializer, context) && !hasRefetchMemberWriteBefore(initializer, symbol.declarationNode, context, null));
47330
- }
47331
- return Boolean(symbol.declarationNode.id === symbol.bindingIdentifier && symbol.initializer && isTanstackRefetchExpression(symbol.initializer, context, null, visitedSymbolIds));
47332
- };
47333
- const isTanstackRefetchCall = (callExpression, context, effectCallback) => {
47334
- return isTanstackRefetchExpression(callExpression.callee, context, effectCallback);
47335
- };
47336
46572
  const queryNoQueryInEffect = defineRule({
47337
46573
  id: "query-no-query-in-effect",
47338
46574
  title: "Query refetch inside useEffect",
@@ -47348,7 +46584,8 @@ const queryNoQueryInEffect = defineRule({
47348
46584
  walkAst(callback, (child) => {
47349
46585
  if (child !== callback && isFunctionLike$2(child) && !effectInvokedFunctions.has(child)) return false;
47350
46586
  if (!isNodeOfType(child, "CallExpression")) return;
47351
- if (isTanstackRefetchCall(child, context, callback)) context.report({
46587
+ const callee = child.callee;
46588
+ if (isNodeOfType(callee, "Identifier") && callee.name === "refetch" || isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && callee.property.name === "refetch") context.report({
47352
46589
  node: child,
47353
46590
  message: "refetch() inside useEffect duplicates work React Query already does, causing extra fetches."
47354
46591
  });
@@ -47357,6 +46594,28 @@ const queryNoQueryInEffect = defineRule({
47357
46594
  });
47358
46595
  //#endregion
47359
46596
  //#region src/plugin/rules/tanstack-query/query-no-rest-destructuring.ts
46597
+ const resolveTanstackQueryHookName = (callExpression) => {
46598
+ const callee = callExpression.callee;
46599
+ if (isNodeOfType(callee, "Identifier")) {
46600
+ const importBinding = getImportBindingForName(callExpression, callee.name);
46601
+ if (importBinding === null) return TANSTACK_QUERY_HOOKS.has(callee.name) ? callee.name : null;
46602
+ if (importBinding.isNamespace || !isTanstackQuerySource(importBinding.source)) return null;
46603
+ return importBinding.exportedName !== null && TANSTACK_QUERY_HOOKS.has(importBinding.exportedName) ? importBinding.exportedName : null;
46604
+ }
46605
+ if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.property, "Identifier") && TANSTACK_QUERY_HOOKS.has(callee.property.name) && isNodeOfType(callee.object, "Identifier")) {
46606
+ const namespaceBinding = getImportBindingForName(callExpression, callee.object.name);
46607
+ if (namespaceBinding?.isNamespace && isTanstackQuerySource(namespaceBinding.source)) return callee.property.name;
46608
+ }
46609
+ return null;
46610
+ };
46611
+ const resolveHookNameFromInitializer = (initializer, scopes) => {
46612
+ if (isNodeOfType(initializer, "CallExpression")) return resolveTanstackQueryHookName(initializer);
46613
+ if (!isNodeOfType(initializer, "Identifier")) return null;
46614
+ const resolvedSymbol = resolveConstIdentifierAlias(initializer, scopes);
46615
+ if (resolvedSymbol?.kind !== "const" || !resolvedSymbol.initializer) return null;
46616
+ if (!isNodeOfType(resolvedSymbol.initializer, "CallExpression")) return null;
46617
+ return resolveTanstackQueryHookName(resolvedSymbol.initializer);
46618
+ };
47360
46619
  const queryNoRestDestructuring = defineRule({
47361
46620
  id: "query-no-rest-destructuring",
47362
46621
  title: "Rest destructuring on query result",
@@ -47368,7 +46627,7 @@ const queryNoRestDestructuring = defineRule({
47368
46627
  if (!isNodeOfType(node.id, "ObjectPattern")) return;
47369
46628
  if (!node.init) return;
47370
46629
  if (!node.id.properties?.some((property) => isNodeOfType(property, "RestElement"))) return;
47371
- const hookName = resolveTanstackQueryHookNameFromInitializer(node.init, context.scopes);
46630
+ const hookName = resolveHookNameFromInitializer(node.init, context.scopes);
47372
46631
  if (!hookName) return;
47373
46632
  context.report({
47374
46633
  node: node.id,
@@ -47612,7 +46871,7 @@ const reactCompilerNoManualMemoization = defineRule({
47612
46871
  });
47613
46872
  //#endregion
47614
46873
  //#region src/plugin/rules/react-builtins/react-in-jsx-scope.ts
47615
- const MESSAGE$7 = "This JSX crashes because `React` isn't in scope.";
46874
+ const MESSAGE$6 = "This JSX crashes because `React` isn't in scope.";
47616
46875
  const reactInJsxScope = defineRule({
47617
46876
  id: "react-in-jsx-scope",
47618
46877
  title: "React not in scope for JSX",
@@ -47624,190 +46883,19 @@ const reactInJsxScope = defineRule({
47624
46883
  if (findVariableInitializer(node, "React")) return;
47625
46884
  context.report({
47626
46885
  node: node.name,
47627
- message: MESSAGE$7
46886
+ message: MESSAGE$6
47628
46887
  });
47629
46888
  },
47630
46889
  JSXFragment(node) {
47631
46890
  if (findVariableInitializer(node, "React")) return;
47632
46891
  context.report({
47633
46892
  node: node.openingFragment,
47634
- message: MESSAGE$7
46893
+ message: MESSAGE$6
47635
46894
  });
47636
46895
  }
47637
46896
  })
47638
46897
  });
47639
46898
  //#endregion
47640
- //#region src/plugin/rules/security/react-markdown-unsanitized-raw-html.ts
47641
- const MESSAGE$6 = "React Markdown parses dynamic raw HTML when `rehype-raw` is enabled. Add `rehype-sanitize` to `rehypePlugins` or sanitize the markdown before rendering it.";
47642
- const REACT_MARKDOWN_MODULE = "react-markdown";
47643
- const REHYPE_RAW_MODULE = "rehype-raw";
47644
- const REHYPE_SANITIZE_MODULE = "rehype-sanitize";
47645
- const DOMPURIFY_MODULES = new Set(["dompurify", "isomorphic-dompurify"]);
47646
- const REACT_MARKDOWN_NAMED_EXPORTS = new Set(["MarkdownAsync", "MarkdownHooks"]);
47647
- const REACT_MARKDOWN_NAMESPACE_EXPORTS = new Set(["default", ...REACT_MARKDOWN_NAMED_EXPORTS]);
47648
- const DEFAULT_EXPORT_NAMES = new Set(["default"]);
47649
- const getImportDeclaration = (symbol) => {
47650
- if (symbol.kind !== "import") return null;
47651
- const importDeclaration = symbol.declarationNode.parent;
47652
- return isNodeOfType(importDeclaration, "ImportDeclaration") ? importDeclaration : null;
47653
- };
47654
- const isImportFromModule = (symbol, moduleName) => getImportDeclaration(symbol)?.source.value === moduleName;
47655
- const isDefaultImportSymbol = (symbol, moduleName) => {
47656
- if (!isImportFromModule(symbol, moduleName)) return false;
47657
- return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || getImportedName(symbol.declarationNode) === "default";
47658
- };
47659
- const resolveImportedIdentifier = (node, scopes) => {
47660
- if (!isNodeOfType(node, "Identifier") && !isNodeOfType(node, "JSXIdentifier")) return null;
47661
- const symbol = resolveConstIdentifierAlias(node, scopes);
47662
- return symbol?.kind === "import" ? symbol : null;
47663
- };
47664
- const getStaticMemberParts = (node) => {
47665
- if (isNodeOfType(node, "MemberExpression")) {
47666
- if (node.computed || !isNodeOfType(node.property, "Identifier")) return null;
47667
- return {
47668
- object: node.object,
47669
- propertyName: node.property.name
47670
- };
47671
- }
47672
- if (isNodeOfType(node, "JSXMemberExpression")) {
47673
- if (!isNodeOfType(node.property, "JSXIdentifier")) return null;
47674
- return {
47675
- object: node.object,
47676
- propertyName: node.property.name
47677
- };
47678
- }
47679
- return null;
47680
- };
47681
- const isNamespaceMemberFromModule = (node, moduleName, memberNames, scopes) => {
47682
- const memberParts = getStaticMemberParts(node);
47683
- if (!memberParts || !memberNames.has(memberParts.propertyName)) return false;
47684
- const namespaceSymbol = resolveImportedIdentifier(memberParts.object, scopes);
47685
- return Boolean(namespaceSymbol && isImportFromModule(namespaceSymbol, moduleName) && isNodeOfType(namespaceSymbol.declarationNode, "ImportNamespaceSpecifier"));
47686
- };
47687
- const isReactMarkdownComponent = (node, scopes) => {
47688
- if (isNodeOfType(node, "JSXIdentifier")) {
47689
- const symbol = resolveImportedIdentifier(node, scopes);
47690
- if (!symbol || !isImportFromModule(symbol, REACT_MARKDOWN_MODULE)) return false;
47691
- if (isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier")) return true;
47692
- const importedName = getImportedName(symbol.declarationNode);
47693
- return importedName === "default" || Boolean(importedName && REACT_MARKDOWN_NAMED_EXPORTS.has(importedName));
47694
- }
47695
- return isNamespaceMemberFromModule(node, REACT_MARKDOWN_MODULE, REACT_MARKDOWN_NAMESPACE_EXPORTS, scopes);
47696
- };
47697
- const isPluginFromModule = (rawNode, moduleName, scopes, visitedSymbolIds) => {
47698
- const node = stripParenExpression(rawNode);
47699
- if (isNodeOfType(node, "Identifier")) {
47700
- const symbol = resolveConstIdentifierAlias(node, scopes);
47701
- if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
47702
- if (isDefaultImportSymbol(symbol, moduleName)) return true;
47703
- if (symbol.kind !== "const" || !symbol.initializer) return false;
47704
- visitedSymbolIds.add(symbol.id);
47705
- return isPluginFromModule(symbol.initializer, moduleName, scopes, visitedSymbolIds);
47706
- }
47707
- if (isNamespaceMemberFromModule(node, moduleName, DEFAULT_EXPORT_NAMES, scopes)) return true;
47708
- if (!isNodeOfType(node, "ArrayExpression")) return false;
47709
- for (const element of node.elements) {
47710
- if (!element || isNodeOfType(element, "SpreadElement")) continue;
47711
- return isPluginFromModule(element, moduleName, scopes, visitedSymbolIds);
47712
- }
47713
- return false;
47714
- };
47715
- const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
47716
- const node = stripParenExpression(rawNode);
47717
- if (isNodeOfType(node, "Identifier")) {
47718
- const symbol = scopes.referenceFor(node)?.resolvedSymbol;
47719
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
47720
- visitedSymbolIds.add(symbol.id);
47721
- return collectPluginEntries(symbol.initializer, scopes, visitedSymbolIds);
47722
- }
47723
- if (!isNodeOfType(node, "ArrayExpression")) return null;
47724
- const entries = [];
47725
- for (const element of node.elements) {
47726
- if (!element) continue;
47727
- if (isNodeOfType(element, "SpreadElement")) {
47728
- const spreadEntries = collectPluginEntries(element.argument, scopes, new Set(visitedSymbolIds));
47729
- if (spreadEntries === null) return null;
47730
- entries.push(...spreadEntries);
47731
- continue;
47732
- }
47733
- entries.push(element);
47734
- }
47735
- return entries;
47736
- };
47737
- const findEffectiveExplicitAttribute = (attributes, attributeName) => {
47738
- for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
47739
- const attribute = attributes[attributeIndex];
47740
- if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
47741
- if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
47742
- }
47743
- return null;
47744
- };
47745
- const getAttributeExpression = (attribute) => {
47746
- if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
47747
- return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
47748
- };
47749
- const isDomPurifyNamespace = (node, scopes) => {
47750
- const symbol = resolveImportedIdentifier(node, scopes);
47751
- if (!symbol) return false;
47752
- const importDeclaration = getImportDeclaration(symbol);
47753
- if (!importDeclaration || !DOMPURIFY_MODULES.has(String(importDeclaration.source.value))) return false;
47754
- return isNodeOfType(symbol.declarationNode, "ImportDefaultSpecifier") || isNodeOfType(symbol.declarationNode, "ImportNamespaceSpecifier") || getImportedName(symbol.declarationNode) === "default";
47755
- };
47756
- const isDomPurifySanitizeCall = (node, scopes) => {
47757
- if (!isNodeOfType(node, "CallExpression")) return false;
47758
- const memberParts = getStaticMemberParts(stripParenExpression(node.callee));
47759
- return Boolean(memberParts && memberParts.propertyName === "sanitize" && isDomPurifyNamespace(memberParts.object, scopes));
47760
- };
47761
- const isStaticOrSanitizedMarkdownExpression = (rawNode, scopes, visitedSymbolIds) => {
47762
- const node = stripParenExpression(rawNode);
47763
- if (isNodeOfType(node, "Literal")) return true;
47764
- if (isNodeOfType(node, "TemplateLiteral")) return node.expressions.every((expression) => isStaticOrSanitizedMarkdownExpression(expression, scopes, new Set(visitedSymbolIds)));
47765
- if (isDomPurifySanitizeCall(node, scopes)) return true;
47766
- if (isNodeOfType(node, "ConditionalExpression")) return isStaticOrSanitizedMarkdownExpression(node.consequent, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.alternate, scopes, new Set(visitedSymbolIds));
47767
- if (isNodeOfType(node, "BinaryExpression") && node.operator === "+") return isStaticOrSanitizedMarkdownExpression(node.left, scopes, new Set(visitedSymbolIds)) && isStaticOrSanitizedMarkdownExpression(node.right, scopes, new Set(visitedSymbolIds));
47768
- if (!isNodeOfType(node, "Identifier")) return false;
47769
- const symbol = scopes.referenceFor(node)?.resolvedSymbol;
47770
- if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return false;
47771
- visitedSymbolIds.add(symbol.id);
47772
- return isStaticOrSanitizedMarkdownExpression(symbol.initializer, scopes, visitedSymbolIds);
47773
- };
47774
- const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
47775
- const jsxElement = openingElement.parent;
47776
- if (!isNodeOfType(jsxElement, "JSXElement")) return false;
47777
- const meaningfulChildren = jsxElement.children.filter((child) => !isNodeOfType(child, "JSXText") || child.value.trim().length > 0);
47778
- if (meaningfulChildren.length > 0) return meaningfulChildren.some((child) => {
47779
- if (isNodeOfType(child, "JSXText")) return false;
47780
- if (!isNodeOfType(child, "JSXExpressionContainer")) return true;
47781
- if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
47782
- return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
47783
- });
47784
- const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
47785
- if (!childrenAttribute) return false;
47786
- const childrenExpression = getAttributeExpression(childrenAttribute);
47787
- return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
47788
- };
47789
- const reactMarkdownUnsanitizedRawHtml = defineRule({
47790
- id: "react-markdown-unsanitized-raw-html",
47791
- title: "Unsanitized raw HTML in React Markdown",
47792
- severity: "warn",
47793
- recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
47794
- create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
47795
- if (!isReactMarkdownComponent(node.name, context.scopes)) return;
47796
- const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
47797
- if (!pluginsAttribute) return;
47798
- const pluginsExpression = getAttributeExpression(pluginsAttribute);
47799
- if (!pluginsExpression) return;
47800
- const pluginEntries = collectPluginEntries(pluginsExpression, context.scopes, /* @__PURE__ */ new Set());
47801
- if (pluginEntries === null) return;
47802
- if (!pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_RAW_MODULE, context.scopes, /* @__PURE__ */ new Set()))) return;
47803
- if (pluginEntries.some((entry) => isPluginFromModule(entry, REHYPE_SANITIZE_MODULE, context.scopes, /* @__PURE__ */ new Set())) || !hasDynamicUnsanitizedChildren(node, context.scopes)) return;
47804
- context.report({
47805
- node,
47806
- message: MESSAGE$6
47807
- });
47808
- } }))
47809
- });
47810
- //#endregion
47811
46899
  //#region src/plugin/utils/collect-react-redux-selector-aliases.ts
47812
46900
  const REACT_REDUX_MODULE = "react-redux";
47813
46901
  const collectReactReduxSelectorAliases = (programRoot) => {
@@ -56235,6 +55323,22 @@ const isInsideClassComponent = (node) => {
56235
55323
  }
56236
55324
  return false;
56237
55325
  };
55326
+ const hasShortCircuitAncestor = (descendant, ancestor) => {
55327
+ let current = descendant.parent;
55328
+ while (current && current !== ancestor) {
55329
+ if (isNodeOfType(current, "ConditionalExpression") && (isWithinRange(descendant, current.consequent) || isWithinRange(descendant, current.alternate))) return true;
55330
+ if (isNodeOfType(current, "LogicalExpression") && (current.operator === "&&" || current.operator === "||" || current.operator === "??") && isWithinRange(descendant, current.right)) return true;
55331
+ if (isNodeOfType(current, "AssignmentPattern") && isWithinRange(descendant, current.right)) return true;
55332
+ current = current.parent ?? null;
55333
+ }
55334
+ return false;
55335
+ };
55336
+ const isWithinRange = (descendant, sibling) => {
55337
+ const descendantSpan = descendant;
55338
+ const siblingSpan = sibling;
55339
+ if (typeof descendantSpan.start !== "number" || typeof siblingSpan.start !== "number" || typeof siblingSpan.end !== "number") return false;
55340
+ return descendantSpan.start >= siblingSpan.start && (descendantSpan.end ?? siblingSpan.end) <= siblingSpan.end;
55341
+ };
56238
55342
  const isInsideTry = (descendant, ancestor) => {
56239
55343
  let current = descendant.parent;
56240
55344
  while (current && current !== ancestor) {
@@ -56426,7 +55530,7 @@ const rulesOfHooks = defineRule({
56426
55530
  });
56427
55531
  return;
56428
55532
  }
56429
- if (isNodeConditionallyExecuted(node, enclosing.node)) {
55533
+ if (hasShortCircuitAncestor(node, enclosing.node)) {
56430
55534
  context.report({
56431
55535
  node: node.callee,
56432
55536
  message: buildConditionalMessage(hookName)
@@ -63041,17 +62145,6 @@ const reactDoctorRules = [
63041
62145
  requires: [...new Set(["react", ...reactInJsxScope.requires ?? []])]
63042
62146
  }
63043
62147
  },
63044
- {
63045
- key: "react-doctor/react-markdown-unsanitized-raw-html",
63046
- id: "react-markdown-unsanitized-raw-html",
63047
- source: "react-doctor",
63048
- originallyExternal: false,
63049
- rule: {
63050
- ...reactMarkdownUnsanitizedRawHtml,
63051
- framework: "global",
63052
- category: "Security"
63053
- }
63054
- },
63055
62148
  {
63056
62149
  key: "react-doctor/redux-useselector-inline-derivation",
63057
62150
  id: "redux-useselector-inline-derivation",