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.
- package/dist/index.js +1843 -1242
- 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
|
|
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 (
|
|
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/
|
|
5191
|
-
const
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
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, "
|
|
5199
|
-
|
|
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
|
|
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, "
|
|
5207
|
-
|
|
5208
|
-
|
|
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
|
-
|
|
5215
|
-
|
|
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
|
-
|
|
5221
|
-
|
|
5222
|
-
const
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
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
|
-
|
|
5233
|
-
|
|
5234
|
-
|
|
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
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
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
|
|
5244
|
-
if (!isNodeOfType(
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
|
|
5252
|
-
|
|
5253
|
-
|
|
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
|
|
5277
|
-
"
|
|
5278
|
-
|
|
5279
|
-
|
|
5280
|
-
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
|
|
5287
|
-
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
"
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
|
|
5294
|
-
|
|
5295
|
-
|
|
5296
|
-
|
|
5297
|
-
|
|
5298
|
-
|
|
5299
|
-
|
|
5300
|
-
|
|
5301
|
-
|
|
5302
|
-
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
|
|
5307
|
-
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
|
|
5311
|
-
|
|
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
|
-
|
|
5514
|
+
tagAsBinding(state, idNode);
|
|
5315
5515
|
};
|
|
5316
|
-
const
|
|
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
|
-
|
|
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(
|
|
5381
|
-
const isLiteralOperand = (operand) =>
|
|
5382
|
-
|
|
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
|
-
|
|
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
|
|
6330
|
+
const declaredPatterns = [];
|
|
5632
6331
|
for (const statement of statements) {
|
|
5633
6332
|
const awaitArgument = getAwaitedCall(statement);
|
|
5634
6333
|
if (!awaitArgument) continue;
|
|
5635
|
-
|
|
5636
|
-
|
|
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
|
-
|
|
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
|
-
|
|
13649
|
-
|
|
13650
|
-
|
|
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
|
|
13653
|
-
const
|
|
13654
|
-
|
|
13655
|
-
|
|
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
|
-
|
|
13676
|
-
|
|
13677
|
-
|
|
13678
|
-
|
|
13679
|
-
|
|
13680
|
-
|
|
13681
|
-
|
|
13682
|
-
|
|
13683
|
-
|
|
13684
|
-
|
|
13685
|
-
|
|
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
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13691
|
-
|
|
13692
|
-
|
|
13693
|
-
|
|
13694
|
-
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
|
|
13698
|
-
|
|
13699
|
-
|
|
13700
|
-
|
|
13701
|
-
|
|
13702
|
-
|
|
13703
|
-
|
|
13704
|
-
|
|
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
|
|
13717
|
-
let
|
|
13718
|
-
|
|
13719
|
-
|
|
13720
|
-
|
|
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
|
-
|
|
26068
|
-
|
|
26069
|
-
const
|
|
26070
|
-
if (
|
|
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
|
|
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 === "~") &&
|
|
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 ?? [])
|
|
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.
|
|
29607
|
-
if (isNodeOfType(node.callee, "Identifier") && STRING_COERCION_FUNCTIONS.has(node.callee.name) &&
|
|
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 (!
|
|
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
|
|
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
|
|
43150
|
-
if (
|
|
43151
|
-
|
|
43152
|
-
|
|
43153
|
-
|
|
43154
|
-
|
|
43155
|
-
|
|
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
|
|
43439
|
-
if (isFunctionLike$1(node)) return node
|
|
43440
|
-
if (isNodeOfType(node, "VariableDeclarator") && node.init && isFunctionLike$1(node.init)) return node.init
|
|
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
|
|
43449
|
-
|
|
43450
|
-
|
|
43451
|
-
|
|
43452
|
-
|
|
43453
|
-
if (
|
|
43454
|
-
if (
|
|
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
|
|
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
|
|
43463
|
-
if (!
|
|
43464
|
-
return
|
|
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
|
-
"
|
|
46403
|
+
"button",
|
|
45813
46404
|
"checkbox",
|
|
45814
|
-
"
|
|
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
|
|
45873
|
-
|
|
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)
|
|
45898
|
-
|
|
45899
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
62427
|
-
if (
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (
|
|
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;
|