oxlint-plugin-react-doctor 0.7.8 → 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 +2665 -1068
- 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;
|
|
@@ -11674,7 +12370,7 @@ const findRenderPhaseComponentOrHook = (node, scopes) => {
|
|
|
11674
12370
|
};
|
|
11675
12371
|
//#endregion
|
|
11676
12372
|
//#region src/plugin/utils/get-function-binding-name.ts
|
|
11677
|
-
const getFunctionBindingIdentifier = (functionNode) => {
|
|
12373
|
+
const getFunctionBindingIdentifier$1 = (functionNode) => {
|
|
11678
12374
|
if (isNodeOfType(functionNode, "FunctionDeclaration") && isNodeOfType(functionNode.id, "Identifier")) return functionNode.id;
|
|
11679
12375
|
const parent = functionNode.parent;
|
|
11680
12376
|
if (isNodeOfType(parent, "VariableDeclarator") && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
@@ -11685,7 +12381,7 @@ const getFunctionBindingIdentifier = (functionNode) => {
|
|
|
11685
12381
|
}
|
|
11686
12382
|
return null;
|
|
11687
12383
|
};
|
|
11688
|
-
const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier(functionNode)?.name ?? null;
|
|
12384
|
+
const getFunctionBindingName$1 = (functionNode) => getFunctionBindingIdentifier$1(functionNode)?.name ?? null;
|
|
11689
12385
|
//#endregion
|
|
11690
12386
|
//#region src/plugin/utils/is-event-handler-attribute.ts
|
|
11691
12387
|
const isEventHandlerAttribute = (node) => isNodeOfType(node, "JSXAttribute") && isNodeOfType(node.name, "JSXIdentifier") && /^on[A-Z]/.test(node.name.name);
|
|
@@ -12272,7 +12968,7 @@ const findDirectCallForReference = (identifier) => {
|
|
|
12272
12968
|
return isNodeOfType(callNode, "CallExpression") && callNode.callee === expressionRoot ? callNode : null;
|
|
12273
12969
|
};
|
|
12274
12970
|
const findSingleDirectInvocation = (functionNode, caller, context) => {
|
|
12275
|
-
const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
|
|
12971
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
12276
12972
|
if (!bindingIdentifier || resolveStableValue(bindingIdentifier, context) !== functionNode) return null;
|
|
12277
12973
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
12278
12974
|
if (!symbol) return null;
|
|
@@ -12809,7 +13505,7 @@ const isReturnedEffectCleanupFunction = (functionNode) => {
|
|
|
12809
13505
|
};
|
|
12810
13506
|
const isPotentiallyReachableFunction = (functionNode, context) => {
|
|
12811
13507
|
if (isInlineRetainedHandlerFunction(functionNode, context) || isReturnedEffectCleanupFunction(functionNode)) return true;
|
|
12812
|
-
const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
|
|
13508
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
12813
13509
|
if (!bindingIdentifier) return false;
|
|
12814
13510
|
const symbol = context.scopes.symbolFor(bindingIdentifier);
|
|
12815
13511
|
if (!symbol) return false;
|
|
@@ -13043,7 +13739,7 @@ const hasGuaranteedRefOwnedUnmountCleanup = (retainedFunction, usage, context) =
|
|
|
13043
13739
|
return didFindCleanupEffect;
|
|
13044
13740
|
};
|
|
13045
13741
|
const isUseSyncExternalStoreSubscribeFunction = (functionNode, context) => {
|
|
13046
|
-
const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
|
|
13742
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
13047
13743
|
if (!bindingIdentifier) return false;
|
|
13048
13744
|
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
13049
13745
|
const isSubscribeBinding = (candidateBinding) => {
|
|
@@ -13211,517 +13907,6 @@ const effectNeedsCleanup = defineRule({
|
|
|
13211
13907
|
}
|
|
13212
13908
|
});
|
|
13213
13909
|
//#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;
|
|
13646
|
-
}
|
|
13647
|
-
}
|
|
13648
|
-
for (const child of scope.children) visitScope(child);
|
|
13649
|
-
};
|
|
13650
|
-
visitScope(rootScope);
|
|
13651
|
-
};
|
|
13652
|
-
const analyzeScopes = (program) => {
|
|
13653
|
-
const rootScope = {
|
|
13654
|
-
id: 0,
|
|
13655
|
-
kind: "module",
|
|
13656
|
-
node: program,
|
|
13657
|
-
parent: null,
|
|
13658
|
-
children: [],
|
|
13659
|
-
symbols: [],
|
|
13660
|
-
references: [],
|
|
13661
|
-
symbolsByName: /* @__PURE__ */ new Map()
|
|
13662
|
-
};
|
|
13663
|
-
const state = {
|
|
13664
|
-
nextScopeId: 1,
|
|
13665
|
-
nextSymbolId: 0,
|
|
13666
|
-
nextReferenceId: 0,
|
|
13667
|
-
currentScope: rootScope,
|
|
13668
|
-
scopeStack: [rootScope],
|
|
13669
|
-
rootScope,
|
|
13670
|
-
nodeScope: /* @__PURE__ */ new WeakMap(),
|
|
13671
|
-
ownScopeForNode: /* @__PURE__ */ new WeakMap(),
|
|
13672
|
-
symbolByBindingIdentifier: /* @__PURE__ */ new WeakMap(),
|
|
13673
|
-
referenceByIdentifier: /* @__PURE__ */ new WeakMap()
|
|
13674
|
-
};
|
|
13675
|
-
state.nodeScope.set(program, rootScope);
|
|
13676
|
-
state.ownScopeForNode.set(program, rootScope);
|
|
13677
|
-
if (isNodeOfType(program, "Program")) for (const statement of program.body) walk(statement, state);
|
|
13678
|
-
else walk(program, state);
|
|
13679
|
-
resolveReferences(rootScope);
|
|
13680
|
-
const scopeFor = (node) => {
|
|
13681
|
-
let current = node;
|
|
13682
|
-
while (current) {
|
|
13683
|
-
const scope = state.nodeScope.get(current);
|
|
13684
|
-
if (scope) return scope;
|
|
13685
|
-
current = current.parent ?? null;
|
|
13686
|
-
}
|
|
13687
|
-
return rootScope;
|
|
13688
|
-
};
|
|
13689
|
-
const symbolFor = (identifier) => {
|
|
13690
|
-
const reference = state.referenceByIdentifier.get(identifier);
|
|
13691
|
-
if (reference) return reference.resolvedSymbol;
|
|
13692
|
-
const symbolForBinding = state.symbolByBindingIdentifier.get(identifier);
|
|
13693
|
-
if (symbolForBinding) return symbolForBinding;
|
|
13694
|
-
return null;
|
|
13695
|
-
};
|
|
13696
|
-
const referenceFor = (identifier) => {
|
|
13697
|
-
return state.referenceByIdentifier.get(identifier) ?? null;
|
|
13698
|
-
};
|
|
13699
|
-
const isGlobalReference = (identifier) => {
|
|
13700
|
-
const reference = state.referenceByIdentifier.get(identifier);
|
|
13701
|
-
if (!reference) return false;
|
|
13702
|
-
return reference.resolvedSymbol === null;
|
|
13703
|
-
};
|
|
13704
|
-
const ownScopeFor = (node) => {
|
|
13705
|
-
return state.ownScopeForNode.get(node) ?? null;
|
|
13706
|
-
};
|
|
13707
|
-
return {
|
|
13708
|
-
rootScope,
|
|
13709
|
-
scopeFor,
|
|
13710
|
-
ownScopeFor,
|
|
13711
|
-
symbolFor,
|
|
13712
|
-
referenceFor,
|
|
13713
|
-
isGlobalReference
|
|
13714
|
-
};
|
|
13715
|
-
};
|
|
13716
|
-
const isDescendantScope = (inner, outer) => {
|
|
13717
|
-
let current = inner;
|
|
13718
|
-
while (current) {
|
|
13719
|
-
if (current === outer) return true;
|
|
13720
|
-
current = current.parent;
|
|
13721
|
-
}
|
|
13722
|
-
return false;
|
|
13723
|
-
};
|
|
13724
|
-
//#endregion
|
|
13725
13910
|
//#region src/plugin/semantic/closure-captures.ts
|
|
13726
13911
|
const computeClosureCaptures = (functionNode, scopes) => {
|
|
13727
13912
|
const functionScope = scopes.ownScopeFor(functionNode) ?? scopes.scopeFor(functionNode);
|
|
@@ -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.";
|
|
@@ -21492,7 +21738,7 @@ const namedCallbackIteratorCallCache = /* @__PURE__ */ new WeakMap();
|
|
|
21492
21738
|
const findNamedCallbackIteratorCall = (functionNode) => {
|
|
21493
21739
|
const cached = namedCallbackIteratorCallCache.get(functionNode);
|
|
21494
21740
|
if (cached !== void 0) return cached;
|
|
21495
|
-
const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
|
|
21741
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
21496
21742
|
if (!bindingIdentifier) {
|
|
21497
21743
|
namedCallbackIteratorCallCache.set(functionNode, null);
|
|
21498
21744
|
return null;
|
|
@@ -22317,7 +22563,7 @@ const isReactSlotType = (typeNode, environment, activeDeclarations) => {
|
|
|
22317
22563
|
}
|
|
22318
22564
|
return environment.reactSlotTypeBindings.has(typeName);
|
|
22319
22565
|
};
|
|
22320
|
-
const getPropertyName = (property) => {
|
|
22566
|
+
const getPropertyName$1 = (property) => {
|
|
22321
22567
|
if (!isNodeOfType(property, "TSPropertySignature") || property.computed) return null;
|
|
22322
22568
|
if (isNodeOfType(property.key, "Identifier")) return property.key.name;
|
|
22323
22569
|
if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
|
|
@@ -22329,7 +22575,7 @@ const collectSlotPropertyNames = (propsType, environment, slotPropertyNames, act
|
|
|
22329
22575
|
if (isNodeOfType(propsType, "TSInterfaceDeclaration")) members = propsType.body.body;
|
|
22330
22576
|
if (members) {
|
|
22331
22577
|
for (const member of members) {
|
|
22332
|
-
const propertyName = getPropertyName(member);
|
|
22578
|
+
const propertyName = getPropertyName$1(member);
|
|
22333
22579
|
if (!propertyName || !isNodeOfType(member, "TSPropertySignature")) continue;
|
|
22334
22580
|
const annotation = member.typeAnnotation;
|
|
22335
22581
|
if (annotation && isNodeOfType(annotation, "TSTypeAnnotation") && isReactSlotType(annotation.typeAnnotation, environment, /* @__PURE__ */ new Set())) slotPropertyNames.add(propertyName);
|
|
@@ -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;
|
|
@@ -31988,17 +32366,611 @@ const noCreateContextInRender = defineRule({
|
|
|
31988
32366
|
} })
|
|
31989
32367
|
});
|
|
31990
32368
|
//#endregion
|
|
32369
|
+
//#region src/plugin/utils/collect-function-children-references.ts
|
|
32370
|
+
const collectFunctionChildrenReferences = (functionNode, scopes) => {
|
|
32371
|
+
if (!isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration")) return null;
|
|
32372
|
+
const rawPropsParameter = functionNode.params[0];
|
|
32373
|
+
const propsParameter = isNodeOfType(rawPropsParameter, "AssignmentPattern") ? rawPropsParameter.left : rawPropsParameter;
|
|
32374
|
+
if (isNodeOfType(propsParameter, "ObjectPattern")) {
|
|
32375
|
+
const childrenProperty = propsParameter.properties.find((property) => isNodeOfType(property, "Property") && getStaticPropertyKeyName(property, { allowComputedString: true }) === "children");
|
|
32376
|
+
if (!childrenProperty || !isNodeOfType(childrenProperty, "Property")) return null;
|
|
32377
|
+
const childrenBinding = isNodeOfType(childrenProperty.value, "AssignmentPattern") ? childrenProperty.value.left : childrenProperty.value;
|
|
32378
|
+
if (!isNodeOfType(childrenBinding, "Identifier")) return null;
|
|
32379
|
+
const childrenSymbol = scopes.symbolFor(childrenBinding);
|
|
32380
|
+
if (!childrenSymbol || childrenSymbol.references.length === 0 || childrenSymbol.references.some((reference) => reference.flag !== "read")) return null;
|
|
32381
|
+
return childrenSymbol.references.map((reference) => reference.identifier);
|
|
32382
|
+
}
|
|
32383
|
+
if (!isNodeOfType(propsParameter, "Identifier")) return null;
|
|
32384
|
+
const propsSymbol = scopes.symbolFor(propsParameter);
|
|
32385
|
+
if (!propsSymbol || propsSymbol.references.length === 0) return null;
|
|
32386
|
+
const childrenReferences = [];
|
|
32387
|
+
for (const reference of propsSymbol.references) {
|
|
32388
|
+
if (reference.flag !== "read") return null;
|
|
32389
|
+
const propertyPath = [];
|
|
32390
|
+
let expression = findTransparentExpressionRoot(reference.identifier);
|
|
32391
|
+
while (expression.parent && isNodeOfType(expression.parent, "MemberExpression") && expression.parent.object === expression) {
|
|
32392
|
+
const propertyName = getStaticPropertyName(expression.parent);
|
|
32393
|
+
if (!propertyName) return null;
|
|
32394
|
+
propertyPath.push(propertyName);
|
|
32395
|
+
expression = findTransparentExpressionRoot(expression.parent);
|
|
32396
|
+
}
|
|
32397
|
+
if (propertyPath[0] !== "children") {
|
|
32398
|
+
if (propertyPath.length === 0) return null;
|
|
32399
|
+
continue;
|
|
32400
|
+
}
|
|
32401
|
+
if (propertyPath.length !== 1) return null;
|
|
32402
|
+
childrenReferences.push(expression);
|
|
32403
|
+
}
|
|
32404
|
+
return childrenReferences.length > 0 ? childrenReferences : null;
|
|
32405
|
+
};
|
|
32406
|
+
//#endregion
|
|
32407
|
+
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
32408
|
+
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
32409
|
+
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
32410
|
+
const expression = stripParenExpression(node);
|
|
32411
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
32412
|
+
const propertyName = getStaticPropertyName(expression);
|
|
32413
|
+
const receiver = stripParenExpression(expression.object);
|
|
32414
|
+
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
32415
|
+
}
|
|
32416
|
+
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
32417
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
32418
|
+
const symbol = scopes.symbolFor(expression);
|
|
32419
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
32420
|
+
visitedSymbolIds.add(symbol.id);
|
|
32421
|
+
if (isImportedFromReact(symbol)) {
|
|
32422
|
+
const importedName = getImportedName(symbol.declarationNode);
|
|
32423
|
+
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
32424
|
+
}
|
|
32425
|
+
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
32426
|
+
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
32427
|
+
};
|
|
32428
|
+
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
32429
|
+
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
32430
|
+
visitedClassNodes.add(classNode);
|
|
32431
|
+
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
32432
|
+
};
|
|
32433
|
+
//#endregion
|
|
32434
|
+
//#region src/plugin/utils/is-proven-intrinsic-jsx-element.ts
|
|
32435
|
+
const isProvenIntrinsicJsxElement = (openingElement, scopes) => {
|
|
32436
|
+
if (!isNodeOfType(openingElement.name, "JSXIdentifier")) return false;
|
|
32437
|
+
const resolvedElementType = resolveJsxElementType(openingElement);
|
|
32438
|
+
if (resolvedElementType[0] === resolvedElementType[0]?.toLowerCase()) return true;
|
|
32439
|
+
const visitedSymbolIds = /* @__PURE__ */ new Set();
|
|
32440
|
+
const isIntrinsicValue = (node) => {
|
|
32441
|
+
const current = findTransparentExpressionRoot(node);
|
|
32442
|
+
if (isNodeOfType(current, "Literal")) return typeof current.value === "string";
|
|
32443
|
+
if (isNodeOfType(current, "Identifier") || isNodeOfType(current, "JSXIdentifier")) {
|
|
32444
|
+
const symbol = scopes.symbolFor(current);
|
|
32445
|
+
if (!symbol || symbol.kind !== "const" || !symbol.initializer || visitedSymbolIds.has(symbol.id)) return false;
|
|
32446
|
+
visitedSymbolIds.add(symbol.id);
|
|
32447
|
+
const isIntrinsic = isIntrinsicValue(symbol.initializer);
|
|
32448
|
+
visitedSymbolIds.delete(symbol.id);
|
|
32449
|
+
return isIntrinsic;
|
|
32450
|
+
}
|
|
32451
|
+
if (isNodeOfType(current, "ConditionalExpression")) return isIntrinsicValue(current.consequent) && isIntrinsicValue(current.alternate);
|
|
32452
|
+
return false;
|
|
32453
|
+
};
|
|
32454
|
+
return isIntrinsicValue(openingElement.name);
|
|
32455
|
+
};
|
|
32456
|
+
//#endregion
|
|
32457
|
+
//#region src/plugin/rules/react-builtins/is-safe-create-ref-callback-current-write.ts
|
|
32458
|
+
const pathStartsWith$1 = (propertyPath, prefix) => prefix.every((propertyName, index) => propertyPath[index] === propertyName);
|
|
32459
|
+
const collectMemberExpression = (identifier) => {
|
|
32460
|
+
let expression = findTransparentExpressionRoot(identifier);
|
|
32461
|
+
while (expression.parent && isNodeOfType(expression.parent, "MemberExpression") && expression.parent.object === expression) {
|
|
32462
|
+
if (!getStaticPropertyName(expression.parent)) return null;
|
|
32463
|
+
expression = findTransparentExpressionRoot(expression.parent);
|
|
32464
|
+
}
|
|
32465
|
+
return expression;
|
|
32466
|
+
};
|
|
32467
|
+
const isInlineIntrinsicRefCallback = (functionNode, scopes) => {
|
|
32468
|
+
const functionExpression = findTransparentExpressionRoot(functionNode);
|
|
32469
|
+
if (!isFunctionLike$1(functionExpression) || functionExpression.async || functionExpression.generator) return false;
|
|
32470
|
+
const container = functionExpression.parent;
|
|
32471
|
+
if (!container || !isNodeOfType(container, "JSXExpressionContainer")) return false;
|
|
32472
|
+
const attribute = container.parent;
|
|
32473
|
+
if (!attribute || !isNodeOfType(attribute, "JSXAttribute") || getJsxAttributeName(attribute.name) !== "ref") return false;
|
|
32474
|
+
const openingElement = attribute.parent;
|
|
32475
|
+
return Boolean(openingElement && isNodeOfType(openingElement, "JSXOpeningElement") && isProvenIntrinsicJsxElement(openingElement, scopes));
|
|
32476
|
+
};
|
|
32477
|
+
const isSafeCreateRefCallbackCurrentWrite = (referenceNode, accessedPropertyPath, targetPropertyPath, scopes) => {
|
|
32478
|
+
if (accessedPropertyPath.length !== targetPropertyPath.length + 1 || !pathStartsWith$1(accessedPropertyPath, targetPropertyPath) || accessedPropertyPath[targetPropertyPath.length] !== "current") return false;
|
|
32479
|
+
const memberExpression = collectMemberExpression(referenceNode);
|
|
32480
|
+
const assignment = memberExpression?.parent;
|
|
32481
|
+
if (!memberExpression || !assignment || !isNodeOfType(assignment, "AssignmentExpression") || assignment.operator !== "=" || assignment.left !== memberExpression) return false;
|
|
32482
|
+
const enclosingFunction = findEnclosingFunction$1(referenceNode);
|
|
32483
|
+
if (!enclosingFunction) return false;
|
|
32484
|
+
if (isInlineIntrinsicRefCallback(enclosingFunction, scopes)) return true;
|
|
32485
|
+
if (!isFunctionLike$1(enclosingFunction) || enclosingFunction.async || enclosingFunction.generator) return false;
|
|
32486
|
+
const callbackFunction = findEnclosingFunction$1(enclosingFunction);
|
|
32487
|
+
if (!callbackFunction || !isInlineIntrinsicRefCallback(callbackFunction, scopes)) return false;
|
|
32488
|
+
const cleanupFunction = findTransparentExpressionRoot(enclosingFunction);
|
|
32489
|
+
const cleanupContainer = cleanupFunction.parent;
|
|
32490
|
+
return Boolean(isNodeOfType(cleanupContainer, "ReturnStatement") && cleanupContainer.argument === cleanupFunction && findEnclosingFunction$1(cleanupContainer) === callbackFunction || isNodeOfType(callbackFunction, "ArrowFunctionExpression") && callbackFunction.body === cleanupFunction);
|
|
32491
|
+
};
|
|
32492
|
+
//#endregion
|
|
32493
|
+
//#region src/plugin/rules/react-builtins/is-value-rendered-in-same-render.ts
|
|
32494
|
+
const isValueRenderedFromOwner = (expressionNode, renderOwner, scopes, visitedSymbolIds, options) => {
|
|
32495
|
+
const expression = findTransparentExpressionRoot(expressionNode);
|
|
32496
|
+
const parent = expression.parent;
|
|
32497
|
+
if (!parent) return false;
|
|
32498
|
+
if (isNodeOfType(parent, "ReturnStatement") && parent.argument === expression) return findEnclosingFunction$1(parent) === renderOwner;
|
|
32499
|
+
if (isNodeOfType(parent, "ArrowFunctionExpression") && parent.body === expression) return parent === renderOwner;
|
|
32500
|
+
if (isNodeOfType(parent, "JSXFragment") && parent.children.some((child) => child === expression)) return isValueRenderedFromOwner(parent, renderOwner, scopes, visitedSymbolIds, options);
|
|
32501
|
+
if (isNodeOfType(parent, "JSXElement") && parent.children.some((child) => child === expression) && (isProvenIntrinsicJsxElement(parent.openingElement, scopes) || isJsxFragmentElement(parent.openingElement, scopes) || options.doesCustomElementRenderChildren?.(parent.openingElement))) return isValueRenderedFromOwner(parent, renderOwner, scopes, visitedSymbolIds, options);
|
|
32502
|
+
if (isNodeOfType(parent, "JSXExpressionContainer") && parent.expression === expression && parent.parent && (isNodeOfType(parent.parent, "JSXFragment") || isNodeOfType(parent.parent, "JSXElement") && (isProvenIntrinsicJsxElement(parent.parent.openingElement, scopes) || isJsxFragmentElement(parent.parent.openingElement, scopes) || options.doesCustomElementRenderChildren?.(parent.parent.openingElement)))) return isValueRenderedFromOwner(parent.parent, renderOwner, scopes, visitedSymbolIds, options);
|
|
32503
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === expression || parent.alternate === expression) || isNodeOfType(parent, "LogicalExpression") && (parent.left === expression || parent.right === expression) || isNodeOfType(parent, "ArrayExpression") && parent.elements.some((element) => element === expression)) return isValueRenderedFromOwner(parent, renderOwner, scopes, visitedSymbolIds, options);
|
|
32504
|
+
if (!isNodeOfType(parent, "VariableDeclarator") || parent.init !== expression || !isNodeOfType(parent.id, "Identifier")) return false;
|
|
32505
|
+
const symbol = scopes.symbolFor(parent.id);
|
|
32506
|
+
if (!symbol || symbol.kind !== "const" || symbol.references.length === 0 || visitedSymbolIds.has(symbol.id)) return false;
|
|
32507
|
+
visitedSymbolIds.add(symbol.id);
|
|
32508
|
+
const isRendered = symbol.references.every((reference) => reference.flag === "read" && isValueRenderedFromOwner(reference.identifier, renderOwner, scopes, visitedSymbolIds, options));
|
|
32509
|
+
visitedSymbolIds.delete(symbol.id);
|
|
32510
|
+
return isRendered;
|
|
32511
|
+
};
|
|
32512
|
+
const isValueRenderedInSameRender = (expressionNode, scopes, options = {}) => isValueRenderedFromOwner(expressionNode, findEnclosingFunction$1(expressionNode), scopes, /* @__PURE__ */ new Set(), options);
|
|
32513
|
+
//#endregion
|
|
32514
|
+
//#region src/plugin/rules/react-builtins/is-create-ref-result-write-only.ts
|
|
32515
|
+
const pathStartsWith = (propertyPath, prefix) => prefix.every((propertyName, index) => propertyPath[index] === propertyName);
|
|
32516
|
+
const pathsOverlap = (firstPath, secondPath) => pathStartsWith(firstPath, secondPath) || pathStartsWith(secondPath, firstPath);
|
|
32517
|
+
const isClosedNoopFunction = (node) => isFunctionLike$1(node) && isNodeOfType(node.body, "BlockStatement") && node.body.body.length === 0;
|
|
32518
|
+
const isProvenReactCall = (callExpression, expectedName, scopes) => isReactApiCall(callExpression, expectedName, scopes, { resolveNamedAliases: true });
|
|
32519
|
+
const objectHasOnlyClosedNoopFunctions = (objectExpression, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
32520
|
+
if (!isNodeOfType(objectExpression, "ObjectExpression")) return false;
|
|
32521
|
+
return objectExpression.properties.every((property) => {
|
|
32522
|
+
if (isNodeOfType(property, "SpreadElement")) {
|
|
32523
|
+
const spreadArgument = findTransparentExpressionRoot(property.argument);
|
|
32524
|
+
if (isNodeOfType(spreadArgument, "ObjectExpression")) return objectHasOnlyClosedNoopFunctions(spreadArgument, scopes, visitedSymbolIds);
|
|
32525
|
+
if (!isNodeOfType(spreadArgument, "Identifier")) return false;
|
|
32526
|
+
const symbol = scopes.symbolFor(spreadArgument);
|
|
32527
|
+
if (!symbol || symbol.kind !== "const" || visitedSymbolIds.has(symbol.id) || !isNodeOfType(symbol.initializer, "ObjectExpression")) return false;
|
|
32528
|
+
visitedSymbolIds.add(symbol.id);
|
|
32529
|
+
const isClosed = objectHasOnlyClosedNoopFunctions(symbol.initializer, scopes, visitedSymbolIds);
|
|
32530
|
+
visitedSymbolIds.delete(symbol.id);
|
|
32531
|
+
return isClosed;
|
|
32532
|
+
}
|
|
32533
|
+
if (!isNodeOfType(property, "Property")) return false;
|
|
32534
|
+
return !isFunctionLike$1(property.value) || isClosedNoopFunction(property.value);
|
|
32535
|
+
});
|
|
32536
|
+
};
|
|
32537
|
+
const getValuePathPropertyName = (memberExpression) => {
|
|
32538
|
+
const propertyName = getStaticPropertyName(memberExpression);
|
|
32539
|
+
if (propertyName) return propertyName;
|
|
32540
|
+
return memberExpression.computed && isNodeOfType(memberExpression.property, "Literal") && typeof memberExpression.property.value === "number" && Number.isSafeInteger(memberExpression.property.value) && memberExpression.property.value >= 0 ? String(memberExpression.property.value) : null;
|
|
32541
|
+
};
|
|
32542
|
+
const collectMemberAccess = (identifier) => {
|
|
32543
|
+
const propertyPath = [];
|
|
32544
|
+
let expression = findTransparentExpressionRoot(identifier);
|
|
32545
|
+
while (expression.parent && isNodeOfType(expression.parent, "MemberExpression") && expression.parent.object === expression) {
|
|
32546
|
+
const propertyName = getValuePathPropertyName(expression.parent);
|
|
32547
|
+
if (!propertyName) return null;
|
|
32548
|
+
propertyPath.push(propertyName);
|
|
32549
|
+
expression = findTransparentExpressionRoot(expression.parent);
|
|
32550
|
+
}
|
|
32551
|
+
return {
|
|
32552
|
+
expression,
|
|
32553
|
+
propertyPath
|
|
32554
|
+
};
|
|
32555
|
+
};
|
|
32556
|
+
const getEnvironment = (program, filename, state) => {
|
|
32557
|
+
const cached = state.environmentsByProgram.get(program);
|
|
32558
|
+
if (cached) return cached;
|
|
32559
|
+
const environment = {
|
|
32560
|
+
filename,
|
|
32561
|
+
program,
|
|
32562
|
+
scopes: analyzeScopes(program)
|
|
32563
|
+
};
|
|
32564
|
+
state.environmentsByProgram.set(program, environment);
|
|
32565
|
+
return environment;
|
|
32566
|
+
};
|
|
32567
|
+
const unwrapProvenReactHocFunction = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
32568
|
+
if (!node) return null;
|
|
32569
|
+
const current = findTransparentExpressionRoot(node);
|
|
32570
|
+
if (isFunctionLike$1(current)) return current;
|
|
32571
|
+
if (isNodeOfType(current, "Identifier")) {
|
|
32572
|
+
const symbol = scopes.symbolFor(current);
|
|
32573
|
+
if (!symbol || visitedSymbolIds.has(symbol.id) || !symbol.initializer || hasSymbolWriteBefore(symbol, current, scopes)) return null;
|
|
32574
|
+
visitedSymbolIds.add(symbol.id);
|
|
32575
|
+
return unwrapProvenReactHocFunction(symbol.initializer, scopes, visitedSymbolIds);
|
|
32576
|
+
}
|
|
32577
|
+
if (!isNodeOfType(current, "CallExpression")) return null;
|
|
32578
|
+
if (!isProvenReactCall(current, "memo", scopes) && !isProvenReactCall(current, "forwardRef", scopes)) return null;
|
|
32579
|
+
const firstArgument = current.arguments[0];
|
|
32580
|
+
if (!firstArgument || isNodeOfType(firstArgument, "SpreadElement")) return null;
|
|
32581
|
+
return unwrapProvenReactHocFunction(firstArgument, scopes, visitedSymbolIds);
|
|
32582
|
+
};
|
|
32583
|
+
const isForwardRefValue = (node, scopes, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
32584
|
+
const current = findTransparentExpressionRoot(node);
|
|
32585
|
+
if (isNodeOfType(current, "Identifier")) {
|
|
32586
|
+
const symbol = scopes.symbolFor(current);
|
|
32587
|
+
if (!symbol || !symbol.initializer || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, current, scopes)) return false;
|
|
32588
|
+
visitedSymbolIds.add(symbol.id);
|
|
32589
|
+
return isForwardRefValue(symbol.initializer, scopes, visitedSymbolIds);
|
|
32590
|
+
}
|
|
32591
|
+
if (!isNodeOfType(current, "CallExpression")) return false;
|
|
32592
|
+
if (isProvenReactCall(current, "forwardRef", scopes)) return true;
|
|
32593
|
+
if (!isProvenReactCall(current, "memo", scopes)) return false;
|
|
32594
|
+
const firstArgument = current.arguments[0];
|
|
32595
|
+
if (!firstArgument || isNodeOfType(firstArgument, "SpreadElement")) return false;
|
|
32596
|
+
return isForwardRefValue(firstArgument, scopes, visitedSymbolIds);
|
|
32597
|
+
};
|
|
32598
|
+
const functionFromExport = (resolved, state) => {
|
|
32599
|
+
const environment = getEnvironment(resolved.programNode, resolved.filePath, state);
|
|
32600
|
+
const functionNode = unwrapProvenReactHocFunction(resolved.exportedNode, environment.scopes);
|
|
32601
|
+
if (!functionNode) return null;
|
|
32602
|
+
return {
|
|
32603
|
+
environment,
|
|
32604
|
+
functionNode,
|
|
32605
|
+
isForwardRef: isForwardRefValue(resolved.exportedNode, environment.scopes)
|
|
32606
|
+
};
|
|
32607
|
+
};
|
|
32608
|
+
const resolveFunctionValue = (identifier, environment, state) => {
|
|
32609
|
+
if (!isNodeOfType(identifier, "Identifier") && !isNodeOfType(identifier, "JSXIdentifier")) return null;
|
|
32610
|
+
const symbol = environment.scopes.symbolFor(identifier);
|
|
32611
|
+
if (symbol && symbol.kind !== "import") {
|
|
32612
|
+
if (hasSymbolWriteBefore(symbol, identifier, environment.scopes)) return null;
|
|
32613
|
+
const functionNode = unwrapProvenReactHocFunction(symbol.initializer, environment.scopes);
|
|
32614
|
+
if (!functionNode) return null;
|
|
32615
|
+
return {
|
|
32616
|
+
environment,
|
|
32617
|
+
functionNode,
|
|
32618
|
+
isForwardRef: Boolean(symbol.initializer && isForwardRefValue(symbol.initializer, environment.scopes))
|
|
32619
|
+
};
|
|
32620
|
+
}
|
|
32621
|
+
const binding = getImportBindingForName(identifier, identifier.name);
|
|
32622
|
+
if (!binding || binding.isNamespace || binding.exportedName === null) return null;
|
|
32623
|
+
const resolved = resolveCrossFileValueExportWithFilePath(environment.filename, binding.source, binding.exportedName);
|
|
32624
|
+
return resolved ? functionFromExport(resolved, state) : null;
|
|
32625
|
+
};
|
|
32626
|
+
const resolveJsxFunctionValue = (elementName, environment, state) => {
|
|
32627
|
+
if (isNodeOfType(elementName, "JSXIdentifier")) return resolveFunctionValue(elementName, environment, state);
|
|
32628
|
+
if (!isNodeOfType(elementName, "JSXMemberExpression") || !isNodeOfType(elementName.object, "JSXIdentifier")) return null;
|
|
32629
|
+
const namespaceBinding = getImportBindingForName(elementName.object, elementName.object.name);
|
|
32630
|
+
if (!namespaceBinding?.isNamespace) return null;
|
|
32631
|
+
const resolved = resolveCrossFileValueExportWithFilePath(environment.filename, namespaceBinding.source, elementName.property.name);
|
|
32632
|
+
return resolved ? functionFromExport(resolved, state) : null;
|
|
32633
|
+
};
|
|
32634
|
+
const isProvenReactClassValue = (node, environment, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
32635
|
+
const current = findTransparentExpressionRoot(node);
|
|
32636
|
+
if (isNodeOfType(current, "Identifier")) {
|
|
32637
|
+
const symbol = environment.scopes.symbolFor(current);
|
|
32638
|
+
if (!symbol || !symbol.initializer || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, current, environment.scopes)) return false;
|
|
32639
|
+
visitedSymbolIds.add(symbol.id);
|
|
32640
|
+
return isProvenReactClassValue(symbol.initializer, environment, visitedSymbolIds);
|
|
32641
|
+
}
|
|
32642
|
+
return isProvenReactClassComponent(current, environment.scopes);
|
|
32643
|
+
};
|
|
32644
|
+
const isProvenClassComponentIdentifier = (identifier, environment, state) => {
|
|
32645
|
+
if (!isNodeOfType(identifier, "JSXIdentifier")) return false;
|
|
32646
|
+
const symbol = environment.scopes.symbolFor(identifier);
|
|
32647
|
+
if (symbol && symbol.kind !== "import") {
|
|
32648
|
+
if (hasSymbolWriteBefore(symbol, identifier, environment.scopes)) return false;
|
|
32649
|
+
return Boolean(symbol.initializer && isProvenReactClassValue(symbol.initializer, environment));
|
|
32650
|
+
}
|
|
32651
|
+
const binding = getImportBindingForName(identifier, identifier.name);
|
|
32652
|
+
if (!binding || binding.isNamespace || binding.exportedName === null) return false;
|
|
32653
|
+
const resolved = resolveCrossFileValueExportWithFilePath(environment.filename, binding.source, binding.exportedName);
|
|
32654
|
+
if (!resolved) return false;
|
|
32655
|
+
const resolvedEnvironment = getEnvironment(resolved.programNode, resolved.filePath, state);
|
|
32656
|
+
return isProvenReactClassValue(resolved.exportedNode, resolvedEnvironment);
|
|
32657
|
+
};
|
|
32658
|
+
const resolveClassValue = (node, environment) => {
|
|
32659
|
+
if (!node) return null;
|
|
32660
|
+
const classNode = findTransparentExpressionRoot(node);
|
|
32661
|
+
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || !isProvenReactClassValue(classNode, environment)) return null;
|
|
32662
|
+
return {
|
|
32663
|
+
classNode,
|
|
32664
|
+
environment
|
|
32665
|
+
};
|
|
32666
|
+
};
|
|
32667
|
+
const resolveJsxClassValue = (elementName, environment, state) => {
|
|
32668
|
+
if (!isNodeOfType(elementName, "JSXIdentifier")) return null;
|
|
32669
|
+
const symbol = environment.scopes.symbolFor(elementName);
|
|
32670
|
+
if (symbol && symbol.kind !== "import") {
|
|
32671
|
+
if (hasSymbolWriteBefore(symbol, elementName, environment.scopes)) return null;
|
|
32672
|
+
return resolveClassValue(symbol.initializer, environment);
|
|
32673
|
+
}
|
|
32674
|
+
const binding = getImportBindingForName(elementName, elementName.name);
|
|
32675
|
+
if (!binding || binding.isNamespace || binding.exportedName === null) return null;
|
|
32676
|
+
const resolved = resolveCrossFileValueExportWithFilePath(environment.filename, binding.source, binding.exportedName);
|
|
32677
|
+
if (!resolved) return null;
|
|
32678
|
+
const resolvedEnvironment = getEnvironment(resolved.programNode, resolved.filePath, state);
|
|
32679
|
+
return resolveClassValue(resolved.exportedNode, resolvedEnvironment);
|
|
32680
|
+
};
|
|
32681
|
+
const findOwnedSymbolValue = (expressionNode, initialPropertyPath, environment) => {
|
|
32682
|
+
const propertyPath = [...initialPropertyPath];
|
|
32683
|
+
let expression = findTransparentExpressionRoot(expressionNode);
|
|
32684
|
+
while (expression.parent) {
|
|
32685
|
+
const parent = expression.parent;
|
|
32686
|
+
if (isNodeOfType(parent, "ArrayExpression")) {
|
|
32687
|
+
if (parent.elements.some((element) => element && isNodeOfType(element, "SpreadElement"))) return null;
|
|
32688
|
+
const elementIndex = parent.elements.findIndex((element) => element === expression);
|
|
32689
|
+
if (elementIndex < 0) return null;
|
|
32690
|
+
propertyPath.unshift(String(elementIndex));
|
|
32691
|
+
expression = findTransparentExpressionRoot(parent);
|
|
32692
|
+
continue;
|
|
32693
|
+
}
|
|
32694
|
+
if (isNodeOfType(parent, "Property") && parent.value === expression) {
|
|
32695
|
+
const propertyName = getStaticPropertyKeyName(parent, { allowComputedString: true });
|
|
32696
|
+
const objectExpression = parent.parent;
|
|
32697
|
+
if (!propertyName || !objectExpression || !objectHasOnlyClosedNoopFunctions(objectExpression, environment.scopes)) return null;
|
|
32698
|
+
propertyPath.unshift(propertyName);
|
|
32699
|
+
expression = findTransparentExpressionRoot(objectExpression);
|
|
32700
|
+
continue;
|
|
32701
|
+
}
|
|
32702
|
+
if (isNodeOfType(parent, "ConditionalExpression") && (parent.consequent === expression || parent.alternate === expression) || isNodeOfType(parent, "LogicalExpression") && (parent.left === expression || parent.right === expression)) {
|
|
32703
|
+
expression = findTransparentExpressionRoot(parent);
|
|
32704
|
+
continue;
|
|
32705
|
+
}
|
|
32706
|
+
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === expression && isNodeOfType(parent.id, "Identifier")) {
|
|
32707
|
+
const symbol = environment.scopes.symbolFor(parent.id);
|
|
32708
|
+
if (!symbol || symbol.kind !== "const") return null;
|
|
32709
|
+
return {
|
|
32710
|
+
environment,
|
|
32711
|
+
propertyPath,
|
|
32712
|
+
symbol
|
|
32713
|
+
};
|
|
32714
|
+
}
|
|
32715
|
+
if (isNodeOfType(parent, "AssignmentExpression") && parent.operator === "=" && parent.right === expression && isNodeOfType(parent.left, "MemberExpression")) {
|
|
32716
|
+
const assignedPropertyPath = [];
|
|
32717
|
+
let assignedExpression = parent.left;
|
|
32718
|
+
while (isNodeOfType(assignedExpression, "MemberExpression")) {
|
|
32719
|
+
const propertyName = getValuePathPropertyName(assignedExpression);
|
|
32720
|
+
if (!propertyName) return null;
|
|
32721
|
+
assignedPropertyPath.unshift(propertyName);
|
|
32722
|
+
assignedExpression = findTransparentExpressionRoot(assignedExpression.object);
|
|
32723
|
+
}
|
|
32724
|
+
if (!isNodeOfType(assignedExpression, "Identifier")) return null;
|
|
32725
|
+
const symbol = environment.scopes.symbolFor(assignedExpression);
|
|
32726
|
+
if (!symbol || symbol.kind !== "const") return null;
|
|
32727
|
+
return {
|
|
32728
|
+
environment,
|
|
32729
|
+
originWriteReference: assignedExpression,
|
|
32730
|
+
propertyPath: [...assignedPropertyPath, ...propertyPath],
|
|
32731
|
+
symbol
|
|
32732
|
+
};
|
|
32733
|
+
}
|
|
32734
|
+
return null;
|
|
32735
|
+
}
|
|
32736
|
+
return null;
|
|
32737
|
+
};
|
|
32738
|
+
const analyzePatternPath = (pattern, propertyPath, environment, state, remainingDepth) => {
|
|
32739
|
+
if (isNodeOfType(pattern, "AssignmentPattern")) return analyzePatternPath(pattern.left, propertyPath, environment, state, remainingDepth);
|
|
32740
|
+
if (isNodeOfType(pattern, "Identifier")) {
|
|
32741
|
+
const symbol = environment.scopes.symbolFor(pattern);
|
|
32742
|
+
return Boolean(symbol && analyzeSymbolValuePath({
|
|
32743
|
+
environment,
|
|
32744
|
+
propertyPath,
|
|
32745
|
+
symbol
|
|
32746
|
+
}, state, remainingDepth - 1));
|
|
32747
|
+
}
|
|
32748
|
+
if (!isNodeOfType(pattern, "ObjectPattern") || propertyPath.length === 0) return false;
|
|
32749
|
+
const [firstPropertyName, ...remainingPropertyPath] = propertyPath;
|
|
32750
|
+
for (const property of pattern.properties) {
|
|
32751
|
+
if (!isNodeOfType(property, "Property")) continue;
|
|
32752
|
+
const propertyName = getStaticPropertyKeyName(property, { allowComputedString: true });
|
|
32753
|
+
if (!propertyName) return false;
|
|
32754
|
+
if (propertyName !== firstPropertyName) continue;
|
|
32755
|
+
return analyzePatternPath(property.value, remainingPropertyPath, environment, state, remainingDepth);
|
|
32756
|
+
}
|
|
32757
|
+
const restProperty = pattern.properties.find((property) => isNodeOfType(property, "RestElement"));
|
|
32758
|
+
return restProperty ? analyzePatternPath(restProperty.argument, propertyPath, environment, state, remainingDepth) : true;
|
|
32759
|
+
};
|
|
32760
|
+
const isIntrinsicReactElementFactoryCall = (callExpression, environment) => {
|
|
32761
|
+
const element = callExpression.arguments[0];
|
|
32762
|
+
if (!element || isNodeOfType(element, "SpreadElement")) return false;
|
|
32763
|
+
if (isProvenReactCall(callExpression, "createElement", environment.scopes)) return isNodeOfType(element, "Literal") && typeof element.value === "string";
|
|
32764
|
+
if (!isProvenReactCall(callExpression, "cloneElement", environment.scopes)) return false;
|
|
32765
|
+
if (!isNodeOfType(element, "JSXElement")) return false;
|
|
32766
|
+
return isProvenIntrinsicJsxElement(element.openingElement, environment.scopes);
|
|
32767
|
+
};
|
|
32768
|
+
const isIntrinsicReactElementRefProperty = (expression, propertyPath, environment, state, remainingDepth) => {
|
|
32769
|
+
if (propertyPath.length > 0) return false;
|
|
32770
|
+
const property = expression.parent;
|
|
32771
|
+
if (!property || !isNodeOfType(property, "Property") || property.value !== expression) return false;
|
|
32772
|
+
if (getStaticPropertyKeyName(property, { allowComputedString: true }) !== "ref") return false;
|
|
32773
|
+
const props = property.parent;
|
|
32774
|
+
if (!props || !isNodeOfType(props, "ObjectExpression")) return false;
|
|
32775
|
+
const callExpression = props.parent;
|
|
32776
|
+
if (!callExpression || !isNodeOfType(callExpression, "CallExpression") || callExpression.arguments[1] !== props || !isIntrinsicReactElementFactoryCall(callExpression, environment) || !isValueRenderedForEnvironment(callExpression, environment, state, remainingDepth)) return false;
|
|
32777
|
+
return true;
|
|
32778
|
+
};
|
|
32779
|
+
const analyzeFunctionInput = (resolvedFunction, parameterIndex, propertyPath, state, remainingDepth) => {
|
|
32780
|
+
if (remainingDepth <= 0 || !isFunctionLike$1(resolvedFunction.functionNode) || resolvedFunction.functionNode.async || resolvedFunction.functionNode.generator) return false;
|
|
32781
|
+
const parameter = resolvedFunction.functionNode.params[parameterIndex];
|
|
32782
|
+
return Boolean(parameter && analyzePatternPath(parameter, propertyPath, resolvedFunction.environment, state, remainingDepth));
|
|
32783
|
+
};
|
|
32784
|
+
const isThisPropsChildren = (node) => {
|
|
32785
|
+
if (!isNodeOfType(node, "MemberExpression") || getStaticPropertyName(node) !== "children") return false;
|
|
32786
|
+
const propsMember = findTransparentExpressionRoot(node.object);
|
|
32787
|
+
return Boolean(isNodeOfType(propsMember, "MemberExpression") && getStaticPropertyName(propsMember) === "props" && isNodeOfType(findTransparentExpressionRoot(propsMember.object), "ThisExpression"));
|
|
32788
|
+
};
|
|
32789
|
+
const isProvenReactContextProvider = (openingElement, environment) => {
|
|
32790
|
+
if (!isNodeOfType(openingElement, "JSXOpeningElement") || !isNodeOfType(openingElement.name, "JSXMemberExpression") || openingElement.name.property.name !== "Provider" || !isNodeOfType(openingElement.name.object, "JSXIdentifier")) return false;
|
|
32791
|
+
const contextSymbol = environment.scopes.symbolFor(openingElement.name.object);
|
|
32792
|
+
return Boolean(contextSymbol?.kind === "const" && contextSymbol.initializer && isNodeOfType(contextSymbol.initializer, "CallExpression") && isProvenReactCall(contextSymbol.initializer, "createContext", environment.scopes));
|
|
32793
|
+
};
|
|
32794
|
+
const isValueRenderedForEnvironment = (expression, environment, state, remainingDepth) => isValueRenderedInSameRender(expression, environment.scopes, { doesCustomElementRenderChildren: (openingElement) => doesCustomElementRenderChildren(openingElement, environment, state, remainingDepth - 1) });
|
|
32795
|
+
const doesFunctionComponentRenderChildren = (resolvedFunction, state, remainingDepth) => {
|
|
32796
|
+
const { functionNode } = resolvedFunction;
|
|
32797
|
+
if (remainingDepth <= 0 || state.activeRenderedChildrenComponents.has(functionNode) || !isNodeOfType(functionNode, "ArrowFunctionExpression") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "FunctionDeclaration") || functionNode.async || functionNode.generator) return false;
|
|
32798
|
+
const childrenReferences = collectFunctionChildrenReferences(functionNode, resolvedFunction.environment.scopes);
|
|
32799
|
+
if (!childrenReferences) return false;
|
|
32800
|
+
state.activeRenderedChildrenComponents.add(functionNode);
|
|
32801
|
+
const isRendered = childrenReferences.every((childrenReference) => isValueRenderedForEnvironment(childrenReference, resolvedFunction.environment, state, remainingDepth));
|
|
32802
|
+
state.activeRenderedChildrenComponents.delete(functionNode);
|
|
32803
|
+
return isRendered;
|
|
32804
|
+
};
|
|
32805
|
+
const doesClassComponentRenderChildren = (resolvedClass, state, remainingDepth) => {
|
|
32806
|
+
const { classNode, environment } = resolvedClass;
|
|
32807
|
+
if (remainingDepth <= 0 || state.activeRenderedChildrenComponents.has(classNode) || !isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression")) return false;
|
|
32808
|
+
const renderMethod = classNode.body.body.find((element) => isNodeOfType(element, "MethodDefinition") && !element.computed && isNodeOfType(element.key, "Identifier") && element.key.name === "render");
|
|
32809
|
+
if (!renderMethod || !isNodeOfType(renderMethod, "MethodDefinition")) return false;
|
|
32810
|
+
const childrenReads = [];
|
|
32811
|
+
walkAst(classNode, (node) => {
|
|
32812
|
+
if (isThisPropsChildren(node)) childrenReads.push(node);
|
|
32813
|
+
});
|
|
32814
|
+
if (childrenReads.length === 0) return false;
|
|
32815
|
+
state.activeRenderedChildrenComponents.add(classNode);
|
|
32816
|
+
const isRendered = childrenReads.every((childrenRead) => findEnclosingFunction$1(childrenRead) === renderMethod.value && isValueRenderedForEnvironment(childrenRead, environment, state, remainingDepth));
|
|
32817
|
+
state.activeRenderedChildrenComponents.delete(classNode);
|
|
32818
|
+
return isRendered;
|
|
32819
|
+
};
|
|
32820
|
+
const doesCustomElementRenderChildren = (openingElement, environment, state, remainingDepth) => {
|
|
32821
|
+
if (remainingDepth <= 0 || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
32822
|
+
if (isProvenReactContextProvider(openingElement, environment)) return true;
|
|
32823
|
+
const resolvedFunction = resolveJsxFunctionValue(openingElement.name, environment, state);
|
|
32824
|
+
if (resolvedFunction) return doesFunctionComponentRenderChildren(resolvedFunction, state, remainingDepth);
|
|
32825
|
+
const resolvedClass = resolveJsxClassValue(openingElement.name, environment, state);
|
|
32826
|
+
return Boolean(resolvedClass && doesClassComponentRenderChildren(resolvedClass, state, remainingDepth));
|
|
32827
|
+
};
|
|
32828
|
+
const analyzeJsxAttributeUse = (attribute, propertyPath, environment, state, remainingDepth) => {
|
|
32829
|
+
const attributeName = getJsxAttributeName(attribute.name);
|
|
32830
|
+
const openingElement = attribute.parent;
|
|
32831
|
+
if (!attributeName || !openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
32832
|
+
const jsxElement = openingElement.parent;
|
|
32833
|
+
if (!jsxElement || !isNodeOfType(jsxElement, "JSXElement") || !isValueRenderedForEnvironment(jsxElement, environment, state, remainingDepth)) return false;
|
|
32834
|
+
if (isProvenIntrinsicJsxElement(openingElement, environment.scopes)) return attributeName === "ref" && propertyPath.length === 0;
|
|
32835
|
+
if (isNodeOfType(openingElement.name, "JSXIdentifier") && attributeName === "ref" && propertyPath.length === 0 && isProvenClassComponentIdentifier(openingElement.name, environment, state)) return true;
|
|
32836
|
+
const resolvedFunction = resolveJsxFunctionValue(openingElement.name, environment, state);
|
|
32837
|
+
if (!resolvedFunction) return false;
|
|
32838
|
+
if (attributeName === "ref" && resolvedFunction.isForwardRef) return analyzeFunctionInput(resolvedFunction, 1, propertyPath, state, remainingDepth);
|
|
32839
|
+
return analyzeFunctionInput(resolvedFunction, 0, [attributeName, ...propertyPath], state, remainingDepth);
|
|
32840
|
+
};
|
|
32841
|
+
const analyzeCallArgumentUse = (callExpression, argumentExpression, propertyPath, environment, state, remainingDepth) => {
|
|
32842
|
+
const argumentIndex = callExpression.arguments.findIndex((argument) => argument === argumentExpression);
|
|
32843
|
+
if (argumentIndex < 0) return false;
|
|
32844
|
+
if (argumentIndex === 0 && isNodeOfType(callExpression.callee, "Identifier") && callExpression.callee.name === "Boolean" && environment.scopes.isGlobalReference(callExpression.callee)) return true;
|
|
32845
|
+
if (argumentIndex === 0 && isProvenReactCall(callExpression, "useImperativeHandle", environment.scopes)) return propertyPath.length === 0;
|
|
32846
|
+
if (argumentIndex === 1 && propertyPath.length === 1 && propertyPath[0] === "ref" && isIntrinsicReactElementFactoryCall(callExpression, environment) && isValueRenderedForEnvironment(callExpression, environment, state, remainingDepth)) return true;
|
|
32847
|
+
const inlineCallee = findTransparentExpressionRoot(callExpression.callee);
|
|
32848
|
+
if (isFunctionLike$1(inlineCallee)) {
|
|
32849
|
+
if (functionContainsReactRenderOutput(inlineCallee, environment.scopes) && !isValueRenderedForEnvironment(callExpression, environment, state, remainingDepth)) return false;
|
|
32850
|
+
return analyzeFunctionInput({
|
|
32851
|
+
environment,
|
|
32852
|
+
functionNode: inlineCallee,
|
|
32853
|
+
isForwardRef: false
|
|
32854
|
+
}, argumentIndex, propertyPath, state, remainingDepth);
|
|
32855
|
+
}
|
|
32856
|
+
if (!isNodeOfType(callExpression.callee, "Identifier")) return false;
|
|
32857
|
+
const resolvedFunction = resolveFunctionValue(callExpression.callee, environment, state);
|
|
32858
|
+
return Boolean(resolvedFunction && (!functionContainsReactRenderOutput(resolvedFunction.functionNode, resolvedFunction.environment.scopes) || isValueRenderedForEnvironment(callExpression, environment, state, remainingDepth)) && analyzeFunctionInput(resolvedFunction, argumentIndex, propertyPath, state, remainingDepth));
|
|
32859
|
+
};
|
|
32860
|
+
const isIntrinsicJsxSpreadRefUse = (expression, propertyPath, environment, state, remainingDepth) => {
|
|
32861
|
+
let spreadAttribute;
|
|
32862
|
+
if (propertyPath.length === 1 && propertyPath[0] === "ref") spreadAttribute = expression.parent;
|
|
32863
|
+
else if (propertyPath.length === 0) {
|
|
32864
|
+
const property = expression.parent;
|
|
32865
|
+
if (!property || !isNodeOfType(property, "Property") || property.value !== expression || getStaticPropertyKeyName(property, { allowComputedString: true }) !== "ref") return false;
|
|
32866
|
+
spreadAttribute = property.parent?.parent;
|
|
32867
|
+
}
|
|
32868
|
+
if (!spreadAttribute || !isNodeOfType(spreadAttribute, "JSXSpreadAttribute")) return false;
|
|
32869
|
+
const openingElement = spreadAttribute.parent;
|
|
32870
|
+
if (!openingElement || !isNodeOfType(openingElement, "JSXOpeningElement")) return false;
|
|
32871
|
+
const jsxElement = openingElement.parent;
|
|
32872
|
+
return Boolean(isProvenIntrinsicJsxElement(openingElement, environment.scopes) && jsxElement && isNodeOfType(jsxElement, "JSXElement") && isValueRenderedForEnvironment(jsxElement, environment, state, remainingDepth));
|
|
32873
|
+
};
|
|
32874
|
+
const analyzeValueUse = (expressionNode, propertyPath, environment, state, remainingDepth) => {
|
|
32875
|
+
const expression = findTransparentExpressionRoot(expressionNode);
|
|
32876
|
+
const parent = expression.parent;
|
|
32877
|
+
if (!parent) return false;
|
|
32878
|
+
if (isNodeOfType(parent, "ExpressionStatement")) return true;
|
|
32879
|
+
if (isNodeOfType(parent, "UnaryExpression") && parent.operator === "void" && parent.argument === expression) return true;
|
|
32880
|
+
if (propertyPath.length === 0 && isNodeOfType(parent, "BinaryExpression") && (parent.operator === "===" || parent.operator === "!==" || parent.operator === "==" || parent.operator === "!=")) {
|
|
32881
|
+
const comparedExpression = parent.left === expression ? parent.right : parent.left;
|
|
32882
|
+
return Boolean(isNodeOfType(expression, "Identifier") && isNodeOfType(comparedExpression, "Identifier") && environment.scopes.symbolFor(expression)?.id === environment.scopes.symbolFor(comparedExpression)?.id);
|
|
32883
|
+
}
|
|
32884
|
+
if (isIntrinsicJsxSpreadRefUse(expression, propertyPath, environment, state, remainingDepth)) return true;
|
|
32885
|
+
if (isIntrinsicReactElementRefProperty(expression, propertyPath, environment, state, remainingDepth)) return true;
|
|
32886
|
+
if (propertyPath.length > 0 && (isNodeOfType(parent, "LogicalExpression") && parent.left === expression || isNodeOfType(parent, "ConditionalExpression") && parent.test === expression || isNodeOfType(parent, "IfStatement") && parent.test === expression || isNodeOfType(parent, "UnaryExpression") && parent.operator === "!" && parent.argument === expression)) return true;
|
|
32887
|
+
if (isNodeOfType(parent, "JSXExpressionContainer") && parent.expression === expression) {
|
|
32888
|
+
const attribute = parent.parent;
|
|
32889
|
+
return Boolean(attribute && isNodeOfType(attribute, "JSXAttribute") && analyzeJsxAttributeUse(attribute, propertyPath, environment, state, remainingDepth));
|
|
32890
|
+
}
|
|
32891
|
+
if (isNodeOfType(parent, "CallExpression")) return analyzeCallArgumentUse(parent, expression, propertyPath, environment, state, remainingDepth);
|
|
32892
|
+
if (isNodeOfType(parent, "VariableDeclarator") && parent.init === expression && !isNodeOfType(parent.id, "Identifier")) return analyzePatternPath(parent.id, propertyPath, environment, state, remainingDepth);
|
|
32893
|
+
const propagatedValue = findOwnedSymbolValue(expression, propertyPath, environment);
|
|
32894
|
+
return Boolean(propagatedValue && analyzeSymbolValuePath(propagatedValue, state, remainingDepth - 1));
|
|
32895
|
+
};
|
|
32896
|
+
const getFunctionBindingIdentifier = (functionNode) => {
|
|
32897
|
+
const parent = functionNode.parent;
|
|
32898
|
+
if (parent && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return parent.id;
|
|
32899
|
+
if ((isNodeOfType(functionNode, "FunctionDeclaration") || isNodeOfType(functionNode, "FunctionExpression")) && functionNode.id) return functionNode.id;
|
|
32900
|
+
return null;
|
|
32901
|
+
};
|
|
32902
|
+
const isSynchronouslyInvokedLocalFunction = (functionNode, callerFunction, scopes) => {
|
|
32903
|
+
if (!functionNode || !callerFunction || !isFunctionLike$1(functionNode) || functionNode.async || functionNode.generator) return false;
|
|
32904
|
+
const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
|
|
32905
|
+
if (!bindingIdentifier) return false;
|
|
32906
|
+
if (((isNodeOfType(functionNode, "FunctionDeclaration") || isNodeOfType(functionNode, "FunctionExpression")) && functionNode.id === bindingIdentifier ? findEnclosingFunction$1(functionNode) : findEnclosingFunction$1(bindingIdentifier)) !== callerFunction) return false;
|
|
32907
|
+
const symbol = isNodeOfType(functionNode, "FunctionDeclaration") ? scopes.ownScopeFor(callerFunction)?.symbolsByName.get(bindingIdentifier.name) : scopes.symbolFor(bindingIdentifier);
|
|
32908
|
+
return Boolean(symbol && symbol.references.length > 0 && symbol.references.every((reference) => {
|
|
32909
|
+
const referenceExpression = findTransparentExpressionRoot(reference.identifier);
|
|
32910
|
+
const callExpression = referenceExpression.parent;
|
|
32911
|
+
return Boolean(callExpression && isNodeOfType(callExpression, "CallExpression") && callExpression.callee === referenceExpression && findEnclosingFunction$1(callExpression) === callerFunction && isValueRenderedInSameRender(callExpression, scopes));
|
|
32912
|
+
}));
|
|
32913
|
+
};
|
|
32914
|
+
const analyzeSymbolValuePath = (valuePath, state, remainingDepth) => {
|
|
32915
|
+
if (remainingDepth <= 0) return false;
|
|
32916
|
+
const activePathKey = `${valuePath.environment.filename}:${valuePath.symbol.id}:${valuePath.propertyPath.join(".")}`;
|
|
32917
|
+
if (state.activePaths.has(activePathKey)) return false;
|
|
32918
|
+
state.activePaths.add(activePathKey);
|
|
32919
|
+
const bindingFunction = findEnclosingFunction$1(valuePath.symbol.bindingIdentifier);
|
|
32920
|
+
const result = valuePath.symbol.references.every((reference) => {
|
|
32921
|
+
if (reference.identifier === valuePath.originWriteReference) return true;
|
|
32922
|
+
const referenceParent = reference.identifier.parent;
|
|
32923
|
+
if (referenceParent && isNodeOfType(referenceParent, "Property") && !referenceParent.computed && referenceParent.key === reference.identifier && referenceParent.value !== reference.identifier) return true;
|
|
32924
|
+
const memberAccess = collectMemberAccess(reference.identifier);
|
|
32925
|
+
if (!memberAccess) return false;
|
|
32926
|
+
if (!pathsOverlap(memberAccess.propertyPath, valuePath.propertyPath)) return true;
|
|
32927
|
+
const referenceFunction = findEnclosingFunction$1(reference.identifier);
|
|
32928
|
+
if (referenceFunction !== bindingFunction && !isSynchronouslyInvokedLocalFunction(referenceFunction, bindingFunction, valuePath.environment.scopes)) {
|
|
32929
|
+
if (isSafeCreateRefCallbackCurrentWrite(reference.identifier, memberAccess.propertyPath, valuePath.propertyPath, valuePath.environment.scopes)) return true;
|
|
32930
|
+
return false;
|
|
32931
|
+
}
|
|
32932
|
+
if (reference.flag !== "read") return false;
|
|
32933
|
+
if (pathStartsWith(memberAccess.propertyPath, valuePath.propertyPath)) {
|
|
32934
|
+
if (memberAccess.propertyPath.length > valuePath.propertyPath.length) {
|
|
32935
|
+
const parent = memberAccess.expression.parent;
|
|
32936
|
+
return Boolean(memberAccess.propertyPath.length === valuePath.propertyPath.length + 1 && memberAccess.propertyPath[valuePath.propertyPath.length] === "current" && parent && isNodeOfType(parent, "UnaryExpression") && parent.operator === "void" && parent.argument === memberAccess.expression);
|
|
32937
|
+
}
|
|
32938
|
+
return analyzeValueUse(memberAccess.expression, [], valuePath.environment, state, remainingDepth);
|
|
32939
|
+
}
|
|
32940
|
+
return analyzeValueUse(memberAccess.expression, valuePath.propertyPath.slice(memberAccess.propertyPath.length), valuePath.environment, state, remainingDepth);
|
|
32941
|
+
});
|
|
32942
|
+
state.activePaths.delete(activePathKey);
|
|
32943
|
+
return result;
|
|
32944
|
+
};
|
|
32945
|
+
const isCreateRefResultWriteOnly = (createRefCall, filename, scopes) => {
|
|
32946
|
+
if (!filename) return false;
|
|
32947
|
+
const program = findProgramRoot(createRefCall);
|
|
32948
|
+
if (!program) return false;
|
|
32949
|
+
const environment = {
|
|
32950
|
+
filename,
|
|
32951
|
+
program,
|
|
32952
|
+
scopes
|
|
32953
|
+
};
|
|
32954
|
+
const state = {
|
|
32955
|
+
activePaths: /* @__PURE__ */ new Set(),
|
|
32956
|
+
activeRenderedChildrenComponents: /* @__PURE__ */ new WeakSet(),
|
|
32957
|
+
environmentsByProgram: new WeakMap([[program, environment]])
|
|
32958
|
+
};
|
|
32959
|
+
const ownedValue = findOwnedSymbolValue(createRefCall, [], environment);
|
|
32960
|
+
return ownedValue ? analyzeSymbolValuePath(ownedValue, state, 12) : analyzeValueUse(createRefCall, [], environment, state, 12);
|
|
32961
|
+
};
|
|
32962
|
+
//#endregion
|
|
31991
32963
|
//#region src/plugin/rules/react-builtins/no-create-ref-in-function-component.ts
|
|
31992
|
-
const MESSAGE$31 = "`createRef()`
|
|
31993
|
-
const isUseMemoCallbackArgument = (functionNode) => {
|
|
32964
|
+
const MESSAGE$31 = "`createRef()` may escape or be observed beyond the render that created it, so a later render can replace the ref object and detach the observed one. Hoist a `useRef()` call to the component's unconditional top level instead.";
|
|
32965
|
+
const isUseMemoCallbackArgument = (functionNode, scopes) => {
|
|
31994
32966
|
const parent = functionNode.parent;
|
|
31995
32967
|
if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
|
|
31996
32968
|
if (parent.arguments?.[0] !== functionNode) return false;
|
|
31997
|
-
return
|
|
32969
|
+
return isReactApiCall(parent, "useMemo", scopes, { resolveNamedAliases: true });
|
|
31998
32970
|
};
|
|
31999
|
-
const findEnclosingRenderFunction = (node) => {
|
|
32971
|
+
const findEnclosingRenderFunction = (node, scopes) => {
|
|
32000
32972
|
let enclosingFunction = findEnclosingFunction$1(node);
|
|
32001
|
-
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
32973
|
+
while (enclosingFunction && isUseMemoCallbackArgument(enclosingFunction, scopes)) enclosingFunction = findEnclosingFunction$1(enclosingFunction);
|
|
32002
32974
|
return enclosingFunction;
|
|
32003
32975
|
};
|
|
32004
32976
|
const noCreateRefInFunctionComponent = defineRule({
|
|
@@ -32007,16 +32979,17 @@ const noCreateRefInFunctionComponent = defineRule({
|
|
|
32007
32979
|
severity: "warn",
|
|
32008
32980
|
recommendation: "Replace `createRef()` with the `useRef()` hook inside function components and hooks. `createRef` is only for class components.",
|
|
32009
32981
|
create: (context) => ({ CallExpression(node) {
|
|
32010
|
-
if (!
|
|
32011
|
-
|
|
32012
|
-
|
|
32013
|
-
|
|
32014
|
-
}
|
|
32015
|
-
const enclosingFunction = findEnclosingRenderFunction(node);
|
|
32982
|
+
if (!isReactApiCall(node, "createRef", context.scopes, {
|
|
32983
|
+
allowGlobalReactNamespace: true,
|
|
32984
|
+
allowUnboundBareCalls: true,
|
|
32985
|
+
resolveNamedAliases: true
|
|
32986
|
+
})) return;
|
|
32987
|
+
const enclosingFunction = findEnclosingRenderFunction(node, context.scopes);
|
|
32016
32988
|
if (!enclosingFunction) return;
|
|
32017
32989
|
const displayName = componentOrHookDisplayNameForFunction(enclosingFunction);
|
|
32018
32990
|
if (!displayName) return;
|
|
32019
32991
|
if (!(isReactHookName(displayName) || functionContainsReactRenderOutput(enclosingFunction, context.scopes, context.cfg))) return;
|
|
32992
|
+
if (isCreateRefResultWriteOnly(node, context.filename, context.scopes)) return;
|
|
32020
32993
|
context.report({
|
|
32021
32994
|
node,
|
|
32022
32995
|
message: MESSAGE$31
|
|
@@ -32541,6 +33514,28 @@ const noDarkModeGlow = defineRule({
|
|
|
32541
33514
|
});
|
|
32542
33515
|
//#endregion
|
|
32543
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
|
+
};
|
|
32544
33539
|
const noDefaultProps = defineRule({
|
|
32545
33540
|
id: "no-default-props",
|
|
32546
33541
|
title: "defaultProps removed in React 19",
|
|
@@ -32556,6 +33551,7 @@ const noDefaultProps = defineRule({
|
|
|
32556
33551
|
if (!isNodeOfType(left.property, "Identifier") || left.property.name !== "defaultProps") return;
|
|
32557
33552
|
if (!isNodeOfType(left.object, "Identifier")) return;
|
|
32558
33553
|
if (!isUppercaseName(left.object.name)) return;
|
|
33554
|
+
if (isStableClassReceiver(left.object, context)) return;
|
|
32559
33555
|
context.report({
|
|
32560
33556
|
node: left,
|
|
32561
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" })\`.`
|
|
@@ -38195,33 +39191,6 @@ const noLegacyClassLifecycles = defineRule({
|
|
|
38195
39191
|
}
|
|
38196
39192
|
});
|
|
38197
39193
|
//#endregion
|
|
38198
|
-
//#region src/plugin/utils/is-proven-react-class-component.ts
|
|
38199
|
-
const REACT_COMPONENT_CLASS_NAMES = new Set(["Component", "PureComponent"]);
|
|
38200
|
-
const isReactComponentClassValue = (node, scopes, visitedClassNodes, visitedSymbolIds) => {
|
|
38201
|
-
const expression = stripParenExpression(node);
|
|
38202
|
-
if (isNodeOfType(expression, "MemberExpression")) {
|
|
38203
|
-
const propertyName = getStaticPropertyName(expression);
|
|
38204
|
-
const receiver = stripParenExpression(expression.object);
|
|
38205
|
-
return Boolean(propertyName && REACT_COMPONENT_CLASS_NAMES.has(propertyName) && isNodeOfType(receiver, "Identifier") && !hasStaticPropertyWriteBefore(receiver, propertyName, expression, scopes) && isReactNamespaceImport(receiver, scopes));
|
|
38206
|
-
}
|
|
38207
|
-
if (isNodeOfType(expression, "ClassExpression")) return isProvenReactClassComponent(expression, scopes, visitedClassNodes, visitedSymbolIds);
|
|
38208
|
-
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
38209
|
-
const symbol = scopes.symbolFor(expression);
|
|
38210
|
-
if (!symbol || visitedSymbolIds.has(symbol.id) || hasSymbolWriteBefore(symbol, expression, scopes)) return false;
|
|
38211
|
-
visitedSymbolIds.add(symbol.id);
|
|
38212
|
-
if (isImportedFromReact(symbol)) {
|
|
38213
|
-
const importedName = getImportedName(symbol.declarationNode);
|
|
38214
|
-
return Boolean(importedName && REACT_COMPONENT_CLASS_NAMES.has(importedName));
|
|
38215
|
-
}
|
|
38216
|
-
if (isNodeOfType(symbol.declarationNode, "ClassDeclaration") || isNodeOfType(symbol.declarationNode, "ClassExpression")) return isProvenReactClassComponent(symbol.declarationNode, scopes, visitedClassNodes, visitedSymbolIds);
|
|
38217
|
-
return Boolean(symbol.kind === "const" && symbol.initializer && isReactComponentClassValue(symbol.initializer, scopes, visitedClassNodes, visitedSymbolIds));
|
|
38218
|
-
};
|
|
38219
|
-
const isProvenReactClassComponent = (classNode, scopes, visitedClassNodes = /* @__PURE__ */ new Set(), visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
38220
|
-
if (!isNodeOfType(classNode, "ClassDeclaration") && !isNodeOfType(classNode, "ClassExpression") || visitedClassNodes.has(classNode) || !classNode.superClass) return false;
|
|
38221
|
-
visitedClassNodes.add(classNode);
|
|
38222
|
-
return isReactComponentClassValue(classNode.superClass, scopes, visitedClassNodes, visitedSymbolIds);
|
|
38223
|
-
};
|
|
38224
|
-
//#endregion
|
|
38225
39194
|
//#region src/plugin/utils/function-contains-proven-react-hook-call.ts
|
|
38226
39195
|
const functionContainsProvenReactHookCall = (functionNode, scopes) => {
|
|
38227
39196
|
if (!isFunctionLike$1(functionNode)) return false;
|
|
@@ -38685,7 +39654,7 @@ const isFunctionInvokedBeforeUsage = (functionNode, usageNode, usageBoundary, sc
|
|
|
38685
39654
|
}
|
|
38686
39655
|
if (isFunctionInvokedBeforeUsage(methodCallFunction, usageNode, usageBoundary, scopes, new Set(visitedSymbolIds), new Set(visitedFunctionNodes))) return true;
|
|
38687
39656
|
}
|
|
38688
|
-
const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
|
|
39657
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
38689
39658
|
if (!bindingIdentifier) return false;
|
|
38690
39659
|
const symbol = scopes.symbolFor(bindingIdentifier);
|
|
38691
39660
|
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
@@ -41228,6 +42197,133 @@ const getCallbackRefProvenance = (analysis, effectCall, callExpression, isReactU
|
|
|
41228
42197
|
}
|
|
41229
42198
|
return callbackPropNames.size > 0 ? { callbackPropNames } : null;
|
|
41230
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
|
+
};
|
|
41231
42327
|
const isWrapperHookCallbackRef = (analysis, ref) => Boolean(ref.resolved?.defs.some((def) => {
|
|
41232
42328
|
const node = def.node;
|
|
41233
42329
|
if (!isNodeOfType(node, "VariableDeclarator") || !node.init) return false;
|
|
@@ -41329,6 +42425,8 @@ const noPassDataToParent = defineRule({
|
|
|
41329
42425
|
if (callbackRefProvenance) {
|
|
41330
42426
|
if ([...callbackRefProvenance.callbackPropNames].some((callbackPropName) => COMMAND_PROP_NAME_PATTERN.test(callbackPropName))) continue;
|
|
41331
42427
|
} else if (calleeNode === identifier) {
|
|
42428
|
+
const callbackPropName = getCommandCallbackPropName(analysis, identifier, isReactUseRefCall);
|
|
42429
|
+
if (callbackPropName && COMMAND_PROP_NAME_PATTERN.test(callbackPropName)) continue;
|
|
41332
42430
|
if (!isDirectParentCallbackRef(analysis, ref)) continue;
|
|
41333
42431
|
if (isNodeOfType(identifier, "Identifier") && COMMAND_PROP_NAME_PATTERN.test(identifier.name)) continue;
|
|
41334
42432
|
} else if (isNodeOfType(calleeNode, "MemberExpression") && stripParenExpression(calleeNode.object) === identifier) {
|
|
@@ -42520,7 +43618,16 @@ const getElementImplicitRoles = (tag) => IMPLICIT_ROLES_BY_TAG.get(tag) ?? EMPTY
|
|
|
42520
43618
|
const getTagsForRole = (role) => TAGS_BY_ROLE.get(role) ?? EMPTY_ROLE_LIST;
|
|
42521
43619
|
//#endregion
|
|
42522
43620
|
//#region src/plugin/utils/get-implicit-role.ts
|
|
42523
|
-
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) => {
|
|
42524
43631
|
const propStringValue = (propName) => {
|
|
42525
43632
|
const attribute = hasJsxPropIgnoreCase(node.attributes, propName);
|
|
42526
43633
|
return attribute ? getJsxPropStringValue(attribute) : null;
|
|
@@ -42578,13 +43685,18 @@ const getImplicitRole = (node, elementType) => {
|
|
|
42578
43685
|
break;
|
|
42579
43686
|
}
|
|
42580
43687
|
case "input": {
|
|
42581
|
-
const
|
|
42582
|
-
if (
|
|
42583
|
-
|
|
42584
|
-
|
|
42585
|
-
|
|
42586
|
-
|
|
42587
|
-
|
|
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 ?? "" : "";
|
|
42588
43700
|
break;
|
|
42589
43701
|
}
|
|
42590
43702
|
case "li":
|
|
@@ -42688,7 +43800,7 @@ const noRedundantRoles = defineRule({
|
|
|
42688
43800
|
if (tag === "td" || tag === "th") {
|
|
42689
43801
|
if (findSameFileTableContext(node, context.settings) !== "table") return;
|
|
42690
43802
|
implicitRoles = [getTableCellPrimaryRole(node, tag)];
|
|
42691
|
-
} 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);
|
|
42692
43804
|
else implicitRoles = getElementImplicitRoles(tag);
|
|
42693
43805
|
const allowedHere = [...DEFAULT_NON_REDUNDANT_ROLES[tag] ?? [], ...settings.exceptions[tag] ?? []];
|
|
42694
43806
|
if (implicitRoles.includes(role) && !allowedHere.includes(role)) context.report({
|
|
@@ -42867,9 +43979,9 @@ const isInsideComponentContext = (node) => {
|
|
|
42867
43979
|
}
|
|
42868
43980
|
return false;
|
|
42869
43981
|
};
|
|
42870
|
-
const
|
|
42871
|
-
if (isFunctionLike$1(node)) return node
|
|
42872
|
-
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;
|
|
42873
43985
|
return null;
|
|
42874
43986
|
};
|
|
42875
43987
|
const isHookCallee = (callee) => {
|
|
@@ -42877,23 +43989,43 @@ const isHookCallee = (callee) => {
|
|
|
42877
43989
|
if (isNodeOfType(callee, "MemberExpression") && !callee.computed && isNodeOfType(callee.object, "Identifier") && isUppercaseName(callee.object.name) && isNodeOfType(callee.property, "Identifier")) return isReactHookName(callee.property.name);
|
|
42878
43990
|
return false;
|
|
42879
43991
|
};
|
|
42880
|
-
const
|
|
42881
|
-
|
|
42882
|
-
|
|
42883
|
-
|
|
42884
|
-
|
|
42885
|
-
if (
|
|
42886
|
-
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
|
+
}
|
|
42887
44019
|
});
|
|
42888
|
-
return
|
|
44020
|
+
return didFindReachableHook;
|
|
42889
44021
|
};
|
|
42890
|
-
const isHookCallingRenderHelper = (symbol) => {
|
|
44022
|
+
const isHookCallingRenderHelper = (symbol, context) => {
|
|
42891
44023
|
if (!symbol) return false;
|
|
42892
44024
|
const declaration = symbol.declarationNode;
|
|
42893
44025
|
if (!isNodeOfType(declaration, "FunctionDeclaration") && !isNodeOfType(declaration, "VariableDeclarator")) return false;
|
|
42894
|
-
const
|
|
42895
|
-
if (!
|
|
42896
|
-
return
|
|
44026
|
+
const functionNode = getFunctionFromDeclaration(declaration);
|
|
44027
|
+
if (!functionNode) return false;
|
|
44028
|
+
return containsReachableHookCall(functionNode, functionNode, context, /* @__PURE__ */ new Set());
|
|
42897
44029
|
};
|
|
42898
44030
|
const noRenderInRender = defineRule({
|
|
42899
44031
|
id: "no-render-in-render",
|
|
@@ -42908,7 +44040,7 @@ const noRenderInRender = defineRule({
|
|
|
42908
44040
|
const calleeName = expression.callee.name;
|
|
42909
44041
|
if (!RENDER_FUNCTION_PATTERN.test(calleeName)) return;
|
|
42910
44042
|
if (!isInsideComponentContext(node)) return;
|
|
42911
|
-
if (!isHookCallingRenderHelper(context.scopes.symbolFor(expression.callee))) return;
|
|
44043
|
+
if (!isHookCallingRenderHelper(context.scopes.symbolFor(expression.callee), context)) return;
|
|
42912
44044
|
context.report({
|
|
42913
44045
|
node: expression,
|
|
42914
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.`
|
|
@@ -42992,6 +44124,31 @@ const noRenderReturnValue = defineRule({
|
|
|
42992
44124
|
});
|
|
42993
44125
|
//#endregion
|
|
42994
44126
|
//#region src/plugin/rules/state-and-effects/no-reset-all-state-on-prop-change.ts
|
|
44127
|
+
const SYNCHRONOUS_ARRAY_CALLBACK_METHOD_NAMES = new Set([
|
|
44128
|
+
"every",
|
|
44129
|
+
"filter",
|
|
44130
|
+
"find",
|
|
44131
|
+
"findIndex",
|
|
44132
|
+
"flatMap",
|
|
44133
|
+
"forEach",
|
|
44134
|
+
"map",
|
|
44135
|
+
"reduce",
|
|
44136
|
+
"reduceRight",
|
|
44137
|
+
"some"
|
|
44138
|
+
]);
|
|
44139
|
+
const createConstantFormula = (constantValue) => ({
|
|
44140
|
+
kind: "constant",
|
|
44141
|
+
constantValue
|
|
44142
|
+
});
|
|
44143
|
+
const createNotFormula = (formula) => ({
|
|
44144
|
+
kind: "not",
|
|
44145
|
+
left: formula
|
|
44146
|
+
});
|
|
44147
|
+
const createBinaryFormula = (kind, left, right) => ({
|
|
44148
|
+
kind,
|
|
44149
|
+
left,
|
|
44150
|
+
right
|
|
44151
|
+
});
|
|
42995
44152
|
const isUndefinedNode = (node) => {
|
|
42996
44153
|
if (node === null || node === void 0) return true;
|
|
42997
44154
|
return isNodeOfType(node, "Identifier") && node.name === "undefined";
|
|
@@ -43053,6 +44210,241 @@ const getLivePropExpressionIdentity = (analysis, context, node, visitedSymbolIds
|
|
|
43053
44210
|
}
|
|
43054
44211
|
return null;
|
|
43055
44212
|
};
|
|
44213
|
+
const getLivePropAtomKey = (identity) => `prop:${identity.propSymbolId}:${JSON.stringify(identity.memberPath)}:${identity.booleanNormalization}`;
|
|
44214
|
+
const getBooleanFormula$1 = (analysis, context, node, protectedSymbolIds, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
44215
|
+
const expression = stripParenExpression(node);
|
|
44216
|
+
if (isNodeOfType(expression, "Literal")) return createConstantFormula(Boolean(expression.value));
|
|
44217
|
+
if (isNodeOfType(expression, "Identifier")) {
|
|
44218
|
+
if (expression.name === "undefined" && context.scopes.isGlobalReference(expression)) return createConstantFormula(false);
|
|
44219
|
+
const symbol = context.scopes.symbolFor(expression);
|
|
44220
|
+
if (!symbol) return null;
|
|
44221
|
+
if (!protectedSymbolIds.has(symbol.id) && symbol.kind === "const" && !visitedSymbolIds.has(symbol.id) && isNodeOfType(symbol.declarationNode, "VariableDeclarator") && symbol.declarationNode.id === symbol.bindingIdentifier) {
|
|
44222
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
44223
|
+
if (initializer) {
|
|
44224
|
+
visitedSymbolIds.add(symbol.id);
|
|
44225
|
+
const initializerFormula = getBooleanFormula$1(analysis, context, initializer, protectedSymbolIds, visitedSymbolIds);
|
|
44226
|
+
if (initializerFormula) return initializerFormula;
|
|
44227
|
+
}
|
|
44228
|
+
}
|
|
44229
|
+
return {
|
|
44230
|
+
kind: "atom",
|
|
44231
|
+
atomKey: `symbol:${symbol.id}`
|
|
44232
|
+
};
|
|
44233
|
+
}
|
|
44234
|
+
if (isNodeOfType(expression, "MemberExpression")) {
|
|
44235
|
+
const identity = getLivePropExpressionIdentity(analysis, context, expression);
|
|
44236
|
+
return identity ? {
|
|
44237
|
+
kind: "atom",
|
|
44238
|
+
atomKey: getLivePropAtomKey(identity)
|
|
44239
|
+
} : null;
|
|
44240
|
+
}
|
|
44241
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") {
|
|
44242
|
+
const argumentFormula = getBooleanFormula$1(analysis, context, expression.argument, protectedSymbolIds, visitedSymbolIds);
|
|
44243
|
+
return argumentFormula ? createNotFormula(argumentFormula) : null;
|
|
44244
|
+
}
|
|
44245
|
+
if (isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Boolean" && context.scopes.isGlobalReference(expression.callee) && expression.arguments.length === 1 && !isNodeOfType(expression.arguments[0], "SpreadElement")) return getBooleanFormula$1(analysis, context, expression.arguments[0], protectedSymbolIds, visitedSymbolIds);
|
|
44246
|
+
if (isNodeOfType(expression, "LogicalExpression") && (expression.operator === "&&" || expression.operator === "||")) {
|
|
44247
|
+
const leftFormula = getBooleanFormula$1(analysis, context, expression.left, protectedSymbolIds, new Set(visitedSymbolIds));
|
|
44248
|
+
const rightFormula = getBooleanFormula$1(analysis, context, expression.right, protectedSymbolIds, new Set(visitedSymbolIds));
|
|
44249
|
+
if (!leftFormula || !rightFormula) return null;
|
|
44250
|
+
return createBinaryFormula(expression.operator === "&&" ? "and" : "or", leftFormula, rightFormula);
|
|
44251
|
+
}
|
|
44252
|
+
if (isNodeOfType(expression, "ConditionalExpression")) {
|
|
44253
|
+
const testFormula = getBooleanFormula$1(analysis, context, expression.test, protectedSymbolIds, new Set(visitedSymbolIds));
|
|
44254
|
+
const consequentFormula = getBooleanFormula$1(analysis, context, expression.consequent, protectedSymbolIds, new Set(visitedSymbolIds));
|
|
44255
|
+
const alternateFormula = getBooleanFormula$1(analysis, context, expression.alternate, protectedSymbolIds, new Set(visitedSymbolIds));
|
|
44256
|
+
if (!testFormula || !consequentFormula || !alternateFormula) return null;
|
|
44257
|
+
return createBinaryFormula("or", createBinaryFormula("and", testFormula, consequentFormula), createBinaryFormula("and", createNotFormula(testFormula), alternateFormula));
|
|
44258
|
+
}
|
|
44259
|
+
if (isNodeOfType(expression, "BinaryExpression") && (expression.operator === "===" || expression.operator === "==" || expression.operator === "!==" || expression.operator === "!=")) {
|
|
44260
|
+
const leftIsBoolean = isNodeOfType(expression.left, "Literal") && typeof expression.left.value === "boolean";
|
|
44261
|
+
if (leftIsBoolean === (isNodeOfType(expression.right, "Literal") && typeof expression.right.value === "boolean")) return null;
|
|
44262
|
+
const booleanLiteral = leftIsBoolean ? expression.left : expression.right;
|
|
44263
|
+
const comparedFormula = getBooleanFormula$1(analysis, context, leftIsBoolean ? expression.right : expression.left, protectedSymbolIds, visitedSymbolIds);
|
|
44264
|
+
if (!comparedFormula || !isNodeOfType(booleanLiteral, "Literal")) return null;
|
|
44265
|
+
return (expression.operator === "===" || expression.operator === "==" ? booleanLiteral.value : !booleanLiteral.value) ? comparedFormula : createNotFormula(comparedFormula);
|
|
44266
|
+
}
|
|
44267
|
+
return null;
|
|
44268
|
+
};
|
|
44269
|
+
const getRequiredTruthyConditions = (analysis, context, node, protectedSymbolIds) => {
|
|
44270
|
+
const formula = getBooleanFormula$1(analysis, context, node, protectedSymbolIds);
|
|
44271
|
+
if (formula) return [formula];
|
|
44272
|
+
const expression = stripParenExpression(node);
|
|
44273
|
+
if (!isNodeOfType(expression, "LogicalExpression") || expression.operator !== "&&") return [];
|
|
44274
|
+
return [...getRequiredTruthyConditions(analysis, context, expression.left, protectedSymbolIds), ...getRequiredTruthyConditions(analysis, context, expression.right, protectedSymbolIds)];
|
|
44275
|
+
};
|
|
44276
|
+
const evaluateBooleanFormula$1 = (formula, assignments) => {
|
|
44277
|
+
if (formula.kind === "constant") return formula.constantValue ?? null;
|
|
44278
|
+
if (formula.kind === "atom") return formula.atomKey === void 0 ? null : assignments.get(formula.atomKey) ?? null;
|
|
44279
|
+
if (formula.kind === "not") {
|
|
44280
|
+
if (!formula.left) return null;
|
|
44281
|
+
const value = evaluateBooleanFormula$1(formula.left, assignments);
|
|
44282
|
+
return value === null ? null : !value;
|
|
44283
|
+
}
|
|
44284
|
+
if (!formula.left || !formula.right) return null;
|
|
44285
|
+
const leftValue = evaluateBooleanFormula$1(formula.left, assignments);
|
|
44286
|
+
const rightValue = evaluateBooleanFormula$1(formula.right, assignments);
|
|
44287
|
+
if (formula.kind === "and") {
|
|
44288
|
+
if (leftValue === false || rightValue === false) return false;
|
|
44289
|
+
return leftValue === true && rightValue === true ? true : null;
|
|
44290
|
+
}
|
|
44291
|
+
if (leftValue === true || rightValue === true) return true;
|
|
44292
|
+
return leftValue === false && rightValue === false ? false : null;
|
|
44293
|
+
};
|
|
44294
|
+
const assignBooleanFact = (facts, atomKey, value) => {
|
|
44295
|
+
const existingValue = facts.assignments.get(atomKey);
|
|
44296
|
+
if (existingValue === void 0) {
|
|
44297
|
+
facts.assignments.set(atomKey, value);
|
|
44298
|
+
facts.didChange = true;
|
|
44299
|
+
} else if (existingValue !== value) facts.didConflict = true;
|
|
44300
|
+
};
|
|
44301
|
+
const addRequiredBooleanFacts = (formula, expectedValue, facts) => {
|
|
44302
|
+
const existingValue = evaluateBooleanFormula$1(formula, facts.assignments);
|
|
44303
|
+
if (existingValue !== null) {
|
|
44304
|
+
if (existingValue !== expectedValue) facts.didConflict = true;
|
|
44305
|
+
return;
|
|
44306
|
+
}
|
|
44307
|
+
if (formula.kind === "atom" && formula.atomKey !== void 0) {
|
|
44308
|
+
assignBooleanFact(facts, formula.atomKey, expectedValue);
|
|
44309
|
+
return;
|
|
44310
|
+
}
|
|
44311
|
+
if (formula.kind === "not" && formula.left) {
|
|
44312
|
+
addRequiredBooleanFacts(formula.left, !expectedValue, facts);
|
|
44313
|
+
return;
|
|
44314
|
+
}
|
|
44315
|
+
if (!formula.left || !formula.right) return;
|
|
44316
|
+
if (formula.kind === "and") {
|
|
44317
|
+
if (expectedValue) {
|
|
44318
|
+
addRequiredBooleanFacts(formula.left, true, facts);
|
|
44319
|
+
addRequiredBooleanFacts(formula.right, true, facts);
|
|
44320
|
+
return;
|
|
44321
|
+
}
|
|
44322
|
+
const leftValue = evaluateBooleanFormula$1(formula.left, facts.assignments);
|
|
44323
|
+
const rightValue = evaluateBooleanFormula$1(formula.right, facts.assignments);
|
|
44324
|
+
if (leftValue === true) addRequiredBooleanFacts(formula.right, false, facts);
|
|
44325
|
+
if (rightValue === true) addRequiredBooleanFacts(formula.left, false, facts);
|
|
44326
|
+
return;
|
|
44327
|
+
}
|
|
44328
|
+
if (!expectedValue) {
|
|
44329
|
+
addRequiredBooleanFacts(formula.left, false, facts);
|
|
44330
|
+
addRequiredBooleanFacts(formula.right, false, facts);
|
|
44331
|
+
return;
|
|
44332
|
+
}
|
|
44333
|
+
const leftValue = evaluateBooleanFormula$1(formula.left, facts.assignments);
|
|
44334
|
+
const rightValue = evaluateBooleanFormula$1(formula.right, facts.assignments);
|
|
44335
|
+
if (leftValue === false) addRequiredBooleanFacts(formula.right, true, facts);
|
|
44336
|
+
if (rightValue === false) addRequiredBooleanFacts(formula.left, true, facts);
|
|
44337
|
+
};
|
|
44338
|
+
const doConditionsImplyFormula = (conditions, target) => {
|
|
44339
|
+
const facts = {
|
|
44340
|
+
assignments: /* @__PURE__ */ new Map(),
|
|
44341
|
+
didConflict: false,
|
|
44342
|
+
didChange: true
|
|
44343
|
+
};
|
|
44344
|
+
while (facts.didChange && !facts.didConflict) {
|
|
44345
|
+
facts.didChange = false;
|
|
44346
|
+
for (const condition of conditions) addRequiredBooleanFacts(condition, true, facts);
|
|
44347
|
+
}
|
|
44348
|
+
return facts.didConflict || evaluateBooleanFormula$1(target, facts.assignments) === true;
|
|
44349
|
+
};
|
|
44350
|
+
const getFunctionBindingSymbol = (functionNode, scopes) => {
|
|
44351
|
+
if (isNodeOfType(functionNode, "FunctionDeclaration") && functionNode.id) return scopes.symbolFor(functionNode.id);
|
|
44352
|
+
const parent = functionNode.parent;
|
|
44353
|
+
if ((isNodeOfType(functionNode, "ArrowFunctionExpression") || isNodeOfType(functionNode, "FunctionExpression")) && isNodeOfType(parent, "VariableDeclarator") && parent.init === functionNode && isNodeOfType(parent.id, "Identifier")) return scopes.symbolFor(parent.id);
|
|
44354
|
+
return null;
|
|
44355
|
+
};
|
|
44356
|
+
const getComponentFunctionNode = (containingNode) => {
|
|
44357
|
+
if (isFunctionLike$1(containingNode)) return containingNode;
|
|
44358
|
+
if (!isNodeOfType(containingNode, "VariableDeclarator") || !containingNode.init) return null;
|
|
44359
|
+
const initializer = stripParenExpression(containingNode.init);
|
|
44360
|
+
if (isFunctionLike$1(initializer)) return initializer;
|
|
44361
|
+
if (!isNodeOfType(initializer, "CallExpression")) return null;
|
|
44362
|
+
const firstArgument = initializer.arguments[0];
|
|
44363
|
+
return firstArgument && !isNodeOfType(firstArgument, "SpreadElement") && isFunctionLike$1(firstArgument) ? firstArgument : null;
|
|
44364
|
+
};
|
|
44365
|
+
const isReferenceDirectlyCalled = (identifier) => {
|
|
44366
|
+
const unwrappedIdentifier = stripParenExpression(identifier);
|
|
44367
|
+
const parent = unwrappedIdentifier.parent;
|
|
44368
|
+
return isNodeOfType(parent, "CallExpression") && parent.callee === unwrappedIdentifier ? parent : null;
|
|
44369
|
+
};
|
|
44370
|
+
const getSynchronousCallbackCall = (functionNode) => {
|
|
44371
|
+
const callExpression = functionNode.parent;
|
|
44372
|
+
if (!isNodeOfType(callExpression, "CallExpression") || !callExpression.arguments.some((argument) => argument === functionNode) || !isNodeOfType(callExpression.callee, "MemberExpression")) return null;
|
|
44373
|
+
const methodName = getStaticMemberPropertyName(callExpression.callee);
|
|
44374
|
+
return methodName && SYNCHRONOUS_ARRAY_CALLBACK_METHOD_NAMES.has(methodName) ? callExpression : null;
|
|
44375
|
+
};
|
|
44376
|
+
const isNodeEvaluatedDuringRender = (node, componentNode, scopes, visitedFunctionSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
44377
|
+
const functionNode = findEnclosingFunction$1(node);
|
|
44378
|
+
if (!functionNode) return false;
|
|
44379
|
+
if (functionNode === componentNode) return true;
|
|
44380
|
+
const synchronousCallbackCall = getSynchronousCallbackCall(functionNode);
|
|
44381
|
+
if (synchronousCallbackCall) return isNodeEvaluatedDuringRender(synchronousCallbackCall, componentNode, scopes, visitedFunctionSymbolIds);
|
|
44382
|
+
if (executesDuringRender(functionNode, scopes)) return isNodeEvaluatedDuringRender(functionNode.parent ?? functionNode, componentNode, scopes, visitedFunctionSymbolIds);
|
|
44383
|
+
const functionSymbol = getFunctionBindingSymbol(functionNode, scopes);
|
|
44384
|
+
if (!functionSymbol || visitedFunctionSymbolIds.has(functionSymbol.id)) return false;
|
|
44385
|
+
visitedFunctionSymbolIds.add(functionSymbol.id);
|
|
44386
|
+
let callCount = 0;
|
|
44387
|
+
for (const reference of functionSymbol.references) {
|
|
44388
|
+
const callExpression = isReferenceDirectlyCalled(reference.identifier);
|
|
44389
|
+
if (!callExpression) return false;
|
|
44390
|
+
if (!isNodeEvaluatedDuringRender(callExpression, componentNode, scopes, new Set(visitedFunctionSymbolIds))) return false;
|
|
44391
|
+
callCount += 1;
|
|
44392
|
+
}
|
|
44393
|
+
return callCount > 0;
|
|
44394
|
+
};
|
|
44395
|
+
const isInlineJsxCallback = (functionNode) => {
|
|
44396
|
+
let ancestor = functionNode.parent;
|
|
44397
|
+
while (ancestor) {
|
|
44398
|
+
if (isNodeOfType(ancestor, "JSXAttribute")) return true;
|
|
44399
|
+
if (isFunctionLike$1(ancestor) || isNodeOfType(ancestor, "Program")) return false;
|
|
44400
|
+
ancestor = ancestor.parent;
|
|
44401
|
+
}
|
|
44402
|
+
return false;
|
|
44403
|
+
};
|
|
44404
|
+
const collectExposureConditions = (analysis, context, node, componentNode, protectedSymbolIds) => {
|
|
44405
|
+
const conditions = [];
|
|
44406
|
+
let child = node;
|
|
44407
|
+
let parent = node.parent;
|
|
44408
|
+
while (parent) {
|
|
44409
|
+
if (isNodeOfType(parent, "LogicalExpression") && parent.right === child) {
|
|
44410
|
+
if (parent.operator === "&&") conditions.push(...getRequiredTruthyConditions(analysis, context, parent.left, protectedSymbolIds));
|
|
44411
|
+
if (parent.operator === "||") {
|
|
44412
|
+
const leftFormula = getBooleanFormula$1(analysis, context, parent.left, protectedSymbolIds);
|
|
44413
|
+
if (leftFormula) conditions.push(createNotFormula(leftFormula));
|
|
44414
|
+
}
|
|
44415
|
+
} else if (isNodeOfType(parent, "ConditionalExpression")) {
|
|
44416
|
+
const testFormula = getBooleanFormula$1(analysis, context, parent.test, protectedSymbolIds);
|
|
44417
|
+
if (testFormula && parent.consequent === child) conditions.push(testFormula);
|
|
44418
|
+
if (testFormula && parent.alternate === child) conditions.push(createNotFormula(testFormula));
|
|
44419
|
+
} else if (isNodeOfType(parent, "IfStatement")) {
|
|
44420
|
+
const testFormula = getBooleanFormula$1(analysis, context, parent.test, protectedSymbolIds);
|
|
44421
|
+
if (testFormula && parent.consequent === child) conditions.push(testFormula);
|
|
44422
|
+
if (testFormula && parent.alternate === child) conditions.push(createNotFormula(testFormula));
|
|
44423
|
+
}
|
|
44424
|
+
if (parent === componentNode) break;
|
|
44425
|
+
if (isFunctionLike$1(parent) && parent !== componentNode && !isInlineJsxCallback(parent)) {
|
|
44426
|
+
const synchronousCallbackCall = getSynchronousCallbackCall(parent);
|
|
44427
|
+
if (synchronousCallbackCall) {
|
|
44428
|
+
child = synchronousCallbackCall;
|
|
44429
|
+
parent = synchronousCallbackCall.parent;
|
|
44430
|
+
continue;
|
|
44431
|
+
}
|
|
44432
|
+
const functionSymbol = getFunctionBindingSymbol(parent, context.scopes);
|
|
44433
|
+
if (functionSymbol?.references.length === 1) {
|
|
44434
|
+
const callExpression = isReferenceDirectlyCalled(functionSymbol.references[0].identifier);
|
|
44435
|
+
if (callExpression) {
|
|
44436
|
+
child = callExpression;
|
|
44437
|
+
parent = callExpression.parent;
|
|
44438
|
+
continue;
|
|
44439
|
+
}
|
|
44440
|
+
}
|
|
44441
|
+
break;
|
|
44442
|
+
}
|
|
44443
|
+
child = parent;
|
|
44444
|
+
parent = parent.parent;
|
|
44445
|
+
}
|
|
44446
|
+
return conditions;
|
|
44447
|
+
};
|
|
43056
44448
|
const isMountSnapshotInitializer = (context, node) => {
|
|
43057
44449
|
if (!isReactApiCall(node, "useMemo", context.scopes, { resolveNamedAliases: true }) || !isNodeOfType(node, "CallExpression")) return false;
|
|
43058
44450
|
const dependencies = node.arguments?.[1];
|
|
@@ -43128,13 +44520,194 @@ const countUseStates = (analysis, componentNode) => {
|
|
|
43128
44520
|
for (const ref of getDownstreamRefs(analysis, componentNode)) if (isState(analysis, ref)) stateVariables.add(ref.resolved);
|
|
43129
44521
|
return stateVariables.size;
|
|
43130
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
|
+
});
|
|
44550
|
+
const getStateSymbolForSetter = (analysis, context, setterReference) => {
|
|
44551
|
+
const useStateDeclaration = getUseStateDecl(analysis, setterReference);
|
|
44552
|
+
if (!useStateDeclaration || !isNodeOfType(useStateDeclaration, "VariableDeclarator") || !isNodeOfType(useStateDeclaration.id, "ArrayPattern")) return null;
|
|
44553
|
+
const stateBinding = useStateDeclaration.id.elements[0];
|
|
44554
|
+
return stateBinding && isNodeOfType(stateBinding, "Identifier") ? context.scopes.symbolFor(stateBinding) : null;
|
|
44555
|
+
};
|
|
44556
|
+
const getPropertyName = (node) => {
|
|
44557
|
+
if (isNodeOfType(node, "Identifier")) return node.name;
|
|
44558
|
+
return isNodeOfType(node, "Literal") && typeof node.value === "string" ? node.value : null;
|
|
44559
|
+
};
|
|
44560
|
+
const isBooleanTypeNode = (node) => {
|
|
44561
|
+
if (!node) return false;
|
|
44562
|
+
if (isNodeOfType(node, "TSBooleanKeyword")) return true;
|
|
44563
|
+
if (!isNodeOfType(node, "TSUnionType")) return false;
|
|
44564
|
+
return node.types.every((typeNode) => isNodeOfType(typeNode, "TSBooleanKeyword") || isNodeOfType(typeNode, "TSUndefinedKeyword") || isNodeOfType(typeNode, "TSNullKeyword"));
|
|
44565
|
+
};
|
|
44566
|
+
const getBooleanPropertyType = (typeNode, propertyName, referenceNode) => {
|
|
44567
|
+
const unwrappedType = isNodeOfType(typeNode, "TSTypeAnnotation") ? typeNode.typeAnnotation : typeNode;
|
|
44568
|
+
if (isNodeOfType(unwrappedType, "TSTypeLiteral")) return unwrappedType.members.some((member) => isNodeOfType(member, "TSPropertySignature") && getPropertyName(member.key) === propertyName && isBooleanTypeNode(member.typeAnnotation?.typeAnnotation));
|
|
44569
|
+
if (!isNodeOfType(unwrappedType, "TSTypeReference") || !isNodeOfType(unwrappedType.typeName, "Identifier")) return false;
|
|
44570
|
+
const typeName = unwrappedType.typeName.name;
|
|
44571
|
+
if (hasEnclosingTypeParameterNamed(referenceNode, typeName)) return false;
|
|
44572
|
+
const programNode = findProgramNode(referenceNode);
|
|
44573
|
+
if (!isNodeOfType(programNode, "Program")) return false;
|
|
44574
|
+
const matchingInterfaces = programNode.body.flatMap((statement) => {
|
|
44575
|
+
const declaration = isNodeOfType(statement, "ExportNamedDeclaration") ? statement.declaration : statement;
|
|
44576
|
+
return isNodeOfType(declaration, "TSInterfaceDeclaration") && declaration.id.name === typeName ? [declaration] : [];
|
|
44577
|
+
});
|
|
44578
|
+
if (matchingInterfaces.length !== 1) return false;
|
|
44579
|
+
let sameNameTypeBindingCount = 0;
|
|
44580
|
+
walkAst(programNode, (candidate) => {
|
|
44581
|
+
const identifier = isNodeOfType(candidate, "TSInterfaceDeclaration") || isNodeOfType(candidate, "TSTypeAliasDeclaration") || isNodeOfType(candidate, "ClassDeclaration") || isNodeOfType(candidate, "ClassExpression") || isNodeOfType(candidate, "TSEnumDeclaration") ? candidate.id : isNodeOfType(candidate, "TSTypeParameter") ? candidate.name : null;
|
|
44582
|
+
if (isNodeOfType(identifier, "Identifier") && identifier.name === typeName) sameNameTypeBindingCount += 1;
|
|
44583
|
+
});
|
|
44584
|
+
if (sameNameTypeBindingCount !== 1) return false;
|
|
44585
|
+
return matchingInterfaces[0].body.body.some((member) => isNodeOfType(member, "TSPropertySignature") && getPropertyName(member.key) === propertyName && isBooleanTypeNode(member.typeAnnotation?.typeAnnotation));
|
|
44586
|
+
};
|
|
44587
|
+
const findProgramNode = (node) => {
|
|
44588
|
+
let currentNode = node;
|
|
44589
|
+
while (currentNode.parent) currentNode = currentNode.parent;
|
|
44590
|
+
return currentNode;
|
|
44591
|
+
};
|
|
44592
|
+
const hasBooleanBindingAnnotation = (symbol, identifier) => {
|
|
44593
|
+
const property = symbol.bindingIdentifier.parent;
|
|
44594
|
+
const objectPattern = property?.parent;
|
|
44595
|
+
if (!isNodeOfType(property, "Property") || !isNodeOfType(objectPattern, "ObjectPattern") || !objectPattern.typeAnnotation) return false;
|
|
44596
|
+
const propertyName = getPropertyName(property.key);
|
|
44597
|
+
return Boolean(propertyName && getBooleanPropertyType(objectPattern.typeAnnotation, propertyName, identifier));
|
|
44598
|
+
};
|
|
44599
|
+
const isBooleanExpression$1 = (context, node, visitedSymbolIds = /* @__PURE__ */ new Set()) => {
|
|
44600
|
+
const expression = stripParenExpression(node);
|
|
44601
|
+
if (isNodeOfType(expression, "Literal")) return typeof expression.value === "boolean";
|
|
44602
|
+
if (isNodeOfType(expression, "UnaryExpression") && expression.operator === "!") return true;
|
|
44603
|
+
if (isNodeOfType(expression, "BinaryExpression")) return [
|
|
44604
|
+
"==",
|
|
44605
|
+
"===",
|
|
44606
|
+
"!=",
|
|
44607
|
+
"!==",
|
|
44608
|
+
"<",
|
|
44609
|
+
"<=",
|
|
44610
|
+
">",
|
|
44611
|
+
">="
|
|
44612
|
+
].includes(expression.operator);
|
|
44613
|
+
if (isNodeOfType(expression, "CallExpression") && isNodeOfType(expression.callee, "Identifier") && expression.callee.name === "Boolean" && context.scopes.isGlobalReference(expression.callee)) return true;
|
|
44614
|
+
if (isNodeOfType(expression, "LogicalExpression")) return isBooleanExpression$1(context, expression.left, new Set(visitedSymbolIds)) && isBooleanExpression$1(context, expression.right, new Set(visitedSymbolIds));
|
|
44615
|
+
if (isNodeOfType(expression, "ConditionalExpression")) return isBooleanExpression$1(context, expression.consequent, new Set(visitedSymbolIds)) && isBooleanExpression$1(context, expression.alternate, new Set(visitedSymbolIds));
|
|
44616
|
+
if (!isNodeOfType(expression, "Identifier")) return false;
|
|
44617
|
+
const symbol = context.scopes.symbolFor(expression);
|
|
44618
|
+
if (!symbol || visitedSymbolIds.has(symbol.id)) return false;
|
|
44619
|
+
if (hasBooleanBindingAnnotation(symbol, expression)) return true;
|
|
44620
|
+
visitedSymbolIds.add(symbol.id);
|
|
44621
|
+
const initializer = getDirectConstInitializer(symbol);
|
|
44622
|
+
return Boolean(initializer && isBooleanExpression$1(context, initializer, visitedSymbolIds));
|
|
44623
|
+
};
|
|
44624
|
+
const getPropDerivedDependencySymbols = (analysis, context, dependencyReferences) => {
|
|
44625
|
+
const symbolsById = /* @__PURE__ */ new Map();
|
|
44626
|
+
for (const dependencyReference of dependencyReferences) {
|
|
44627
|
+
if (!getUpstreamRefs(analysis, dependencyReference).some((upstreamReference) => isProp(analysis, upstreamReference))) continue;
|
|
44628
|
+
const symbol = context.scopes.symbolFor(dependencyReference.identifier);
|
|
44629
|
+
if (symbol) symbolsById.set(symbol.id, symbol);
|
|
44630
|
+
}
|
|
44631
|
+
return [...symbolsById.values()];
|
|
44632
|
+
};
|
|
44633
|
+
const getImpliedDependencyValue = (conditions, dependencyFormulas) => {
|
|
44634
|
+
const impliesTrue = dependencyFormulas.some((dependencyFormula) => doConditionsImplyFormula(conditions, dependencyFormula));
|
|
44635
|
+
if (impliesTrue === dependencyFormulas.some((dependencyFormula) => doConditionsImplyFormula(conditions, createNotFormula(dependencyFormula)))) return null;
|
|
44636
|
+
return impliesTrue;
|
|
44637
|
+
};
|
|
44638
|
+
const getSetterExposureConditions = (analysis, context, setterReference, componentNode, protectedSymbolIds) => {
|
|
44639
|
+
const functionNode = findEnclosingFunction$1(setterReference.identifier);
|
|
44640
|
+
if (!functionNode) return null;
|
|
44641
|
+
if (isInlineJsxCallback(functionNode)) return [collectExposureConditions(analysis, context, functionNode, componentNode, protectedSymbolIds)];
|
|
44642
|
+
const functionSymbol = getFunctionBindingSymbol(functionNode, context.scopes);
|
|
44643
|
+
if (!functionSymbol || functionSymbol.references.length === 0) return null;
|
|
44644
|
+
const conditionsByReference = [];
|
|
44645
|
+
for (const reference of functionSymbol.references) {
|
|
44646
|
+
if (isReferenceDirectlyCalled(reference.identifier)) return null;
|
|
44647
|
+
let ancestor = reference.identifier.parent;
|
|
44648
|
+
while (ancestor && !isNodeOfType(ancestor, "JSXAttribute") && !isFunctionLike$1(ancestor)) ancestor = ancestor.parent;
|
|
44649
|
+
if (!isNodeOfType(ancestor, "JSXAttribute")) return null;
|
|
44650
|
+
conditionsByReference.push(collectExposureConditions(analysis, context, reference.identifier, componentNode, protectedSymbolIds));
|
|
44651
|
+
}
|
|
44652
|
+
return conditionsByReference;
|
|
44653
|
+
};
|
|
44654
|
+
const areAllSetterWritesVisibilityGuarded = (analysis, context, componentNode, resetSetterReferences, dependencySymbolIds, dependencyFormulas, visibleDependencyValue) => {
|
|
44655
|
+
const resetIdentifiers = new Set(resetSetterReferences.map((setterReference) => setterReference.identifier));
|
|
44656
|
+
const setterVariables = new Set(resetSetterReferences.map((reference) => reference.resolved));
|
|
44657
|
+
for (const setterVariable of setterVariables) {
|
|
44658
|
+
if (!setterVariable) return false;
|
|
44659
|
+
for (const setterReference of setterVariable.references) {
|
|
44660
|
+
if (setterVariable.identifiers.some((identifier) => identifier === setterReference.identifier)) continue;
|
|
44661
|
+
if (resetIdentifiers.has(setterReference.identifier)) continue;
|
|
44662
|
+
if (!getCallExpr(setterReference)) return false;
|
|
44663
|
+
const conditionsByExposure = getSetterExposureConditions(analysis, context, setterReference, componentNode, dependencySymbolIds);
|
|
44664
|
+
if (!conditionsByExposure || conditionsByExposure.some((conditions) => getImpliedDependencyValue(conditions, dependencyFormulas) !== visibleDependencyValue)) return false;
|
|
44665
|
+
}
|
|
44666
|
+
}
|
|
44667
|
+
return true;
|
|
44668
|
+
};
|
|
44669
|
+
const areAllResetStateReadsHiddenUntilReset = (analysis, context, componentNode, setterReferences, dependencyReferences) => {
|
|
44670
|
+
if (dependencyReferences.length !== 1) return false;
|
|
44671
|
+
const dependencySymbols = getPropDerivedDependencySymbols(analysis, context, dependencyReferences);
|
|
44672
|
+
if (dependencySymbols.length === 0 || dependencySymbols.some((symbol) => !isBooleanExpression$1(context, symbol.bindingIdentifier) || symbol.references.some((reference) => reference.flag !== "read"))) return false;
|
|
44673
|
+
const dependencySymbolIds = new Set(dependencySymbols.map((symbol) => symbol.id));
|
|
44674
|
+
const dependencyFormulas = dependencySymbols.map((symbol) => ({
|
|
44675
|
+
kind: "atom",
|
|
44676
|
+
atomKey: `symbol:${symbol.id}`
|
|
44677
|
+
}));
|
|
44678
|
+
const resetStateSymbolsById = /* @__PURE__ */ new Map();
|
|
44679
|
+
for (const setterReference of setterReferences) {
|
|
44680
|
+
const stateSymbol = getStateSymbolForSetter(analysis, context, setterReference);
|
|
44681
|
+
if (!stateSymbol) return false;
|
|
44682
|
+
resetStateSymbolsById.set(stateSymbol.id, stateSymbol);
|
|
44683
|
+
}
|
|
44684
|
+
let visibleDependencyValue = null;
|
|
44685
|
+
for (const stateSymbol of resetStateSymbolsById.values()) {
|
|
44686
|
+
let exposedReadCount = 0;
|
|
44687
|
+
for (const reference of stateSymbol.references) {
|
|
44688
|
+
if (reference.flag === "write") continue;
|
|
44689
|
+
const isRenderRead = isNodeEvaluatedDuringRender(reference.identifier, componentNode, context.scopes);
|
|
44690
|
+
const functionNode = findEnclosingFunction$1(reference.identifier);
|
|
44691
|
+
if (!isRenderRead && functionNode && !isInlineJsxCallback(functionNode)) return false;
|
|
44692
|
+
const referenceDependencyValue = getImpliedDependencyValue(collectExposureConditions(analysis, context, reference.identifier, componentNode, dependencySymbolIds), dependencyFormulas);
|
|
44693
|
+
if (referenceDependencyValue === null) return false;
|
|
44694
|
+
if (visibleDependencyValue !== null && visibleDependencyValue !== referenceDependencyValue) return false;
|
|
44695
|
+
visibleDependencyValue = referenceDependencyValue;
|
|
44696
|
+
exposedReadCount += 1;
|
|
44697
|
+
}
|
|
44698
|
+
if (exposedReadCount === 0) return false;
|
|
44699
|
+
}
|
|
44700
|
+
return Boolean(resetStateSymbolsById.size > 0 && visibleDependencyValue !== null && areAllSetterWritesVisibilityGuarded(analysis, context, componentNode, setterReferences, dependencySymbolIds, dependencyFormulas, visibleDependencyValue));
|
|
44701
|
+
};
|
|
43131
44702
|
const findPropUsedToResetAllState = (analysis, context, effectFnRefs, depsRefs, useEffectNode, effectFn) => {
|
|
43132
44703
|
const stateSetterRefs = effectFnRefs.filter((ref) => isSyncStateSetterCall(analysis, ref, effectFn));
|
|
43133
44704
|
if (stateSetterRefs.length === 0) return null;
|
|
43134
44705
|
if (!stateSetterRefs.every((ref) => isSetStateToInitialValue(analysis, context, ref))) return null;
|
|
43135
|
-
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;
|
|
43136
44707
|
const containing = findContainingNode(analysis, useEffectNode);
|
|
43137
44708
|
if (new Set(stateSetterRefs.map((setterRef) => setterRef.resolved)).size !== countUseStates(analysis, containing)) return null;
|
|
44709
|
+
const componentFunctionNode = containing ? getComponentFunctionNode(containing) : null;
|
|
44710
|
+
if (componentFunctionNode && areAllResetStateReadsHiddenUntilReset(analysis, context, componentFunctionNode, stateSetterRefs, depsRefs)) return null;
|
|
43138
44711
|
for (const depRef of depsRefs) for (const upRef of getUpstreamRefs(analysis, depRef)) if (isProp(analysis, upRef)) return upRef;
|
|
43139
44712
|
return null;
|
|
43140
44713
|
};
|
|
@@ -44827,9 +46400,13 @@ const UNCONTROLLED_INPUT_TAGS = new Set([
|
|
|
44827
46400
|
"select"
|
|
44828
46401
|
]);
|
|
44829
46402
|
const VALUE_BYPASS_INPUT_TYPES = new Set([
|
|
44830
|
-
"
|
|
46403
|
+
"button",
|
|
44831
46404
|
"checkbox",
|
|
44832
|
-
"
|
|
46405
|
+
"hidden",
|
|
46406
|
+
"image",
|
|
46407
|
+
"radio",
|
|
46408
|
+
"reset",
|
|
46409
|
+
"submit"
|
|
44833
46410
|
]);
|
|
44834
46411
|
const VALUE_PARTNER_ATTRIBUTES = [
|
|
44835
46412
|
"onChange",
|
|
@@ -44838,12 +46415,6 @@ const VALUE_PARTNER_ATTRIBUTES = [
|
|
|
44838
46415
|
"disabled"
|
|
44839
46416
|
];
|
|
44840
46417
|
const isLiteralFalseAttributeValue = (attribute) => isNodeOfType(attribute.value, "JSXExpressionContainer") && isNodeOfType(attribute.value.expression, "Literal") && attribute.value.expression.value === false;
|
|
44841
|
-
const getInputTypeLiteral = (attributes) => {
|
|
44842
|
-
const typeAttribute = findJsxAttribute(attributes, "type");
|
|
44843
|
-
if (!typeAttribute || !isNodeOfType(typeAttribute.value, "Literal")) return null;
|
|
44844
|
-
const value = typeAttribute.value.value;
|
|
44845
|
-
return typeof value === "string" ? value : null;
|
|
44846
|
-
};
|
|
44847
46418
|
const isUseStateUndefinedInitializer = (init) => {
|
|
44848
46419
|
if (!init || !isNodeOfType(init, "CallExpression")) return false;
|
|
44849
46420
|
if (!isHookCall$2(init, "useState")) return false;
|
|
@@ -44886,9 +46457,18 @@ const noUncontrolledInput = defineRule({
|
|
|
44886
46457
|
if (hasJsxSpreadAttribute(attributes)) return;
|
|
44887
46458
|
const valueAttribute = findJsxAttribute(attributes, "value");
|
|
44888
46459
|
if (!valueAttribute) return;
|
|
46460
|
+
let inputTypeCandidates = null;
|
|
46461
|
+
let hasExplicitInputType = false;
|
|
46462
|
+
let doesTypeBypassMissingOnChange = false;
|
|
46463
|
+
let doesTypeUseCheckedControlledness = false;
|
|
46464
|
+
let couldTypeUseCheckedControlledness = false;
|
|
44889
46465
|
if (tagName === "input") {
|
|
44890
|
-
const
|
|
44891
|
-
|
|
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")));
|
|
44892
46472
|
}
|
|
44893
46473
|
const hasAllowedPartner = VALUE_PARTNER_ATTRIBUTES.some((partnerAttributeName) => {
|
|
44894
46474
|
const partnerAttribute = findJsxAttribute(attributes, partnerAttributeName);
|
|
@@ -44896,12 +46476,12 @@ const noUncontrolledInput = defineRule({
|
|
|
44896
46476
|
if (partnerAttributeName === "disabled" && isLiteralFalseAttributeValue(partnerAttribute)) return false;
|
|
44897
46477
|
return true;
|
|
44898
46478
|
});
|
|
44899
|
-
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) {
|
|
44900
46480
|
const stateName = valueAttribute.value.expression.name;
|
|
44901
|
-
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)";
|
|
44902
46482
|
context.report({
|
|
44903
46483
|
node: child,
|
|
44904
|
-
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("")\`).`
|
|
44905
46485
|
});
|
|
44906
46486
|
return;
|
|
44907
46487
|
}
|
|
@@ -44912,10 +46492,13 @@ const noUncontrolledInput = defineRule({
|
|
|
44912
46492
|
});
|
|
44913
46493
|
return;
|
|
44914
46494
|
}
|
|
44915
|
-
if (!hasAllowedPartner)
|
|
44916
|
-
|
|
44917
|
-
|
|
44918
|
-
|
|
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
|
+
}
|
|
44919
46502
|
});
|
|
44920
46503
|
};
|
|
44921
46504
|
return {
|
|
@@ -48268,7 +49851,8 @@ const onlyExportComponents = defineRule({
|
|
|
48268
49851
|
const declaration = child.declaration;
|
|
48269
49852
|
if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id) {
|
|
48270
49853
|
isExportedNodeIds.add(declaration);
|
|
48271
|
-
|
|
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 : {
|
|
48272
49856
|
kind: "non-component",
|
|
48273
49857
|
reportNode: declaration.id
|
|
48274
49858
|
});
|
|
@@ -51228,14 +52812,6 @@ const collectPluginEntries = (rawNode, scopes, visitedSymbolIds) => {
|
|
|
51228
52812
|
}
|
|
51229
52813
|
return entries;
|
|
51230
52814
|
};
|
|
51231
|
-
const findEffectiveExplicitAttribute = (attributes, attributeName) => {
|
|
51232
|
-
for (let attributeIndex = attributes.length - 1; attributeIndex >= 0; attributeIndex -= 1) {
|
|
51233
|
-
const attribute = attributes[attributeIndex];
|
|
51234
|
-
if (!attribute || isNodeOfType(attribute, "JSXSpreadAttribute")) return null;
|
|
51235
|
-
if (isNodeOfType(attribute, "JSXAttribute") && isNodeOfType(attribute.name, "JSXIdentifier") && attribute.name.name === attributeName) return attribute;
|
|
51236
|
-
}
|
|
51237
|
-
return null;
|
|
51238
|
-
};
|
|
51239
52815
|
const getAttributeExpression = (attribute) => {
|
|
51240
52816
|
if (!isNodeOfType(attribute.value, "JSXExpressionContainer")) return null;
|
|
51241
52817
|
return isNodeOfType(attribute.value.expression, "JSXEmptyExpression") ? null : attribute.value.expression;
|
|
@@ -51275,7 +52851,7 @@ const hasDynamicUnsanitizedChildren = (openingElement, scopes) => {
|
|
|
51275
52851
|
if (isNodeOfType(child.expression, "JSXEmptyExpression")) return false;
|
|
51276
52852
|
return !isStaticOrSanitizedMarkdownExpression(child.expression, scopes, /* @__PURE__ */ new Set());
|
|
51277
52853
|
});
|
|
51278
|
-
const childrenAttribute =
|
|
52854
|
+
const childrenAttribute = getAuthoritativeJsxAttribute(openingElement.attributes, "children");
|
|
51279
52855
|
if (!childrenAttribute) return false;
|
|
51280
52856
|
const childrenExpression = getAttributeExpression(childrenAttribute);
|
|
51281
52857
|
return Boolean(childrenExpression && !isStaticOrSanitizedMarkdownExpression(childrenExpression, scopes, /* @__PURE__ */ new Set()));
|
|
@@ -51287,7 +52863,7 @@ const reactMarkdownUnsanitizedRawHtml = defineRule({
|
|
|
51287
52863
|
recommendation: "Add `rehype-sanitize` to `rehypePlugins` or sanitize dynamic markdown before rendering. `skipHtml` does not disable HTML already parsed by `rehype-raw`.",
|
|
51288
52864
|
create: skipNonProductionFiles((context) => ({ JSXOpeningElement(node) {
|
|
51289
52865
|
if (!isReactMarkdownComponent(node.name, context.scopes)) return;
|
|
51290
|
-
const pluginsAttribute =
|
|
52866
|
+
const pluginsAttribute = getAuthoritativeJsxAttribute(node.attributes, "rehypePlugins");
|
|
51291
52867
|
if (!pluginsAttribute) return;
|
|
51292
52868
|
const pluginsExpression = getAttributeExpression(pluginsAttribute);
|
|
51293
52869
|
if (!pluginsExpression) return;
|
|
@@ -54722,6 +56298,22 @@ const isLikelyNumericExpression = (node) => {
|
|
|
54722
56298
|
if (isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && isNumericName(node.property.name)) return true;
|
|
54723
56299
|
return false;
|
|
54724
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
|
+
};
|
|
54725
56317
|
const rnNoFalsyAndRender = defineRule({
|
|
54726
56318
|
id: "rn-no-falsy-and-render",
|
|
54727
56319
|
title: "Numeric && renders bare zero",
|
|
@@ -54740,6 +56332,7 @@ const rnNoFalsyAndRender = defineRule({
|
|
|
54740
56332
|
if (!(isNodeOfType(node.right, "JSXElement") || isNodeOfType(node.right, "JSXFragment"))) return;
|
|
54741
56333
|
const parent = node.parent;
|
|
54742
56334
|
if (!(isNodeOfType(parent, "JSXExpressionContainer") || isNodeOfType(parent, "LogicalExpression") && parent.operator === "&&")) return;
|
|
56335
|
+
if (isRenderedInsideIntrinsicDomHost(node, context.scopes)) return;
|
|
54743
56336
|
const left = node.left;
|
|
54744
56337
|
if (!left) return;
|
|
54745
56338
|
if (!isLikelyNumericExpression(left)) return;
|
|
@@ -59772,7 +61365,7 @@ const roleSupportsAriaProps = defineRule({
|
|
|
59772
61365
|
if (!ariaAttributes) return;
|
|
59773
61366
|
const elementType = getElementType(node, context.settings);
|
|
59774
61367
|
const roleAttribute = hasJsxPropIgnoreCase(node.attributes, "role");
|
|
59775
|
-
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);
|
|
59776
61369
|
if (roleCandidates === null || roleCandidates.length === 0) return;
|
|
59777
61370
|
const supportedSets = [];
|
|
59778
61371
|
for (const role of roleCandidates) {
|
|
@@ -61430,29 +63023,19 @@ const serverNoMutableModuleState = defineRule({
|
|
|
61430
63023
|
});
|
|
61431
63024
|
//#endregion
|
|
61432
63025
|
//#region src/plugin/rules/server/server-sequential-independent-await.ts
|
|
61433
|
-
const collectDeclaredNames = (declaration) => {
|
|
61434
|
-
const names = /* @__PURE__ */ new Set();
|
|
61435
|
-
if (!isNodeOfType(declaration, "VariableDeclaration")) return names;
|
|
61436
|
-
for (const declarator of declaration.declarations ?? []) collectPatternNames(declarator.id, names);
|
|
61437
|
-
return names;
|
|
61438
|
-
};
|
|
61439
63026
|
const declarationStartsWithAwait = (declaration) => {
|
|
61440
63027
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
61441
63028
|
for (const declarator of declaration.declarations ?? []) if (isNodeOfType(declarator.init, "AwaitExpression")) return true;
|
|
61442
63029
|
return false;
|
|
61443
63030
|
};
|
|
61444
|
-
const
|
|
61445
|
-
if (
|
|
63031
|
+
const declarationReadsAnyPatternBinding = (declaration, patterns, context) => {
|
|
63032
|
+
if (patterns.length === 0) return false;
|
|
61446
63033
|
if (!isNodeOfType(declaration, "VariableDeclaration")) return false;
|
|
61447
|
-
let didRead = false;
|
|
61448
63034
|
for (const declarator of declaration.declarations ?? []) {
|
|
61449
63035
|
if (!declarator.init) continue;
|
|
61450
|
-
|
|
61451
|
-
if (didRead) return;
|
|
61452
|
-
if (isNodeOfType(child, "Identifier") && names.has(child.name)) didRead = true;
|
|
61453
|
-
});
|
|
63036
|
+
if (expressionReadsPatternBinding(declarator.init, patterns, context.scopes)) return true;
|
|
61454
63037
|
}
|
|
61455
|
-
return
|
|
63038
|
+
return false;
|
|
61456
63039
|
};
|
|
61457
63040
|
const GATE_LEADING_VERBS = new Set([
|
|
61458
63041
|
"require",
|
|
@@ -61526,11 +63109,11 @@ const serverSequentialIndependentAwait = defineRule({
|
|
|
61526
63109
|
const currentStatement = statements[statementIndex];
|
|
61527
63110
|
if (!isNodeOfType(currentStatement, "VariableDeclaration")) continue;
|
|
61528
63111
|
if (!declarationStartsWithAwait(currentStatement)) continue;
|
|
61529
|
-
const
|
|
63112
|
+
const declaredPatterns = currentStatement.declarations.map((declarator) => declarator.id);
|
|
61530
63113
|
const nextStatement = statements[statementIndex + 1];
|
|
61531
63114
|
if (!isNodeOfType(nextStatement, "VariableDeclaration")) continue;
|
|
61532
63115
|
if (!declarationStartsWithAwait(nextStatement)) continue;
|
|
61533
|
-
if (
|
|
63116
|
+
if (declarationReadsAnyPatternBinding(nextStatement, declaredPatterns, context)) continue;
|
|
61534
63117
|
if (declarationAwaitsExistingPromise(nextStatement)) continue;
|
|
61535
63118
|
if (declarationAwaitsRequestScopedCall(currentStatement) || declarationAwaitsRequestScopedCall(nextStatement)) continue;
|
|
61536
63119
|
if (declarationAwaitsGate(currentStatement, context)) continue;
|
|
@@ -62551,7 +64134,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
62551
64134
|
return false;
|
|
62552
64135
|
};
|
|
62553
64136
|
const isWiredAsEventHandler = (functionNode) => {
|
|
62554
|
-
const bindingIdentifier = getFunctionBindingIdentifier(functionNode);
|
|
64137
|
+
const bindingIdentifier = getFunctionBindingIdentifier$1(functionNode);
|
|
62555
64138
|
if (!bindingIdentifier) return false;
|
|
62556
64139
|
const bindingSymbol = context.scopes.symbolFor(bindingIdentifier);
|
|
62557
64140
|
if (!bindingSymbol) return false;
|
|
@@ -62571,7 +64154,7 @@ const tanstackStartNoNavigateInRender = defineRule({
|
|
|
62571
64154
|
return false;
|
|
62572
64155
|
};
|
|
62573
64156
|
const isSynchronouslyInvokedAnonymousWrapper = (functionNode) => {
|
|
62574
|
-
if (getFunctionBindingIdentifier(functionNode)) return false;
|
|
64157
|
+
if (getFunctionBindingIdentifier$1(functionNode)) return false;
|
|
62575
64158
|
const parent = functionNode.parent;
|
|
62576
64159
|
if (!isNodeOfType(parent, "CallExpression")) return false;
|
|
62577
64160
|
return parent.callee === functionNode || (parent.arguments ?? []).some((callArgument) => callArgument === functionNode);
|
|
@@ -68503,6 +70086,7 @@ const CROSS_FILE_RULE_IDS = new Set([
|
|
|
68503
70086
|
"no-indeterminate-attribute",
|
|
68504
70087
|
"no-locale-format-in-render",
|
|
68505
70088
|
"no-match-media-in-state-initializer",
|
|
70089
|
+
"no-create-ref-in-function-component",
|
|
68506
70090
|
"no-adjust-state-on-prop-change",
|
|
68507
70091
|
"no-derived-state",
|
|
68508
70092
|
"no-derived-state-effect",
|
|
@@ -68607,6 +70191,18 @@ const collectForwardedHookDependencies = ({ absoluteFilePath, staticImports }) =
|
|
|
68607
70191
|
collectProgramDependencies(resolved.filePath, resolved.programNode, 4);
|
|
68608
70192
|
}
|
|
68609
70193
|
};
|
|
70194
|
+
const collectCreateRefDependencies = ({ absoluteFilePath, program }) => {
|
|
70195
|
+
attachParentReferences(program);
|
|
70196
|
+
const scopes = analyzeScopes(program);
|
|
70197
|
+
walkAst(program, (node) => {
|
|
70198
|
+
if (!isNodeOfType(node, "CallExpression") || !isReactApiCall(node, "createRef", scopes, {
|
|
70199
|
+
allowGlobalReactNamespace: true,
|
|
70200
|
+
allowUnboundBareCalls: true,
|
|
70201
|
+
resolveNamedAliases: true
|
|
70202
|
+
})) return;
|
|
70203
|
+
isCreateRefResultWriteOnly(node, absoluteFilePath, scopes);
|
|
70204
|
+
});
|
|
70205
|
+
};
|
|
68610
70206
|
const collectMutatingReducerDependencies = ({ absoluteFilePath, sourceText, staticImports, program }) => {
|
|
68611
70207
|
const namedUseReducerLocals = /* @__PURE__ */ new Set();
|
|
68612
70208
|
const reactObjectLocals = /* @__PURE__ */ new Set();
|
|
@@ -68695,6 +70291,7 @@ const CROSS_FILE_DEPENDENCY_COLLECTORS = new Map([
|
|
|
68695
70291
|
["no-indeterminate-attribute", collectNearestManifestDependencies],
|
|
68696
70292
|
["no-locale-format-in-render", collectNearestManifestDependencies],
|
|
68697
70293
|
["no-match-media-in-state-initializer", collectNearestManifestDependencies],
|
|
70294
|
+
["no-create-ref-in-function-component", collectCreateRefDependencies],
|
|
68698
70295
|
["no-adjust-state-on-prop-change", collectEffectValueHelperDependencies],
|
|
68699
70296
|
["no-derived-state", collectEffectValueHelperDependencies],
|
|
68700
70297
|
["no-derived-state-effect", collectEffectValueHelperDependencies],
|