oxlint-plugin-react-doctor 0.7.9-dev.194cc06 → 0.7.9-dev.61111f1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +1843 -1242
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2339,8 +2339,309 @@ const anchorAmbiguousText = defineRule({
2339
2339
  }
2340
2340
  });
2341
2341
  //#endregion
2342
+ //#region src/plugin/constants/aria-roles.ts
2343
+ const VALID_ARIA_ROLES = new Set([
2344
+ "alert",
2345
+ "alertdialog",
2346
+ "application",
2347
+ "article",
2348
+ "banner",
2349
+ "blockquote",
2350
+ "button",
2351
+ "caption",
2352
+ "cell",
2353
+ "checkbox",
2354
+ "code",
2355
+ "columnheader",
2356
+ "combobox",
2357
+ "complementary",
2358
+ "contentinfo",
2359
+ "definition",
2360
+ "deletion",
2361
+ "dialog",
2362
+ "directory",
2363
+ "doc-abstract",
2364
+ "doc-acknowledgments",
2365
+ "doc-afterword",
2366
+ "doc-appendix",
2367
+ "doc-backlink",
2368
+ "doc-biblioentry",
2369
+ "doc-bibliography",
2370
+ "doc-biblioref",
2371
+ "doc-chapter",
2372
+ "doc-colophon",
2373
+ "doc-conclusion",
2374
+ "doc-cover",
2375
+ "doc-credit",
2376
+ "doc-credits",
2377
+ "doc-dedication",
2378
+ "doc-endnote",
2379
+ "doc-endnotes",
2380
+ "doc-epigraph",
2381
+ "doc-epilogue",
2382
+ "doc-errata",
2383
+ "doc-example",
2384
+ "doc-footnote",
2385
+ "doc-foreword",
2386
+ "doc-glossary",
2387
+ "doc-glossref",
2388
+ "doc-index",
2389
+ "doc-introduction",
2390
+ "doc-noteref",
2391
+ "doc-notice",
2392
+ "doc-pagebreak",
2393
+ "doc-pagelist",
2394
+ "doc-part",
2395
+ "doc-preface",
2396
+ "doc-prologue",
2397
+ "doc-pullquote",
2398
+ "doc-qna",
2399
+ "doc-subtitle",
2400
+ "doc-tip",
2401
+ "doc-toc",
2402
+ "document",
2403
+ "emphasis",
2404
+ "feed",
2405
+ "figure",
2406
+ "form",
2407
+ "generic",
2408
+ "graphics-document",
2409
+ "graphics-object",
2410
+ "graphics-symbol",
2411
+ "grid",
2412
+ "gridcell",
2413
+ "group",
2414
+ "heading",
2415
+ "img",
2416
+ "insertion",
2417
+ "link",
2418
+ "list",
2419
+ "listbox",
2420
+ "listitem",
2421
+ "log",
2422
+ "main",
2423
+ "mark",
2424
+ "marquee",
2425
+ "math",
2426
+ "menu",
2427
+ "menubar",
2428
+ "menuitem",
2429
+ "menuitemcheckbox",
2430
+ "menuitemradio",
2431
+ "meter",
2432
+ "navigation",
2433
+ "none",
2434
+ "note",
2435
+ "option",
2436
+ "paragraph",
2437
+ "presentation",
2438
+ "progressbar",
2439
+ "radio",
2440
+ "radiogroup",
2441
+ "region",
2442
+ "row",
2443
+ "rowgroup",
2444
+ "rowheader",
2445
+ "scrollbar",
2446
+ "search",
2447
+ "searchbox",
2448
+ "separator",
2449
+ "slider",
2450
+ "spinbutton",
2451
+ "status",
2452
+ "strong",
2453
+ "subscript",
2454
+ "superscript",
2455
+ "switch",
2456
+ "tab",
2457
+ "table",
2458
+ "tablist",
2459
+ "tabpanel",
2460
+ "term",
2461
+ "textbox",
2462
+ "time",
2463
+ "timer",
2464
+ "toolbar",
2465
+ "tooltip",
2466
+ "tree",
2467
+ "treegrid",
2468
+ "treeitem"
2469
+ ]);
2470
+ const INTERACTIVE_ROLES = new Set([
2471
+ "button",
2472
+ "checkbox",
2473
+ "columnheader",
2474
+ "combobox",
2475
+ "grid",
2476
+ "gridcell",
2477
+ "link",
2478
+ "listbox",
2479
+ "menu",
2480
+ "menubar",
2481
+ "menuitem",
2482
+ "menuitemcheckbox",
2483
+ "menuitemradio",
2484
+ "option",
2485
+ "radio",
2486
+ "radiogroup",
2487
+ "row",
2488
+ "rowheader",
2489
+ "scrollbar",
2490
+ "searchbox",
2491
+ "separator",
2492
+ "slider",
2493
+ "spinbutton",
2494
+ "switch",
2495
+ "tab",
2496
+ "tablist",
2497
+ "textbox",
2498
+ "toolbar",
2499
+ "tree",
2500
+ "treegrid",
2501
+ "treeitem"
2502
+ ]);
2503
+ const NON_INTERACTIVE_ROLES = new Set([
2504
+ "alert",
2505
+ "alertdialog",
2506
+ "application",
2507
+ "article",
2508
+ "banner",
2509
+ "blockquote",
2510
+ "caption",
2511
+ "cell",
2512
+ "complementary",
2513
+ "contentinfo",
2514
+ "definition",
2515
+ "deletion",
2516
+ "dialog",
2517
+ "directory",
2518
+ "document",
2519
+ "feed",
2520
+ "figure",
2521
+ "form",
2522
+ "group",
2523
+ "heading",
2524
+ "img",
2525
+ "insertion",
2526
+ "list",
2527
+ "listitem",
2528
+ "log",
2529
+ "main",
2530
+ "marquee",
2531
+ "math",
2532
+ "navigation",
2533
+ "note",
2534
+ "paragraph",
2535
+ "progressbar",
2536
+ "region",
2537
+ "rowgroup",
2538
+ "search",
2539
+ "status",
2540
+ "table",
2541
+ "tabpanel",
2542
+ "term",
2543
+ "time",
2544
+ "timer",
2545
+ "tooltip"
2546
+ ]);
2547
+ const ABSTRACT_ROLES = new Set([
2548
+ "command",
2549
+ "composite",
2550
+ "input",
2551
+ "landmark",
2552
+ "range",
2553
+ "roletype",
2554
+ "section",
2555
+ "sectionhead",
2556
+ "select",
2557
+ "structure",
2558
+ "widget",
2559
+ "window"
2560
+ ]);
2561
+ const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
2562
+ //#endregion
2563
+ //#region src/plugin/utils/read-static-boolean.ts
2564
+ const readStaticBoolean = (node) => {
2565
+ if (!node) return null;
2566
+ const unwrappedNode = stripParenExpression(node);
2567
+ if (!isNodeOfType(unwrappedNode, "Literal") || typeof unwrappedNode.value !== "boolean") return null;
2568
+ return unwrappedNode.value;
2569
+ };
2570
+ //#endregion
2571
+ //#region src/plugin/utils/get-jsx-prop-static-string-values.ts
2572
+ const MAX_CONST_RESOLUTION_HOPS = 4;
2573
+ const resolveStaticStringValues = (rawExpression, scopes, maximumConstAliases, shouldFoldStaticConditions) => {
2574
+ const staticStringValues = [];
2575
+ const workItems = [{
2576
+ expression: rawExpression,
2577
+ remainingConstAliases: maximumConstAliases,
2578
+ resolvingSymbols: /* @__PURE__ */ new Set()
2579
+ }];
2580
+ while (workItems.length > 0) {
2581
+ const workItem = workItems.pop();
2582
+ if (!workItem) continue;
2583
+ const expression = stripParenExpression(workItem.expression);
2584
+ if (isNodeOfType(expression, "Literal")) {
2585
+ if (typeof expression.value !== "string") return null;
2586
+ staticStringValues.push(expression.value);
2587
+ continue;
2588
+ }
2589
+ if (isNodeOfType(expression, "TemplateLiteral")) {
2590
+ const staticValue = getStaticTemplateLiteralValue(expression);
2591
+ if (staticValue === null) return null;
2592
+ staticStringValues.push(staticValue);
2593
+ continue;
2594
+ }
2595
+ if (isNodeOfType(expression, "ConditionalExpression")) {
2596
+ const staticTestValue = shouldFoldStaticConditions ? readStaticBoolean(expression.test) : null;
2597
+ if (staticTestValue !== null) {
2598
+ workItems.push({
2599
+ expression: staticTestValue ? expression.consequent : expression.alternate,
2600
+ remainingConstAliases: workItem.remainingConstAliases,
2601
+ resolvingSymbols: workItem.resolvingSymbols
2602
+ });
2603
+ continue;
2604
+ }
2605
+ workItems.push({
2606
+ expression: expression.alternate,
2607
+ remainingConstAliases: workItem.remainingConstAliases,
2608
+ resolvingSymbols: new Set(workItem.resolvingSymbols)
2609
+ });
2610
+ workItems.push({
2611
+ expression: expression.consequent,
2612
+ remainingConstAliases: workItem.remainingConstAliases,
2613
+ resolvingSymbols: new Set(workItem.resolvingSymbols)
2614
+ });
2615
+ continue;
2616
+ }
2617
+ if (isNodeOfType(expression, "Identifier")) {
2618
+ const symbol = scopes.referenceFor(expression)?.resolvedSymbol;
2619
+ if (!symbol || symbol.kind !== "const" || !symbol.initializer || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier || workItem.remainingConstAliases === 0 || workItem.resolvingSymbols.has(symbol)) return null;
2620
+ workItem.resolvingSymbols.add(symbol);
2621
+ workItems.push({
2622
+ expression: symbol.initializer,
2623
+ remainingConstAliases: workItem.remainingConstAliases === null ? null : workItem.remainingConstAliases - 1,
2624
+ resolvingSymbols: workItem.resolvingSymbols
2625
+ });
2626
+ continue;
2627
+ }
2628
+ return null;
2629
+ }
2630
+ return staticStringValues;
2631
+ };
2632
+ const getJsxPropStaticStringValuesWithMode = (attribute, scopes, maximumConstAliases, shouldFoldStaticConditions) => {
2633
+ const value = attribute.value;
2634
+ if (!value) return null;
2635
+ if (isNodeOfType(value, "Literal")) return typeof value.value === "string" ? [value.value] : null;
2636
+ if (isNodeOfType(value, "JSXExpressionContainer")) return resolveStaticStringValues(value.expression, scopes, maximumConstAliases, shouldFoldStaticConditions);
2637
+ return null;
2638
+ };
2639
+ const getJsxPropStaticStringValues = (attribute, scopes) => getJsxPropStaticStringValuesWithMode(attribute, scopes, MAX_CONST_RESOLUTION_HOPS, false);
2640
+ const getJsxPropExhaustiveStaticStringValues = (attribute, scopes) => getJsxPropStaticStringValuesWithMode(attribute, scopes, null, true);
2641
+ //#endregion
2342
2642
  //#region src/plugin/rules/a11y/anchor-has-content.ts
2343
2643
  const MESSAGE$62 = "Blind users can't follow this link because screen readers announce nothing, so add visible text, `aria-label`, or `aria-labelledby`.";
2644
+ const hasLinkRole = (roleValue) => roleValue.trim().split(/\s+/).find((roleToken) => VALID_ARIA_ROLES.has(roleToken)) === "link";
2344
2645
  const isTransComponentsTemplate = (node) => {
2345
2646
  let current = node.parent;
2346
2647
  while (current) {
@@ -2358,7 +2659,7 @@ const anchorHasContent = defineRule({
2358
2659
  title: "Anchor has no content",
2359
2660
  tags: ["react-jsx-only"],
2360
2661
  severity: "warn",
2361
- recommendation: "Put readable text inside every `<a>`.",
2662
+ recommendation: "Put readable text inside every link.",
2362
2663
  category: "Accessibility",
2363
2664
  create: (context) => {
2364
2665
  const isTestlikeFile = isTestlikeFilename(context.filename);
@@ -2366,6 +2667,11 @@ const anchorHasContent = defineRule({
2366
2667
  if (isTestlikeFile) return;
2367
2668
  const opening = node.openingElement;
2368
2669
  if (getElementType(opening, context.settings) !== "a") return;
2670
+ const hrefAttribute = hasJsxPropIgnoreCase(opening.attributes, "href");
2671
+ const roleAttribute = hasJsxPropIgnoreCase(opening.attributes, "role");
2672
+ const roleValues = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : [];
2673
+ const canHaveLinkRole = roleValues === null || roleValues.some((roleValue) => hasLinkRole(roleValue));
2674
+ if (!hrefAttribute && !canHaveLinkRole) return;
2369
2675
  if (isHiddenFromScreenReader(opening, context.settings)) return;
2370
2676
  if (objectHasAccessibleChild(node, context.settings)) return;
2371
2677
  for (const attribute of [
@@ -2382,39 +2688,6 @@ const anchorHasContent = defineRule({
2382
2688
  }
2383
2689
  });
2384
2690
  //#endregion
2385
- //#region src/plugin/utils/get-jsx-prop-static-string-values.ts
2386
- const MAX_CONST_RESOLUTION_HOPS = 4;
2387
- const resolveStaticStringValues = (rawExpression, scopes, remainingHops) => {
2388
- const expression = stripParenExpression(rawExpression);
2389
- if (isNodeOfType(expression, "Literal")) return typeof expression.value === "string" ? [expression.value] : null;
2390
- if (isNodeOfType(expression, "TemplateLiteral")) {
2391
- const staticValue = getStaticTemplateLiteralValue(expression);
2392
- return staticValue === null ? null : [staticValue];
2393
- }
2394
- if (isNodeOfType(expression, "ConditionalExpression")) {
2395
- const consequentValues = resolveStaticStringValues(expression.consequent, scopes, remainingHops);
2396
- if (consequentValues === null) return null;
2397
- const alternateValues = resolveStaticStringValues(expression.alternate, scopes, remainingHops);
2398
- if (alternateValues === null) return null;
2399
- return [...consequentValues, ...alternateValues];
2400
- }
2401
- if (isNodeOfType(expression, "Identifier")) {
2402
- if (remainingHops === 0) return null;
2403
- const symbol = scopes.referenceFor(expression)?.resolvedSymbol;
2404
- if (!symbol || symbol.kind !== "const" || !symbol.initializer) return null;
2405
- if (!isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
2406
- return resolveStaticStringValues(symbol.initializer, scopes, remainingHops - 1);
2407
- }
2408
- return null;
2409
- };
2410
- const getJsxPropStaticStringValues = (attribute, scopes) => {
2411
- const value = attribute.value;
2412
- if (!value) return null;
2413
- if (isNodeOfType(value, "Literal")) return typeof value.value === "string" ? [value.value] : null;
2414
- if (isNodeOfType(value, "JSXExpressionContainer")) return resolveStaticStringValues(value.expression, scopes, MAX_CONST_RESOLUTION_HOPS);
2415
- return null;
2416
- };
2417
- //#endregion
2418
2691
  //#region src/plugin/rules/a11y/anchor-is-valid.ts
2419
2692
  const MESSAGE_MISSING_HREF = "Keyboard users can't reach this link because it has no `href`, so add a real `href` (or use `<button>` for actions).";
2420
2693
  const MESSAGE_INCORRECT_HREF = "Keyboard users can't reach this link because its `href` goes nowhere (`#`, `javascript:`, or empty), so point it at a real destination.";
@@ -2737,6 +3010,95 @@ const NON_INTERACTIVE_ELEMENTS = new Set([
2737
3010
  "ul"
2738
3011
  ]);
2739
3012
  //#endregion
3013
+ //#region src/plugin/utils/get-direct-const-initializer.ts
3014
+ const getDirectConstInitializer = (symbol) => {
3015
+ if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
3016
+ return symbol.initializer;
3017
+ };
3018
+ //#endregion
3019
+ //#region src/plugin/utils/can-content-editable-be-tabbable.ts
3020
+ const ENABLED_CONTENT_EDITABLE_VALUES = new Set([
3021
+ "",
3022
+ "plaintext-only",
3023
+ "true"
3024
+ ]);
3025
+ const ENABLED_CONTENT_EDITABLE = {
3026
+ canBeDisabled: false,
3027
+ canBeEnabled: true,
3028
+ canBeInherited: false
3029
+ };
3030
+ const DISABLED_CONTENT_EDITABLE = {
3031
+ canBeDisabled: true,
3032
+ canBeEnabled: false,
3033
+ canBeInherited: false
3034
+ };
3035
+ const INHERITED_CONTENT_EDITABLE = {
3036
+ canBeDisabled: false,
3037
+ canBeEnabled: false,
3038
+ canBeInherited: true
3039
+ };
3040
+ const UNKNOWN_CONTENT_EDITABLE = {
3041
+ canBeDisabled: true,
3042
+ canBeEnabled: true,
3043
+ canBeInherited: true
3044
+ };
3045
+ const mergeContentEditablePossibilities = (left, right) => ({
3046
+ canBeDisabled: left.canBeDisabled || right.canBeDisabled,
3047
+ canBeEnabled: left.canBeEnabled || right.canBeEnabled,
3048
+ canBeInherited: left.canBeInherited || right.canBeInherited
3049
+ });
3050
+ const resolveContentEditableExpression = (rawExpression, scopes, visitedSymbolIds) => {
3051
+ const expression = stripParenExpression(rawExpression);
3052
+ if (isNodeOfType(expression, "Literal")) {
3053
+ if (typeof expression.value === "boolean") return expression.value ? ENABLED_CONTENT_EDITABLE : DISABLED_CONTENT_EDITABLE;
3054
+ if (typeof expression.value === "string") {
3055
+ const contentEditableValue = expression.value.toLowerCase();
3056
+ if (ENABLED_CONTENT_EDITABLE_VALUES.has(contentEditableValue)) return ENABLED_CONTENT_EDITABLE;
3057
+ return contentEditableValue === "false" ? DISABLED_CONTENT_EDITABLE : INHERITED_CONTENT_EDITABLE;
3058
+ }
3059
+ return INHERITED_CONTENT_EDITABLE;
3060
+ }
3061
+ if (isNodeOfType(expression, "ConditionalExpression")) return mergeContentEditablePossibilities(resolveContentEditableExpression(expression.consequent, scopes, visitedSymbolIds), resolveContentEditableExpression(expression.alternate, scopes, visitedSymbolIds));
3062
+ if (isNodeOfType(expression, "Identifier")) {
3063
+ const symbol = scopes.referenceFor(expression)?.resolvedSymbol;
3064
+ if (!symbol || visitedSymbolIds.has(symbol.id)) return UNKNOWN_CONTENT_EDITABLE;
3065
+ const initializer = getDirectConstInitializer(symbol);
3066
+ if (!initializer) return UNKNOWN_CONTENT_EDITABLE;
3067
+ const nextVisitedSymbolIds = new Set(visitedSymbolIds);
3068
+ nextVisitedSymbolIds.add(symbol.id);
3069
+ return resolveContentEditableExpression(initializer, scopes, nextVisitedSymbolIds);
3070
+ }
3071
+ return UNKNOWN_CONTENT_EDITABLE;
3072
+ };
3073
+ const getContentEditablePossibilities = (node, scopes) => {
3074
+ const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
3075
+ if (!attribute) return null;
3076
+ if (!attribute.value) return ENABLED_CONTENT_EDITABLE;
3077
+ if (isNodeOfType(attribute.value, "Literal")) return resolveContentEditableExpression(attribute.value, scopes, /* @__PURE__ */ new Set());
3078
+ if (isNodeOfType(attribute.value, "JSXExpressionContainer")) return resolveContentEditableExpression(attribute.value.expression, scopes, /* @__PURE__ */ new Set());
3079
+ return UNKNOWN_CONTENT_EDITABLE;
3080
+ };
3081
+ const hasDefinitelyEnabledContentEditableAncestor = (node, scopes, settings) => {
3082
+ let ancestor = node.parent?.parent;
3083
+ while (ancestor) {
3084
+ if (isNodeOfType(ancestor, "JSXElement")) {
3085
+ const openingElement = ancestor.openingElement;
3086
+ if (!HTML_TAGS.has(getElementType(openingElement, settings))) return false;
3087
+ const possibilities = getContentEditablePossibilities(openingElement, scopes);
3088
+ if (possibilities) {
3089
+ if (possibilities.canBeEnabled && !possibilities.canBeDisabled && !possibilities.canBeInherited) return true;
3090
+ if (!(possibilities.canBeInherited && !possibilities.canBeDisabled && !possibilities.canBeEnabled)) return false;
3091
+ }
3092
+ }
3093
+ ancestor = ancestor.parent;
3094
+ }
3095
+ return false;
3096
+ };
3097
+ const canContentEditableBeTabbable = (node, scopes, settings) => {
3098
+ if (!getContentEditablePossibilities(node, scopes)?.canBeEnabled) return false;
3099
+ return !hasDefinitelyEnabledContentEditableAncestor(node, scopes, settings);
3100
+ };
3101
+ //#endregion
2740
3102
  //#region src/plugin/utils/is-interactive-element.ts
2741
3103
  const isInteractiveElement = (tagName, openingElement) => {
2742
3104
  switch (tagName) {
@@ -2809,19 +3171,6 @@ const parseJsxValue = (value) => {
2809
3171
  //#endregion
2810
3172
  //#region src/plugin/rules/a11y/aria-activedescendant-has-tabindex.ts
2811
3173
  const MESSAGE$61 = "Keyboard users can't focus this element with `aria-activedescendant` because it isn't tabbable, so add `tabIndex={0}`.";
2812
- const mayBeContentEditable = (node) => {
2813
- const attribute = hasJsxPropIgnoreCase(node.attributes, "contenteditable");
2814
- if (!attribute) return false;
2815
- if (!attribute.value) return true;
2816
- const stringValue = getJsxPropStringValue(attribute);
2817
- if (stringValue !== null) return stringValue !== "false";
2818
- const value = attribute.value;
2819
- if (isNodeOfType(value, "JSXExpressionContainer")) {
2820
- const expression = value.expression;
2821
- if (isNodeOfType(expression, "Literal")) return expression.value !== false && expression.value !== "false";
2822
- }
2823
- return true;
2824
- };
2825
3174
  const ariaActivedescendantHasTabindex = defineRule({
2826
3175
  id: "aria-activedescendant-has-tabindex",
2827
3176
  title: "aria-activedescendant missing tabindex",
@@ -2844,7 +3193,7 @@ const ariaActivedescendantHasTabindex = defineRule({
2844
3193
  return;
2845
3194
  }
2846
3195
  if (isInteractiveElement(tag, node)) return;
2847
- if (mayBeContentEditable(node)) return;
3196
+ if (canContentEditableBeTabbable(node, context.scopes, context.settings)) return;
2848
3197
  context.report({
2849
3198
  node: node.name,
2850
3199
  message: MESSAGE$61
@@ -3297,227 +3646,6 @@ const ariaProptypes = defineRule({
3297
3646
  } })
3298
3647
  });
3299
3648
  //#endregion
3300
- //#region src/plugin/constants/aria-roles.ts
3301
- const VALID_ARIA_ROLES = new Set([
3302
- "alert",
3303
- "alertdialog",
3304
- "application",
3305
- "article",
3306
- "banner",
3307
- "blockquote",
3308
- "button",
3309
- "caption",
3310
- "cell",
3311
- "checkbox",
3312
- "code",
3313
- "columnheader",
3314
- "combobox",
3315
- "complementary",
3316
- "contentinfo",
3317
- "definition",
3318
- "deletion",
3319
- "dialog",
3320
- "directory",
3321
- "doc-abstract",
3322
- "doc-acknowledgments",
3323
- "doc-afterword",
3324
- "doc-appendix",
3325
- "doc-backlink",
3326
- "doc-biblioentry",
3327
- "doc-bibliography",
3328
- "doc-biblioref",
3329
- "doc-chapter",
3330
- "doc-colophon",
3331
- "doc-conclusion",
3332
- "doc-cover",
3333
- "doc-credit",
3334
- "doc-credits",
3335
- "doc-dedication",
3336
- "doc-endnote",
3337
- "doc-endnotes",
3338
- "doc-epigraph",
3339
- "doc-epilogue",
3340
- "doc-errata",
3341
- "doc-example",
3342
- "doc-footnote",
3343
- "doc-foreword",
3344
- "doc-glossary",
3345
- "doc-glossref",
3346
- "doc-index",
3347
- "doc-introduction",
3348
- "doc-noteref",
3349
- "doc-notice",
3350
- "doc-pagebreak",
3351
- "doc-pagelist",
3352
- "doc-part",
3353
- "doc-preface",
3354
- "doc-prologue",
3355
- "doc-pullquote",
3356
- "doc-qna",
3357
- "doc-subtitle",
3358
- "doc-tip",
3359
- "doc-toc",
3360
- "document",
3361
- "emphasis",
3362
- "feed",
3363
- "figure",
3364
- "form",
3365
- "generic",
3366
- "graphics-document",
3367
- "graphics-object",
3368
- "graphics-symbol",
3369
- "grid",
3370
- "gridcell",
3371
- "group",
3372
- "heading",
3373
- "img",
3374
- "insertion",
3375
- "link",
3376
- "list",
3377
- "listbox",
3378
- "listitem",
3379
- "log",
3380
- "main",
3381
- "mark",
3382
- "marquee",
3383
- "math",
3384
- "menu",
3385
- "menubar",
3386
- "menuitem",
3387
- "menuitemcheckbox",
3388
- "menuitemradio",
3389
- "meter",
3390
- "navigation",
3391
- "none",
3392
- "note",
3393
- "option",
3394
- "paragraph",
3395
- "presentation",
3396
- "progressbar",
3397
- "radio",
3398
- "radiogroup",
3399
- "region",
3400
- "row",
3401
- "rowgroup",
3402
- "rowheader",
3403
- "scrollbar",
3404
- "search",
3405
- "searchbox",
3406
- "separator",
3407
- "slider",
3408
- "spinbutton",
3409
- "status",
3410
- "strong",
3411
- "subscript",
3412
- "superscript",
3413
- "switch",
3414
- "tab",
3415
- "table",
3416
- "tablist",
3417
- "tabpanel",
3418
- "term",
3419
- "textbox",
3420
- "time",
3421
- "timer",
3422
- "toolbar",
3423
- "tooltip",
3424
- "tree",
3425
- "treegrid",
3426
- "treeitem"
3427
- ]);
3428
- const INTERACTIVE_ROLES = new Set([
3429
- "button",
3430
- "checkbox",
3431
- "columnheader",
3432
- "combobox",
3433
- "grid",
3434
- "gridcell",
3435
- "link",
3436
- "listbox",
3437
- "menu",
3438
- "menubar",
3439
- "menuitem",
3440
- "menuitemcheckbox",
3441
- "menuitemradio",
3442
- "option",
3443
- "radio",
3444
- "radiogroup",
3445
- "row",
3446
- "rowheader",
3447
- "scrollbar",
3448
- "searchbox",
3449
- "separator",
3450
- "slider",
3451
- "spinbutton",
3452
- "switch",
3453
- "tab",
3454
- "tablist",
3455
- "textbox",
3456
- "toolbar",
3457
- "tree",
3458
- "treegrid",
3459
- "treeitem"
3460
- ]);
3461
- const NON_INTERACTIVE_ROLES = new Set([
3462
- "alert",
3463
- "alertdialog",
3464
- "application",
3465
- "article",
3466
- "banner",
3467
- "blockquote",
3468
- "caption",
3469
- "cell",
3470
- "complementary",
3471
- "contentinfo",
3472
- "definition",
3473
- "deletion",
3474
- "dialog",
3475
- "directory",
3476
- "document",
3477
- "feed",
3478
- "figure",
3479
- "form",
3480
- "group",
3481
- "heading",
3482
- "img",
3483
- "insertion",
3484
- "list",
3485
- "listitem",
3486
- "log",
3487
- "main",
3488
- "marquee",
3489
- "math",
3490
- "navigation",
3491
- "note",
3492
- "paragraph",
3493
- "progressbar",
3494
- "region",
3495
- "rowgroup",
3496
- "search",
3497
- "status",
3498
- "table",
3499
- "tabpanel",
3500
- "term",
3501
- "time",
3502
- "timer",
3503
- "tooltip"
3504
- ]);
3505
- const ABSTRACT_ROLES = new Set([
3506
- "command",
3507
- "composite",
3508
- "input",
3509
- "landmark",
3510
- "range",
3511
- "roletype",
3512
- "section",
3513
- "sectionhead",
3514
- "select",
3515
- "structure",
3516
- "widget",
3517
- "window"
3518
- ]);
3519
- const PRESENTATION_ROLES$1 = new Set(["presentation", "none"]);
3520
- //#endregion
3521
3649
  //#region src/plugin/rules/a11y/aria-role.ts
3522
3650
  const buildBaseMessage = (suffix) => `This \`role\` is not a valid ARIA role, so assistive tech cannot expose it correctly. Use a real, non-abstract role.${suffix}`;
3523
3651
  const resolveSettings$50 = (settings) => {
@@ -5187,133 +5315,644 @@ const asyncAwaitInLoop = defineRule({
5187
5315
  }
5188
5316
  });
5189
5317
  //#endregion
5190
- //#region src/plugin/utils/collect-pattern-default-reference-names.ts
5191
- const collectPatternDefaultReferenceNames = (pattern, into) => {
5192
- if (!pattern) return;
5193
- if (isNodeOfType(pattern, "AssignmentPattern")) {
5194
- collectReferenceIdentifierNames(pattern.right, into);
5195
- collectPatternDefaultReferenceNames(pattern.left, into);
5318
+ //#region src/plugin/semantic/scope-analysis.ts
5319
+ const isHoistedBindingKind = (kind) => kind === "var" || kind === "function";
5320
+ const findHoistTargetScope = (scope) => {
5321
+ let current = scope;
5322
+ while (current) {
5323
+ if (current.kind === "module" || current.kind === "function" || current.kind === "arrow-function" || current.kind === "method") return current;
5324
+ current = current.parent;
5325
+ }
5326
+ return scope;
5327
+ };
5328
+ const createScope = (kind, node, parent, state) => {
5329
+ const scope = {
5330
+ id: state.nextScopeId++,
5331
+ kind,
5332
+ node,
5333
+ parent,
5334
+ children: [],
5335
+ symbols: [],
5336
+ references: [],
5337
+ symbolsByName: /* @__PURE__ */ new Map()
5338
+ };
5339
+ if (parent) parent.children.push(scope);
5340
+ return scope;
5341
+ };
5342
+ const pushScope = (kind, node, state) => {
5343
+ const scope = createScope(kind, node, state.currentScope, state);
5344
+ state.scopeStack.push(scope);
5345
+ state.currentScope = scope;
5346
+ state.ownScopeForNode.set(node, scope);
5347
+ return scope;
5348
+ };
5349
+ const popScope = (state) => {
5350
+ state.scopeStack.pop();
5351
+ const previous = state.scopeStack[state.scopeStack.length - 1];
5352
+ if (!previous) throw new Error("scope stack underflow");
5353
+ state.currentScope = previous;
5354
+ };
5355
+ const recordSymbol = (scope, state, options) => {
5356
+ const symbol = {
5357
+ id: state.nextSymbolId++,
5358
+ name: options.name,
5359
+ kind: options.kind,
5360
+ bindingIdentifier: options.bindingIdentifier,
5361
+ declarationNode: options.declarationNode,
5362
+ scope,
5363
+ initializer: options.initializer,
5364
+ references: []
5365
+ };
5366
+ scope.symbols.push(symbol);
5367
+ scope.symbolsByName.set(options.name, symbol);
5368
+ state.symbolByBindingIdentifier.set(options.bindingIdentifier, symbol);
5369
+ return symbol;
5370
+ };
5371
+ const collectBindingNamesFromPattern = (pattern) => {
5372
+ const out = [];
5373
+ const visit = (node) => {
5374
+ if (isNodeOfType(node, "Identifier")) {
5375
+ out.push(node);
5376
+ return;
5377
+ }
5378
+ if (isNodeOfType(node, "ObjectPattern")) {
5379
+ for (const property of node.properties) if (isNodeOfType(property, "Property")) {
5380
+ const propValue = property.value;
5381
+ visit(propValue);
5382
+ } else if (isNodeOfType(property, "RestElement")) visit(property.argument);
5383
+ return;
5384
+ }
5385
+ if (isNodeOfType(node, "ArrayPattern")) {
5386
+ for (const element of node.elements) if (element) visit(element);
5387
+ return;
5388
+ }
5389
+ if (isNodeOfType(node, "RestElement")) {
5390
+ visit(node.argument);
5391
+ return;
5392
+ }
5393
+ if (isNodeOfType(node, "AssignmentPattern")) visit(node.left);
5394
+ };
5395
+ visit(pattern);
5396
+ return out;
5397
+ };
5398
+ const visitDestructuringDeclarations = (pattern, baseInitializer, scope, state, symbolKind, declarationNode) => {
5399
+ if (isNodeOfType(pattern, "Identifier")) {
5400
+ recordSymbol(scope, state, {
5401
+ name: pattern.name,
5402
+ kind: symbolKind,
5403
+ bindingIdentifier: pattern,
5404
+ declarationNode,
5405
+ initializer: baseInitializer
5406
+ });
5196
5407
  return;
5197
5408
  }
5198
- if (isNodeOfType(pattern, "RestElement")) {
5199
- collectPatternDefaultReferenceNames(pattern.argument, into);
5409
+ if (isNodeOfType(pattern, "ObjectPattern")) {
5410
+ for (const property of pattern.properties) if (isNodeOfType(property, "Property")) {
5411
+ const propertyValue = property.value;
5412
+ visitDestructuringDeclarations(propertyValue, isNodeOfType(propertyValue, "AssignmentPattern") ? propertyValue.right : baseInitializer, scope, state, symbolKind, declarationNode);
5413
+ } else if (isNodeOfType(property, "RestElement")) visitDestructuringDeclarations(property.argument, baseInitializer, scope, state, symbolKind, declarationNode);
5200
5414
  return;
5201
5415
  }
5202
5416
  if (isNodeOfType(pattern, "ArrayPattern")) {
5203
- for (const element of pattern.elements ?? []) collectPatternDefaultReferenceNames(element, into);
5417
+ for (const element of pattern.elements) {
5418
+ if (!element) continue;
5419
+ visitDestructuringDeclarations(element, isNodeOfType(element, "AssignmentPattern") ? element.right ?? null : baseInitializer, scope, state, symbolKind, declarationNode);
5420
+ }
5204
5421
  return;
5205
5422
  }
5206
- if (isNodeOfType(pattern, "ObjectPattern")) {
5207
- for (const property of pattern.properties ?? []) if (isNodeOfType(property, "RestElement")) collectPatternDefaultReferenceNames(property.argument, into);
5208
- else if (isNodeOfType(property, "Property")) {
5209
- if (property.computed) collectReferenceIdentifierNames(property.key, into);
5210
- collectPatternDefaultReferenceNames(property.value, into);
5211
- }
5423
+ if (isNodeOfType(pattern, "AssignmentPattern")) {
5424
+ visitDestructuringDeclarations(pattern.left, pattern.right ?? null, scope, state, symbolKind, declarationNode);
5425
+ return;
5212
5426
  }
5427
+ if (isNodeOfType(pattern, "RestElement")) visitDestructuringDeclarations(pattern.argument, null, scope, state, symbolKind, declarationNode);
5213
5428
  };
5214
- //#endregion
5215
- //#region src/plugin/utils/is-bare-await-expression-statement.ts
5216
- const isBareAwaitExpressionStatement = (statement) => {
5217
- if (!isNodeOfType(statement, "ExpressionStatement")) return false;
5218
- return isNodeOfType(statement.expression, "AwaitExpression");
5429
+ const tagAsBinding = (state, identifier) => {
5430
+ bindingPositionMarker.add(identifier);
5219
5431
  };
5220
- //#endregion
5221
- //#region src/plugin/utils/is-early-exit-if-statement.ts
5222
- const isEarlyExitStatement = (statement) => isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
5223
- const isEarlyExitIfStatement = (statement) => {
5224
- if (!isNodeOfType(statement, "IfStatement")) return false;
5225
- const consequent = statement.consequent;
5226
- if (!consequent) return false;
5227
- if (isEarlyExitStatement(consequent)) return true;
5228
- if (!isNodeOfType(consequent, "BlockStatement")) return false;
5229
- for (const inner of consequent.body ?? []) if (isEarlyExitStatement(inner)) return true;
5230
- return false;
5432
+ const bindingPositionMarker = /* @__PURE__ */ new WeakSet();
5433
+ const recordReference = (state, identifier, flag) => {
5434
+ const reference = {
5435
+ id: state.nextReferenceId++,
5436
+ identifier,
5437
+ resolvedSymbol: null,
5438
+ flag,
5439
+ scope: state.currentScope
5440
+ };
5441
+ state.currentScope.references.push(reference);
5442
+ state.referenceByIdentifier.set(identifier, reference);
5231
5443
  };
5232
- //#endregion
5233
- //#region src/plugin/rules/performance/async-defer-await.ts
5234
- const hasAnyIdentifierName = (identifierNames, candidateNames) => {
5235
- for (const candidateName of candidateNames) if (identifierNames.has(candidateName)) return true;
5236
- return false;
5444
+ const isFunctionBodyBlock = (block) => {
5445
+ if (!block.parent) return false;
5446
+ return isFunctionLike$1(block.parent);
5237
5447
  };
5238
- const collectDeclaratorDependencyIdentifierNames = (declarator, into) => {
5239
- if (!isNodeOfType(declarator, "VariableDeclarator")) return;
5240
- collectReferenceIdentifierNames(declarator.init, into);
5241
- collectPatternDefaultReferenceNames(declarator.id, into);
5448
+ const isCatchClauseBlock = (block) => block.parent !== null && block.parent !== void 0 && block.parent.type === "CatchClause";
5449
+ const handleVariableDeclaration = (declaration, state) => {
5450
+ if (!isNodeOfType(declaration, "VariableDeclaration")) return;
5451
+ const symbolKind = declaration.kind === "var" ? "var" : declaration.kind === "let" ? "let" : declaration.kind === "const" ? "const" : "using";
5452
+ const targetScope = isHoistedBindingKind(symbolKind) ? findHoistTargetScope(state.currentScope) : state.currentScope;
5453
+ for (const declarator of declaration.declarations) {
5454
+ const declaratorNode = declarator;
5455
+ const init = declarator.init ?? null;
5456
+ visitDestructuringDeclarations(declarator.id, init, targetScope, state, symbolKind, declaratorNode);
5457
+ for (const identifier of collectBindingNamesFromPattern(declarator.id)) tagAsBinding(state, identifier);
5458
+ }
5242
5459
  };
5243
- const processVariableDeclaration = (declaration, awaitedBindingNames) => {
5244
- if (!isNodeOfType(declaration, "VariableDeclaration")) return {
5245
- didIntroduceAwait: false,
5246
- didGrowBindings: false
5247
- };
5248
- let didIntroduceAwait = false;
5249
- const sizeBeforeAll = awaitedBindingNames.size;
5250
- let hasChanged = true;
5251
- while (hasChanged) {
5252
- hasChanged = false;
5253
- for (const declarator of declaration.declarations ?? []) {
5254
- if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
5255
- if (containsDirectAwait(declarator.init) || containsDirectAwait(declarator.id)) {
5256
- didIntroduceAwait = true;
5257
- const sizeBefore = awaitedBindingNames.size;
5258
- collectPatternNames(declarator.id, awaitedBindingNames);
5259
- if (awaitedBindingNames.size > sizeBefore) hasChanged = true;
5260
- continue;
5261
- }
5262
- const dependencyIdentifiers = /* @__PURE__ */ new Set();
5263
- collectDeclaratorDependencyIdentifierNames(declarator, dependencyIdentifiers);
5264
- if (!hasAnyIdentifierName(dependencyIdentifiers, awaitedBindingNames)) continue;
5265
- const sizeBefore = awaitedBindingNames.size;
5266
- collectPatternNames(declarator.id, awaitedBindingNames);
5267
- if (awaitedBindingNames.size > sizeBefore) hasChanged = true;
5268
- }
5460
+ const handleFunctionDeclaration = (fn, state) => {
5461
+ if (!isNodeOfType(fn, "FunctionDeclaration")) return;
5462
+ if (fn.id) {
5463
+ recordSymbol(findHoistTargetScope(state.currentScope), state, {
5464
+ name: fn.id.name,
5465
+ kind: "function",
5466
+ bindingIdentifier: fn.id,
5467
+ declarationNode: fn,
5468
+ initializer: fn
5469
+ });
5470
+ tagAsBinding(state, fn.id);
5269
5471
  }
5270
- const didGrowBindings = awaitedBindingNames.size > sizeBeforeAll;
5271
- return {
5272
- didIntroduceAwait,
5273
- didGrowBindings
5274
- };
5275
5472
  };
5276
- const CANCELLATION_GUARD_NAMES = new Set([
5277
- "cancelled",
5278
- "canceled",
5279
- "isCancelled",
5280
- "isCanceled",
5281
- "aborted",
5282
- "isAborted",
5283
- "disposed",
5284
- "isDisposed",
5285
- "destroyed",
5286
- "isDestroyed",
5287
- "stopped",
5288
- "isStopped",
5289
- "mounted",
5290
- "isMounted",
5291
- "unmounted",
5292
- "isUnmounted",
5293
- "active",
5294
- "isActive",
5295
- "stale",
5296
- "isStale",
5297
- "signal",
5298
- "abortSignal",
5299
- "abortController"
5300
- ]);
5301
- const testReadsRefCurrent = (test) => {
5302
- let didFindRefCurrentRead = false;
5303
- walkAst(test, (child) => {
5304
- if (didFindRefCurrentRead) return false;
5305
- if (!isNodeOfType(child, "MemberExpression") || child.computed) return;
5306
- if (!isNodeOfType(child.property, "Identifier") || child.property.name !== "current") return;
5307
- if (!isNodeOfType(child.object, "Identifier")) return;
5308
- const refCandidateName = child.object.name;
5309
- if (refCandidateName.endsWith("Ref") && refCandidateName.length > 3) {
5310
- didFindRefCurrentRead = true;
5311
- return false;
5312
- }
5473
+ const handleClassDeclaration = (cls, state) => {
5474
+ if (!isNodeOfType(cls, "ClassDeclaration")) return;
5475
+ if (cls.id) {
5476
+ recordSymbol(state.currentScope, state, {
5477
+ name: cls.id.name,
5478
+ kind: "class",
5479
+ bindingIdentifier: cls.id,
5480
+ declarationNode: cls,
5481
+ initializer: cls
5482
+ });
5483
+ tagAsBinding(state, cls.id);
5484
+ }
5485
+ };
5486
+ const handleImportDeclaration = (importDeclaration, state) => {
5487
+ if (!isNodeOfType(importDeclaration, "ImportDeclaration")) return;
5488
+ const target = findHoistTargetScope(state.currentScope);
5489
+ for (const specifier of importDeclaration.specifiers) {
5490
+ const local = specifier.local;
5491
+ if (!isNodeOfType(local, "Identifier")) continue;
5492
+ recordSymbol(target, state, {
5493
+ name: local.name,
5494
+ kind: "import",
5495
+ bindingIdentifier: local,
5496
+ declarationNode: specifier,
5497
+ initializer: specifier
5498
+ });
5499
+ tagAsBinding(state, local);
5500
+ }
5501
+ };
5502
+ const handleTsDeclarations = (node, state) => {
5503
+ if (node.type !== "TSImportEqualsDeclaration" && node.type !== "TSEnumDeclaration" && node.type !== "TSTypeAliasDeclaration" && node.type !== "TSInterfaceDeclaration" && node.type !== "TSModuleDeclaration") return;
5504
+ const idNode = node.id;
5505
+ if (!idNode || !isNodeOfType(idNode, "Identifier")) return;
5506
+ const kind = node.type === "TSImportEqualsDeclaration" ? "ts-import-equals" : node.type === "TSEnumDeclaration" ? "ts-enum" : node.type === "TSTypeAliasDeclaration" ? "ts-type-alias" : node.type === "TSInterfaceDeclaration" ? "ts-interface" : "ts-module";
5507
+ recordSymbol(findHoistTargetScope(state.currentScope), state, {
5508
+ name: idNode.name,
5509
+ kind,
5510
+ bindingIdentifier: idNode,
5511
+ declarationNode: node,
5512
+ initializer: null
5313
5513
  });
5314
- return didFindRefCurrentRead;
5514
+ tagAsBinding(state, idNode);
5315
5515
  };
5316
- const CANCELLATION_NAME_FRAGMENTS = [
5516
+ const handleFunctionParameters = (params, scope, state) => {
5517
+ for (const param of params) {
5518
+ visitDestructuringDeclarations(param, null, scope, state, "parameter", param);
5519
+ for (const identifier of collectBindingNamesFromPattern(param)) tagAsBinding(state, identifier);
5520
+ }
5521
+ };
5522
+ const shouldPushBlockScope = (block) => {
5523
+ if (!isNodeOfType(block, "BlockStatement")) return false;
5524
+ if (isFunctionBodyBlock(block)) return false;
5525
+ if (isCatchClauseBlock(block)) return false;
5526
+ if (block.parent && block.parent.type === "TSModuleDeclaration") return false;
5527
+ return true;
5528
+ };
5529
+ const isJsxIdentifierBindingReference = (identifier) => {
5530
+ const parent = identifier.parent;
5531
+ if (!parent) return false;
5532
+ if (parent.type === "JSXMemberExpression") return parent.object === identifier;
5533
+ if (parent.type === "JSXNamespacedName") return false;
5534
+ const ASCII_LOWERCASE_A = 97;
5535
+ const ASCII_LOWERCASE_Z = 122;
5536
+ const firstCharCode = identifier.name.charCodeAt(0);
5537
+ if (firstCharCode >= ASCII_LOWERCASE_A && firstCharCode <= ASCII_LOWERCASE_Z) return false;
5538
+ return true;
5539
+ };
5540
+ const isNonReferencePosition = (identifier) => {
5541
+ const parent = identifier.parent;
5542
+ if (!parent) return false;
5543
+ switch (parent.type) {
5544
+ case "MemberExpression": return parent.property === identifier && !parent.computed;
5545
+ case "Property": return parent.key === identifier && !parent.computed && !parent.shorthand;
5546
+ case "MethodDefinition":
5547
+ case "PropertyDefinition": return parent.key === identifier && !parent.computed;
5548
+ case "JSXAttribute": return parent.name === identifier;
5549
+ case "ImportSpecifier": return parent.imported === identifier;
5550
+ case "ExportSpecifier": return parent.exported === identifier && parent.local !== parent.exported;
5551
+ case "LabeledStatement":
5552
+ case "BreakStatement":
5553
+ case "ContinueStatement": return parent.label === identifier;
5554
+ default: return false;
5555
+ }
5556
+ };
5557
+ const inferReferenceFlag = (identifier) => {
5558
+ const parent = identifier.parent;
5559
+ if (!parent) return "read";
5560
+ switch (parent.type) {
5561
+ case "AssignmentExpression":
5562
+ if (parent.left === identifier) return parent.operator === "=" ? "write" : "read-write";
5563
+ return "read";
5564
+ case "UpdateExpression": return "read-write";
5565
+ case "ForInStatement":
5566
+ case "ForOfStatement": return parent.left === identifier ? "write" : "read";
5567
+ default: return "read";
5568
+ }
5569
+ };
5570
+ const setNodeScope = (node, state) => {
5571
+ state.nodeScope.set(node, state.currentScope);
5572
+ };
5573
+ const walkParameterReferences = (pattern, state) => {
5574
+ if (isNodeOfType(pattern, "AssignmentPattern")) {
5575
+ walkParameterReferences(pattern.left, state);
5576
+ const defaultValue = pattern.right ?? null;
5577
+ if (defaultValue) walk(defaultValue, state);
5578
+ return;
5579
+ }
5580
+ if (isNodeOfType(pattern, "ObjectPattern")) {
5581
+ for (const property of pattern.properties) {
5582
+ const propertyNode = property;
5583
+ if (isNodeOfType(propertyNode, "RestElement")) {
5584
+ walkParameterReferences(propertyNode.argument, state);
5585
+ continue;
5586
+ }
5587
+ if (!isNodeOfType(propertyNode, "Property")) continue;
5588
+ const propertyDetail = propertyNode;
5589
+ if (propertyDetail.computed) walk(propertyDetail.key, state);
5590
+ walkParameterReferences(propertyDetail.value, state);
5591
+ }
5592
+ return;
5593
+ }
5594
+ if (isNodeOfType(pattern, "ArrayPattern")) {
5595
+ for (const element of pattern.elements) if (element) walkParameterReferences(element, state);
5596
+ return;
5597
+ }
5598
+ if (isNodeOfType(pattern, "RestElement")) walkParameterReferences(pattern.argument, state);
5599
+ };
5600
+ const walk = (node, state) => {
5601
+ if (isFunctionLike$1(node)) {
5602
+ if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
5603
+ const functionParams = node.params ?? [];
5604
+ for (const param of functionParams) {
5605
+ if (!("decorators" in param) || !Array.isArray(param.decorators)) continue;
5606
+ for (const decorator of param.decorators) if (isAstNode(decorator)) walk(decorator, state);
5607
+ }
5608
+ setNodeScope(node, state);
5609
+ const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
5610
+ if ((isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && node.id) {
5611
+ recordSymbol(fnScope, state, {
5612
+ name: node.id.name,
5613
+ kind: "function",
5614
+ bindingIdentifier: node.id,
5615
+ declarationNode: node,
5616
+ initializer: node
5617
+ });
5618
+ tagAsBinding(state, node.id);
5619
+ }
5620
+ handleFunctionParameters(functionParams, fnScope, state);
5621
+ for (const param of functionParams) walkParameterReferences(param, state);
5622
+ const body = node.body;
5623
+ if (body) walk(body, state);
5624
+ popScope(state);
5625
+ return;
5626
+ }
5627
+ if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
5628
+ if (isNodeOfType(node, "ClassDeclaration") && node.id) handleClassDeclaration(node, state);
5629
+ if (Array.isArray(node.decorators)) {
5630
+ for (const decorator of node.decorators) if (isAstNode(decorator)) walk(decorator, state);
5631
+ }
5632
+ const classScope = pushScope("class", node, state);
5633
+ setNodeScope(node, state);
5634
+ if (isNodeOfType(node, "ClassExpression") && node.id) {
5635
+ recordSymbol(classScope, state, {
5636
+ name: node.id.name,
5637
+ kind: "class",
5638
+ bindingIdentifier: node.id,
5639
+ declarationNode: node,
5640
+ initializer: node
5641
+ });
5642
+ tagAsBinding(state, node.id);
5643
+ }
5644
+ if (node.superClass) walk(node.superClass, state);
5645
+ if (node.body) walk(node.body, state);
5646
+ popScope(state);
5647
+ return;
5648
+ }
5649
+ if (isNodeOfType(node, "CatchClause")) {
5650
+ const catchScope = pushScope("catch", node, state);
5651
+ setNodeScope(node, state);
5652
+ if (node.param) {
5653
+ visitDestructuringDeclarations(node.param, null, catchScope, state, "catch-clause-parameter", node);
5654
+ for (const identifier of collectBindingNamesFromPattern(node.param)) tagAsBinding(state, identifier);
5655
+ }
5656
+ if (node.body) walk(node.body, state);
5657
+ popScope(state);
5658
+ return;
5659
+ }
5660
+ if (isNodeOfType(node, "ForStatement") || isNodeOfType(node, "ForInStatement") || isNodeOfType(node, "ForOfStatement")) {
5661
+ pushScope("for", node, state);
5662
+ setNodeScope(node, state);
5663
+ const nodeRecord = node;
5664
+ for (const key of Object.keys(nodeRecord)) {
5665
+ if (key === "parent") continue;
5666
+ if (TYPE_POSITION_CHILD_KEYS.has(key)) continue;
5667
+ const child = nodeRecord[key];
5668
+ if (Array.isArray(child)) {
5669
+ for (const item of child) if (isAstNode(item)) walk(item, state);
5670
+ } else if (isAstNode(child)) walk(child, state);
5671
+ }
5672
+ popScope(state);
5673
+ return;
5674
+ }
5675
+ if (isNodeOfType(node, "SwitchStatement")) {
5676
+ pushScope("switch", node, state);
5677
+ setNodeScope(node, state);
5678
+ if (node.discriminant) walk(node.discriminant, state);
5679
+ for (const switchCase of node.cases) walk(switchCase, state);
5680
+ popScope(state);
5681
+ return;
5682
+ }
5683
+ if (isNodeOfType(node, "TSModuleDeclaration")) {
5684
+ const moduleScope = pushScope("ts-module", node, state);
5685
+ setNodeScope(node, state);
5686
+ if (node.id && isNodeOfType(node.id, "Identifier")) {
5687
+ const identifier = node.id;
5688
+ recordSymbol(findHoistTargetScope(moduleScope.parent ?? state.currentScope), state, {
5689
+ name: identifier.name,
5690
+ kind: "ts-module",
5691
+ bindingIdentifier: identifier,
5692
+ declarationNode: node,
5693
+ initializer: null
5694
+ });
5695
+ tagAsBinding(state, identifier);
5696
+ }
5697
+ if (node.body) walk(node.body, state);
5698
+ popScope(state);
5699
+ return;
5700
+ }
5701
+ if (isNodeOfType(node, "TSEnumDeclaration")) {
5702
+ handleTsDeclarations(node, state);
5703
+ pushScope("ts-enum", node, state);
5704
+ setNodeScope(node, state);
5705
+ const members = node.members ?? [];
5706
+ for (const member of members) walk(member, state);
5707
+ popScope(state);
5708
+ return;
5709
+ }
5710
+ if (isNodeOfType(node, "BlockStatement") && shouldPushBlockScope(node)) {
5711
+ pushScope("block", node, state);
5712
+ setNodeScope(node, state);
5713
+ for (const statement of node.body) walk(statement, state);
5714
+ popScope(state);
5715
+ return;
5716
+ }
5717
+ setNodeScope(node, state);
5718
+ if (isNodeOfType(node, "VariableDeclaration")) handleVariableDeclaration(node, state);
5719
+ else if (isNodeOfType(node, "FunctionDeclaration")) handleFunctionDeclaration(node, state);
5720
+ else if (isNodeOfType(node, "ClassDeclaration")) handleClassDeclaration(node, state);
5721
+ else if (isNodeOfType(node, "ImportDeclaration")) handleImportDeclaration(node, state);
5722
+ else if (node.type === "TSImportEqualsDeclaration" || node.type === "TSTypeAliasDeclaration" || node.type === "TSInterfaceDeclaration") handleTsDeclarations(node, state);
5723
+ if ((isNodeOfType(node, "Identifier") || isNodeOfType(node, "JSXIdentifier")) && !bindingPositionMarker.has(node) && !isNonReferencePosition(node)) if (isNodeOfType(node, "JSXIdentifier")) {
5724
+ if (isJsxIdentifierBindingReference(node)) recordReference(state, node, inferReferenceFlag(node));
5725
+ } else recordReference(state, node, inferReferenceFlag(node));
5726
+ const nodeRecord = node;
5727
+ for (const key of Object.keys(nodeRecord)) {
5728
+ if (key === "parent") continue;
5729
+ if (TYPE_POSITION_CHILD_KEYS.has(key)) continue;
5730
+ const child = nodeRecord[key];
5731
+ if (Array.isArray(child)) {
5732
+ for (const item of child) if (isAstNode(item)) walk(item, state);
5733
+ } else if (isAstNode(child)) walk(child, state);
5734
+ }
5735
+ };
5736
+ const resolveReferences = (rootScope) => {
5737
+ const visitScope = (scope) => {
5738
+ for (const reference of scope.references) {
5739
+ const name = reference.identifier.name;
5740
+ if (typeof name !== "string") continue;
5741
+ let lookup = scope;
5742
+ while (lookup) {
5743
+ const found = lookup.symbolsByName.get(name);
5744
+ if (found) {
5745
+ reference.resolvedSymbol = found;
5746
+ found.references.push(reference);
5747
+ break;
5748
+ }
5749
+ lookup = lookup.parent;
5750
+ }
5751
+ }
5752
+ for (const child of scope.children) visitScope(child);
5753
+ };
5754
+ visitScope(rootScope);
5755
+ };
5756
+ const analyzeScopes = (program) => {
5757
+ const rootScope = {
5758
+ id: 0,
5759
+ kind: "module",
5760
+ node: program,
5761
+ parent: null,
5762
+ children: [],
5763
+ symbols: [],
5764
+ references: [],
5765
+ symbolsByName: /* @__PURE__ */ new Map()
5766
+ };
5767
+ const state = {
5768
+ nextScopeId: 1,
5769
+ nextSymbolId: 0,
5770
+ nextReferenceId: 0,
5771
+ currentScope: rootScope,
5772
+ scopeStack: [rootScope],
5773
+ rootScope,
5774
+ nodeScope: /* @__PURE__ */ new WeakMap(),
5775
+ ownScopeForNode: /* @__PURE__ */ new WeakMap(),
5776
+ symbolByBindingIdentifier: /* @__PURE__ */ new WeakMap(),
5777
+ referenceByIdentifier: /* @__PURE__ */ new WeakMap()
5778
+ };
5779
+ state.nodeScope.set(program, rootScope);
5780
+ state.ownScopeForNode.set(program, rootScope);
5781
+ if (isNodeOfType(program, "Program")) for (const statement of program.body) walk(statement, state);
5782
+ else walk(program, state);
5783
+ resolveReferences(rootScope);
5784
+ const scopeFor = (node) => {
5785
+ let current = node;
5786
+ while (current) {
5787
+ const scope = state.nodeScope.get(current);
5788
+ if (scope) return scope;
5789
+ current = current.parent ?? null;
5790
+ }
5791
+ return rootScope;
5792
+ };
5793
+ const symbolFor = (identifier) => {
5794
+ const reference = state.referenceByIdentifier.get(identifier);
5795
+ if (reference) return reference.resolvedSymbol;
5796
+ const symbolForBinding = state.symbolByBindingIdentifier.get(identifier);
5797
+ if (symbolForBinding) return symbolForBinding;
5798
+ return null;
5799
+ };
5800
+ const referenceFor = (identifier) => {
5801
+ return state.referenceByIdentifier.get(identifier) ?? null;
5802
+ };
5803
+ const isGlobalReference = (identifier) => {
5804
+ const reference = state.referenceByIdentifier.get(identifier);
5805
+ if (!reference) return false;
5806
+ return reference.resolvedSymbol === null;
5807
+ };
5808
+ const ownScopeFor = (node) => {
5809
+ return state.ownScopeForNode.get(node) ?? null;
5810
+ };
5811
+ return {
5812
+ rootScope,
5813
+ scopeFor,
5814
+ ownScopeFor,
5815
+ symbolFor,
5816
+ referenceFor,
5817
+ isGlobalReference
5818
+ };
5819
+ };
5820
+ const isDescendantScope = (inner, outer) => {
5821
+ let current = inner;
5822
+ while (current) {
5823
+ if (current === outer) return true;
5824
+ current = current.parent;
5825
+ }
5826
+ return false;
5827
+ };
5828
+ //#endregion
5829
+ //#region src/plugin/utils/collect-pattern-default-reference-names.ts
5830
+ const collectPatternDefaultReferenceNames = (pattern, into) => {
5831
+ if (!pattern) return;
5832
+ if (isNodeOfType(pattern, "AssignmentPattern")) {
5833
+ collectReferenceIdentifierNames(pattern.right, into);
5834
+ collectPatternDefaultReferenceNames(pattern.left, into);
5835
+ return;
5836
+ }
5837
+ if (isNodeOfType(pattern, "RestElement")) {
5838
+ collectPatternDefaultReferenceNames(pattern.argument, into);
5839
+ return;
5840
+ }
5841
+ if (isNodeOfType(pattern, "ArrayPattern")) {
5842
+ for (const element of pattern.elements ?? []) collectPatternDefaultReferenceNames(element, into);
5843
+ return;
5844
+ }
5845
+ if (isNodeOfType(pattern, "ObjectPattern")) {
5846
+ for (const property of pattern.properties ?? []) if (isNodeOfType(property, "RestElement")) collectPatternDefaultReferenceNames(property.argument, into);
5847
+ else if (isNodeOfType(property, "Property")) {
5848
+ if (property.computed) collectReferenceIdentifierNames(property.key, into);
5849
+ collectPatternDefaultReferenceNames(property.value, into);
5850
+ }
5851
+ }
5852
+ };
5853
+ //#endregion
5854
+ //#region src/plugin/utils/is-bare-await-expression-statement.ts
5855
+ const isBareAwaitExpressionStatement = (statement) => {
5856
+ if (!isNodeOfType(statement, "ExpressionStatement")) return false;
5857
+ return isNodeOfType(statement.expression, "AwaitExpression");
5858
+ };
5859
+ //#endregion
5860
+ //#region src/plugin/utils/is-early-exit-if-statement.ts
5861
+ const isEarlyExitStatement = (statement) => isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
5862
+ const isEarlyExitIfStatement = (statement) => {
5863
+ if (!isNodeOfType(statement, "IfStatement")) return false;
5864
+ const consequent = statement.consequent;
5865
+ if (!consequent) return false;
5866
+ if (isEarlyExitStatement(consequent)) return true;
5867
+ if (!isNodeOfType(consequent, "BlockStatement")) return false;
5868
+ for (const inner of consequent.body ?? []) if (isEarlyExitStatement(inner)) return true;
5869
+ return false;
5870
+ };
5871
+ //#endregion
5872
+ //#region src/plugin/rules/performance/async-defer-await.ts
5873
+ const hasAnyIdentifierName = (identifierNames, candidateNames) => {
5874
+ for (const candidateName of candidateNames) if (identifierNames.has(candidateName)) return true;
5875
+ return false;
5876
+ };
5877
+ const collectDeclaratorDependencyIdentifierNames = (declarator, into) => {
5878
+ if (!isNodeOfType(declarator, "VariableDeclarator")) return;
5879
+ collectReferenceIdentifierNames(declarator.init, into);
5880
+ collectPatternDefaultReferenceNames(declarator.id, into);
5881
+ };
5882
+ const processVariableDeclaration = (declaration, awaitedBindingNames) => {
5883
+ if (!isNodeOfType(declaration, "VariableDeclaration")) return {
5884
+ didIntroduceAwait: false,
5885
+ didGrowBindings: false
5886
+ };
5887
+ let didIntroduceAwait = false;
5888
+ const sizeBeforeAll = awaitedBindingNames.size;
5889
+ let hasChanged = true;
5890
+ while (hasChanged) {
5891
+ hasChanged = false;
5892
+ for (const declarator of declaration.declarations ?? []) {
5893
+ if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
5894
+ if (containsDirectAwait(declarator.init) || containsDirectAwait(declarator.id)) {
5895
+ didIntroduceAwait = true;
5896
+ const sizeBefore = awaitedBindingNames.size;
5897
+ collectPatternNames(declarator.id, awaitedBindingNames);
5898
+ if (awaitedBindingNames.size > sizeBefore) hasChanged = true;
5899
+ continue;
5900
+ }
5901
+ const dependencyIdentifiers = /* @__PURE__ */ new Set();
5902
+ collectDeclaratorDependencyIdentifierNames(declarator, dependencyIdentifiers);
5903
+ if (!hasAnyIdentifierName(dependencyIdentifiers, awaitedBindingNames)) continue;
5904
+ const sizeBefore = awaitedBindingNames.size;
5905
+ collectPatternNames(declarator.id, awaitedBindingNames);
5906
+ if (awaitedBindingNames.size > sizeBefore) hasChanged = true;
5907
+ }
5908
+ }
5909
+ const didGrowBindings = awaitedBindingNames.size > sizeBeforeAll;
5910
+ return {
5911
+ didIntroduceAwait,
5912
+ didGrowBindings
5913
+ };
5914
+ };
5915
+ const CANCELLATION_GUARD_NAMES = new Set([
5916
+ "cancelled",
5917
+ "canceled",
5918
+ "isCancelled",
5919
+ "isCanceled",
5920
+ "aborted",
5921
+ "isAborted",
5922
+ "disposed",
5923
+ "isDisposed",
5924
+ "destroyed",
5925
+ "isDestroyed",
5926
+ "stopped",
5927
+ "isStopped",
5928
+ "mounted",
5929
+ "isMounted",
5930
+ "unmounted",
5931
+ "isUnmounted",
5932
+ "active",
5933
+ "isActive",
5934
+ "stale",
5935
+ "isStale",
5936
+ "signal",
5937
+ "abortSignal",
5938
+ "abortController"
5939
+ ]);
5940
+ const testReadsRefCurrent = (test) => {
5941
+ let didFindRefCurrentRead = false;
5942
+ walkAst(test, (child) => {
5943
+ if (didFindRefCurrentRead) return false;
5944
+ if (!isNodeOfType(child, "MemberExpression") || child.computed) return;
5945
+ if (!isNodeOfType(child.property, "Identifier") || child.property.name !== "current") return;
5946
+ if (!isNodeOfType(child.object, "Identifier")) return;
5947
+ const refCandidateName = child.object.name;
5948
+ if (refCandidateName.endsWith("Ref") && refCandidateName.length > 3) {
5949
+ didFindRefCurrentRead = true;
5950
+ return false;
5951
+ }
5952
+ });
5953
+ return didFindRefCurrentRead;
5954
+ };
5955
+ const CANCELLATION_NAME_FRAGMENTS = [
5317
5956
  "cancel",
5318
5957
  "abort",
5319
5958
  "dispos",
@@ -5371,15 +6010,56 @@ const guardTestReadsMutableEnvironment = (test) => {
5371
6010
  };
5372
6011
  const isNonLiteralComparisonTest = (test) => {
5373
6012
  if (!test) return false;
5374
- if (!isNodeOfType(test, "BinaryExpression")) return false;
6013
+ const unwrappedTest = stripParenExpression(test);
6014
+ if (!isNodeOfType(unwrappedTest, "BinaryExpression")) return false;
5375
6015
  if (![
5376
6016
  "===",
5377
6017
  "!==",
5378
6018
  "==",
5379
6019
  "!="
5380
- ].includes(test.operator)) return false;
5381
- const isLiteralOperand = (operand) => isNodeOfType(operand, "Literal") || isNodeOfType(operand, "TemplateLiteral") || isNodeOfType(operand, "UnaryExpression") && isLiteralOperand(operand.argument);
5382
- return !isLiteralOperand(test.left) && !isLiteralOperand(test.right);
6020
+ ].includes(unwrappedTest.operator)) return false;
6021
+ const isLiteralOperand = (operand) => {
6022
+ const unwrappedOperand = stripParenExpression(operand);
6023
+ return isNodeOfType(unwrappedOperand, "Literal") || isNodeOfType(unwrappedOperand, "TemplateLiteral") || isNodeOfType(unwrappedOperand, "UnaryExpression") && isLiteralOperand(unwrappedOperand.argument);
6024
+ };
6025
+ return !isLiteralOperand(unwrappedTest.left) && !isLiteralOperand(unwrappedTest.right);
6026
+ };
6027
+ const isLocalConstSnapshotOperand = (operand, scopes, functionScope) => {
6028
+ const unwrappedOperand = stripParenExpression(operand);
6029
+ if (!isNodeOfType(unwrappedOperand, "Identifier")) return false;
6030
+ const symbol = scopes.symbolFor(unwrappedOperand);
6031
+ return Boolean(symbol && symbol.kind === "const" && symbol.initializer && isDescendantScope(symbol.scope, functionScope));
6032
+ };
6033
+ const isLiveFreshnessOperand = (operand, scopes, functionScope) => {
6034
+ const unwrappedOperand = stripParenExpression(operand);
6035
+ if (isNodeOfType(unwrappedOperand, "MemberExpression")) return true;
6036
+ if (!isNodeOfType(unwrappedOperand, "Identifier")) return false;
6037
+ const symbol = scopes.symbolFor(unwrappedOperand);
6038
+ return Boolean(symbol && (symbol.kind === "let" || symbol.kind === "var" || symbol.kind === "import") && !isDescendantScope(symbol.scope, functionScope));
6039
+ };
6040
+ const isProvenFreshnessComparison = (test, scopes, functionScope) => {
6041
+ const unwrappedTest = stripParenExpression(test);
6042
+ if (!isNonLiteralComparisonTest(unwrappedTest)) return false;
6043
+ if (!isNodeOfType(unwrappedTest, "BinaryExpression")) return false;
6044
+ return isLocalConstSnapshotOperand(unwrappedTest.left, scopes, functionScope) && isLiveFreshnessOperand(unwrappedTest.right, scopes, functionScope) || isLocalConstSnapshotOperand(unwrappedTest.right, scopes, functionScope) && isLiveFreshnessOperand(unwrappedTest.left, scopes, functionScope);
6045
+ };
6046
+ const isLogicalCompositionOfFreshnessComparisons = (test, scopes, functionScope) => {
6047
+ if (!functionScope) return false;
6048
+ if (!test) return false;
6049
+ const unwrappedTest = stripParenExpression(test);
6050
+ if (!isNodeOfType(unwrappedTest, "LogicalExpression") || unwrappedTest.operator !== "&&" && unwrappedTest.operator !== "||") return false;
6051
+ const pendingTests = [unwrappedTest.left, unwrappedTest.right];
6052
+ while (pendingTests.length > 0) {
6053
+ const candidateTest = pendingTests.pop();
6054
+ if (!candidateTest) continue;
6055
+ const unwrappedCandidateTest = stripParenExpression(candidateTest);
6056
+ if (isNodeOfType(unwrappedCandidateTest, "LogicalExpression") && (unwrappedCandidateTest.operator === "&&" || unwrappedCandidateTest.operator === "||")) {
6057
+ pendingTests.push(unwrappedCandidateTest.left, unwrappedCandidateTest.right);
6058
+ continue;
6059
+ }
6060
+ if (!isProvenFreshnessComparison(unwrappedCandidateTest, scopes, functionScope)) return false;
6061
+ }
6062
+ return true;
5383
6063
  };
5384
6064
  const guardConsequentPerformsSideEffects = (consequent) => {
5385
6065
  if (!consequent) return false;
@@ -5484,7 +6164,9 @@ const asyncDeferAwait = defineRule({
5484
6164
  statementIndex = window.guardCandidateIndex - 1;
5485
6165
  continue;
5486
6166
  }
5487
- if (isCancellationGuardTest(guardStatement.test) || guardTestReadsMutableEnvironment(guardStatement.test) || isNonLiteralComparisonTest(guardStatement.test) || guardTestReadsReassignedLocal(guardStatement.test, guardStatement) || guardConsequentPerformsSideEffects(guardStatement.consequent)) {
6167
+ const enclosingFunction = findEnclosingFunction(guardStatement);
6168
+ const functionScope = enclosingFunction ? context.scopes.ownScopeFor(enclosingFunction) : null;
6169
+ if (isCancellationGuardTest(guardStatement.test) || guardTestReadsMutableEnvironment(guardStatement.test) || isNonLiteralComparisonTest(guardStatement.test) || isLogicalCompositionOfFreshnessComparisons(guardStatement.test, context.scopes, functionScope) || guardTestReadsReassignedLocal(guardStatement.test, guardStatement) || guardConsequentPerformsSideEffects(guardStatement.consequent)) {
5488
6170
  statementIndex = window.guardCandidateIndex - 1;
5489
6171
  continue;
5490
6172
  }
@@ -5523,6 +6205,23 @@ const asyncDeferAwait = defineRule({
5523
6205
  }
5524
6206
  });
5525
6207
  //#endregion
6208
+ //#region src/plugin/utils/expression-reads-pattern-binding.ts
6209
+ const expressionReadsPatternBinding = (expression, patterns, scopes) => {
6210
+ const bindingSymbolIds = /* @__PURE__ */ new Set();
6211
+ for (const pattern of patterns) walkAst(pattern, (child) => {
6212
+ if (!isNodeOfType(child, "Identifier")) return;
6213
+ const symbol = scopes.symbolFor(child);
6214
+ if (symbol?.bindingIdentifier === child) bindingSymbolIds.add(symbol.id);
6215
+ });
6216
+ let didReadBinding = false;
6217
+ walkAst(expression, (child) => {
6218
+ if (didReadBinding || !isNodeOfType(child, "Identifier")) return;
6219
+ const reference = scopes.referenceFor(child);
6220
+ if (reference?.flag !== "write" && reference?.resolvedSymbol && bindingSymbolIds.has(reference.resolvedSymbol.id)) didReadBinding = true;
6221
+ });
6222
+ return didReadBinding;
6223
+ };
6224
+ //#endregion
5526
6225
  //#region src/plugin/utils/get-callee-identifier-trail.ts
5527
6226
  const getCalleeIdentifierTrail = (call) => {
5528
6227
  let entry = call;
@@ -5628,16 +6327,12 @@ const isInsideTransactionCallback = (node) => {
5628
6327
  return false;
5629
6328
  };
5630
6329
  const reportIfIndependent = (statements, context) => {
5631
- const declaredNames = /* @__PURE__ */ new Set();
6330
+ const declaredPatterns = [];
5632
6331
  for (const statement of statements) {
5633
6332
  const awaitArgument = getAwaitedCall(statement);
5634
6333
  if (!awaitArgument) continue;
5635
- let referencesEarlierResult = false;
5636
- walkAst(awaitArgument, (child) => {
5637
- if (isNodeOfType(child, "Identifier") && declaredNames.has(child.name)) referencesEarlierResult = true;
5638
- });
5639
- if (referencesEarlierResult) return;
5640
- if (isNodeOfType(statement, "VariableDeclaration") && statement.declarations[0]?.id) collectPatternNames(statement.declarations[0].id, declaredNames);
6334
+ if (expressionReadsPatternBinding(awaitArgument, declaredPatterns, context.scopes)) return;
6335
+ if (isNodeOfType(statement, "VariableDeclaration") && statement.declarations[0]?.id) declaredPatterns.push(statement.declarations[0].id);
5641
6336
  }
5642
6337
  context.report({
5643
6338
  node: statements[0],
@@ -7121,12 +7816,6 @@ const clientLocalstorageNoVersion = defineRule({
7121
7816
  }
7122
7817
  });
7123
7818
  //#endregion
7124
- //#region src/plugin/utils/get-direct-const-initializer.ts
7125
- const getDirectConstInitializer = (symbol) => {
7126
- if (symbol.kind !== "const" || !isNodeOfType(symbol.declarationNode, "VariableDeclarator") || symbol.declarationNode.id !== symbol.bindingIdentifier) return null;
7127
- return symbol.initializer;
7128
- };
7129
- //#endregion
7130
7819
  //#region src/plugin/utils/get-range-start.ts
7131
7820
  const getRangeStart = (node) => {
7132
7821
  const rangeStart = node.range?.[0];
@@ -7787,10 +8476,21 @@ const isDestructuredReactApiBinding = (identifier, apiNames, scopes, options) =>
7787
8476
  };
7788
8477
  const isReactApiCall = (node, apiNames, scopes, options = {}) => {
7789
8478
  if (!isNodeOfType(node, "CallExpression")) return false;
7790
- const callee = stripParenExpression(node.callee);
8479
+ return isReactApiCallee(node.callee, apiNames, scopes, options, /* @__PURE__ */ new Set());
8480
+ };
8481
+ const isReactApiCallee = (rawCallee, apiNames, scopes, options, visitedSymbolIds) => {
8482
+ const callee = stripParenExpression(rawCallee);
8483
+ if (options.resolveConditionalAliases && isNodeOfType(callee, "ConditionalExpression")) return isReactApiCallee(callee.consequent, apiNames, scopes, options, new Set(visitedSymbolIds)) && isReactApiCallee(callee.alternate, apiNames, scopes, options, new Set(visitedSymbolIds));
7791
8484
  if (isNodeOfType(callee, "Identifier")) {
7792
8485
  if (isNamedReactApiImport(callee, apiNames, scopes, Boolean(options.resolveNamedAliases))) return true;
7793
8486
  if (options.resolveNamedAliases && isDestructuredReactApiBinding(callee, apiNames, scopes, options)) return true;
8487
+ if (options.resolveConditionalAliases) {
8488
+ const symbol = scopes.symbolFor(callee);
8489
+ if (symbol?.kind === "const" && symbol.initializer && !visitedSymbolIds.has(symbol.id)) {
8490
+ visitedSymbolIds.add(symbol.id);
8491
+ return isReactApiCallee(symbol.initializer, apiNames, scopes, options, visitedSymbolIds);
8492
+ }
8493
+ }
7794
8494
  return Boolean(options.allowUnboundBareCalls && includesApiName(apiNames, callee.name) && scopes.isGlobalReference(callee));
7795
8495
  }
7796
8496
  if (!isNodeOfType(callee, "MemberExpression") || !includesApiName(apiNames, getStaticPropertyName(callee) ?? "")) return false;
@@ -10812,14 +11512,6 @@ const displayName = defineRule({
10812
11512
  }
10813
11513
  });
10814
11514
  //#endregion
10815
- //#region src/plugin/utils/read-static-boolean.ts
10816
- const readStaticBoolean = (node) => {
10817
- if (!node) return null;
10818
- const unwrappedNode = stripParenExpression(node);
10819
- if (!isNodeOfType(unwrappedNode, "Literal") || typeof unwrappedNode.value !== "boolean") return null;
10820
- return unwrappedNode.value;
10821
- };
10822
- //#endregion
10823
11515
  //#region src/plugin/rules/state-and-effects/utils/build-listener-cleanup-mismatch-message.ts
10824
11516
  const buildListenerCleanupMismatchMessage = (eventName, registrationCapture, removalCapture, callbackComparison) => {
10825
11517
  const hasCallbackMismatch = callbackComparison === "different";
@@ -11655,6 +12347,10 @@ const isConstAliasOfGlobalArrayFrom = (node, scopes) => {
11655
12347
  };
11656
12348
  const executesDuringRender = (functionNode, scopes) => {
11657
12349
  const parent = functionNode.parent;
12350
+ if (isNodeOfType(parent, "NewExpression")) {
12351
+ const callee = stripParenExpression(parent.callee);
12352
+ return Boolean(scopes && parent.arguments?.[0] === functionNode && isNodeOfType(callee, "Identifier") && callee.name === "Promise" && scopes.isGlobalReference(callee));
12353
+ }
11658
12354
  if (!isNodeOfType(parent, "CallExpression")) return false;
11659
12355
  if (parent.callee === functionNode) return true;
11660
12356
  if ((scopes ? isReactApiCall(parent, REACT_RENDER_PHASE_HOOK_NAMES, scopes, { allowGlobalReactNamespace: true }) : isHookCall$2(parent, REACT_RENDER_PHASE_HOOK_NAMES)) && parent.arguments?.[0] === functionNode) return true;
@@ -12977,750 +13673,239 @@ const cleanupFunctionReleasesRefOwnedUsage = (cleanupFunction, componentFunction
12977
13673
  hasUnsafeRefWrite = true;
12978
13674
  return false;
12979
13675
  }
12980
- return;
12981
- }
12982
- if (isNodeOfType(child, "CallExpression")) {
12983
- if ((child.arguments ?? []).some((argumentNode) => {
12984
- const argumentKey = resolveExpressionKey(argumentNode, context);
12985
- return argumentKey !== null && (argumentKey === storage.refKey || argumentKey === storage.refCurrentKey);
12986
- })) {
12987
- hasUnsafeRefWrite = true;
12988
- return false;
12989
- }
12990
- return;
12991
- }
12992
- if (!isNodeOfType(child, "AssignmentExpression")) return;
12993
- const assignmentTarget = stripParenExpression(child.left);
12994
- if (isNodeOfType(assignmentTarget, "MemberExpression") && assignmentTarget.computed && resolveExpressionKey(assignmentTarget.object, context) === storage.refCurrentKey) {
12995
- hasUnsafeRefWrite = true;
12996
- return false;
12997
- }
12998
- const assignedKey = resolveExpressionKey(child.left, context);
12999
- if (assignedKey === storage.handlerKey) {
13000
- hasUnsafeRefWrite = true;
13001
- return false;
13002
- }
13003
- if (assignedKey !== storage.refCurrentKey) return;
13004
- const assignedValue = stripParenExpression(child.right);
13005
- if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction$1(child) === cleanupFunction) return;
13006
- const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
13007
- const storesMatchingHandler = assignedSessionProperties.every((property) => isNodeOfType(property, "Property")) && assignedSessionProperties.some((property) => isNodeOfType(property, "Property") && `${storage.refCurrentKey}.${getStaticPropertyKeyName(property) ?? ""}` === storage.handlerKey && resolveExpressionKey(property.value, context) === usage.handlerKey);
13008
- if (findEnclosingFunction$1(child) !== retainedFunction || !storesMatchingHandler) {
13009
- hasUnsafeRefWrite = true;
13010
- return false;
13011
- }
13012
- });
13013
- return !hasUnsafeRefWrite;
13014
- };
13015
- const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retainedFunction, usage, context) => {
13016
- const matchesReturnedCleanup = (returnedValue) => {
13017
- const cleanupFunction = resolveRefOwnedCleanupFunction(returnedValue, context);
13018
- return Boolean(cleanupFunction && cleanupFunctionReleasesRefOwnedUsage(cleanupFunction, componentFunction, retainedFunction, usage, context));
13019
- };
13020
- if (!isFunctionLike$1(effectCallback)) return false;
13021
- if (!isNodeOfType(effectCallback.body, "BlockStatement")) return matchesReturnedCleanup(stripParenExpression(effectCallback.body));
13022
- const matchingReturns = [];
13023
- walkInsideStatementBlocks(effectCallback.body, (child) => {
13024
- if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
13025
- });
13026
- return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
13027
- };
13028
- const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
13029
- const componentFunction = findEnclosingFunction$1(retainedFunction);
13030
- if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
13031
- let didFindCleanupEffect = false;
13032
- walkAst(componentFunction.body, (child) => {
13033
- if (didFindCleanupEffect) return false;
13034
- if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
13035
- const effectStatement = findTransparentExpressionRoot(child).parent;
13036
- if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
13037
- const effectCallback = getEffectCallback(child);
13038
- if (effectCallback && effectReturnsRefOwnedCleanup(effectCallback, componentFunction, retainedFunction, usage, context)) {
13039
- didFindCleanupEffect = true;
13040
- return false;
13041
- }
13042
- });
13043
- return didFindCleanupEffect;
13044
- };
13045
- const isUseSyncExternalStoreSubscribeFunction = (functionNode, context) => {
13046
- const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
13047
- if (!bindingIdentifier) return false;
13048
- const visitedSymbolIds = /* @__PURE__ */ new Set();
13049
- const isSubscribeBinding = (candidateBinding) => {
13050
- const symbol = context.scopes.symbolFor(candidateBinding);
13051
- if (!symbol || visitedSymbolIds.has(symbol.id) || symbol.references.length === 0) return false;
13052
- visitedSymbolIds.add(symbol.id);
13053
- return symbol.references.every((reference) => {
13054
- const referenceRoot = findTransparentExpressionRoot(reference.identifier);
13055
- const referenceParent = referenceRoot.parent;
13056
- if (isNodeOfType(referenceParent, "CallExpression") && referenceParent.arguments?.[0] === referenceRoot) return isReactApiCall(referenceParent, "useSyncExternalStore", context.scopes);
13057
- const aliasDeclaration = referenceParent?.parent;
13058
- return Boolean(isNodeOfType(referenceParent, "VariableDeclarator") && referenceParent.init === referenceRoot && isNodeOfType(referenceParent.id, "Identifier") && isNodeOfType(aliasDeclaration, "VariableDeclaration") && aliasDeclaration.kind === "const" && isSubscribeBinding(referenceParent.id));
13059
- });
13060
- };
13061
- return isSubscribeBinding(bindingIdentifier);
13062
- };
13063
- const doesResourceResultEscape = (resourceNode, allowConciseReturnEscape) => {
13064
- let currentNode = resourceNode;
13065
- let parentNode = currentNode.parent;
13066
- while (parentNode) {
13067
- if (isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode) return true;
13068
- if (isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode && allowConciseReturnEscape) return true;
13069
- if (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
13070
- currentNode = parentNode;
13071
- parentNode = currentNode.parent;
13072
- continue;
13073
- }
13074
- return false;
13075
- }
13076
- return false;
13077
- };
13078
- const findRetainedFunctionLeak = (retainedFunction, context) => {
13079
- if (!isFunctionLike$1(retainedFunction)) return null;
13080
- const body = retainedFunction.body;
13081
- if (!body) return null;
13082
- let leak = null;
13083
- const allowConciseReturnEscape = !isInlineRetainedHandlerFunction(retainedFunction, context);
13084
- const isExternalStoreSubscribeFunction = isUseSyncExternalStoreSubscribeFunction(retainedFunction, context);
13085
- const hasReleaseForUsage = (usage) => isExternalStoreSubscribeFunction ? effectHasCleanupForUsage(retainedFunction, usage, context) : fileContainsReleaseForUsage(usage, context) || hasGuaranteedRefOwnedUnmountCleanup(retainedFunction, usage, context);
13086
- walkAst(body, (child) => {
13087
- if (leak !== null) return false;
13088
- if (isFunctionLike$1(child)) return false;
13089
- if (isSocketConstruction(child) && !doesResourceResultEscape(child, false)) {
13090
- const socketUsage = {
13091
- kind: "socket",
13092
- node: child,
13093
- resourceName: isNodeOfType(child.callee, "Identifier") ? child.callee.name : "WebSocket",
13094
- handleKey: findAssignedResourceKey(child, context),
13095
- receiverKey: null,
13096
- registrationVerbName: null,
13097
- eventKey: null,
13098
- handlerKey: null
13099
- };
13100
- if (!hasReleaseForUsage(socketUsage)) {
13101
- leak = socketUsage;
13102
- return false;
13103
- }
13104
- }
13105
- if (!isNodeOfType(child, "CallExpression")) return;
13106
- if (isNodeOfType(child.callee, "Identifier") && child.callee.name === "setInterval" && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
13107
- const timerUsage = {
13108
- kind: "timer",
13109
- node: child,
13110
- resourceName: "setInterval",
13111
- handleKey: findAssignedResourceKey(child, context),
13112
- receiverKey: null,
13113
- registrationVerbName: "setInterval",
13114
- eventKey: null,
13115
- handlerKey: null
13116
- };
13117
- if (!hasReleaseForUsage(timerUsage)) {
13118
- leak = timerUsage;
13119
- return false;
13120
- }
13121
- }
13122
- if (isSubscribeOrObserveCall(child) && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
13123
- const registrationDetails = getCallRegistrationDetails(child, context);
13124
- const subscriptionUsage = {
13125
- kind: "subscribe",
13126
- node: child,
13127
- resourceName: registrationDetails.registrationVerbName ?? "subscribe",
13128
- handleKey: findAssignedResourceKey(child, context),
13129
- ...registrationDetails
13130
- };
13131
- if (!hasSelfReleasingListenerOptions(child, context) && !hasReleaseForUsage(subscriptionUsage)) leak = subscriptionUsage;
13132
- return false;
13133
- }
13134
- });
13135
- return leak;
13136
- };
13137
- const isRetainedComponentScopeFunction = (functionNode) => {
13138
- if (isNodeOfType(functionNode, "FunctionDeclaration")) return enclosingComponentOrHookName(functionNode) !== null;
13139
- if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression")) return false;
13140
- if (!isNodeOfType(functionNode.parent, "VariableDeclarator")) return false;
13141
- return enclosingComponentOrHookName(functionNode) !== null;
13142
- };
13143
- const isDirectJsxEventHandlerValue = (expression) => {
13144
- const expressionRoot = findTransparentExpressionRoot(expression);
13145
- const expressionContainer = expressionRoot.parent;
13146
- return isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === expressionRoot && isEventHandlerAttribute(expressionContainer.parent);
13147
- };
13148
- const isInlineRetainedHandlerFunction = (functionNode, context) => {
13149
- if (!isFunctionLike$1(functionNode)) return false;
13150
- const functionRoot = findTransparentExpressionRoot(functionNode);
13151
- const callbackCall = functionRoot.parent;
13152
- if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
13153
- const parentNode = functionNode.parent;
13154
- if (isDirectJsxEventHandlerValue(functionNode)) return true;
13155
- if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
13156
- const propertyName = getStaticPropertyKeyName(parentNode);
13157
- if (!propertyName || !/^on[A-Z]/.test(propertyName)) return false;
13158
- const objectExpression = parentNode.parent;
13159
- if (!isNodeOfType(objectExpression, "ObjectExpression")) return false;
13160
- const objectParent = objectExpression.parent;
13161
- return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
13162
- };
13163
- const effectNeedsCleanup = defineRule({
13164
- id: "effect-needs-cleanup",
13165
- title: "Effect subscription or timer never cleaned up",
13166
- severity: "error",
13167
- tags: ["test-noise"],
13168
- recommendation: "Return a cleanup function that stops the subscription or timer: `return () => target.removeEventListener(name, handler)` for listeners, `return () => clearInterval(id)` or `clearTimeout(id)` for timers, `return () => observer.disconnect()` for observers, `return () => socket.close()` for connections, or `return unsubscribe` if the subscribe call already gave you one.",
13169
- create: (context) => {
13170
- const reportRetainedLeak = (retainedFunction) => {
13171
- if (!isPotentiallyReachableFunction(retainedFunction, context)) return;
13172
- const leak = findRetainedFunctionLeak(retainedFunction, context);
13173
- if (!leak) return;
13174
- const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
13175
- context.report({
13176
- node: leak.node,
13177
- message: `\`${leak.resourceName}\` creates a ${resourceNoun} in a function that outlives the render, with no cleanup path. Store the handle and release it, or move this into a useEffect that returns cleanup, so it does not leak after unmount.`
13178
- });
13179
- };
13180
- return {
13181
- CallExpression(node) {
13182
- if (isHookCall$2(node, "useCallback")) {
13183
- const retainedCallback = getEffectCallback(node);
13184
- if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
13185
- return;
13186
- }
13187
- if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
13188
- const callback = getEffectCallback(node);
13189
- if (!callback) return;
13190
- const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
13191
- if (usages.length === 0) return;
13192
- const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
13193
- if (!firstUsage) return;
13194
- const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
13195
- const hookName = getCalleeName$2(node) ?? "effect";
13196
- context.report({
13197
- node,
13198
- message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without guaranteed cleanup. Return a cleanup function that owns every allocation so it does not leak after unmount.`
13199
- });
13200
- },
13201
- FunctionDeclaration(node) {
13202
- if (isRetainedComponentScopeFunction(node)) reportRetainedLeak(node);
13203
- },
13204
- ArrowFunctionExpression(node) {
13205
- if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
13206
- },
13207
- FunctionExpression(node) {
13208
- if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
13209
- }
13210
- };
13211
- }
13212
- });
13213
- //#endregion
13214
- //#region src/plugin/semantic/scope-analysis.ts
13215
- const isHoistedBindingKind = (kind) => kind === "var" || kind === "function";
13216
- const findHoistTargetScope = (scope) => {
13217
- let current = scope;
13218
- while (current) {
13219
- if (current.kind === "module" || current.kind === "function" || current.kind === "arrow-function" || current.kind === "method") return current;
13220
- current = current.parent;
13221
- }
13222
- return scope;
13223
- };
13224
- const createScope = (kind, node, parent, state) => {
13225
- const scope = {
13226
- id: state.nextScopeId++,
13227
- kind,
13228
- node,
13229
- parent,
13230
- children: [],
13231
- symbols: [],
13232
- references: [],
13233
- symbolsByName: /* @__PURE__ */ new Map()
13234
- };
13235
- if (parent) parent.children.push(scope);
13236
- return scope;
13237
- };
13238
- const pushScope = (kind, node, state) => {
13239
- const scope = createScope(kind, node, state.currentScope, state);
13240
- state.scopeStack.push(scope);
13241
- state.currentScope = scope;
13242
- state.ownScopeForNode.set(node, scope);
13243
- return scope;
13244
- };
13245
- const popScope = (state) => {
13246
- state.scopeStack.pop();
13247
- const previous = state.scopeStack[state.scopeStack.length - 1];
13248
- if (!previous) throw new Error("scope stack underflow");
13249
- state.currentScope = previous;
13250
- };
13251
- const recordSymbol = (scope, state, options) => {
13252
- const symbol = {
13253
- id: state.nextSymbolId++,
13254
- name: options.name,
13255
- kind: options.kind,
13256
- bindingIdentifier: options.bindingIdentifier,
13257
- declarationNode: options.declarationNode,
13258
- scope,
13259
- initializer: options.initializer,
13260
- references: []
13261
- };
13262
- scope.symbols.push(symbol);
13263
- scope.symbolsByName.set(options.name, symbol);
13264
- state.symbolByBindingIdentifier.set(options.bindingIdentifier, symbol);
13265
- return symbol;
13266
- };
13267
- const collectBindingNamesFromPattern = (pattern) => {
13268
- const out = [];
13269
- const visit = (node) => {
13270
- if (isNodeOfType(node, "Identifier")) {
13271
- out.push(node);
13272
- return;
13273
- }
13274
- if (isNodeOfType(node, "ObjectPattern")) {
13275
- for (const property of node.properties) if (isNodeOfType(property, "Property")) {
13276
- const propValue = property.value;
13277
- visit(propValue);
13278
- } else if (isNodeOfType(property, "RestElement")) visit(property.argument);
13279
- return;
13280
- }
13281
- if (isNodeOfType(node, "ArrayPattern")) {
13282
- for (const element of node.elements) if (element) visit(element);
13283
- return;
13284
- }
13285
- if (isNodeOfType(node, "RestElement")) {
13286
- visit(node.argument);
13287
- return;
13288
- }
13289
- if (isNodeOfType(node, "AssignmentPattern")) visit(node.left);
13290
- };
13291
- visit(pattern);
13292
- return out;
13293
- };
13294
- const visitDestructuringDeclarations = (pattern, baseInitializer, scope, state, symbolKind, declarationNode) => {
13295
- if (isNodeOfType(pattern, "Identifier")) {
13296
- recordSymbol(scope, state, {
13297
- name: pattern.name,
13298
- kind: symbolKind,
13299
- bindingIdentifier: pattern,
13300
- declarationNode,
13301
- initializer: baseInitializer
13302
- });
13303
- return;
13304
- }
13305
- if (isNodeOfType(pattern, "ObjectPattern")) {
13306
- for (const property of pattern.properties) if (isNodeOfType(property, "Property")) {
13307
- const propertyValue = property.value;
13308
- visitDestructuringDeclarations(propertyValue, isNodeOfType(propertyValue, "AssignmentPattern") ? propertyValue.right : baseInitializer, scope, state, symbolKind, declarationNode);
13309
- } else if (isNodeOfType(property, "RestElement")) visitDestructuringDeclarations(property.argument, baseInitializer, scope, state, symbolKind, declarationNode);
13310
- return;
13311
- }
13312
- if (isNodeOfType(pattern, "ArrayPattern")) {
13313
- for (const element of pattern.elements) {
13314
- if (!element) continue;
13315
- visitDestructuringDeclarations(element, isNodeOfType(element, "AssignmentPattern") ? element.right ?? null : baseInitializer, scope, state, symbolKind, declarationNode);
13316
- }
13317
- return;
13318
- }
13319
- if (isNodeOfType(pattern, "AssignmentPattern")) {
13320
- visitDestructuringDeclarations(pattern.left, pattern.right ?? null, scope, state, symbolKind, declarationNode);
13321
- return;
13322
- }
13323
- if (isNodeOfType(pattern, "RestElement")) visitDestructuringDeclarations(pattern.argument, null, scope, state, symbolKind, declarationNode);
13324
- };
13325
- const tagAsBinding = (state, identifier) => {
13326
- bindingPositionMarker.add(identifier);
13327
- };
13328
- const bindingPositionMarker = /* @__PURE__ */ new WeakSet();
13329
- const recordReference = (state, identifier, flag) => {
13330
- const reference = {
13331
- id: state.nextReferenceId++,
13332
- identifier,
13333
- resolvedSymbol: null,
13334
- flag,
13335
- scope: state.currentScope
13336
- };
13337
- state.currentScope.references.push(reference);
13338
- state.referenceByIdentifier.set(identifier, reference);
13339
- };
13340
- const isFunctionBodyBlock = (block) => {
13341
- if (!block.parent) return false;
13342
- return isFunctionLike$1(block.parent);
13343
- };
13344
- const isCatchClauseBlock = (block) => block.parent !== null && block.parent !== void 0 && block.parent.type === "CatchClause";
13345
- const handleVariableDeclaration = (declaration, state) => {
13346
- if (!isNodeOfType(declaration, "VariableDeclaration")) return;
13347
- const symbolKind = declaration.kind === "var" ? "var" : declaration.kind === "let" ? "let" : declaration.kind === "const" ? "const" : "using";
13348
- const targetScope = isHoistedBindingKind(symbolKind) ? findHoistTargetScope(state.currentScope) : state.currentScope;
13349
- for (const declarator of declaration.declarations) {
13350
- const declaratorNode = declarator;
13351
- const init = declarator.init ?? null;
13352
- visitDestructuringDeclarations(declarator.id, init, targetScope, state, symbolKind, declaratorNode);
13353
- for (const identifier of collectBindingNamesFromPattern(declarator.id)) tagAsBinding(state, identifier);
13354
- }
13355
- };
13356
- const handleFunctionDeclaration = (fn, state) => {
13357
- if (!isNodeOfType(fn, "FunctionDeclaration")) return;
13358
- if (fn.id) {
13359
- recordSymbol(findHoistTargetScope(state.currentScope), state, {
13360
- name: fn.id.name,
13361
- kind: "function",
13362
- bindingIdentifier: fn.id,
13363
- declarationNode: fn,
13364
- initializer: fn
13365
- });
13366
- tagAsBinding(state, fn.id);
13367
- }
13368
- };
13369
- const handleClassDeclaration = (cls, state) => {
13370
- if (!isNodeOfType(cls, "ClassDeclaration")) return;
13371
- if (cls.id) {
13372
- recordSymbol(state.currentScope, state, {
13373
- name: cls.id.name,
13374
- kind: "class",
13375
- bindingIdentifier: cls.id,
13376
- declarationNode: cls,
13377
- initializer: cls
13378
- });
13379
- tagAsBinding(state, cls.id);
13380
- }
13381
- };
13382
- const handleImportDeclaration = (importDeclaration, state) => {
13383
- if (!isNodeOfType(importDeclaration, "ImportDeclaration")) return;
13384
- const target = findHoistTargetScope(state.currentScope);
13385
- for (const specifier of importDeclaration.specifiers) {
13386
- const local = specifier.local;
13387
- if (!isNodeOfType(local, "Identifier")) continue;
13388
- recordSymbol(target, state, {
13389
- name: local.name,
13390
- kind: "import",
13391
- bindingIdentifier: local,
13392
- declarationNode: specifier,
13393
- initializer: specifier
13394
- });
13395
- tagAsBinding(state, local);
13396
- }
13397
- };
13398
- const handleTsDeclarations = (node, state) => {
13399
- if (node.type !== "TSImportEqualsDeclaration" && node.type !== "TSEnumDeclaration" && node.type !== "TSTypeAliasDeclaration" && node.type !== "TSInterfaceDeclaration" && node.type !== "TSModuleDeclaration") return;
13400
- const idNode = node.id;
13401
- if (!idNode || !isNodeOfType(idNode, "Identifier")) return;
13402
- const kind = node.type === "TSImportEqualsDeclaration" ? "ts-import-equals" : node.type === "TSEnumDeclaration" ? "ts-enum" : node.type === "TSTypeAliasDeclaration" ? "ts-type-alias" : node.type === "TSInterfaceDeclaration" ? "ts-interface" : "ts-module";
13403
- recordSymbol(findHoistTargetScope(state.currentScope), state, {
13404
- name: idNode.name,
13405
- kind,
13406
- bindingIdentifier: idNode,
13407
- declarationNode: node,
13408
- initializer: null
13409
- });
13410
- tagAsBinding(state, idNode);
13411
- };
13412
- const handleFunctionParameters = (params, scope, state) => {
13413
- for (const param of params) {
13414
- visitDestructuringDeclarations(param, null, scope, state, "parameter", param);
13415
- for (const identifier of collectBindingNamesFromPattern(param)) tagAsBinding(state, identifier);
13416
- }
13417
- };
13418
- const shouldPushBlockScope = (block) => {
13419
- if (!isNodeOfType(block, "BlockStatement")) return false;
13420
- if (isFunctionBodyBlock(block)) return false;
13421
- if (isCatchClauseBlock(block)) return false;
13422
- if (block.parent && block.parent.type === "TSModuleDeclaration") return false;
13423
- return true;
13424
- };
13425
- const isJsxIdentifierBindingReference = (identifier) => {
13426
- const parent = identifier.parent;
13427
- if (!parent) return false;
13428
- if (parent.type === "JSXMemberExpression") return parent.object === identifier;
13429
- if (parent.type === "JSXNamespacedName") return false;
13430
- const ASCII_LOWERCASE_A = 97;
13431
- const ASCII_LOWERCASE_Z = 122;
13432
- const firstCharCode = identifier.name.charCodeAt(0);
13433
- if (firstCharCode >= ASCII_LOWERCASE_A && firstCharCode <= ASCII_LOWERCASE_Z) return false;
13434
- return true;
13435
- };
13436
- const isNonReferencePosition = (identifier) => {
13437
- const parent = identifier.parent;
13438
- if (!parent) return false;
13439
- switch (parent.type) {
13440
- case "MemberExpression": return parent.property === identifier && !parent.computed;
13441
- case "Property": return parent.key === identifier && !parent.computed && !parent.shorthand;
13442
- case "MethodDefinition":
13443
- case "PropertyDefinition": return parent.key === identifier && !parent.computed;
13444
- case "JSXAttribute": return parent.name === identifier;
13445
- case "ImportSpecifier": return parent.imported === identifier;
13446
- case "ExportSpecifier": return parent.exported === identifier && parent.local !== parent.exported;
13447
- case "LabeledStatement":
13448
- case "BreakStatement":
13449
- case "ContinueStatement": return parent.label === identifier;
13450
- default: return false;
13451
- }
13452
- };
13453
- const inferReferenceFlag = (identifier) => {
13454
- const parent = identifier.parent;
13455
- if (!parent) return "read";
13456
- switch (parent.type) {
13457
- case "AssignmentExpression":
13458
- if (parent.left === identifier) return parent.operator === "=" ? "write" : "read-write";
13459
- return "read";
13460
- case "UpdateExpression": return "read-write";
13461
- case "ForInStatement":
13462
- case "ForOfStatement": return parent.left === identifier ? "write" : "read";
13463
- default: return "read";
13464
- }
13465
- };
13466
- const setNodeScope = (node, state) => {
13467
- state.nodeScope.set(node, state.currentScope);
13468
- };
13469
- const walkParameterReferences = (pattern, state) => {
13470
- if (isNodeOfType(pattern, "AssignmentPattern")) {
13471
- walkParameterReferences(pattern.left, state);
13472
- const defaultValue = pattern.right ?? null;
13473
- if (defaultValue) walk(defaultValue, state);
13474
- return;
13475
- }
13476
- if (isNodeOfType(pattern, "ObjectPattern")) {
13477
- for (const property of pattern.properties) {
13478
- const propertyNode = property;
13479
- if (isNodeOfType(propertyNode, "RestElement")) {
13480
- walkParameterReferences(propertyNode.argument, state);
13481
- continue;
13482
- }
13483
- if (!isNodeOfType(propertyNode, "Property")) continue;
13484
- const propertyDetail = propertyNode;
13485
- if (propertyDetail.computed) walk(propertyDetail.key, state);
13486
- walkParameterReferences(propertyDetail.value, state);
13487
- }
13488
- return;
13489
- }
13490
- if (isNodeOfType(pattern, "ArrayPattern")) {
13491
- for (const element of pattern.elements) if (element) walkParameterReferences(element, state);
13492
- return;
13493
- }
13494
- if (isNodeOfType(pattern, "RestElement")) walkParameterReferences(pattern.argument, state);
13495
- };
13496
- const walk = (node, state) => {
13497
- if (isFunctionLike$1(node)) {
13498
- if (isNodeOfType(node, "FunctionDeclaration") && node.id) handleFunctionDeclaration(node, state);
13499
- const functionParams = node.params ?? [];
13500
- for (const param of functionParams) {
13501
- if (!("decorators" in param) || !Array.isArray(param.decorators)) continue;
13502
- for (const decorator of param.decorators) if (isAstNode(decorator)) walk(decorator, state);
13503
- }
13504
- setNodeScope(node, state);
13505
- const fnScope = pushScope(node.type === "ArrowFunctionExpression" ? "arrow-function" : "function", node, state);
13506
- if ((isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "FunctionDeclaration")) && node.id) {
13507
- recordSymbol(fnScope, state, {
13508
- name: node.id.name,
13509
- kind: "function",
13510
- bindingIdentifier: node.id,
13511
- declarationNode: node,
13512
- initializer: node
13513
- });
13514
- tagAsBinding(state, node.id);
13515
- }
13516
- handleFunctionParameters(functionParams, fnScope, state);
13517
- for (const param of functionParams) walkParameterReferences(param, state);
13518
- const body = node.body;
13519
- if (body) walk(body, state);
13520
- popScope(state);
13521
- return;
13522
- }
13523
- if (isNodeOfType(node, "ClassDeclaration") || isNodeOfType(node, "ClassExpression")) {
13524
- if (isNodeOfType(node, "ClassDeclaration") && node.id) handleClassDeclaration(node, state);
13525
- if (Array.isArray(node.decorators)) {
13526
- for (const decorator of node.decorators) if (isAstNode(decorator)) walk(decorator, state);
13527
- }
13528
- const classScope = pushScope("class", node, state);
13529
- setNodeScope(node, state);
13530
- if (isNodeOfType(node, "ClassExpression") && node.id) {
13531
- recordSymbol(classScope, state, {
13532
- name: node.id.name,
13533
- kind: "class",
13534
- bindingIdentifier: node.id,
13535
- declarationNode: node,
13536
- initializer: node
13537
- });
13538
- tagAsBinding(state, node.id);
13539
- }
13540
- if (node.superClass) walk(node.superClass, state);
13541
- if (node.body) walk(node.body, state);
13542
- popScope(state);
13543
- return;
13544
- }
13545
- if (isNodeOfType(node, "CatchClause")) {
13546
- const catchScope = pushScope("catch", node, state);
13547
- setNodeScope(node, state);
13548
- if (node.param) {
13549
- visitDestructuringDeclarations(node.param, null, catchScope, state, "catch-clause-parameter", node);
13550
- for (const identifier of collectBindingNamesFromPattern(node.param)) tagAsBinding(state, identifier);
13551
- }
13552
- if (node.body) walk(node.body, state);
13553
- popScope(state);
13554
- return;
13555
- }
13556
- if (isNodeOfType(node, "ForStatement") || isNodeOfType(node, "ForInStatement") || isNodeOfType(node, "ForOfStatement")) {
13557
- pushScope("for", node, state);
13558
- setNodeScope(node, state);
13559
- const nodeRecord = node;
13560
- for (const key of Object.keys(nodeRecord)) {
13561
- if (key === "parent") continue;
13562
- if (TYPE_POSITION_CHILD_KEYS.has(key)) continue;
13563
- const child = nodeRecord[key];
13564
- if (Array.isArray(child)) {
13565
- for (const item of child) if (isAstNode(item)) walk(item, state);
13566
- } else if (isAstNode(child)) walk(child, state);
13567
- }
13568
- popScope(state);
13569
- return;
13570
- }
13571
- if (isNodeOfType(node, "SwitchStatement")) {
13572
- pushScope("switch", node, state);
13573
- setNodeScope(node, state);
13574
- if (node.discriminant) walk(node.discriminant, state);
13575
- for (const switchCase of node.cases) walk(switchCase, state);
13576
- popScope(state);
13577
- return;
13578
- }
13579
- if (isNodeOfType(node, "TSModuleDeclaration")) {
13580
- const moduleScope = pushScope("ts-module", node, state);
13581
- setNodeScope(node, state);
13582
- if (node.id && isNodeOfType(node.id, "Identifier")) {
13583
- const identifier = node.id;
13584
- recordSymbol(findHoistTargetScope(moduleScope.parent ?? state.currentScope), state, {
13585
- name: identifier.name,
13586
- kind: "ts-module",
13587
- bindingIdentifier: identifier,
13588
- declarationNode: node,
13589
- initializer: null
13590
- });
13591
- tagAsBinding(state, identifier);
13592
- }
13593
- if (node.body) walk(node.body, state);
13594
- popScope(state);
13595
- return;
13596
- }
13597
- if (isNodeOfType(node, "TSEnumDeclaration")) {
13598
- handleTsDeclarations(node, state);
13599
- pushScope("ts-enum", node, state);
13600
- setNodeScope(node, state);
13601
- const members = node.members ?? [];
13602
- for (const member of members) walk(member, state);
13603
- popScope(state);
13604
- return;
13605
- }
13606
- if (isNodeOfType(node, "BlockStatement") && shouldPushBlockScope(node)) {
13607
- pushScope("block", node, state);
13608
- setNodeScope(node, state);
13609
- for (const statement of node.body) walk(statement, state);
13610
- popScope(state);
13611
- return;
13612
- }
13613
- setNodeScope(node, state);
13614
- if (isNodeOfType(node, "VariableDeclaration")) handleVariableDeclaration(node, state);
13615
- else if (isNodeOfType(node, "FunctionDeclaration")) handleFunctionDeclaration(node, state);
13616
- else if (isNodeOfType(node, "ClassDeclaration")) handleClassDeclaration(node, state);
13617
- else if (isNodeOfType(node, "ImportDeclaration")) handleImportDeclaration(node, state);
13618
- else if (node.type === "TSImportEqualsDeclaration" || node.type === "TSTypeAliasDeclaration" || node.type === "TSInterfaceDeclaration") handleTsDeclarations(node, state);
13619
- if ((isNodeOfType(node, "Identifier") || isNodeOfType(node, "JSXIdentifier")) && !bindingPositionMarker.has(node) && !isNonReferencePosition(node)) if (isNodeOfType(node, "JSXIdentifier")) {
13620
- if (isJsxIdentifierBindingReference(node)) recordReference(state, node, inferReferenceFlag(node));
13621
- } else recordReference(state, node, inferReferenceFlag(node));
13622
- const nodeRecord = node;
13623
- for (const key of Object.keys(nodeRecord)) {
13624
- if (key === "parent") continue;
13625
- if (TYPE_POSITION_CHILD_KEYS.has(key)) continue;
13626
- const child = nodeRecord[key];
13627
- if (Array.isArray(child)) {
13628
- for (const item of child) if (isAstNode(item)) walk(item, state);
13629
- } else if (isAstNode(child)) walk(child, state);
13630
- }
13631
- };
13632
- const resolveReferences = (rootScope) => {
13633
- const visitScope = (scope) => {
13634
- for (const reference of scope.references) {
13635
- const name = reference.identifier.name;
13636
- if (typeof name !== "string") continue;
13637
- let lookup = scope;
13638
- while (lookup) {
13639
- const found = lookup.symbolsByName.get(name);
13640
- if (found) {
13641
- reference.resolvedSymbol = found;
13642
- found.references.push(reference);
13643
- break;
13644
- }
13645
- lookup = lookup.parent;
13676
+ return;
13677
+ }
13678
+ if (isNodeOfType(child, "CallExpression")) {
13679
+ if ((child.arguments ?? []).some((argumentNode) => {
13680
+ const argumentKey = resolveExpressionKey(argumentNode, context);
13681
+ return argumentKey !== null && (argumentKey === storage.refKey || argumentKey === storage.refCurrentKey);
13682
+ })) {
13683
+ hasUnsafeRefWrite = true;
13684
+ return false;
13646
13685
  }
13686
+ return;
13647
13687
  }
13648
- for (const child of scope.children) visitScope(child);
13649
- };
13650
- visitScope(rootScope);
13688
+ if (!isNodeOfType(child, "AssignmentExpression")) return;
13689
+ const assignmentTarget = stripParenExpression(child.left);
13690
+ if (isNodeOfType(assignmentTarget, "MemberExpression") && assignmentTarget.computed && resolveExpressionKey(assignmentTarget.object, context) === storage.refCurrentKey) {
13691
+ hasUnsafeRefWrite = true;
13692
+ return false;
13693
+ }
13694
+ const assignedKey = resolveExpressionKey(child.left, context);
13695
+ if (assignedKey === storage.handlerKey) {
13696
+ hasUnsafeRefWrite = true;
13697
+ return false;
13698
+ }
13699
+ if (assignedKey !== storage.refCurrentKey) return;
13700
+ const assignedValue = stripParenExpression(child.right);
13701
+ if (isNodeOfType(assignedValue, "Literal") && assignedValue.value === null && findEnclosingFunction$1(child) === cleanupFunction) return;
13702
+ const assignedSessionProperties = (isNodeOfType(assignedValue, "ObjectExpression") ? assignedValue : null)?.properties ?? [];
13703
+ const storesMatchingHandler = assignedSessionProperties.every((property) => isNodeOfType(property, "Property")) && assignedSessionProperties.some((property) => isNodeOfType(property, "Property") && `${storage.refCurrentKey}.${getStaticPropertyKeyName(property) ?? ""}` === storage.handlerKey && resolveExpressionKey(property.value, context) === usage.handlerKey);
13704
+ if (findEnclosingFunction$1(child) !== retainedFunction || !storesMatchingHandler) {
13705
+ hasUnsafeRefWrite = true;
13706
+ return false;
13707
+ }
13708
+ });
13709
+ return !hasUnsafeRefWrite;
13651
13710
  };
13652
- const analyzeScopes = (program) => {
13653
- const rootScope = {
13654
- id: 0,
13655
- kind: "module",
13656
- node: program,
13657
- parent: null,
13658
- children: [],
13659
- symbols: [],
13660
- references: [],
13661
- symbolsByName: /* @__PURE__ */ new Map()
13662
- };
13663
- const state = {
13664
- nextScopeId: 1,
13665
- nextSymbolId: 0,
13666
- nextReferenceId: 0,
13667
- currentScope: rootScope,
13668
- scopeStack: [rootScope],
13669
- rootScope,
13670
- nodeScope: /* @__PURE__ */ new WeakMap(),
13671
- ownScopeForNode: /* @__PURE__ */ new WeakMap(),
13672
- symbolByBindingIdentifier: /* @__PURE__ */ new WeakMap(),
13673
- referenceByIdentifier: /* @__PURE__ */ new WeakMap()
13711
+ const effectReturnsRefOwnedCleanup = (effectCallback, componentFunction, retainedFunction, usage, context) => {
13712
+ const matchesReturnedCleanup = (returnedValue) => {
13713
+ const cleanupFunction = resolveRefOwnedCleanupFunction(returnedValue, context);
13714
+ return Boolean(cleanupFunction && cleanupFunctionReleasesRefOwnedUsage(cleanupFunction, componentFunction, retainedFunction, usage, context));
13674
13715
  };
13675
- state.nodeScope.set(program, rootScope);
13676
- state.ownScopeForNode.set(program, rootScope);
13677
- if (isNodeOfType(program, "Program")) for (const statement of program.body) walk(statement, state);
13678
- else walk(program, state);
13679
- resolveReferences(rootScope);
13680
- const scopeFor = (node) => {
13681
- let current = node;
13682
- while (current) {
13683
- const scope = state.nodeScope.get(current);
13684
- if (scope) return scope;
13685
- current = current.parent ?? null;
13716
+ if (!isFunctionLike$1(effectCallback)) return false;
13717
+ if (!isNodeOfType(effectCallback.body, "BlockStatement")) return matchesReturnedCleanup(stripParenExpression(effectCallback.body));
13718
+ const matchingReturns = [];
13719
+ walkInsideStatementBlocks(effectCallback.body, (child) => {
13720
+ if (isNodeOfType(child, "ReturnStatement") && child.argument && matchesReturnedCleanup(stripParenExpression(child.argument))) matchingReturns.push(child);
13721
+ });
13722
+ return doMatchingNodesCoverEveryPathFromFunctionEntry(effectCallback, matchingReturns, context);
13723
+ };
13724
+ const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) => {
13725
+ const componentFunction = findEnclosingFunction$1(retainedFunction);
13726
+ if (!componentFunction || !isFunctionLike$1(componentFunction)) return false;
13727
+ let didFindCleanupEffect = false;
13728
+ walkAst(componentFunction.body, (child) => {
13729
+ if (didFindCleanupEffect) return false;
13730
+ if (!isNodeOfType(child, "CallExpression") || findEnclosingFunction$1(child) !== componentFunction || !isReactApiCall(child, "useEffect", context.scopes)) return;
13731
+ const effectStatement = findTransparentExpressionRoot(child).parent;
13732
+ if (!isNodeOfType(effectStatement, "ExpressionStatement") || effectStatement.parent !== componentFunction.body) return;
13733
+ const effectCallback = getEffectCallback(child);
13734
+ if (effectCallback && effectReturnsRefOwnedCleanup(effectCallback, componentFunction, retainedFunction, usage, context)) {
13735
+ didFindCleanupEffect = true;
13736
+ return false;
13686
13737
  }
13687
- return rootScope;
13688
- };
13689
- const symbolFor = (identifier) => {
13690
- const reference = state.referenceByIdentifier.get(identifier);
13691
- if (reference) return reference.resolvedSymbol;
13692
- const symbolForBinding = state.symbolByBindingIdentifier.get(identifier);
13693
- if (symbolForBinding) return symbolForBinding;
13694
- return null;
13695
- };
13696
- const referenceFor = (identifier) => {
13697
- return state.referenceByIdentifier.get(identifier) ?? null;
13698
- };
13699
- const isGlobalReference = (identifier) => {
13700
- const reference = state.referenceByIdentifier.get(identifier);
13701
- if (!reference) return false;
13702
- return reference.resolvedSymbol === null;
13703
- };
13704
- const ownScopeFor = (node) => {
13705
- return state.ownScopeForNode.get(node) ?? null;
13706
- };
13707
- return {
13708
- rootScope,
13709
- scopeFor,
13710
- ownScopeFor,
13711
- symbolFor,
13712
- referenceFor,
13713
- isGlobalReference
13738
+ });
13739
+ return didFindCleanupEffect;
13740
+ };
13741
+ const isUseSyncExternalStoreSubscribeFunction = (functionNode, context) => {
13742
+ const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
13743
+ if (!bindingIdentifier) return false;
13744
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
13745
+ const isSubscribeBinding = (candidateBinding) => {
13746
+ const symbol = context.scopes.symbolFor(candidateBinding);
13747
+ if (!symbol || visitedSymbolIds.has(symbol.id) || symbol.references.length === 0) return false;
13748
+ visitedSymbolIds.add(symbol.id);
13749
+ return symbol.references.every((reference) => {
13750
+ const referenceRoot = findTransparentExpressionRoot(reference.identifier);
13751
+ const referenceParent = referenceRoot.parent;
13752
+ if (isNodeOfType(referenceParent, "CallExpression") && referenceParent.arguments?.[0] === referenceRoot) return isReactApiCall(referenceParent, "useSyncExternalStore", context.scopes);
13753
+ const aliasDeclaration = referenceParent?.parent;
13754
+ return Boolean(isNodeOfType(referenceParent, "VariableDeclarator") && referenceParent.init === referenceRoot && isNodeOfType(referenceParent.id, "Identifier") && isNodeOfType(aliasDeclaration, "VariableDeclaration") && aliasDeclaration.kind === "const" && isSubscribeBinding(referenceParent.id));
13755
+ });
13714
13756
  };
13757
+ return isSubscribeBinding(bindingIdentifier);
13715
13758
  };
13716
- const isDescendantScope = (inner, outer) => {
13717
- let current = inner;
13718
- while (current) {
13719
- if (current === outer) return true;
13720
- current = current.parent;
13759
+ const doesResourceResultEscape = (resourceNode, allowConciseReturnEscape) => {
13760
+ let currentNode = resourceNode;
13761
+ let parentNode = currentNode.parent;
13762
+ while (parentNode) {
13763
+ if (isNodeOfType(parentNode, "ReturnStatement") && parentNode.argument === currentNode) return true;
13764
+ if (isNodeOfType(parentNode, "ArrowFunctionExpression") && parentNode.body === currentNode && allowConciseReturnEscape) return true;
13765
+ if (isNodeOfType(parentNode, "ChainExpression") || isNodeOfType(parentNode, "TSAsExpression") || isNodeOfType(parentNode, "TSNonNullExpression")) {
13766
+ currentNode = parentNode;
13767
+ parentNode = currentNode.parent;
13768
+ continue;
13769
+ }
13770
+ return false;
13721
13771
  }
13722
13772
  return false;
13723
13773
  };
13774
+ const findRetainedFunctionLeak = (retainedFunction, context) => {
13775
+ if (!isFunctionLike$1(retainedFunction)) return null;
13776
+ const body = retainedFunction.body;
13777
+ if (!body) return null;
13778
+ let leak = null;
13779
+ const allowConciseReturnEscape = !isInlineRetainedHandlerFunction(retainedFunction, context);
13780
+ const isExternalStoreSubscribeFunction = isUseSyncExternalStoreSubscribeFunction(retainedFunction, context);
13781
+ const hasReleaseForUsage = (usage) => isExternalStoreSubscribeFunction ? effectHasCleanupForUsage(retainedFunction, usage, context) : fileContainsReleaseForUsage(usage, context) || hasGuaranteedRefOwnedUnmountCleanup(retainedFunction, usage, context);
13782
+ walkAst(body, (child) => {
13783
+ if (leak !== null) return false;
13784
+ if (isFunctionLike$1(child)) return false;
13785
+ if (isSocketConstruction(child) && !doesResourceResultEscape(child, false)) {
13786
+ const socketUsage = {
13787
+ kind: "socket",
13788
+ node: child,
13789
+ resourceName: isNodeOfType(child.callee, "Identifier") ? child.callee.name : "WebSocket",
13790
+ handleKey: findAssignedResourceKey(child, context),
13791
+ receiverKey: null,
13792
+ registrationVerbName: null,
13793
+ eventKey: null,
13794
+ handlerKey: null
13795
+ };
13796
+ if (!hasReleaseForUsage(socketUsage)) {
13797
+ leak = socketUsage;
13798
+ return false;
13799
+ }
13800
+ }
13801
+ if (!isNodeOfType(child, "CallExpression")) return;
13802
+ if (isNodeOfType(child.callee, "Identifier") && child.callee.name === "setInterval" && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
13803
+ const timerUsage = {
13804
+ kind: "timer",
13805
+ node: child,
13806
+ resourceName: "setInterval",
13807
+ handleKey: findAssignedResourceKey(child, context),
13808
+ receiverKey: null,
13809
+ registrationVerbName: "setInterval",
13810
+ eventKey: null,
13811
+ handlerKey: null
13812
+ };
13813
+ if (!hasReleaseForUsage(timerUsage)) {
13814
+ leak = timerUsage;
13815
+ return false;
13816
+ }
13817
+ }
13818
+ if (isSubscribeOrObserveCall(child) && !doesResourceResultEscape(child, allowConciseReturnEscape)) {
13819
+ const registrationDetails = getCallRegistrationDetails(child, context);
13820
+ const subscriptionUsage = {
13821
+ kind: "subscribe",
13822
+ node: child,
13823
+ resourceName: registrationDetails.registrationVerbName ?? "subscribe",
13824
+ handleKey: findAssignedResourceKey(child, context),
13825
+ ...registrationDetails
13826
+ };
13827
+ if (!hasSelfReleasingListenerOptions(child, context) && !hasReleaseForUsage(subscriptionUsage)) leak = subscriptionUsage;
13828
+ return false;
13829
+ }
13830
+ });
13831
+ return leak;
13832
+ };
13833
+ const isRetainedComponentScopeFunction = (functionNode) => {
13834
+ if (isNodeOfType(functionNode, "FunctionDeclaration")) return enclosingComponentOrHookName(functionNode) !== null;
13835
+ if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression")) return false;
13836
+ if (!isNodeOfType(functionNode.parent, "VariableDeclarator")) return false;
13837
+ return enclosingComponentOrHookName(functionNode) !== null;
13838
+ };
13839
+ const isDirectJsxEventHandlerValue = (expression) => {
13840
+ const expressionRoot = findTransparentExpressionRoot(expression);
13841
+ const expressionContainer = expressionRoot.parent;
13842
+ return isNodeOfType(expressionContainer, "JSXExpressionContainer") && expressionContainer.expression === expressionRoot && isEventHandlerAttribute(expressionContainer.parent);
13843
+ };
13844
+ const isInlineRetainedHandlerFunction = (functionNode, context) => {
13845
+ if (!isFunctionLike$1(functionNode)) return false;
13846
+ const functionRoot = findTransparentExpressionRoot(functionNode);
13847
+ const callbackCall = functionRoot.parent;
13848
+ if (isNodeOfType(callbackCall, "CallExpression") && callbackCall.arguments?.[0] === functionRoot && isHookCall$2(callbackCall, "useCallback") && isDirectJsxEventHandlerValue(callbackCall)) return true;
13849
+ const parentNode = functionNode.parent;
13850
+ if (isDirectJsxEventHandlerValue(functionNode)) return true;
13851
+ if (!isNodeOfType(parentNode, "Property") || parentNode.value !== functionNode || parentNode.computed) return false;
13852
+ const propertyName = getStaticPropertyKeyName(parentNode);
13853
+ if (!propertyName || !/^on[A-Z]/.test(propertyName)) return false;
13854
+ const objectExpression = parentNode.parent;
13855
+ if (!isNodeOfType(objectExpression, "ObjectExpression")) return false;
13856
+ const objectParent = objectExpression.parent;
13857
+ return (isNodeOfType(objectParent, "CallExpression") && objectParent.arguments.some((argument) => argument === objectExpression) || isNodeOfType(objectParent, "JSXExpressionContainer")) && findRenderPhaseComponentOrHook(parentNode, context.scopes) !== null;
13858
+ };
13859
+ const effectNeedsCleanup = defineRule({
13860
+ id: "effect-needs-cleanup",
13861
+ title: "Effect subscription or timer never cleaned up",
13862
+ severity: "error",
13863
+ tags: ["test-noise"],
13864
+ recommendation: "Return a cleanup function that stops the subscription or timer: `return () => target.removeEventListener(name, handler)` for listeners, `return () => clearInterval(id)` or `clearTimeout(id)` for timers, `return () => observer.disconnect()` for observers, `return () => socket.close()` for connections, or `return unsubscribe` if the subscribe call already gave you one.",
13865
+ create: (context) => {
13866
+ const reportRetainedLeak = (retainedFunction) => {
13867
+ if (!isPotentiallyReachableFunction(retainedFunction, context)) return;
13868
+ const leak = findRetainedFunctionLeak(retainedFunction, context);
13869
+ if (!leak) return;
13870
+ const resourceNoun = RESOURCE_NOUN_BY_KIND[leak.kind];
13871
+ context.report({
13872
+ node: leak.node,
13873
+ message: `\`${leak.resourceName}\` creates a ${resourceNoun} in a function that outlives the render, with no cleanup path. Store the handle and release it, or move this into a useEffect that returns cleanup, so it does not leak after unmount.`
13874
+ });
13875
+ };
13876
+ return {
13877
+ CallExpression(node) {
13878
+ if (isHookCall$2(node, "useCallback")) {
13879
+ const retainedCallback = getEffectCallback(node);
13880
+ if (retainedCallback && !isInlineRetainedHandlerFunction(retainedCallback, context)) reportRetainedLeak(retainedCallback);
13881
+ return;
13882
+ }
13883
+ if (!isHookCall$2(node, CLEANUP_EFFECT_HOOK_NAMES)) return;
13884
+ const callback = getEffectCallback(node);
13885
+ if (!callback) return;
13886
+ const usages = removeSynchronouslyReleasedUsages(callback, findSubscribeLikeUsages(callback, context), context);
13887
+ if (usages.length === 0) return;
13888
+ const firstUsage = findFirstUsageWithoutCleanup(callback, usages, context);
13889
+ if (!firstUsage) return;
13890
+ const resourceNoun = RESOURCE_NOUN_BY_KIND[firstUsage.kind];
13891
+ const hookName = getCalleeName$2(node) ?? "effect";
13892
+ context.report({
13893
+ node,
13894
+ message: `\`${firstUsage.resourceName}\` creates a ${resourceNoun} in ${hookName} without guaranteed cleanup. Return a cleanup function that owns every allocation so it does not leak after unmount.`
13895
+ });
13896
+ },
13897
+ FunctionDeclaration(node) {
13898
+ if (isRetainedComponentScopeFunction(node)) reportRetainedLeak(node);
13899
+ },
13900
+ ArrowFunctionExpression(node) {
13901
+ if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
13902
+ },
13903
+ FunctionExpression(node) {
13904
+ if (isRetainedComponentScopeFunction(node) || isInlineRetainedHandlerFunction(node, context)) reportRetainedLeak(node);
13905
+ }
13906
+ };
13907
+ }
13908
+ });
13724
13909
  //#endregion
13725
13910
  //#region src/plugin/semantic/closure-captures.ts
13726
13911
  const computeClosureCaptures = (functionNode, scopes) => {
@@ -17006,8 +17191,86 @@ const htmlNoNestedInteractive = defineRule({
17006
17191
  } })
17007
17192
  });
17008
17193
  //#endregion
17194
+ //#region src/plugin/utils/get-authoritative-jsx-attribute.ts
17195
+ const getAuthoritativeJsxAttribute = (attributes, targetName, isCaseSensitive = true) => {
17196
+ const normalizedTargetName = isCaseSensitive ? targetName : targetName.toLowerCase();
17197
+ for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
17198
+ const attribute = attributes[attributeIndex];
17199
+ if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
17200
+ if (!isNodeOfType(attribute, "JSXAttribute")) continue;
17201
+ const attributeName = getJsxAttributeName(attribute.name);
17202
+ if ((isCaseSensitive ? attributeName : attributeName?.toLowerCase()) === normalizedTargetName) return attribute;
17203
+ }
17204
+ return null;
17205
+ };
17206
+ //#endregion
17207
+ //#region src/plugin/utils/is-jsx-fragment-element.ts
17208
+ const isJsxFragmentElement = (node, scopes) => {
17209
+ if (!isNodeOfType(node, "JSXOpeningElement")) return false;
17210
+ const elementName = node.name;
17211
+ if (isNodeOfType(elementName, "JSXIdentifier")) {
17212
+ if (!scopes) return elementName.name === "Fragment";
17213
+ const symbol = resolveConstIdentifierAlias(elementName, scopes);
17214
+ if (!symbol) return elementName.name === "Fragment" && scopes.isGlobalReference(elementName);
17215
+ return isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment";
17216
+ }
17217
+ if (isNodeOfType(elementName, "JSXMemberExpression")) {
17218
+ if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
17219
+ if (elementName.property.name !== "Fragment") return false;
17220
+ if (!scopes) return elementName.object.name === "React";
17221
+ if (isReactNamespaceImport(elementName.object, scopes)) return true;
17222
+ return elementName.object.name === "React" && scopes.isGlobalReference(elementName.object);
17223
+ }
17224
+ return false;
17225
+ };
17226
+ //#endregion
17009
17227
  //#region src/plugin/rules/a11y/iframe-has-title.ts
17010
17228
  const MESSAGE$51 = "Screen reader users cannot identify this `<iframe>` because it has no title. Add a `title` that describes its content.";
17229
+ const isStaticallyAriaHidden = (openingElement) => {
17230
+ const ariaHiddenAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "aria-hidden", false);
17231
+ if (!ariaHiddenAttribute) return false;
17232
+ if (!ariaHiddenAttribute.value) return true;
17233
+ const value = ariaHiddenAttribute.value;
17234
+ if (isNodeOfType(value, "Literal")) return value.value === true || value.value === "true";
17235
+ if (!isNodeOfType(value, "JSXExpressionContainer")) return false;
17236
+ return isNodeOfType(value.expression, "Literal") && (value.expression.value === true || value.expression.value === "true");
17237
+ };
17238
+ const hasStaticallyNegativeTabIndex = (openingElement) => {
17239
+ const tabIndexAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "tabIndex", false);
17240
+ if (!tabIndexAttribute) return false;
17241
+ const value = tabIndexAttribute.value;
17242
+ if (value && isNodeOfType(value, "JSXExpressionContainer") && isNodeOfType(value.expression, "ConditionalExpression") && !isNodeOfType(value.expression.test, "Literal")) return false;
17243
+ const tabIndexValue = parseJsxValue(value);
17244
+ return tabIndexValue !== null && tabIndexValue < 0;
17245
+ };
17246
+ const hasStaticallyDecorativeRole = (openingElement, scopes) => {
17247
+ const roleAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "role", false);
17248
+ if (!roleAttribute) return false;
17249
+ const roleCandidates = getJsxPropStaticStringValues(roleAttribute, scopes);
17250
+ return roleCandidates !== null && roleCandidates.length > 0 && roleCandidates.every((roleCandidate) => {
17251
+ const firstValidRole = roleCandidate.split(/\s+/).find((roleToken) => VALID_ARIA_ROLES.has(roleToken));
17252
+ return firstValidRole !== void 0 && PRESENTATION_ROLES$1.has(firstValidRole);
17253
+ });
17254
+ };
17255
+ const isInsideStaticallyHiddenJsxSubtree = (openingElement, scopes) => {
17256
+ if (isStaticallyAriaHidden(openingElement)) return true;
17257
+ let ancestor = openingElement.parent?.parent;
17258
+ while (ancestor) {
17259
+ if (isNodeOfType(ancestor, "JSXAttribute")) {
17260
+ if (!(isNodeOfType(ancestor.name, "JSXIdentifier") && ancestor.name.name === "children")) return false;
17261
+ }
17262
+ if (isNodeOfType(ancestor, "JSXElement") && !isJsxFragmentElement(ancestor.openingElement, scopes)) {
17263
+ const ancestorName = ancestor.openingElement.name;
17264
+ if (!isNodeOfType(ancestorName, "JSXIdentifier")) return false;
17265
+ const firstCharacter = ancestorName.name[0];
17266
+ if (!(firstCharacter === firstCharacter?.toLowerCase())) return false;
17267
+ if (isStaticallyAriaHidden(ancestor.openingElement)) return true;
17268
+ }
17269
+ if (isNodeOfType(ancestor, "CallExpression") || isNodeOfType(ancestor, "NewExpression") || isNodeOfType(ancestor, "VariableDeclarator") || isNodeOfType(ancestor, "AssignmentExpression") || isNodeOfType(ancestor, "Property") || isFunctionLike$1(ancestor) || isNodeOfType(ancestor, "Program")) return false;
17270
+ ancestor = ancestor.parent;
17271
+ }
17272
+ return false;
17273
+ };
17011
17274
  const evaluateTitleValue = (value) => {
17012
17275
  if (!value) return "missing";
17013
17276
  if (isNodeOfType(value, "Literal")) {
@@ -17042,6 +17305,9 @@ const iframeHasTitle = defineRule({
17042
17305
  create: (context) => ({ JSXOpeningElement(node) {
17043
17306
  const tag = getElementType(node, context.settings);
17044
17307
  if (tag !== "iframe") return;
17308
+ if (isInsideStaticallyHiddenJsxSubtree(node, context.scopes)) return;
17309
+ if (hasStaticallyNegativeTabIndex(node)) return;
17310
+ if (hasStaticallyDecorativeRole(node, context.scopes)) return;
17045
17311
  const hasSpread = node.attributes.some((attribute) => isNodeOfType(attribute, "JSXSpreadAttribute"));
17046
17312
  const titleAttr = hasJsxPropIgnoreCase(node.attributes, "title");
17047
17313
  if (!titleAttr) {
@@ -17574,7 +17840,7 @@ const interactiveSupportsFocus = defineRule({
17574
17840
  if (!hasInteractiveHandler) return;
17575
17841
  const elementType = getElementType(node, context.settings);
17576
17842
  if (!HTML_TAGS.has(elementType)) return;
17577
- if (isDisabledElement(node) || isHiddenFromScreenReader(node, context.settings) || isPresentationRole(node)) return;
17843
+ if (canContentEditableBeTabbable(node, context.scopes, context.settings) || isDisabledElement(node) || isHiddenFromScreenReader(node, context.settings) || isPresentationRole(node)) return;
17578
17844
  const hasTabIndex = Boolean(hasJsxPropIgnoreCase(node.attributes, "tabIndex"));
17579
17845
  const hasId = Boolean(hasJsxPropIgnoreCase(node.attributes, "id"));
17580
17846
  for (const role of roleCandidates) {
@@ -21134,26 +21400,6 @@ const jsxFilenameExtension = defineRule({
21134
21400
  }
21135
21401
  });
21136
21402
  //#endregion
21137
- //#region src/plugin/utils/is-jsx-fragment-element.ts
21138
- const isJsxFragmentElement = (node, scopes) => {
21139
- if (!isNodeOfType(node, "JSXOpeningElement")) return false;
21140
- const elementName = node.name;
21141
- if (isNodeOfType(elementName, "JSXIdentifier")) {
21142
- if (!scopes) return elementName.name === "Fragment";
21143
- const symbol = resolveConstIdentifierAlias(elementName, scopes);
21144
- if (!symbol) return elementName.name === "Fragment" && scopes.isGlobalReference(elementName);
21145
- return isImportedFromReact(symbol) && getImportedName(symbol.declarationNode) === "Fragment";
21146
- }
21147
- if (isNodeOfType(elementName, "JSXMemberExpression")) {
21148
- if (!isNodeOfType(elementName.object, "JSXIdentifier")) return false;
21149
- if (elementName.property.name !== "Fragment") return false;
21150
- if (!scopes) return elementName.object.name === "React";
21151
- if (isReactNamespaceImport(elementName.object, scopes)) return true;
21152
- return elementName.object.name === "React" && scopes.isGlobalReference(elementName.object);
21153
- }
21154
- return false;
21155
- };
21156
- //#endregion
21157
21403
  //#region src/plugin/rules/react-builtins/jsx-fragments.ts
21158
21404
  const SYNTAX_MESSAGE = "`<React.Fragment>` is used where shorthand fragments are configured, so similar wrappers look different across the codebase.";
21159
21405
  const ELEMENT_MESSAGE = "Fragment shorthand is used where explicit fragments are configured, so similar wrappers look different across the codebase.";
@@ -26051,6 +26297,7 @@ const nextjsNoClientSideRedirect = defineRule({
26051
26297
  });
26052
26298
  //#endregion
26053
26299
  //#region src/plugin/rules/nextjs/nextjs-no-css-link.ts
26300
+ const HTTP_STYLESHEET_URL_PATTERN = /^https?:\/\//i;
26054
26301
  const nextjsNoCssLink = defineRule({
26055
26302
  id: "nextjs-no-css-link",
26056
26303
  title: "Linked stylesheet bypasses Next.js CSS optimization",
@@ -26064,10 +26311,10 @@ const nextjsNoCssLink = defineRule({
26064
26311
  const relAttribute = findJsxAttribute(attributes, "rel");
26065
26312
  if (!relAttribute?.value) return;
26066
26313
  if ((isNodeOfType(relAttribute.value, "Literal") ? relAttribute.value.value : null) !== "stylesheet") return;
26067
- const hrefAttribute = findJsxAttribute(attributes, "href");
26068
- if (!hrefAttribute?.value) return;
26069
- const hrefValue = isNodeOfType(hrefAttribute.value, "Literal") ? hrefAttribute.value.value : null;
26070
- if (typeof hrefValue === "string" && GOOGLE_FONTS_PATTERN.test(hrefValue)) return;
26314
+ if (!findJsxAttribute(attributes, "href")?.value) return;
26315
+ const authoritativeHrefAttribute = getAuthoritativeJsxAttribute(attributes, "href");
26316
+ const hrefCandidates = authoritativeHrefAttribute ? getJsxPropStaticStringValues(authoritativeHrefAttribute, context.scopes) : null;
26317
+ if (hrefCandidates !== null && hrefCandidates.length > 0 && hrefCandidates.every((hrefCandidate) => HTTP_STYLESHEET_URL_PATTERN.test(hrefCandidate))) return;
26071
26318
  context.report({
26072
26319
  node,
26073
26320
  message: "This <link rel=\"stylesheet\"> bypasses Next.js CSS handling, so the CSS loads unbundled and unoptimized."
@@ -29593,18 +29840,143 @@ const ARITHMETIC_KEY_OPERATORS = new Set([
29593
29840
  "%"
29594
29841
  ]);
29595
29842
  const bindingMutationResultsByProgram = /* @__PURE__ */ new WeakMap();
29596
- const extractCandidateIdentifiers = (expression) => {
29843
+ const UNKNOWN_STATIC_KEY_BRANCH_VALUE = {
29844
+ isNullish: null,
29845
+ isTruthy: null
29846
+ };
29847
+ const mergeStaticKeyBranchValues = (firstValue, secondValue) => ({
29848
+ isNullish: firstValue.isNullish === secondValue.isNullish ? firstValue.isNullish : null,
29849
+ isTruthy: firstValue.isTruthy === secondValue.isTruthy ? firstValue.isTruthy : null
29850
+ });
29851
+ const readStaticKeyBranchValue = (expression, depth) => {
29852
+ if (depth > TYPE_RESOLUTION_DEPTH_LIMIT) return null;
29853
+ const node = stripParenExpression(expression);
29854
+ if (isNodeOfType(node, "Literal")) return {
29855
+ isNullish: node.value === null,
29856
+ isTruthy: Boolean(node.value)
29857
+ };
29858
+ if (isNodeOfType(node, "ArrayExpression") || isNodeOfType(node, "ObjectExpression") || isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ClassExpression") || isNodeOfType(node, "NewExpression")) return {
29859
+ isNullish: false,
29860
+ isTruthy: true
29861
+ };
29862
+ if (isNodeOfType(node, "TemplateLiteral")) return {
29863
+ isNullish: false,
29864
+ isTruthy: (node.quasis ?? []).some((quasi) => typeof quasi.value?.cooked === "string" && quasi.value.cooked.length > 0) ? true : null
29865
+ };
29866
+ if (isNodeOfType(node, "BinaryExpression")) return {
29867
+ isNullish: false,
29868
+ isTruthy: null
29869
+ };
29870
+ if (isNodeOfType(node, "Identifier")) {
29871
+ const binding = findVariableInitializer(node, node.name);
29872
+ if (!binding) {
29873
+ if (node.name === "undefined") return {
29874
+ isNullish: true,
29875
+ isTruthy: false
29876
+ };
29877
+ if (node.name === "NaN") return {
29878
+ isNullish: false,
29879
+ isTruthy: false
29880
+ };
29881
+ if (node.name === "Infinity") return {
29882
+ isNullish: false,
29883
+ isTruthy: true
29884
+ };
29885
+ return null;
29886
+ }
29887
+ if (!isConstDeclaredBinding(binding) || !binding.initializer) return null;
29888
+ const declarator = binding.bindingIdentifier.parent;
29889
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || declarator.id !== binding.bindingIdentifier || declarator.init !== binding.initializer) return null;
29890
+ return readStaticKeyBranchValue(binding.initializer, depth + 1);
29891
+ }
29892
+ if (isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier")) {
29893
+ if (!Boolean(findVariableInitializer(node.callee, node.callee.name)) && STRING_COERCION_FUNCTIONS.has(node.callee.name)) return {
29894
+ isNullish: false,
29895
+ isTruthy: null
29896
+ };
29897
+ }
29898
+ if (isNodeOfType(node, "UnaryExpression")) {
29899
+ if (node.operator === "void") return {
29900
+ isNullish: true,
29901
+ isTruthy: false
29902
+ };
29903
+ if (node.operator === "typeof") return {
29904
+ isNullish: false,
29905
+ isTruthy: true
29906
+ };
29907
+ if (node.operator === "+" || node.operator === "-" || node.operator === "~") return {
29908
+ isNullish: false,
29909
+ isTruthy: null
29910
+ };
29911
+ if (node.operator !== "!") return null;
29912
+ const argumentValue = readStaticKeyBranchValue(node.argument, depth + 1);
29913
+ if (!argumentValue || argumentValue.isTruthy === null) return null;
29914
+ return {
29915
+ isNullish: false,
29916
+ isTruthy: !argumentValue.isTruthy
29917
+ };
29918
+ }
29919
+ if (isNodeOfType(node, "SequenceExpression")) {
29920
+ const finalExpression = node.expressions.at(-1);
29921
+ return finalExpression ? readStaticKeyBranchValue(finalExpression, depth + 1) : null;
29922
+ }
29923
+ if (isNodeOfType(node, "LogicalExpression")) {
29924
+ const leftValue = readStaticKeyBranchValue(node.left, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE;
29925
+ if (node.operator === "&&" && leftValue.isTruthy !== null) return leftValue.isTruthy ? readStaticKeyBranchValue(node.right, depth + 1) : leftValue;
29926
+ if (node.operator === "||" && leftValue.isTruthy !== null) return leftValue.isTruthy ? leftValue : readStaticKeyBranchValue(node.right, depth + 1);
29927
+ if (node.operator === "??" && leftValue.isNullish !== null) return leftValue.isNullish ? readStaticKeyBranchValue(node.right, depth + 1) : leftValue;
29928
+ const rightValue = readStaticKeyBranchValue(node.right, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE;
29929
+ if (node.operator === "||") return {
29930
+ isNullish: rightValue.isNullish === false ? false : null,
29931
+ isTruthy: rightValue.isTruthy === true ? true : null
29932
+ };
29933
+ if (node.operator === "&&") return {
29934
+ isNullish: leftValue.isNullish === false && rightValue.isNullish === false ? false : null,
29935
+ isTruthy: rightValue.isTruthy === false ? false : null
29936
+ };
29937
+ return {
29938
+ isNullish: rightValue.isNullish === false ? false : null,
29939
+ isTruthy: leftValue.isTruthy !== null && leftValue.isTruthy === rightValue.isTruthy ? leftValue.isTruthy : null
29940
+ };
29941
+ }
29942
+ if (isNodeOfType(node, "ConditionalExpression")) {
29943
+ const testValue = readStaticKeyBranchValue(node.test, depth + 1);
29944
+ if (testValue?.isTruthy !== null && testValue?.isTruthy !== void 0) return readStaticKeyBranchValue(testValue.isTruthy ? node.consequent : node.alternate, depth + 1);
29945
+ return mergeStaticKeyBranchValues(readStaticKeyBranchValue(node.consequent, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE, readStaticKeyBranchValue(node.alternate, depth + 1) ?? UNKNOWN_STATIC_KEY_BRANCH_VALUE);
29946
+ }
29947
+ return null;
29948
+ };
29949
+ const extractCandidateIdentifiers = (expression, depth = 0) => {
29950
+ if (depth > TYPE_RESOLUTION_DEPTH_LIMIT) return [];
29597
29951
  const node = stripParenExpression(expression);
29598
29952
  if (isNodeOfType(node, "Identifier")) return [node];
29599
- if (isNodeOfType(node, "UnaryExpression") && (node.operator === "-" || node.operator === "+" || node.operator === "~") && isNodeOfType(node.argument, "Identifier")) return [node.argument];
29953
+ if (isNodeOfType(node, "UnaryExpression") && (node.operator === "-" || node.operator === "+" || node.operator === "~") && node.argument) return extractCandidateIdentifiers(node.argument, depth + 1);
29600
29954
  if (isNodeOfType(node, "TemplateLiteral")) {
29601
29955
  const identifiers = [];
29602
- for (const templateExpression of node.expressions ?? []) if (isNodeOfType(templateExpression, "Identifier")) identifiers.push(templateExpression);
29956
+ for (const templateExpression of node.expressions ?? []) identifiers.push(...extractCandidateIdentifiers(templateExpression, depth + 1));
29603
29957
  return identifiers;
29604
29958
  }
29959
+ if (isNodeOfType(node, "LogicalExpression")) {
29960
+ const leftValue = readStaticKeyBranchValue(node.left, 0);
29961
+ if (leftValue) {
29962
+ if (node.operator === "&&" && leftValue.isTruthy !== null) return extractCandidateIdentifiers(leftValue.isTruthy ? node.right : node.left, depth + 1);
29963
+ if (node.operator === "||" && leftValue.isTruthy !== null) return extractCandidateIdentifiers(leftValue.isTruthy ? node.left : node.right, depth + 1);
29964
+ if (node.operator === "??" && leftValue.isNullish !== null) return extractCandidateIdentifiers(leftValue.isNullish ? node.right : node.left, depth + 1);
29965
+ }
29966
+ return [...extractCandidateIdentifiers(node.left, depth + 1), ...extractCandidateIdentifiers(node.right, depth + 1)];
29967
+ }
29968
+ if (isNodeOfType(node, "ConditionalExpression")) {
29969
+ const testValue = readStaticKeyBranchValue(node.test, 0);
29970
+ if (testValue && testValue.isTruthy !== null) return extractCandidateIdentifiers(testValue.isTruthy ? node.consequent : node.alternate, depth + 1);
29971
+ return [...extractCandidateIdentifiers(node.consequent, depth + 1), ...extractCandidateIdentifiers(node.alternate, depth + 1)];
29972
+ }
29973
+ if (isNodeOfType(node, "SequenceExpression")) {
29974
+ const finalExpression = node.expressions.at(-1);
29975
+ return finalExpression ? extractCandidateIdentifiers(finalExpression, depth + 1) : [];
29976
+ }
29605
29977
  if (isNodeOfType(node, "CallExpression")) {
29606
- if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.object, "Identifier") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "toString") return [node.callee.object];
29607
- if (isNodeOfType(node.callee, "Identifier") && STRING_COERCION_FUNCTIONS.has(node.callee.name) && isNodeOfType(node.arguments?.[0], "Identifier")) return [node.arguments[0]];
29978
+ if (isNodeOfType(node.callee, "MemberExpression") && isNodeOfType(node.callee.property, "Identifier") && node.callee.property.name === "toString") return extractCandidateIdentifiers(node.callee.object, depth + 1);
29979
+ if (isNodeOfType(node.callee, "Identifier") && STRING_COERCION_FUNCTIONS.has(node.callee.name) && !findVariableInitializer(node.callee, node.callee.name) && node.arguments?.[0]) return extractCandidateIdentifiers(node.arguments[0], depth + 1);
29608
29980
  return [];
29609
29981
  }
29610
29982
  if (isNodeOfType(node, "BinaryExpression")) {
@@ -31757,6 +32129,7 @@ const createStateTriggerReachability = ({ analysis, context, effectFunction }) =
31757
32129
  };
31758
32130
  //#endregion
31759
32131
  //#region src/plugin/rules/state-and-effects/no-chain-state-updates.ts
32132
+ const APPLICABLE_EFFECT_HOOK_NAMES = new Set(["useEffect", "useLayoutEffect"]);
31760
32133
  const getUseStateDeclarator = (ref) => (ref.resolved?.defs ?? []).map((def) => def.node).find((node) => isNodeOfType(node, "VariableDeclarator") && isNodeOfType(node.id, "ArrayPattern")) ?? null;
31761
32134
  const isDeclaredWithin = (node, container) => {
31762
32135
  let walker = node;
@@ -31808,7 +32181,12 @@ const noChainStateUpdates = defineRule({
31808
32181
  tags: ["test-noise"],
31809
32182
  recommendation: "Set all the related state together in the event handler that starts it, instead of having one useEffect react to a state change and set more state. See https://react.dev/learn/you-might-not-need-an-effect#chains-of-computations",
31810
32183
  create: (context) => ({ CallExpression(node) {
31811
- if (!isUseEffect(node)) return;
32184
+ if (!isReactApiCall(node, APPLICABLE_EFFECT_HOOK_NAMES, context.scopes, {
32185
+ allowGlobalReactNamespace: true,
32186
+ allowUnboundBareCalls: true,
32187
+ resolveConditionalAliases: true,
32188
+ resolveNamedAliases: true
32189
+ })) return;
31812
32190
  const analysis = getProgramAnalysis(node);
31813
32191
  if (!analysis) return;
31814
32192
  if (hasCleanup(analysis, node)) return;
@@ -33136,6 +33514,28 @@ const noDarkModeGlow = defineRule({
33136
33514
  });
33137
33515
  //#endregion
33138
33516
  //#region src/plugin/rules/architecture/no-default-props.ts
33517
+ const hasReachingWriteOnAliasPath = (receiver, context) => {
33518
+ const visitedSymbolIds = /* @__PURE__ */ new Set();
33519
+ let reference = receiver;
33520
+ let symbol = context.scopes.symbolFor(reference);
33521
+ while (symbol) {
33522
+ if (visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, reference, context.scopes)) return true;
33523
+ visitedSymbolIds.add(symbol.id);
33524
+ if (symbol.kind !== "const" || !symbol.initializer) return false;
33525
+ const initializer = stripParenExpression(symbol.initializer);
33526
+ if (!isNodeOfType(initializer, "Identifier")) return false;
33527
+ reference = initializer;
33528
+ symbol = context.scopes.symbolFor(reference);
33529
+ }
33530
+ return false;
33531
+ };
33532
+ const isStableClassReceiver = (receiver, context) => {
33533
+ const symbol = resolveConstIdentifierAlias(receiver, context.scopes);
33534
+ if (!symbol || hasReachingWriteOnAliasPath(receiver, context)) return false;
33535
+ if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return true;
33536
+ const initializer = symbol.initializer ? stripParenExpression(symbol.initializer) : null;
33537
+ return Boolean(symbol.kind === "const" && isNodeOfType(initializer, "ClassExpression"));
33538
+ };
33139
33539
  const noDefaultProps = defineRule({
33140
33540
  id: "no-default-props",
33141
33541
  title: "defaultProps removed in React 19",
@@ -33151,6 +33551,7 @@ const noDefaultProps = defineRule({
33151
33551
  if (!isNodeOfType(left.property, "Identifier") || left.property.name !== "defaultProps") return;
33152
33552
  if (!isNodeOfType(left.object, "Identifier")) return;
33153
33553
  if (!isUppercaseName(left.object.name)) return;
33554
+ if (isStableClassReceiver(left.object, context)) return;
33154
33555
  context.report({
33155
33556
  node: left,
33156
33557
  message: `${left.object.name}.defaultProps stops applying in React 19, so your users see missing defaults. Set them in the destructured props parameter instead, like \`function ${left.object.name}({ size = "md" })\`.`
@@ -41796,6 +42197,133 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
41796
42197
  }
41797
42198
  return callbackPropNames.size > 0 ? { callbackPropNames } : null;
41798
42199
  };
42200
+ const isParentPropsContextMerge = (analysis, expression) => {
42201
+ let currentExpression = stripParenExpression(expression);
42202
+ const visitedVariables = /* @__PURE__ */ new Set();
42203
+ while (isNodeOfType(currentExpression, "Identifier")) {
42204
+ const currentReference = getRef(analysis, currentExpression);
42205
+ const currentVariable = currentReference?.resolved;
42206
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return false;
42207
+ visitedVariables.add(currentVariable);
42208
+ const definitions = currentVariable.defs.filter((definition) => isNodeOfType(definition.node, "VariableDeclarator"));
42209
+ if (definitions.length !== 1) return false;
42210
+ const declarator = definitions[0]?.node;
42211
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || getDeclarationKind(declarator) !== "const" || !declarator.init) return false;
42212
+ currentExpression = stripParenExpression(declarator.init);
42213
+ }
42214
+ if (!isNodeOfType(currentExpression, "ObjectExpression")) return false;
42215
+ const [contextSpread, propsSpread] = currentExpression.properties ?? [];
42216
+ if (currentExpression.properties?.length !== 2 || !contextSpread || !propsSpread || !isNodeOfType(contextSpread, "SpreadElement") || !isNodeOfType(propsSpread, "SpreadElement")) return false;
42217
+ const propsExpression = stripParenExpression(propsSpread.argument);
42218
+ if (!isNodeOfType(propsExpression, "Identifier")) return false;
42219
+ const propsReference = getRef(analysis, propsExpression);
42220
+ if (!propsReference?.resolved || !isWholePropsObjectReference(analysis, propsReference) || hasMutableBindingWrite(propsReference) || propsReference.resolved.references.some((candidateReference) => candidateReference !== propsReference)) return false;
42221
+ const contextExpression = stripParenExpression(contextSpread.argument);
42222
+ if (!isNodeOfType(contextExpression, "Identifier")) return false;
42223
+ const contextReference = getRef(analysis, contextExpression);
42224
+ if (!contextReference?.resolved || hasMutableBindingWrite(contextReference) || contextReference.resolved.references.some((candidateReference) => !candidateReference.init && candidateReference !== contextReference)) return false;
42225
+ const contextInitializer = contextReference.resolved?.defs.map((definition) => definition.node).find((definitionNode) => isNodeOfType(definitionNode, "VariableDeclarator"));
42226
+ if (!contextInitializer || !isNodeOfType(contextInitializer, "VariableDeclarator") || getDeclarationKind(contextInitializer) !== "const" || !contextInitializer.init || !isNodeOfType(contextInitializer.init, "CallExpression")) return false;
42227
+ const contextHook = stripParenExpression(contextInitializer.init.callee);
42228
+ if (!isNodeOfType(contextHook, "Identifier") || !/^use[A-Z].*Context$/.test(contextHook.name)) return false;
42229
+ const contextHookReference = getRef(analysis, contextHook);
42230
+ return Boolean(contextHookReference?.resolved?.defs.some((definition) => definition.type === "ImportBinding"));
42231
+ };
42232
+ const getImmutableParentCallbackPropName = (analysis, expression) => {
42233
+ let currentExpression = stripParenExpression(expression);
42234
+ const visitedVariables = /* @__PURE__ */ new Set();
42235
+ while (isNodeOfType(currentExpression, "Identifier")) {
42236
+ const currentReference = getRef(analysis, currentExpression);
42237
+ const currentVariable = currentReference?.resolved;
42238
+ if (!currentReference || !currentVariable || visitedVariables.has(currentVariable) || hasMutableBindingWrite(currentReference)) return null;
42239
+ visitedVariables.add(currentVariable);
42240
+ const definition = currentVariable.defs.length === 1 ? currentVariable.defs[0] : null;
42241
+ const bindingIdentifier = definition?.name;
42242
+ if (bindingIdentifier && isNodeOfType(bindingIdentifier.parent, "AssignmentPattern")) return null;
42243
+ const definitionNode = definition?.node;
42244
+ if (!definitionNode || !isNodeOfType(definitionNode, "VariableDeclarator")) return getParentCallbackPropName(analysis, currentExpression);
42245
+ if (getDeclarationKind(definitionNode) !== "const" || !definitionNode.init) return null;
42246
+ if (isNodeOfType(definitionNode.id, "ObjectPattern")) return getParentCallbackPropName(analysis, currentExpression) ?? (isParentPropsContextMerge(analysis, definitionNode.init) ? getDestructuredBindingPropertyName(bindingIdentifier ?? currentExpression) : null);
42247
+ if (!isNodeOfType(definitionNode.id, "Identifier")) return null;
42248
+ currentExpression = stripParenExpression(definitionNode.init);
42249
+ if (!isNodeOfType(currentExpression, "Identifier") && !isNodeOfType(currentExpression, "MemberExpression")) return null;
42250
+ }
42251
+ return null;
42252
+ };
42253
+ const objectExpressionPreservesCallbackProperty = (analysis, expression, propertyName) => {
42254
+ const unwrappedExpression = stripParenExpression(expression);
42255
+ if (!isNodeOfType(unwrappedExpression, "ObjectExpression")) return false;
42256
+ let callbackSourceName = null;
42257
+ for (const property of unwrappedExpression.properties ?? []) {
42258
+ if (!isNodeOfType(property, "Property")) return false;
42259
+ const candidatePropertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
42260
+ if (candidatePropertyName === null) return false;
42261
+ if (candidatePropertyName !== propertyName) continue;
42262
+ if (callbackSourceName || property.kind !== "init") return false;
42263
+ if (!isNodeOfType(stripParenExpression(property.value), "Identifier")) return false;
42264
+ callbackSourceName = getImmutableParentCallbackPropName(analysis, property.value);
42265
+ if (!callbackSourceName) return false;
42266
+ }
42267
+ return callbackSourceName === propertyName;
42268
+ };
42269
+ const refCurrentObjectPreservesCallbackProperty = (analysis, expression, propertyName, isReactUseRefCall) => {
42270
+ const unwrappedExpression = stripParenExpression(expression);
42271
+ if (!isNodeOfType(unwrappedExpression, "MemberExpression") || getStaticMemberPropertyName(unwrappedExpression) !== "current") return false;
42272
+ const bindingProvenance = getRefBindingProvenance(analysis, stripParenExpression(unwrappedExpression.object), isReactUseRefCall);
42273
+ if (!bindingProvenance) return false;
42274
+ const initializer = bindingProvenance.refCall.arguments?.[0];
42275
+ if (!initializer || !objectExpressionPreservesCallbackProperty(analysis, initializer, propertyName)) return false;
42276
+ for (const variable of bindingProvenance.variables) for (const candidateReference of variable.references) {
42277
+ if (candidateReference.init) continue;
42278
+ const identifier = candidateReference.identifier;
42279
+ if (getRefAliasDeclarator(identifier)) continue;
42280
+ const currentMember = getRefMember(identifier);
42281
+ if (!currentMember || getStaticMemberPropertyName(currentMember) !== "current") return false;
42282
+ const currentAssignment = getRefMemberAssignment(identifier);
42283
+ if (currentAssignment) {
42284
+ if (currentAssignment.operator !== "=" || !objectExpressionPreservesCallbackProperty(analysis, currentAssignment.right, propertyName)) return false;
42285
+ continue;
42286
+ }
42287
+ const currentRoot = findTransparentExpressionRoot(currentMember);
42288
+ const currentParent = currentRoot.parent;
42289
+ if (currentParent && isNodeOfType(currentParent, "VariableDeclarator") && currentParent.init === currentRoot && isNodeOfType(currentParent.id, "ObjectPattern")) continue;
42290
+ if (currentParent && isNodeOfType(currentParent, "MemberExpression") && currentParent.object === currentRoot && getStaticMemberPropertyName(currentParent) === propertyName) {
42291
+ const propertyParent = findTransparentExpressionRoot(currentParent).parent;
42292
+ if (!propertyParent || !isNodeOfType(propertyParent, "VariableDeclarator")) return false;
42293
+ continue;
42294
+ }
42295
+ return false;
42296
+ }
42297
+ return true;
42298
+ };
42299
+ const getCommandCallbackPropName = (analysis, expression, isReactUseRefCall) => {
42300
+ const directCallbackName = getImmutableParentCallbackPropName(analysis, expression);
42301
+ if (directCallbackName && COMMAND_PROP_NAME_PATTERN.test(directCallbackName)) return directCallbackName;
42302
+ let currentExpression = stripParenExpression(expression);
42303
+ const visitedVariables = /* @__PURE__ */ new Set();
42304
+ while (isNodeOfType(currentExpression, "Identifier")) {
42305
+ const callbackReference = getRef(analysis, currentExpression);
42306
+ const callbackVariable = callbackReference?.resolved;
42307
+ if (!callbackReference || !callbackVariable || visitedVariables.has(callbackVariable) || hasMutableBindingWrite(callbackReference)) return null;
42308
+ visitedVariables.add(callbackVariable);
42309
+ const definition = callbackVariable.defs.length === 1 ? callbackVariable.defs[0] : null;
42310
+ const declarator = definition?.node;
42311
+ if (!declarator || !isNodeOfType(declarator, "VariableDeclarator") || getDeclarationKind(declarator) !== "const" || !declarator.init) return null;
42312
+ const bindingIdentifier = definition?.name;
42313
+ if (bindingIdentifier && isNodeOfType(bindingIdentifier.parent, "AssignmentPattern")) return null;
42314
+ if (isNodeOfType(declarator.id, "ObjectPattern")) {
42315
+ const propertyName = bindingIdentifier ? getDestructuredBindingPropertyName(bindingIdentifier) : null;
42316
+ if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
42317
+ return refCurrentObjectPreservesCallbackProperty(analysis, declarator.init, propertyName, isReactUseRefCall) ? propertyName : null;
42318
+ }
42319
+ if (!isNodeOfType(declarator.id, "Identifier")) return null;
42320
+ currentExpression = stripParenExpression(declarator.init);
42321
+ }
42322
+ if (!isNodeOfType(currentExpression, "MemberExpression")) return null;
42323
+ const propertyName = getStaticMemberPropertyName(currentExpression);
42324
+ if (!propertyName || !COMMAND_PROP_NAME_PATTERN.test(propertyName)) return null;
42325
+ return refCurrentObjectPreservesCallbackProperty(analysis, currentExpression.object, propertyName, isReactUseRefCall) ? propertyName : null;
42326
+ };
41799
42327
  const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
41800
42328
  const node = def.node;
41801
42329
  if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
@@ -41897,6 +42425,8 @@ const noPassDataToParent = defineRule({
41897
42425
  if (callbackRefProvenance) {
41898
42426
  if ([...callbackRefProvenance.callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
41899
42427
  } else if (calleeNode === identifier) {
42428
+ const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
42429
+ if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
41900
42430
  if (!isDirectParentCallbackRef(analysis, ref)) continue;
41901
42431
  if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
41902
42432
  } else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
@@ -43088,7 +43618,16 @@ const getElementImplicitRoles = (tag) => IMPLICIT_ROLES_BY_TAG.get(tag) ?? EMPTY
43088
43618
  const getTagsForRole = (role) => TAGS_BY_ROLE.get(role) ?? EMPTY_ROLE_LIST;
43089
43619
  //#endregion
43090
43620
  //#region src/plugin/utils/get-implicit-role.ts
43091
- const getImplicitRole = (node, elementType) => {
43621
+ const getInputTypeImplicitRole = (inputTypeValue) => {
43622
+ const inputType = inputTypeValue.toLowerCase();
43623
+ if (inputType === "button" || inputType === "image" || inputType === "reset" || inputType === "submit") return "button";
43624
+ if (inputType === "checkbox") return "checkbox";
43625
+ if (inputType === "number") return "spinbutton";
43626
+ if (inputType === "radio") return "radio";
43627
+ if (inputType === "range") return "slider";
43628
+ return "textbox";
43629
+ };
43630
+ const getImplicitRole = (node, elementType, scopes) => {
43092
43631
  const propStringValue = (propName) => {
43093
43632
  const attribute = hasJsxPropIgnoreCase(node.attributes, propName);
43094
43633
  return attribute ? getJsxPropStringValue(attribute) : null;
@@ -43146,13 +43685,18 @@ const getImplicitRole = (node, elementType) => {
43146
43685
  break;
43147
43686
  }
43148
43687
  case "input": {
43149
- const inputType = propStringValue("type");
43150
- if (inputType === null) implicit = "textbox";
43151
- else if (inputType === "button" || inputType === "image" || inputType === "reset" || inputType === "submit") implicit = "button";
43152
- else if (inputType === "checkbox") implicit = "checkbox";
43153
- else if (inputType === "radio") implicit = "radio";
43154
- else if (inputType === "range") implicit = "slider";
43155
- else implicit = "textbox";
43688
+ const inputTypeAttribute = hasJsxPropIgnoreCase(node.attributes, "type");
43689
+ if (!inputTypeAttribute) {
43690
+ implicit = "textbox";
43691
+ break;
43692
+ }
43693
+ const inputTypeValues = getJsxPropStaticStringValues(inputTypeAttribute, scopes);
43694
+ if (inputTypeValues === null) {
43695
+ implicit = "";
43696
+ break;
43697
+ }
43698
+ const implicitRoles = new Set(inputTypeValues.map(getInputTypeImplicitRole));
43699
+ implicit = implicitRoles.size === 1 ? implicitRoles.values().next().value ?? "" : "";
43156
43700
  break;
43157
43701
  }
43158
43702
  case "li":
@@ -43256,7 +43800,7 @@ const noRedundantRoles = defineRule({
43256
43800
  if (tag === "td" || tag === "th") {
43257
43801
  if (findSameFileTableContext(node, context.settings) !== "table") return;
43258
43802
  implicitRoles = [getTableCellPrimaryRole(node, tag)];
43259
- } else if (ATTRIBUTE_DEPENDENT_IMPLICIT_ROLE_TAGS.has(tag)) implicitRoles = [getImplicitRole(node, tag)].filter((resolvedRole) => resolvedRole !== null);
43803
+ } else if (ATTRIBUTE_DEPENDENT_IMPLICIT_ROLE_TAGS.has(tag)) implicitRoles = [getImplicitRole(node, tag, context.scopes)].filter((resolvedRole) => resolvedRole !== null);
43260
43804
  else implicitRoles = getElementImplicitRoles(tag);
43261
43805
  const allowedHere = [...DEFAULT_NON_REDUNDANT_ROLES[tag] ?? [], ...settings.exceptions[tag] ?? []];
43262
43806
  if (implicitRoles.includes(role) && !allowedHere.includes(role)) context.report({
@@ -43435,9 +43979,9 @@ const isInsideComponentContext = (node) => {
43435
43979
  }
43436
43980
  return false;
43437
43981
  };
43438
- const functionBodyOf = (node) => {
43439
- if (isFunctionLike$1(node)) return node.body ?? null;
43440
- if (isNodeOfType(node, "VariableDeclarator") && node.init && isFunctionLike$1(node.init)) return node.init.body ?? null;
43982
+ const getFunctionFromDeclaration = (node) => {
43983
+ if (isFunctionLike$1(node)) return node;
43984
+ if (isNodeOfType(node, "VariableDeclarator") && node.init && isFunctionLike$1(node.init)) return node.init;
43441
43985
  return null;
43442
43986
  };
43443
43987
  const isHookCallee = (callee) => {
@@ -43445,23 +43989,43 @@ const isHookCallee = (callee) => {
43445
43989
  if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && isUppercaseName(callee.object.name) && isNodeOfType(callee.property, "Identifier")) return isReactHookName(callee.property.name);
43446
43990
  return false;
43447
43991
  };
43448
- const containsHookCall = (body) => {
43449
- let found = false;
43450
- walkAst(body, (child) => {
43451
- if (found) return false;
43452
- if (child !== body && isFunctionLike$1(child) && isComponentFunction$1(child)) return false;
43453
- if (!isNodeOfType(child, "CallExpression")) return;
43454
- if (isHookCallee(child.callee)) found = true;
43992
+ const containsReachableHookCall = (functionNode, rootFunction, context, visitedFunctions) => {
43993
+ if (!isFunctionLike$1(functionNode) || visitedFunctions.has(functionNode)) return false;
43994
+ visitedFunctions.add(functionNode);
43995
+ let didFindReachableHook = false;
43996
+ walkAst(functionNode.body, (child) => {
43997
+ if (didFindReachableHook) return false;
43998
+ if (isFunctionLike$1(child) && !executesDuringRender(child, context.scopes)) return false;
43999
+ if (!isNodeOfType(child, "CallExpression") && !isNodeOfType(child, "NewExpression")) return;
44000
+ if (isNodeOfType(child, "CallExpression")) {
44001
+ if (isHookCallee(child.callee)) {
44002
+ didFindReachableHook = true;
44003
+ return false;
44004
+ }
44005
+ const calledFunction = resolveExactLocalFunction(child.callee, context.scopes);
44006
+ if (calledFunction && isAstDescendant(calledFunction, rootFunction) && containsReachableHookCall(calledFunction, rootFunction, context, visitedFunctions)) {
44007
+ didFindReachableHook = true;
44008
+ return false;
44009
+ }
44010
+ }
44011
+ for (const callArgument of child.arguments ?? []) {
44012
+ if (!executesDuringRender(callArgument, context.scopes)) continue;
44013
+ const callbackFunction = resolveExactLocalFunction(callArgument, context.scopes);
44014
+ if (callbackFunction && isAstDescendant(callbackFunction, rootFunction) && containsReachableHookCall(callbackFunction, rootFunction, context, visitedFunctions)) {
44015
+ didFindReachableHook = true;
44016
+ return false;
44017
+ }
44018
+ }
43455
44019
  });
43456
- return found;
44020
+ return didFindReachableHook;
43457
44021
  };
43458
- const isHookCallingRenderHelper = (symbol) => {
44022
+ const isHookCallingRenderHelper = (symbol, context) => {
43459
44023
  if (!symbol) return false;
43460
44024
  const declaration = symbol.declarationNode;
43461
44025
  if (!isNodeOfType(declaration, "FunctionDeclaration") && !isNodeOfType(declaration, "VariableDeclarator")) return false;
43462
- const body = functionBodyOf(declaration);
43463
- if (!body) return false;
43464
- return containsHookCall(body);
44026
+ const functionNode = getFunctionFromDeclaration(declaration);
44027
+ if (!functionNode) return false;
44028
+ return containsReachableHookCall(functionNode, functionNode, context, /* @__PURE__ */ new Set());
43465
44029
  };
43466
44030
  const noRenderInRender = defineRule({
43467
44031
  id: "no-render-in-render",
@@ -43476,7 +44040,7 @@ const noRenderInRender = defineRule({
43476
44040
  const calleeName = expression.callee.name;
43477
44041
  if (!RENDER_FUNCTION_PATTERN.test(calleeName)) return;
43478
44042
  if (!isInsideComponentContext(node)) return;
43479
- if (!isHookCallingRenderHelper(context.scopes.symbolFor(expression.callee))) return;
44043
+ if (!isHookCallingRenderHelper(context.scopes.symbolFor(expression.callee), context)) return;
43480
44044
  context.report({
43481
44045
  node: expression,
43482
44046
  message: `"${calleeName}()" hides a component behind an inline call, so pull it into its own component and render it as JSX so React can track it.`
@@ -43956,6 +44520,33 @@ const countUseStates = (analysis, componentNode) => {
43956
44520
  for (const ref of getDownstreamRefs(analysis, componentNode)) if (isState(analysis, ref)) stateVariables.add(ref.resolved);
43957
44521
  return stateVariables.size;
43958
44522
  };
44523
+ const hasUnconditionalAwaitBefore = (functionNode, boundaryNode, context) => {
44524
+ const boundaryStart = getRangeStart(boundaryNode);
44525
+ if (boundaryStart === null) return false;
44526
+ let didFindAwait = false;
44527
+ walkAst(functionNode, (child) => {
44528
+ if (didFindAwait) return false;
44529
+ if (child !== functionNode && isFunctionLike$1(child)) return false;
44530
+ if (!isNodeOfType(child, "AwaitExpression") || !isNodeReachableWithinFunction(child, context) || !context.cfg.isUnconditionalFromEntry(child)) return;
44531
+ const awaitStart = getRangeStart(child);
44532
+ if (awaitStart !== null && awaitStart < boundaryStart) {
44533
+ didFindAwait = true;
44534
+ return false;
44535
+ }
44536
+ });
44537
+ return didFindAwait;
44538
+ };
44539
+ const helperReloadsSetterAfterAwait = (analysis, context, helperFunction, setterReference) => getDownstreamRefs(analysis, helperFunction).some((helperReference) => {
44540
+ if (!setterReference.resolved || helperReference.resolved !== setterReference.resolved) return false;
44541
+ const helperSetterCall = getCallExpr(helperReference);
44542
+ return Boolean(helperSetterCall && findEnclosingFunction$1(helperSetterCall) === helperFunction && isNodeReachableWithinFunction(helperSetterCall, context) && hasUnconditionalAwaitBefore(helperFunction, helperSetterCall, context));
44543
+ });
44544
+ const effectInvokesAsyncSetterReloadHelper = (analysis, context, effectReferences, effectFunction, setterReference) => effectReferences.some((effectReference) => {
44545
+ const helperCall = getCallExpr(effectReference);
44546
+ if (!isNodeOfType(helperCall, "CallExpression") || findEnclosingFunction$1(helperCall) !== effectFunction || !isNodeReachableWithinFunction(helperCall, context)) return false;
44547
+ const helperFunction = resolveExactLocalFunction(helperCall.callee, context.scopes);
44548
+ return Boolean(helperFunction && helperFunction !== effectFunction && helperReloadsSetterAfterAwait(analysis, context, helperFunction, setterReference));
44549
+ });
43959
44550
  const getStateSymbolForSetter = (analysis, context, setterReference) => {
43960
44551
  const useStateDeclaration = getUseStateDecl(analysis, setterReference);
43961
44552
  if (!useStateDeclaration || !isNodeOfType(useStateDeclaration, "VariableDeclarator") || !isNodeOfType(useStateDeclaration.id, "ArrayPattern")) return null;
@@ -44112,7 +44703,7 @@ const findPropUsedToResetAllState = (analysis, context, effectFnRefs, depsRefs,
44112
44703
  const stateSetterRefs = effectFnRefs.filter((ref) => isSyncStateSetterCall(analysis, ref, effectFn));
44113
44704
  if (stateSetterRefs.length === 0) return null;
44114
44705
  if (!stateSetterRefs.every((ref) => isSetStateToInitialValue(analysis, context, ref))) return null;
44115
- if (stateSetterRefs.every((setterRef) => effectFnRefs.some((otherRef) => otherRef !== setterRef && otherRef.resolved === setterRef.resolved && Boolean(getCallExpr(otherRef)) && !isSyncStateSetterCall(analysis, otherRef, effectFn)))) return null;
44706
+ if (stateSetterRefs.every((setterRef) => effectFnRefs.some((otherRef) => otherRef !== setterRef && otherRef.resolved === setterRef.resolved && Boolean(getCallExpr(otherRef)) && !isSyncStateSetterCall(analysis, otherRef, effectFn)) || effectInvokesAsyncSetterReloadHelper(analysis, context, effectFnRefs, effectFn, setterRef))) return null;
44116
44707
  const containing = findContainingNode(analysis, useEffectNode);
44117
44708
  if (new Set(stateSetterRefs.map((setterRef) => setterRef.resolved)).size !== countUseStates(analysis, containing)) return null;
44118
44709
  const componentFunctionNode = containing ? getComponentFunctionNode(containing) : null;
@@ -45809,9 +46400,13 @@ const UNCONTROLLED_INPUT_TAGS = new Set([
45809
46400
  "select"
45810
46401
  ]);
45811
46402
  const VALUE_BYPASS_INPUT_TYPES = new Set([
45812
- "hidden",
46403
+ "button",
45813
46404
  "checkbox",
45814
- "radio"
46405
+ "hidden",
46406
+ "image",
46407
+ "radio",
46408
+ "reset",
46409
+ "submit"
45815
46410
  ]);
45816
46411
  const VALUE_PARTNER_ATTRIBUTES = [
45817
46412
  "onChange",
@@ -45820,12 +46415,6 @@ const VALUE_PARTNER_ATTRIBUTES = [
45820
46415
  "disabled"
45821
46416
  ];
45822
46417
  const isLiteralFalseAttributeValue = (attribute) => isNodeOfType(attribute.value, "JSXExpressionContainer") && isNodeOfType(attribute.value.expression, "Literal") && attribute.value.expression.value === false;
45823
- const getInputTypeLiteral = (attributes) => {
45824
- const typeAttribute = findJsxAttribute(attributes, "type");
45825
- if (!typeAttribute || !isNodeOfType(typeAttribute.value, "Literal")) return null;
45826
- const value = typeAttribute.value.value;
45827
- return typeof value === "string" ? value : null;
45828
- };
45829
46418
  const isUseStateUndefinedInitializer = (init) => {
45830
46419
  if (!init || !isNodeOfType(init, "CallExpression")) return false;
45831
46420
  if (!isHookCall$2(init, "useState")) return false;
@@ -45868,9 +46457,18 @@ const noUncontrolledInput = defineRule({
45868
46457
  if (hasJsxSpreadAttribute(attributes)) return;
45869
46458
  const valueAttribute = findJsxAttribute(attributes, "value");
45870
46459
  if (!valueAttribute) return;
46460
+ let inputTypeCandidates = null;
46461
+ let hasExplicitInputType = false;
46462
+ let doesTypeBypassMissingOnChange = false;
46463
+ let doesTypeUseCheckedControlledness = false;
46464
+ let couldTypeUseCheckedControlledness = false;
45871
46465
  if (tagName === "input") {
45872
- const inputType = getInputTypeLiteral(attributes);
45873
- if (inputType !== null && VALUE_BYPASS_INPUT_TYPES.has(inputType)) return;
46466
+ const typeAttribute = findJsxAttribute([...attributes].reverse(), "type");
46467
+ hasExplicitInputType = Boolean(typeAttribute);
46468
+ inputTypeCandidates = typeAttribute ? getJsxPropExhaustiveStaticStringValues(typeAttribute, context.scopes) : null;
46469
+ doesTypeBypassMissingOnChange = Boolean(inputTypeCandidates !== null && inputTypeCandidates.length > 0 && inputTypeCandidates.every((inputTypeCandidate) => VALUE_BYPASS_INPUT_TYPES.has(inputTypeCandidate.toLowerCase())));
46470
+ doesTypeUseCheckedControlledness = Boolean(inputTypeCandidates !== null && inputTypeCandidates.length > 0 && inputTypeCandidates.every((inputTypeCandidate) => inputTypeCandidate === "checkbox" || inputTypeCandidate === "radio"));
46471
+ couldTypeUseCheckedControlledness = Boolean(hasExplicitInputType && (inputTypeCandidates === null || inputTypeCandidates.some((inputTypeCandidate) => inputTypeCandidate === "checkbox" || inputTypeCandidate === "radio")));
45874
46472
  }
45875
46473
  const hasAllowedPartner = VALUE_PARTNER_ATTRIBUTES.some((partnerAttributeName) => {
45876
46474
  const partnerAttribute = findJsxAttribute(attributes, partnerAttributeName);
@@ -45878,12 +46476,12 @@ const noUncontrolledInput = defineRule({
45878
46476
  if (partnerAttributeName === "disabled" && isLiteralFalseAttributeValue(partnerAttribute)) return false;
45879
46477
  return true;
45880
46478
  });
45881
- if (isNodeOfType(valueAttribute.value, "JSXExpressionContainer") && isNodeOfType(valueAttribute.value.expression, "Identifier") && undefinedInitialStateNames.has(valueAttribute.value.expression.name)) {
46479
+ if (isNodeOfType(valueAttribute.value, "JSXExpressionContainer") && isNodeOfType(valueAttribute.value.expression, "Identifier") && undefinedInitialStateNames.has(valueAttribute.value.expression.name) && !doesTypeUseCheckedControlledness) {
45882
46480
  const stateName = valueAttribute.value.expression.name;
45883
- const partnerHint = hasAllowedPartner ? "Give useState a starting value" : "Give useState a starting value and add onChange (or readOnly)";
46481
+ const partnerHint = hasAllowedPartner || doesTypeBypassMissingOnChange || couldTypeUseCheckedControlledness ? "Give useState a starting value" : "Give useState a starting value and add onChange (or readOnly)";
45884
46482
  context.report({
45885
46483
  node: child,
45886
- message: `This can trigger a console warning and reset the field because "${stateName}" starts undefined, so <${tagName} value={${stateName}}> flips from uncontrolled to controlled. ${partnerHint} (e.g. \`useState("")\`).`
46484
+ message: couldTypeUseCheckedControlledness ? `When \`type\` resolves to a value-controlled input type, this can trigger a console warning and reset the field because "${stateName}" starts undefined, so <input value={${stateName}}> can flip from uncontrolled to controlled. ${partnerHint} (e.g. \`useState("")\`).` : `This can trigger a console warning and reset the field because "${stateName}" starts undefined, so <${tagName} value={${stateName}}> flips from uncontrolled to controlled. ${partnerHint} (e.g. \`useState("")\`).`
45887
46485
  });
45888
46486
  return;
45889
46487
  }
@@ -45894,10 +46492,13 @@ const noUncontrolledInput = defineRule({
45894
46492
  });
45895
46493
  return;
45896
46494
  }
45897
- if (!hasAllowedPartner) context.report({
45898
- node: child,
45899
- message: `Your users can't type in this <${tagName} value={...}> because it has no \`onChange\` or \`readOnly\`, so add \`onChange\` (or \`readOnly\` if that's intended).`
45900
- });
46495
+ if (!hasAllowedPartner && !doesTypeBypassMissingOnChange) {
46496
+ const couldResolveToReadOnlyValueType = tagName === "input" && hasExplicitInputType && (inputTypeCandidates === null || inputTypeCandidates.some((inputTypeCandidate) => VALUE_BYPASS_INPUT_TYPES.has(inputTypeCandidate.toLowerCase())));
46497
+ context.report({
46498
+ node: child,
46499
+ message: couldResolveToReadOnlyValueType ? `When \`type\` resolves to an editable input type, users can't type in this <input value={...}> because it has no \`onChange\` or \`readOnly\`. Add \`onChange\` or \`readOnly\` unless \`type\` is always a read-only-value input type.` : `Your users can't type in this <${tagName} value={...}> because it has no \`onChange\` or \`readOnly\`, so add \`onChange\` (or \`readOnly\` if that's intended).`
46500
+ });
46501
+ }
45901
46502
  });
45902
46503
  };
45903
46504
  return {
@@ -49250,7 +49851,8 @@ const onlyExportComponents = defineRule({
49250
49851
  const declaration = child.declaration;
49251
49852
  if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
49252
49853
  isExportedNodeIds.add(declaration);
49253
- exports.push(functionHasReactRenderSemantics(declaration, state) || localComponentNames.has(declaration.id.name) ? classifyExport(declaration.id.name, declaration.id, true, null, state) : {
49854
+ const classifiedExport = classifyExport(declaration.id.name, declaration.id, true, null, state);
49855
+ exports.push(functionHasReactRenderSemantics(declaration, state) || localComponentNames.has(declaration.id.name) || classifiedExport.kind === "allowed" ? classifiedExport : {
49254
49856
  kind: "non-component",
49255
49857
  reportNode: declaration.id
49256
49858
  });
@@ -52210,14 +52812,6 @@ const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
52210
52812
  }
52211
52813
  return entries;
52212
52814
  };
52213
- const findEffectiveExplicitAttribute = (attributes, attributeName) => {
52214
- for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
52215
- const attribute = attributes[attributeIndex];
52216
- if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
52217
- if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
52218
- }
52219
- return null;
52220
- };
52221
52815
  const getAttributeExpression = (attribute) => {
52222
52816
  if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
52223
52817
  return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
@@ -52257,7 +52851,7 @@ const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
52257
52851
  if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
52258
52852
  return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
52259
52853
  });
52260
- const childrenAttribute = findEffectiveExplicitAttribute(openingElement.attributes, "children");
52854
+ const childrenAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "children");
52261
52855
  if (!childrenAttribute) return false;
52262
52856
  const childrenExpression = getAttributeExpression(childrenAttribute);
52263
52857
  return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
@@ -52269,7 +52863,7 @@ const reactMarkdownUnsanitizedRawHtml = defineRule({
52269
52863
  recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
52270
52864
  create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
52271
52865
  if (!isReactMarkdownComponent(node.name, context.scopes)) return;
52272
- const pluginsAttribute = findEffectiveExplicitAttribute(node.attributes, "rehypePlugins");
52866
+ const pluginsAttribute = getAuthoritativeJsxAttribute(node.attributes, "rehypePlugins");
52273
52867
  if (!pluginsAttribute) return;
52274
52868
  const pluginsExpression = getAttributeExpression(pluginsAttribute);
52275
52869
  if (!pluginsExpression) return;
@@ -55704,6 +56298,22 @@ const isLikelyNumericExpression = (node) => {
55704
56298
  if (isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && isNumericName(node.property.name)) return true;
55705
56299
  return false;
55706
56300
  };
56301
+ const isRenderedInsideIntrinsicDomHost = (node, scopes) => {
56302
+ let ancestor = node.parent;
56303
+ while (ancestor) {
56304
+ if (isNodeOfType(ancestor, "JSXAttribute")) {
56305
+ if (!(isNodeOfType(ancestor.name, "JSXIdentifier") && ancestor.name.name === "children")) return false;
56306
+ }
56307
+ if (isNodeOfType(ancestor, "JSXElement") && !isJsxFragmentElement(ancestor.openingElement, scopes)) {
56308
+ const elementName = ancestor.openingElement.name;
56309
+ if (!isNodeOfType(elementName, "JSXIdentifier")) return false;
56310
+ return HTML_TAGS.has(elementName.name) || SVG_TAGS.has(elementName.name);
56311
+ }
56312
+ if (isNodeOfType(ancestor, "CallExpression") || isNodeOfType(ancestor, "NewExpression") || isNodeOfType(ancestor, "VariableDeclarator") || isNodeOfType(ancestor, "AssignmentExpression") || isNodeOfType(ancestor, "Property") || isFunctionLike$1(ancestor) || isNodeOfType(ancestor, "Program")) return false;
56313
+ ancestor = ancestor.parent;
56314
+ }
56315
+ return false;
56316
+ };
55707
56317
  const rnNoFalsyAndRender = defineRule({
55708
56318
  id: "rn-no-falsy-and-render",
55709
56319
  title: "Numeric && renders bare zero",
@@ -55722,6 +56332,7 @@ const rnNoFalsyAndRender = defineRule({
55722
56332
  if (!(isNodeOfType(node.right, "JSXElement") || isNodeOfType(node.right, "JSXFragment"))) return;
55723
56333
  const parent = node.parent;
55724
56334
  if (!(isNodeOfType(parent, "JSXExpressionContainer") || isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&")) return;
56335
+ if (isRenderedInsideIntrinsicDomHost(node, context.scopes)) return;
55725
56336
  const left = node.left;
55726
56337
  if (!left) return;
55727
56338
  if (!isLikelyNumericExpression(left)) return;
@@ -60754,7 +61365,7 @@ const roleSupportsAriaProps = defineRule({
60754
61365
  if (!ariaAttributes) return;
60755
61366
  const elementType = getElementType(node, context.settings);
60756
61367
  const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
60757
- const roleCandidates = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : [getImplicitRole(node, elementType)].filter((role) => role !== null);
61368
+ const roleCandidates = roleAttribute ? getJsxPropStaticStringValues(roleAttribute, context.scopes) : [getImplicitRole(node, elementType, context.scopes)].filter((role) => role !== null);
60758
61369
  if (roleCandidates === null || roleCandidates.length === 0) return;
60759
61370
  const supportedSets = [];
60760
61371
  for (const role of roleCandidates) {
@@ -62412,29 +63023,19 @@ const serverNoMutableModuleState = defineRule({
62412
63023
  });
62413
63024
  //#endregion
62414
63025
  //#region src/plugin/rules/server/server-sequential-independent-await.ts
62415
- const collectDeclaredNames = (declaration) => {
62416
- const names = /* @__PURE__ */ new Set();
62417
- if (!isNodeOfType(declaration, "VariableDeclaration")) return names;
62418
- for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, names);
62419
- return names;
62420
- };
62421
63026
  const declarationStartsWithAwait = (declaration) => {
62422
63027
  if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
62423
63028
  for (const declarator of declaration.declarations ?? []) if (isNodeOfType(declarator.init, "AwaitExpression")) return true;
62424
63029
  return false;
62425
63030
  };
62426
- const declarationReadsAnyName = (declaration, names) => {
62427
- if (names.size === 0) return false;
63031
+ const declarationReadsAnyPatternBinding = (declaration, patterns, context) => {
63032
+ if (patterns.length === 0) return false;
62428
63033
  if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
62429
- let didRead = false;
62430
63034
  for (const declarator of declaration.declarations ?? []) {
62431
63035
  if (!declarator.init) continue;
62432
- walkAst(declarator.init, (child) => {
62433
- if (didRead) return;
62434
- if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
62435
- });
63036
+ if (expressionReadsPatternBinding(declarator.init, patterns, context.scopes)) return true;
62436
63037
  }
62437
- return didRead;
63038
+ return false;
62438
63039
  };
62439
63040
  const GATE_LEADING_VERBS = new Set([
62440
63041
  "require",
@@ -62508,11 +63109,11 @@ const serverSequentialIndependentAwait = defineRule({
62508
63109
  const currentStatement = statements[statementIndex];
62509
63110
  if (!isNodeOfType(currentStatement, "VariableDeclaration")) continue;
62510
63111
  if (!declarationStartsWithAwait(currentStatement)) continue;
62511
- const declaredNames = collectDeclaredNames(currentStatement);
63112
+ const declaredPatterns = currentStatement.declarations.map((declarator) => declarator.id);
62512
63113
  const nextStatement = statements[statementIndex + 1];
62513
63114
  if (!isNodeOfType(nextStatement, "VariableDeclaration")) continue;
62514
63115
  if (!declarationStartsWithAwait(nextStatement)) continue;
62515
- if (declarationReadsAnyName(nextStatement, declaredNames)) continue;
63116
+ if (declarationReadsAnyPatternBinding(nextStatement, declaredPatterns, context)) continue;
62516
63117
  if (declarationAwaitsExistingPromise(nextStatement)) continue;
62517
63118
  if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
62518
63119
  if (declarationAwaitsGate(currentStatement, context)) continue;