oxlint-plugin-react-doctor 0.2.0-beta.3 → 0.2.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +2595 -1266
  3. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -47,6 +47,25 @@ const MUTATING_HTTP_METHODS = new Set([
47
47
  "DELETE",
48
48
  "PATCH"
49
49
  ]);
50
+ const SAFE_MUTABLE_CONSTRUCTOR_NAMES = new Set([
51
+ "Map",
52
+ "Set",
53
+ "WeakMap",
54
+ "WeakSet",
55
+ "Headers",
56
+ "URLSearchParams",
57
+ "FormData",
58
+ "Response",
59
+ "NextResponse"
60
+ ]);
61
+ const RESPONSE_FACTORY_OBJECTS = new Set(["Response", "NextResponse"]);
62
+ const RESPONSE_FACTORY_METHODS = new Set([
63
+ "json",
64
+ "redirect",
65
+ "next",
66
+ "rewrite",
67
+ "error"
68
+ ]);
50
69
  //#endregion
51
70
  //#region src/plugin/constants/dom.ts
52
71
  const PASSIVE_EVENT_NAMES = new Set([
@@ -472,22 +491,238 @@ const asyncAwaitInLoop = defineRule({
472
491
  }
473
492
  });
474
493
  //#endregion
475
- //#region src/plugin/rules/performance/async-defer-await.ts
476
- const collectIdentifierNames = (node, into) => {
494
+ //#region src/plugin/utils/collect-pattern-names.ts
495
+ const collectPatternNames = (pattern, into) => {
496
+ if (!pattern) return;
497
+ if (isNodeOfType(pattern, "Identifier")) {
498
+ into.add(pattern.name);
499
+ return;
500
+ }
501
+ if (isNodeOfType(pattern, "AssignmentPattern")) {
502
+ collectPatternNames(pattern.left, into);
503
+ return;
504
+ }
505
+ if (isNodeOfType(pattern, "RestElement")) {
506
+ collectPatternNames(pattern.argument, into);
507
+ return;
508
+ }
509
+ if (isNodeOfType(pattern, "ArrayPattern")) {
510
+ for (const element of pattern.elements ?? []) collectPatternNames(element, into);
511
+ return;
512
+ }
513
+ if (isNodeOfType(pattern, "ObjectPattern")) for (const property of pattern.properties ?? []) {
514
+ if (isNodeOfType(property, "RestElement")) {
515
+ collectPatternNames(property.argument, into);
516
+ continue;
517
+ }
518
+ if (isNodeOfType(property, "Property")) collectPatternNames(property.value, into);
519
+ }
520
+ };
521
+ //#endregion
522
+ //#region src/plugin/utils/collect-reference-identifier-names.ts
523
+ const TYPE_POSITION_KEYS = new Set([
524
+ "typeAnnotation",
525
+ "typeParameters",
526
+ "typeArguments",
527
+ "returnType",
528
+ "superTypeArguments",
529
+ "superTypeParameters"
530
+ ]);
531
+ const collectScopedReferencesInPattern = (pattern, into, shadowed) => {
532
+ if (!pattern) return;
533
+ if (isNodeOfType(pattern, "Identifier")) return;
534
+ if (isNodeOfType(pattern, "AssignmentPattern")) {
535
+ collectScopedReferenceIdentifierNames(pattern.right, into, shadowed);
536
+ collectScopedReferencesInPattern(pattern.left, into, shadowed);
537
+ return;
538
+ }
539
+ if (isNodeOfType(pattern, "RestElement")) {
540
+ collectScopedReferencesInPattern(pattern.argument, into, shadowed);
541
+ return;
542
+ }
543
+ if (isNodeOfType(pattern, "ArrayPattern")) {
544
+ for (const element of pattern.elements ?? []) collectScopedReferencesInPattern(element, into, shadowed);
545
+ return;
546
+ }
547
+ if (isNodeOfType(pattern, "ObjectPattern")) for (const property of pattern.properties ?? []) {
548
+ if (isNodeOfType(property, "RestElement")) {
549
+ collectScopedReferencesInPattern(property.argument, into, shadowed);
550
+ continue;
551
+ }
552
+ if (!isNodeOfType(property, "Property")) continue;
553
+ if (property.computed) collectScopedReferenceIdentifierNames(property.key, into, shadowed);
554
+ collectScopedReferencesInPattern(property.value, into, shadowed);
555
+ }
556
+ };
557
+ const collectFromFunction = (functionNode, into, shadowed) => {
558
+ if (!isNodeOfType(functionNode, "FunctionDeclaration") && !isNodeOfType(functionNode, "FunctionExpression") && !isNodeOfType(functionNode, "ArrowFunctionExpression")) return;
559
+ const innerShadowed = new Set(shadowed);
560
+ for (const param of functionNode.params ?? []) collectPatternNames(param, innerShadowed);
561
+ for (const param of functionNode.params ?? []) collectScopedReferencesInPattern(param, into, innerShadowed);
562
+ collectScopedReferenceIdentifierNames(functionNode.body, into, innerShadowed);
563
+ };
564
+ const collectScopedReferenceIdentifierNames = (node, into, shadowed) => {
477
565
  if (!node) return;
566
+ if (isNodeOfType(node, "Identifier")) {
567
+ if (!shadowed.has(node.name)) into.add(node.name);
568
+ return;
569
+ }
570
+ if (isNodeOfType(node, "MemberExpression")) {
571
+ collectScopedReferenceIdentifierNames(node.object, into, shadowed);
572
+ if (node.computed) collectScopedReferenceIdentifierNames(node.property, into, shadowed);
573
+ return;
574
+ }
575
+ if (isNodeOfType(node, "Property")) {
576
+ if (node.computed) collectScopedReferenceIdentifierNames(node.key, into, shadowed);
577
+ collectScopedReferenceIdentifierNames(node.value, into, shadowed);
578
+ return;
579
+ }
580
+ if (isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression")) {
581
+ collectFromFunction(node, into, shadowed);
582
+ return;
583
+ }
584
+ if (isNodeOfType(node, "TSAsExpression") || isNodeOfType(node, "TSSatisfiesExpression") || isNodeOfType(node, "TSTypeAssertion") || isNodeOfType(node, "TSNonNullExpression") || isNodeOfType(node, "TSInstantiationExpression")) {
585
+ collectScopedReferenceIdentifierNames(node.expression, into, shadowed);
586
+ return;
587
+ }
588
+ if (typeof node.type === "string" && node.type.startsWith("TS")) return;
589
+ for (const [key, child] of Object.entries(node)) {
590
+ if (key === "parent") continue;
591
+ if (TYPE_POSITION_KEYS.has(key)) continue;
592
+ if (Array.isArray(child)) {
593
+ for (const item of child) if (isAstNode(item)) collectScopedReferenceIdentifierNames(item, into, shadowed);
594
+ } else if (isAstNode(child)) collectScopedReferenceIdentifierNames(child, into, shadowed);
595
+ }
596
+ };
597
+ const collectReferenceIdentifierNames = (node, into) => collectScopedReferenceIdentifierNames(node, into, /* @__PURE__ */ new Set());
598
+ //#endregion
599
+ //#region src/plugin/utils/collect-pattern-default-reference-names.ts
600
+ const collectPatternDefaultReferenceNames = (pattern, into) => {
601
+ if (!pattern) return;
602
+ if (isNodeOfType(pattern, "AssignmentPattern")) {
603
+ collectReferenceIdentifierNames(pattern.right, into);
604
+ collectPatternDefaultReferenceNames(pattern.left, into);
605
+ return;
606
+ }
607
+ if (isNodeOfType(pattern, "RestElement")) {
608
+ collectPatternDefaultReferenceNames(pattern.argument, into);
609
+ return;
610
+ }
611
+ if (isNodeOfType(pattern, "ArrayPattern")) {
612
+ for (const element of pattern.elements ?? []) collectPatternDefaultReferenceNames(element, into);
613
+ return;
614
+ }
615
+ if (isNodeOfType(pattern, "ObjectPattern")) {
616
+ for (const property of pattern.properties ?? []) if (isNodeOfType(property, "RestElement")) collectPatternDefaultReferenceNames(property.argument, into);
617
+ else if (isNodeOfType(property, "Property")) {
618
+ if (property.computed) collectReferenceIdentifierNames(property.key, into);
619
+ collectPatternDefaultReferenceNames(property.value, into);
620
+ }
621
+ }
622
+ };
623
+ //#endregion
624
+ //#region src/plugin/utils/contains-direct-await.ts
625
+ const containsDirectAwait = (node) => {
626
+ if (!node) return false;
627
+ let foundAwait = false;
478
628
  walkAst(node, (child) => {
479
- if (isNodeOfType(child, "Identifier")) into.add(child.name);
629
+ if (foundAwait) return false;
630
+ if (isNodeOfType(child, "FunctionDeclaration") || isNodeOfType(child, "FunctionExpression") || isNodeOfType(child, "ArrowFunctionExpression")) return false;
631
+ if (isNodeOfType(child, "AwaitExpression")) {
632
+ foundAwait = true;
633
+ return false;
634
+ }
480
635
  });
636
+ return foundAwait;
637
+ };
638
+ //#endregion
639
+ //#region src/plugin/utils/is-bare-await-expression-statement.ts
640
+ const isBareAwaitExpressionStatement = (statement) => {
641
+ if (!isNodeOfType(statement, "ExpressionStatement")) return false;
642
+ return isNodeOfType(statement.expression, "AwaitExpression");
481
643
  };
482
- const isEarlyReturnIfStatement = (statement) => {
644
+ //#endregion
645
+ //#region src/plugin/utils/is-early-exit-if-statement.ts
646
+ const isEarlyExitStatement = (statement) => isNodeOfType(statement, "ReturnStatement") || isNodeOfType(statement, "ThrowStatement") || isNodeOfType(statement, "ContinueStatement") || isNodeOfType(statement, "BreakStatement");
647
+ const isEarlyExitIfStatement = (statement) => {
483
648
  if (!isNodeOfType(statement, "IfStatement")) return false;
484
649
  const consequent = statement.consequent;
485
650
  if (!consequent) return false;
486
- if (isNodeOfType(consequent, "ReturnStatement")) return true;
651
+ if (isEarlyExitStatement(consequent)) return true;
487
652
  if (!isNodeOfType(consequent, "BlockStatement")) return false;
488
- for (const inner of consequent.body ?? []) if (isNodeOfType(inner, "ReturnStatement")) return true;
653
+ for (const inner of consequent.body ?? []) if (isEarlyExitStatement(inner)) return true;
654
+ return false;
655
+ };
656
+ //#endregion
657
+ //#region src/plugin/rules/performance/async-defer-await.ts
658
+ const hasAnyIdentifierName = (identifierNames, candidateNames) => {
659
+ for (const candidateName of candidateNames) if (identifierNames.has(candidateName)) return true;
489
660
  return false;
490
661
  };
662
+ const collectDeclaratorDependencyIdentifierNames = (declarator, into) => {
663
+ if (!isNodeOfType(declarator, "VariableDeclarator")) return;
664
+ collectReferenceIdentifierNames(declarator.init, into);
665
+ collectPatternDefaultReferenceNames(declarator.id, into);
666
+ };
667
+ const processVariableDeclaration = (declaration, awaitedBindingNames) => {
668
+ if (!isNodeOfType(declaration, "VariableDeclaration")) return {
669
+ didIntroduceAwait: false,
670
+ didGrowBindings: false
671
+ };
672
+ let didIntroduceAwait = false;
673
+ const sizeBeforeAll = awaitedBindingNames.size;
674
+ let hasChanged = true;
675
+ while (hasChanged) {
676
+ hasChanged = false;
677
+ for (const declarator of declaration.declarations ?? []) {
678
+ if (!isNodeOfType(declarator, "VariableDeclarator")) continue;
679
+ if (containsDirectAwait(declarator.init) || containsDirectAwait(declarator.id)) {
680
+ didIntroduceAwait = true;
681
+ const sizeBefore = awaitedBindingNames.size;
682
+ collectPatternNames(declarator.id, awaitedBindingNames);
683
+ if (awaitedBindingNames.size > sizeBefore) hasChanged = true;
684
+ continue;
685
+ }
686
+ const dependencyIdentifiers = /* @__PURE__ */ new Set();
687
+ collectDeclaratorDependencyIdentifierNames(declarator, dependencyIdentifiers);
688
+ if (!hasAnyIdentifierName(dependencyIdentifiers, awaitedBindingNames)) continue;
689
+ const sizeBefore = awaitedBindingNames.size;
690
+ collectPatternNames(declarator.id, awaitedBindingNames);
691
+ if (awaitedBindingNames.size > sizeBefore) hasChanged = true;
692
+ }
693
+ }
694
+ const didGrowBindings = awaitedBindingNames.size > sizeBeforeAll;
695
+ return {
696
+ didIntroduceAwait,
697
+ didGrowBindings
698
+ };
699
+ };
700
+ const collectAwaitWindow = (statements, startIndex) => {
701
+ const firstStatement = statements[startIndex];
702
+ const awaitedBindingNames = /* @__PURE__ */ new Set();
703
+ let isAwaitingStatement = false;
704
+ if (isNodeOfType(firstStatement, "VariableDeclaration")) {
705
+ if (processVariableDeclaration(firstStatement, awaitedBindingNames).didIntroduceAwait) isAwaitingStatement = true;
706
+ } else if (isBareAwaitExpressionStatement(firstStatement)) isAwaitingStatement = true;
707
+ if (!isAwaitingStatement) return null;
708
+ let cursor = startIndex + 1;
709
+ while (cursor < statements.length) {
710
+ const candidate = statements[cursor];
711
+ if (isBareAwaitExpressionStatement(candidate)) {
712
+ cursor++;
713
+ continue;
714
+ }
715
+ if (!isNodeOfType(candidate, "VariableDeclaration")) break;
716
+ const result = processVariableDeclaration(candidate, awaitedBindingNames);
717
+ if (!result.didIntroduceAwait && !result.didGrowBindings) break;
718
+ cursor++;
719
+ }
720
+ return {
721
+ firstAwaitStatement: firstStatement,
722
+ awaitedBindingNames,
723
+ guardCandidateIndex: cursor
724
+ };
725
+ };
491
726
  const asyncDeferAwait = defineRule({
492
727
  id: "async-defer-await",
493
728
  severity: "warn",
@@ -495,38 +730,44 @@ const asyncDeferAwait = defineRule({
495
730
  create: (context) => {
496
731
  const inspectStatements = (statements) => {
497
732
  for (let statementIndex = 0; statementIndex < statements.length - 1; statementIndex++) {
498
- const currentStatement = statements[statementIndex];
499
- if (!isNodeOfType(currentStatement, "VariableDeclaration")) continue;
500
- const awaitedBindingNames = /* @__PURE__ */ new Set();
501
- let didAwait = false;
502
- for (const declarator of currentStatement.declarations ?? []) if (isNodeOfType(declarator.init, "AwaitExpression")) {
503
- didAwait = true;
504
- if (isNodeOfType(declarator.id, "Identifier")) awaitedBindingNames.add(declarator.id.name);
505
- else if (isNodeOfType(declarator.id, "ObjectPattern")) {
506
- for (const property of declarator.id.properties ?? []) if (isNodeOfType(property, "Property") && isNodeOfType(property.value, "Identifier")) awaitedBindingNames.add(property.value.name);
507
- }
733
+ const window = collectAwaitWindow(statements, statementIndex);
734
+ if (!window) continue;
735
+ if (window.guardCandidateIndex >= statements.length) continue;
736
+ const guardStatement = statements[window.guardCandidateIndex];
737
+ if (!isEarlyExitIfStatement(guardStatement)) continue;
738
+ if (!isNodeOfType(guardStatement, "IfStatement")) continue;
739
+ const testIdentifierNames = /* @__PURE__ */ new Set();
740
+ collectReferenceIdentifierNames(guardStatement.test, testIdentifierNames);
741
+ if (hasAnyIdentifierName(testIdentifierNames, window.awaitedBindingNames)) {
742
+ statementIndex = window.guardCandidateIndex - 1;
743
+ continue;
744
+ }
745
+ const consequentIdentifierNames = /* @__PURE__ */ new Set();
746
+ collectReferenceIdentifierNames(guardStatement.consequent, consequentIdentifierNames);
747
+ if (hasAnyIdentifierName(consequentIdentifierNames, window.awaitedBindingNames)) {
748
+ statementIndex = window.guardCandidateIndex - 1;
749
+ continue;
508
750
  }
509
- if (!didAwait) continue;
510
- const nextStatement = statements[statementIndex + 1];
511
- if (!isEarlyReturnIfStatement(nextStatement)) continue;
512
- if (!isNodeOfType(nextStatement, "IfStatement")) continue;
513
- const testIdentifiers = /* @__PURE__ */ new Set();
514
- collectIdentifierNames(nextStatement.test, testIdentifiers);
515
- if ([...awaitedBindingNames].some((name) => testIdentifiers.has(name))) continue;
516
- const consequentIdentifiers = /* @__PURE__ */ new Set();
517
- collectIdentifierNames(nextStatement.consequent, consequentIdentifiers);
518
- if ([...awaitedBindingNames].some((name) => consequentIdentifiers.has(name))) continue;
519
751
  context.report({
520
- node: currentStatement,
752
+ node: window.firstAwaitStatement,
521
753
  message: "await blocks the function before an early-return that doesn't use the awaited value — move the await after the synchronous guard so the skip path stays fast"
522
754
  });
755
+ statementIndex = window.guardCandidateIndex - 1;
523
756
  }
524
757
  };
758
+ const inspectAllStatementBlocks = (functionBody) => {
759
+ if (!functionBody) return;
760
+ walkAst(functionBody, (descendant) => {
761
+ if (isNodeOfType(descendant, "FunctionDeclaration") || isNodeOfType(descendant, "FunctionExpression") || isNodeOfType(descendant, "ArrowFunctionExpression")) return false;
762
+ if (isNodeOfType(descendant, "BlockStatement")) inspectStatements(descendant.body ?? []);
763
+ else if (isNodeOfType(descendant, "SwitchCase")) inspectStatements(descendant.consequent ?? []);
764
+ });
765
+ };
525
766
  const enterFunction = (node) => {
526
767
  if (!isNodeOfType(node, "FunctionDeclaration") && !isNodeOfType(node, "FunctionExpression") && !isNodeOfType(node, "ArrowFunctionExpression")) return;
527
768
  if (!node.async) return;
528
769
  if (!isNodeOfType(node.body, "BlockStatement")) return;
529
- inspectStatements(node.body.body ?? []);
770
+ inspectAllStatementBlocks(node.body);
530
771
  };
531
772
  return {
532
773
  FunctionDeclaration: enterFunction,
@@ -575,9 +816,221 @@ const CHAINABLE_ITERATION_METHODS = new Set([
575
816
  "forEach",
576
817
  "flatMap"
577
818
  ]);
578
- const TEST_FILE_PATTERN = /\.(?:test|spec|stories)\.[tj]sx?$/;
819
+ const ITERATOR_PRODUCING_METHOD_NAMES = new Set([
820
+ "values",
821
+ "keys",
822
+ "entries"
823
+ ]);
824
+ const BROWSER_TEST_FILE_PATTERN = /\.browser\.[cm]?[jt]sx?$/;
825
+ const TEST_LIBRARY_IMPORT_SOURCES = new Set([
826
+ "vitest",
827
+ "jest",
828
+ "mocha",
829
+ "chai",
830
+ "sinon",
831
+ "expect",
832
+ "ava",
833
+ "uvu",
834
+ "node:test",
835
+ "bun:test",
836
+ "@testing-library/react",
837
+ "@testing-library/react-native",
838
+ "@testing-library/react-hooks",
839
+ "@testing-library/dom",
840
+ "@testing-library/user-event",
841
+ "@testing-library/jest-dom",
842
+ "@testing-library/vue",
843
+ "@testing-library/svelte",
844
+ "@testing-library/preact",
845
+ "@testing-library/cypress",
846
+ "playwright",
847
+ "playwright-core",
848
+ "@playwright/test",
849
+ "@playwright/experimental-ct-react",
850
+ "@playwright/experimental-ct-react17",
851
+ "cypress",
852
+ "@cypress/react",
853
+ "@cypress/react18",
854
+ "@storybook/test",
855
+ "@storybook/test-runner",
856
+ "@storybook/testing-library",
857
+ "@storybook/jest",
858
+ "puppeteer",
859
+ "puppeteer-core",
860
+ "webdriverio",
861
+ "@wdio/globals",
862
+ "@nuxt/test-utils"
863
+ ]);
864
+ const TEST_LIBRARY_IMPORT_SOURCE_PREFIXES = [
865
+ "vitest/",
866
+ "@vitest/",
867
+ "@jest/",
868
+ "@testing-library/",
869
+ "@playwright/",
870
+ "@storybook/test/",
871
+ "@storybook/test-runner/",
872
+ "@storybook/testing-library/",
873
+ "@cypress/",
874
+ "@nuxt/test-utils/"
875
+ ];
876
+ const ORDERED_UI_FLOW_CALLEE_NAMES = new Set([
877
+ "render",
878
+ "rerender",
879
+ "renderHook",
880
+ "renderToString",
881
+ "renderToStaticMarkup",
882
+ "act",
883
+ "click",
884
+ "dblClick",
885
+ "dblclick",
886
+ "tripleClick",
887
+ "tap",
888
+ "press",
889
+ "longPress",
890
+ "type",
891
+ "clear",
892
+ "fill",
893
+ "focus",
894
+ "blur",
895
+ "hover",
896
+ "unhover",
897
+ "check",
898
+ "uncheck",
899
+ "selectOption",
900
+ "selectOptions",
901
+ "setChecked",
902
+ "setInputFiles",
903
+ "scrollIntoViewIfNeeded",
904
+ "dragTo",
905
+ "dragAndDrop",
906
+ "drop",
907
+ "evaluate",
908
+ "evaluateHandle",
909
+ "waitFor",
910
+ "waitForLoadState",
911
+ "waitForSelector",
912
+ "waitForURL",
913
+ "waitForResponse",
914
+ "waitForRequest",
915
+ "waitForEvent",
916
+ "waitForFunction",
917
+ "waitForElementToBeRemoved",
918
+ "goto",
919
+ "goBack",
920
+ "goForward",
921
+ "reload",
922
+ "screenshot",
923
+ "snapshot",
924
+ "toMatchSnapshot",
925
+ "toMatchInlineSnapshot",
926
+ "expect",
927
+ "expectTypeOf",
928
+ "step",
929
+ "describe",
930
+ "test",
931
+ "it",
932
+ "beforeAll",
933
+ "beforeEach",
934
+ "afterAll",
935
+ "afterEach",
936
+ "play",
937
+ "userEvent",
938
+ "screen",
939
+ "within"
940
+ ]);
941
+ const ORDERED_UI_FLOW_CALLEE_PREFIXES = ["findBy", "findAllBy"];
942
+ const INTENTIONAL_SEQUENCING_CALLEE_NAMES = new Set([
943
+ "sleep",
944
+ "delay",
945
+ "wait",
946
+ "pause",
947
+ "throttle",
948
+ "debounce",
949
+ "tick",
950
+ "nextTick",
951
+ "advanceTimersByTime",
952
+ "advanceTimersByTimeAsync",
953
+ "runAllTimers",
954
+ "runAllTimersAsync",
955
+ "runOnlyPendingTimers",
956
+ "runOnlyPendingTimersAsync",
957
+ "setTimeout",
958
+ "setInterval",
959
+ "requestAnimationFrame",
960
+ "requestIdleCallback",
961
+ "animate",
962
+ "transition",
963
+ "spring",
964
+ "tween",
965
+ "stagger",
966
+ "sequence",
967
+ "timeline",
968
+ "scrub"
969
+ ]);
970
+ //#endregion
971
+ //#region src/plugin/utils/get-callee-identifier-trail.ts
972
+ const getCalleeIdentifierTrail = (call) => {
973
+ let entry = call;
974
+ while (isNodeOfType(entry, "ChainExpression")) entry = entry.expression;
975
+ if (!isNodeOfType(entry, "CallExpression") && !isNodeOfType(entry, "NewExpression")) return [];
976
+ const trail = [];
977
+ let cursor = entry.callee;
978
+ while (cursor) {
979
+ if (isNodeOfType(cursor, "ChainExpression")) {
980
+ cursor = cursor.expression;
981
+ continue;
982
+ }
983
+ if (isNodeOfType(cursor, "MemberExpression")) {
984
+ if (isNodeOfType(cursor.property, "Identifier")) trail.push(cursor.property.name);
985
+ cursor = cursor.object;
986
+ continue;
987
+ }
988
+ if (isNodeOfType(cursor, "CallExpression")) {
989
+ cursor = cursor.callee;
990
+ continue;
991
+ }
992
+ if (isNodeOfType(cursor, "Identifier")) trail.push(cursor.name);
993
+ break;
994
+ }
995
+ return trail;
996
+ };
997
+ //#endregion
998
+ //#region src/plugin/utils/is-test-library-import-source.ts
999
+ const isTestLibraryImportSource = (source) => {
1000
+ if (typeof source !== "string" || source.length === 0) return false;
1001
+ if (TEST_LIBRARY_IMPORT_SOURCES.has(source)) return true;
1002
+ return TEST_LIBRARY_IMPORT_SOURCE_PREFIXES.some((prefix) => source.startsWith(prefix));
1003
+ };
579
1004
  //#endregion
580
1005
  //#region src/plugin/rules/js-performance/async-parallel.ts
1006
+ const getAwaitedCall = (statement) => {
1007
+ if (isNodeOfType(statement, "VariableDeclaration")) {
1008
+ const declarator = statement.declarations?.[0];
1009
+ if (declarator && isNodeOfType(declarator.init, "AwaitExpression")) return declarator.init.argument ?? null;
1010
+ }
1011
+ if (isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "AwaitExpression")) return statement.expression.argument ?? null;
1012
+ return null;
1013
+ };
1014
+ const isOrderedUiFlowName = (name) => {
1015
+ if (ORDERED_UI_FLOW_CALLEE_NAMES.has(name)) return true;
1016
+ return ORDERED_UI_FLOW_CALLEE_PREFIXES.some((prefix) => name.startsWith(prefix));
1017
+ };
1018
+ const isOrderedUiFlowAwait = (awaitedCall) => {
1019
+ if (!awaitedCall) return false;
1020
+ return getCalleeIdentifierTrail(awaitedCall).some(isOrderedUiFlowName);
1021
+ };
1022
+ const isIntentionalSequencingAwait = (awaitedCall) => {
1023
+ if (!awaitedCall) return false;
1024
+ return getCalleeIdentifierTrail(awaitedCall).some((name) => INTENTIONAL_SEQUENCING_CALLEE_NAMES.has(name));
1025
+ };
1026
+ const sequenceContainsSerializationSignal = (statements) => {
1027
+ for (const statement of statements) {
1028
+ const awaitedCall = getAwaitedCall(statement);
1029
+ if (isOrderedUiFlowAwait(awaitedCall)) return true;
1030
+ if (isIntentionalSequencingAwait(awaitedCall)) return true;
1031
+ }
1032
+ return false;
1033
+ };
581
1034
  const reportIfIndependent = (statements, context) => {
582
1035
  const declaredNames = /* @__PURE__ */ new Set();
583
1036
  for (const statement of statements) {
@@ -599,22 +1052,33 @@ const reportIfIndependent = (statements, context) => {
599
1052
  };
600
1053
  const asyncParallel = defineRule({
601
1054
  id: "async-parallel",
1055
+ tags: ["test-noise"],
602
1056
  severity: "warn",
603
1057
  recommendation: "Use `const [a, b] = await Promise.all([fetchA(), fetchB()])` to run independent operations concurrently",
604
1058
  create: (context) => {
605
1059
  const filename = context.getFilename?.() ?? "";
606
- const isTestFile = TEST_FILE_PATTERN.test(filename);
607
- return { BlockStatement(node) {
608
- if (isTestFile) return;
609
- const consecutiveAwaitStatements = [];
610
- const flushConsecutiveAwaits = () => {
611
- if (consecutiveAwaitStatements.length >= 3) reportIfIndependent(consecutiveAwaitStatements, context);
612
- consecutiveAwaitStatements.length = 0;
613
- };
614
- for (const statement of node.body ?? []) if (isNodeOfType(statement, "VariableDeclaration") && statement.declarations?.length === 1 && isNodeOfType(statement.declarations[0].init, "AwaitExpression") || isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "AwaitExpression")) consecutiveAwaitStatements.push(statement);
615
- else flushConsecutiveAwaits();
616
- flushConsecutiveAwaits();
617
- } };
1060
+ const isBrowserTestFile = BROWSER_TEST_FILE_PATTERN.test(filename);
1061
+ let hasTestLibraryImport = false;
1062
+ const shouldSkipFile = () => isBrowserTestFile || hasTestLibraryImport;
1063
+ return {
1064
+ ImportDeclaration(node) {
1065
+ if (hasTestLibraryImport) return;
1066
+ if (isTestLibraryImportSource(node.source?.value)) hasTestLibraryImport = true;
1067
+ },
1068
+ BlockStatement(node) {
1069
+ if (shouldSkipFile()) return;
1070
+ const consecutiveAwaitStatements = [];
1071
+ const flushConsecutiveAwaits = () => {
1072
+ if (consecutiveAwaitStatements.length >= 3) {
1073
+ if (!sequenceContainsSerializationSignal(consecutiveAwaitStatements)) reportIfIndependent(consecutiveAwaitStatements, context);
1074
+ }
1075
+ consecutiveAwaitStatements.length = 0;
1076
+ };
1077
+ for (const statement of node.body ?? []) if (isNodeOfType(statement, "VariableDeclaration") && statement.declarations?.length === 1 && isNodeOfType(statement.declarations[0].init, "AwaitExpression") || isNodeOfType(statement, "ExpressionStatement") && isNodeOfType(statement.expression, "AwaitExpression")) consecutiveAwaitStatements.push(statement);
1078
+ else flushConsecutiveAwaits();
1079
+ flushConsecutiveAwaits();
1080
+ }
1081
+ };
618
1082
  }
619
1083
  });
620
1084
  //#endregion
@@ -1323,27 +1787,80 @@ const jsCacheStorage = defineRule({
1323
1787
  });
1324
1788
  //#endregion
1325
1789
  //#region src/plugin/rules/js-performance/js-combine-iterations.ts
1790
+ const isIteratorProducingCall = (callExpression, generatorNamesInFile) => {
1791
+ const callee = callExpression.callee;
1792
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.object, "Identifier") && callee.object.name === "Iterator" && isNodeOfType(callee.property, "Identifier") && callee.property.name === "from") return true;
1793
+ if (isNodeOfType(callee, "Identifier") && generatorNamesInFile.has(callee.name)) return true;
1794
+ if (isNodeOfType(callee, "MemberExpression") && isNodeOfType(callee.property, "Identifier") && ITERATOR_PRODUCING_METHOD_NAMES.has(callee.property.name)) {
1795
+ const receiver = callee.object;
1796
+ if (isNodeOfType(receiver, "Identifier") && receiver.name === "Object") return false;
1797
+ return true;
1798
+ }
1799
+ return false;
1800
+ };
1801
+ const isChainPassThroughCall = (callExpression) => {
1802
+ const callee = callExpression.callee;
1803
+ if (!isNodeOfType(callee, "MemberExpression")) return false;
1804
+ if (!isNodeOfType(callee.property, "Identifier")) return false;
1805
+ return CHAINABLE_ITERATION_METHODS.has(callee.property.name);
1806
+ };
1807
+ const isReceiverChainIteratorRooted = (receiverNode, generatorNamesInFile) => {
1808
+ let cursor = receiverNode;
1809
+ while (cursor) {
1810
+ if (isNodeOfType(cursor, "ChainExpression")) {
1811
+ cursor = cursor.expression;
1812
+ continue;
1813
+ }
1814
+ if (!isNodeOfType(cursor, "CallExpression")) return false;
1815
+ if (isIteratorProducingCall(cursor, generatorNamesInFile)) return true;
1816
+ if (!isChainPassThroughCall(cursor)) return false;
1817
+ const nextCallee = cursor.callee;
1818
+ if (!isNodeOfType(nextCallee, "MemberExpression")) return false;
1819
+ cursor = nextCallee.object;
1820
+ }
1821
+ return false;
1822
+ };
1823
+ const collectGeneratorNames = (programNode) => {
1824
+ const generatorNames = /* @__PURE__ */ new Set();
1825
+ walkAst(programNode, (child) => {
1826
+ if (isNodeOfType(child, "FunctionDeclaration") && child.generator === true && isNodeOfType(child.id, "Identifier")) {
1827
+ generatorNames.add(child.id.name);
1828
+ return;
1829
+ }
1830
+ if (isNodeOfType(child, "VariableDeclarator") && isNodeOfType(child.id, "Identifier") && isNodeOfType(child.init, "FunctionExpression") && child.init.generator === true) generatorNames.add(child.id.name);
1831
+ });
1832
+ return generatorNames;
1833
+ };
1326
1834
  const jsCombineIterations = defineRule({
1327
1835
  id: "js-combine-iterations",
1328
1836
  severity: "warn",
1329
1837
  recommendation: "Combine `.map().filter()` (or similar chains) into a single pass with `.reduce()` or a `for...of` loop to avoid iterating the array twice",
1330
- create: (context) => ({ CallExpression(node) {
1331
- if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
1332
- const outerMethod = node.callee.property.name;
1333
- if (!CHAINABLE_ITERATION_METHODS.has(outerMethod)) return;
1334
- const innerCall = node.callee.object;
1335
- if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
1336
- const innerMethod = innerCall.callee.property.name;
1337
- if (!CHAINABLE_ITERATION_METHODS.has(innerMethod)) return;
1338
- if (innerMethod === "map" && outerMethod === "filter") {
1339
- const filterArgument = node.arguments?.[0];
1340
- if (isNodeOfType(filterArgument, "Identifier") && filterArgument.name === "Boolean" || isNodeOfType(filterArgument, "ArrowFunctionExpression") && filterArgument.params?.length === 1 && isNodeOfType(filterArgument.body, "Identifier") && isNodeOfType(filterArgument.params[0], "Identifier") && filterArgument.body.name === filterArgument.params[0].name) return;
1341
- }
1342
- context.report({
1343
- node,
1344
- message: `.${innerMethod}().${outerMethod}() iterates the array twice combine into a single loop with .reduce() or for...of`
1345
- });
1346
- } })
1838
+ create: (context) => {
1839
+ let generatorNamesInFile = /* @__PURE__ */ new Set();
1840
+ return {
1841
+ Program(programNode) {
1842
+ generatorNamesInFile = collectGeneratorNames(programNode);
1843
+ },
1844
+ CallExpression(node) {
1845
+ if (!isNodeOfType(node.callee, "MemberExpression") || !isNodeOfType(node.callee.property, "Identifier")) return;
1846
+ const outerMethod = node.callee.property.name;
1847
+ if (!CHAINABLE_ITERATION_METHODS.has(outerMethod)) return;
1848
+ const innerCall = node.callee.object;
1849
+ if (!isNodeOfType(innerCall, "CallExpression") || !isNodeOfType(innerCall.callee, "MemberExpression") || !isNodeOfType(innerCall.callee.property, "Identifier")) return;
1850
+ const innerMethod = innerCall.callee.property.name;
1851
+ if (!CHAINABLE_ITERATION_METHODS.has(innerMethod)) return;
1852
+ if (innerMethod === "map" && outerMethod === "filter") {
1853
+ const filterArgument = node.arguments?.[0];
1854
+ if (isNodeOfType(filterArgument, "Identifier") && filterArgument.name === "Boolean" || isNodeOfType(filterArgument, "ArrowFunctionExpression") && filterArgument.params?.length === 1 && isNodeOfType(filterArgument.body, "Identifier") && isNodeOfType(filterArgument.params[0], "Identifier") && filterArgument.body.name === filterArgument.params[0].name) return;
1855
+ }
1856
+ if (isReceiverChainIteratorRooted(innerCall.callee.object, generatorNamesInFile)) return;
1857
+ context.report({
1858
+ node,
1859
+ message: `.${innerMethod}().${outerMethod}() iterates the array twice — combine into a single loop with .reduce() or for...of`
1860
+ });
1861
+ }
1862
+ };
1863
+ }
1347
1864
  });
1348
1865
  //#endregion
1349
1866
  //#region src/plugin/rules/js-performance/js-early-exit.ts
@@ -1479,7 +1996,80 @@ const jsIndexMaps = defineRule({
1479
1996
  } })
1480
1997
  });
1481
1998
  //#endregion
1999
+ //#region src/plugin/utils/are-expressions-structurally-equal.ts
2000
+ const areExpressionsStructurallyEqual = (a, b) => {
2001
+ if (!a || !b) return a === b;
2002
+ if (a.type !== b.type) return false;
2003
+ if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
2004
+ if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
2005
+ if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
2006
+ if (a.computed !== b.computed) return false;
2007
+ return areExpressionsStructurallyEqual(a.object, b.object) && areExpressionsStructurallyEqual(a.property, b.property);
2008
+ }
2009
+ if (isNodeOfType(a, "CallExpression") && isNodeOfType(b, "CallExpression")) {
2010
+ if (!areExpressionsStructurallyEqual(a.callee, b.callee)) return false;
2011
+ const argumentsA = a.arguments ?? [];
2012
+ const argumentsB = b.arguments ?? [];
2013
+ if (argumentsA.length !== argumentsB.length) return false;
2014
+ return argumentsA.every((argument, index) => areExpressionsStructurallyEqual(argument, argumentsB[index]));
2015
+ }
2016
+ return false;
2017
+ };
2018
+ //#endregion
2019
+ //#region src/plugin/utils/flatten-logical-and-chain.ts
2020
+ const flattenLogicalAndChain = (node) => {
2021
+ if (isNodeOfType(node, "LogicalExpression") && node.operator === "&&") return [...flattenLogicalAndChain(node.left), ...flattenLogicalAndChain(node.right)];
2022
+ return [node];
2023
+ };
2024
+ //#endregion
2025
+ //#region src/plugin/utils/collect-earlier-and-guard-operands.ts
2026
+ const collectEarlierAndGuardOperands = (node) => {
2027
+ const earlierOperands = [];
2028
+ let currentNode = node;
2029
+ let parentNode = currentNode.parent ?? null;
2030
+ while (parentNode) {
2031
+ if (isNodeOfType(parentNode, "LogicalExpression")) {
2032
+ if (parentNode.operator === "&&" && parentNode.right === currentNode) earlierOperands.push(...flattenLogicalAndChain(parentNode.left));
2033
+ currentNode = parentNode;
2034
+ parentNode = currentNode.parent ?? null;
2035
+ continue;
2036
+ }
2037
+ if (isNodeOfType(parentNode, "ChainExpression")) {
2038
+ currentNode = parentNode;
2039
+ parentNode = currentNode.parent ?? null;
2040
+ continue;
2041
+ }
2042
+ break;
2043
+ }
2044
+ return earlierOperands;
2045
+ };
2046
+ //#endregion
1482
2047
  //#region src/plugin/rules/js-performance/js-length-check-first.ts
2048
+ const findIndexedArrayObject = (callbackBody, indexParameterName) => {
2049
+ let indexedArrayObject = null;
2050
+ walkAst(callbackBody, (child) => {
2051
+ if (indexedArrayObject) return;
2052
+ if (isNodeOfType(child, "MemberExpression") && child.computed && isNodeOfType(child.property, "Identifier") && child.property.name === indexParameterName) indexedArrayObject = child.object;
2053
+ });
2054
+ return indexedArrayObject;
2055
+ };
2056
+ const unwrapChainExpression$1 = (node) => isNodeOfType(node, "ChainExpression") ? node.expression : node;
2057
+ const isMatchingLengthEqualityGuard = (guardOperand, receiverArray, indexedArray) => {
2058
+ const binaryGuard = unwrapChainExpression$1(guardOperand);
2059
+ if (!isNodeOfType(binaryGuard, "BinaryExpression")) return false;
2060
+ if (binaryGuard.operator !== "===" && binaryGuard.operator !== "==") return false;
2061
+ const leftSide = unwrapChainExpression$1(binaryGuard.left);
2062
+ const rightSide = unwrapChainExpression$1(binaryGuard.right);
2063
+ if (!isMemberProperty(leftSide, "length")) return false;
2064
+ if (!isMemberProperty(rightSide, "length")) return false;
2065
+ const leftLengthObject = unwrapChainExpression$1(leftSide.object);
2066
+ const rightLengthObject = unwrapChainExpression$1(rightSide.object);
2067
+ const normalizedReceiver = unwrapChainExpression$1(receiverArray);
2068
+ const normalizedIndexed = unwrapChainExpression$1(indexedArray);
2069
+ const matchesReceiverThenIndexed = areExpressionsStructurallyEqual(leftLengthObject, normalizedReceiver) && areExpressionsStructurallyEqual(rightLengthObject, normalizedIndexed);
2070
+ const matchesIndexedThenReceiver = areExpressionsStructurallyEqual(leftLengthObject, normalizedIndexed) && areExpressionsStructurallyEqual(rightLengthObject, normalizedReceiver);
2071
+ return matchesReceiverThenIndexed || matchesIndexedThenReceiver;
2072
+ };
1483
2073
  const jsLengthCheckFirst = defineRule({
1484
2074
  id: "js-length-check-first",
1485
2075
  severity: "warn",
@@ -1490,20 +2080,14 @@ const jsLengthCheckFirst = defineRule({
1490
2080
  if (node.callee.property.name !== "every") return;
1491
2081
  const callback = node.arguments?.[0];
1492
2082
  if (!isNodeOfType(callback, "ArrowFunctionExpression") && !isNodeOfType(callback, "FunctionExpression")) return;
1493
- const params = callback.params ?? [];
1494
- if (params.length < 2) return;
1495
- let referencesOtherArrayByIndex = false;
1496
- walkAst(callback.body, (child) => {
1497
- if (referencesOtherArrayByIndex) return;
1498
- if (isNodeOfType(child, "MemberExpression") && child.computed && isNodeOfType(child.property, "Identifier") && isNodeOfType(params[1], "Identifier") && child.property.name === params[1].name) referencesOtherArrayByIndex = true;
1499
- });
1500
- if (!referencesOtherArrayByIndex) return;
1501
- let guard = node.parent ?? null;
1502
- while (guard && !isNodeOfType(guard, "LogicalExpression") && !isNodeOfType(guard, "IfStatement")) guard = guard.parent ?? null;
1503
- if (isNodeOfType(guard, "LogicalExpression") && guard.operator === "&&") {
1504
- const left = guard.left;
1505
- if (isNodeOfType(left, "BinaryExpression") && left.operator === "===" && (isMemberProperty(left.left, "length") || isMemberProperty(left.right, "length"))) return;
1506
- }
2083
+ const callbackParameters = callback.params ?? [];
2084
+ if (callbackParameters.length < 2) return;
2085
+ const indexParameter = callbackParameters[1];
2086
+ if (!isNodeOfType(indexParameter, "Identifier")) return;
2087
+ const indexedArrayObject = findIndexedArrayObject(callback.body, indexParameter.name);
2088
+ if (!indexedArrayObject) return;
2089
+ const receiverArrayObject = node.callee.object;
2090
+ if (collectEarlierAndGuardOperands(node).some((guardOperand) => isMatchingLengthEqualityGuard(guardOperand, receiverArrayObject, indexedArrayObject))) return;
1507
2091
  context.report({
1508
2092
  node,
1509
2093
  message: ".every() over an array compared to another array — short-circuit with `a.length === b.length && a.every(...)` so unequal-length arrays exit immediately"
@@ -1795,6 +2379,7 @@ const GOOGLE_FONTS_PATTERN = /fonts\.googleapis\.com/;
1795
2379
  const POLYFILL_SCRIPT_PATTERN = /polyfill\.io|polyfill\.min\.js|cdn\.polyfill/;
1796
2380
  const APP_DIRECTORY_PATTERN = /\/app\//;
1797
2381
  const ROUTE_HANDLER_FILE_PATTERN = /\/route\.(tsx?|jsx?)$/;
2382
+ const CRON_ROUTE_PATTERN = /\/(?:cron|jobs\/cron)(?:\/|$)/i;
1798
2383
  const MUTATING_ROUTE_SEGMENTS = new Set([
1799
2384
  "logout",
1800
2385
  "log-out",
@@ -2069,19 +2654,116 @@ const nextjsNoRedirectInTryCatch = defineRule({
2069
2654
  }
2070
2655
  });
2071
2656
  //#endregion
2657
+ //#region src/plugin/utils/is-cookies-call.ts
2658
+ const isCookiesCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "cookies";
2659
+ //#endregion
2660
+ //#region src/plugin/utils/is-cookies-or-awaited-cookies-call.ts
2661
+ const isCookiesOrAwaitedCookiesCall = (node) => {
2662
+ if (isCookiesCall(node)) return true;
2663
+ if (isNodeOfType(node, "AwaitExpression") && node.argument) return isCookiesCall(node.argument);
2664
+ return false;
2665
+ };
2666
+ //#endregion
2667
+ //#region src/plugin/utils/collect-locally-scoped-cookie-bindings.ts
2668
+ const collectLocallyScopedCookieBindings = (handlerBody) => {
2669
+ const cookieBindingNames = /* @__PURE__ */ new Set();
2670
+ walkInsideStatementBlocks(handlerBody, (node) => {
2671
+ if (!isNodeOfType(node, "VariableDeclarator")) return;
2672
+ if (!node.init) return;
2673
+ if (!isCookiesOrAwaitedCookiesCall(node.init)) return;
2674
+ collectPatternNames(node.id, cookieBindingNames);
2675
+ });
2676
+ return cookieBindingNames;
2677
+ };
2678
+ //#endregion
2679
+ //#region src/plugin/utils/is-safe-mutable-receiver-source.ts
2680
+ const SAFE_INTRINSIC_PROPERTY_NAMES = new Set(["headers", "searchParams"]);
2681
+ const unwrapAwait = (node) => isNodeOfType(node, "AwaitExpression") && node.argument ? node.argument : node;
2682
+ const isSafeConstructorNew = (node) => isNodeOfType(node, "NewExpression") && isNodeOfType(node.callee, "Identifier") && SAFE_MUTABLE_CONSTRUCTOR_NAMES.has(node.callee.name);
2683
+ const isResponseFactoryCall = (node) => {
2684
+ if (!isNodeOfType(node, "CallExpression")) return false;
2685
+ if (!isNodeOfType(node.callee, "MemberExpression")) return false;
2686
+ if (!isNodeOfType(node.callee.object, "Identifier")) return false;
2687
+ if (!isNodeOfType(node.callee.property, "Identifier")) return false;
2688
+ return RESPONSE_FACTORY_OBJECTS.has(node.callee.object.name) && RESPONSE_FACTORY_METHODS.has(node.callee.property.name);
2689
+ };
2690
+ const isHeadersFunctionCall = (node) => isNodeOfType(node, "CallExpression") && isNodeOfType(node.callee, "Identifier") && node.callee.name === "headers";
2691
+ const isSafeIntrinsicMemberAccess = (node) => isNodeOfType(node, "MemberExpression") && isNodeOfType(node.property, "Identifier") && SAFE_INTRINSIC_PROPERTY_NAMES.has(node.property.name);
2692
+ const isSafeMutableReceiverSource = (initNode) => {
2693
+ const unwrapped = unwrapAwait(initNode);
2694
+ if (isSafeConstructorNew(unwrapped)) return true;
2695
+ if (isResponseFactoryCall(unwrapped)) return true;
2696
+ if (isSafeIntrinsicMemberAccess(unwrapped)) return true;
2697
+ if (isHeadersFunctionCall(unwrapped)) return true;
2698
+ return false;
2699
+ };
2700
+ const isSafeReceiverChainNode = (node, locallyScopedSafeBindings) => {
2701
+ if (isSafeConstructorNew(node)) return true;
2702
+ if (isResponseFactoryCall(node)) return true;
2703
+ if (isSafeIntrinsicMemberAccess(node)) return true;
2704
+ if (isHeadersFunctionCall(node)) return true;
2705
+ if (isNodeOfType(node, "Identifier") && locallyScopedSafeBindings.has(node.name)) return true;
2706
+ return false;
2707
+ };
2708
+ //#endregion
2709
+ //#region src/plugin/utils/collect-locally-scoped-safe-bindings.ts
2710
+ const collectLocallyScopedSafeBindings = (handlerBody) => {
2711
+ const safeBindingNames = /* @__PURE__ */ new Set();
2712
+ walkInsideStatementBlocks(handlerBody, (node) => {
2713
+ if (!isNodeOfType(node, "VariableDeclarator")) return;
2714
+ if (!node.init) return;
2715
+ if (!isSafeMutableReceiverSource(node.init)) return;
2716
+ collectPatternNames(node.id, safeBindingNames);
2717
+ });
2718
+ return safeBindingNames;
2719
+ };
2720
+ //#endregion
2721
+ //#region src/plugin/utils/is-safe-receiver-chain.ts
2722
+ const isSafeReceiverChain = (receiverNode, locallyScopedSafeBindings) => {
2723
+ let current = receiverNode;
2724
+ while (current) {
2725
+ if (isSafeReceiverChainNode(current, locallyScopedSafeBindings)) return true;
2726
+ if (isNodeOfType(current, "MemberExpression")) {
2727
+ current = current.object;
2728
+ continue;
2729
+ }
2730
+ if (isNodeOfType(current, "ChainExpression")) {
2731
+ current = current.expression;
2732
+ continue;
2733
+ }
2734
+ if (isNodeOfType(current, "AwaitExpression")) {
2735
+ current = current.argument;
2736
+ continue;
2737
+ }
2738
+ if (isNodeOfType(current, "TSNonNullExpression") || isNodeOfType(current, "TSAsExpression")) {
2739
+ current = current.expression;
2740
+ continue;
2741
+ }
2742
+ return false;
2743
+ }
2744
+ return false;
2745
+ };
2746
+ //#endregion
2072
2747
  //#region src/plugin/utils/find-side-effect.ts
2748
+ const EMPTY_BINDING_SET = /* @__PURE__ */ new Set();
2749
+ const COOKIE_MUTATION_METHOD_NAMES = new Set([
2750
+ "set",
2751
+ "append",
2752
+ "delete"
2753
+ ]);
2073
2754
  const isMutatingMethodProperty = (property) => isNodeOfType(property, "Property") && isNodeOfType(property.key, "Identifier") && property.key.name === "method" && isNodeOfType(property.value, "Literal") && typeof property.value.value === "string" && MUTATING_HTTP_METHODS.has(property.value.value.toUpperCase());
2074
- const isCookiesOrHeadersCall = (node, methodName) => {
2075
- if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "MemberExpression")) return false;
2076
- const { object, property } = node.callee;
2077
- if (!isNodeOfType(property, "Identifier") || !MUTATION_METHOD_NAMES.has(property.name)) return false;
2078
- if (!isNodeOfType(object, "CallExpression") || !isNodeOfType(object.callee, "Identifier")) return false;
2079
- return object.callee.name === methodName;
2755
+ const isCookieReceiver = (receiverNode, locallyScopedCookieBindings) => {
2756
+ if (isCookiesOrAwaitedCookiesCall(receiverNode)) return true;
2757
+ if (isNodeOfType(receiverNode, "Identifier")) return locallyScopedCookieBindings.has(receiverNode.name);
2758
+ return false;
2080
2759
  };
2081
- const isMutatingDbCall = (node) => {
2082
- if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "MemberExpression")) return false;
2083
- const { property } = node.callee;
2084
- return isNodeOfType(property, "Identifier") && MUTATION_METHOD_NAMES.has(property.name);
2760
+ const getCookieMutationMethodName = (node, locallyScopedCookieBindings) => {
2761
+ if (!isNodeOfType(node, "CallExpression")) return null;
2762
+ if (!isNodeOfType(node.callee, "MemberExpression")) return null;
2763
+ if (!isNodeOfType(node.callee.property, "Identifier")) return null;
2764
+ if (!COOKIE_MUTATION_METHOD_NAMES.has(node.callee.property.name)) return null;
2765
+ if (!isCookieReceiver(node.callee.object, locallyScopedCookieBindings)) return null;
2766
+ return node.callee.property.name;
2085
2767
  };
2086
2768
  const isMutatingFetchCall = (node) => {
2087
2769
  if (!isNodeOfType(node, "CallExpression")) return false;
@@ -2090,25 +2772,30 @@ const isMutatingFetchCall = (node) => {
2090
2772
  if (!optionsArgument || !isNodeOfType(optionsArgument, "ObjectExpression")) return false;
2091
2773
  return Boolean(optionsArgument.properties?.some(isMutatingMethodProperty));
2092
2774
  };
2093
- const getCookiesOrHeadersMethodName = (child, apiName) => {
2094
- if (!isCookiesOrHeadersCall(child, apiName)) return null;
2095
- if (!isNodeOfType(child, "CallExpression")) return null;
2096
- if (!isNodeOfType(child.callee, "MemberExpression")) return null;
2097
- if (!isNodeOfType(child.callee.property, "Identifier")) return null;
2098
- return child.callee.property.name;
2775
+ const isMutatingDbCall = (node, locallyScopedSafeBindings) => {
2776
+ if (!isNodeOfType(node, "CallExpression") || !isNodeOfType(node.callee, "MemberExpression")) return false;
2777
+ const { property, object } = node.callee;
2778
+ if (!isNodeOfType(property, "Identifier") || !MUTATION_METHOD_NAMES.has(property.name)) return false;
2779
+ if (isSafeReceiverChain(object, locallyScopedSafeBindings)) return false;
2780
+ return true;
2099
2781
  };
2100
- const findSideEffect = (node) => {
2782
+ const getDbCallDescription = (node) => {
2783
+ if (!isNodeOfType(node, "CallExpression")) return ".unknown()";
2784
+ if (!isNodeOfType(node.callee, "MemberExpression")) return ".unknown()";
2785
+ if (!isNodeOfType(node.callee.property, "Identifier")) return ".unknown()";
2786
+ const methodName = node.callee.property.name;
2787
+ const rootObjectName = isNodeOfType(node.callee.object, "Identifier") ? node.callee.object.name : null;
2788
+ return rootObjectName ? `${rootObjectName}.${methodName}()` : `.${methodName}()`;
2789
+ };
2790
+ const findSideEffect = (node, options = {}) => {
2791
+ const locallyScopedSafeBindings = options.locallyScopedSafeBindings ?? EMPTY_BINDING_SET;
2792
+ const locallyScopedCookieBindings = options.locallyScopedCookieBindings ?? EMPTY_BINDING_SET;
2101
2793
  let sideEffectDescription = null;
2102
2794
  walkAst(node, (child) => {
2103
2795
  if (sideEffectDescription) return;
2104
- const cookiesMethodName = getCookiesOrHeadersMethodName(child, "cookies");
2105
- if (cookiesMethodName) {
2106
- sideEffectDescription = `cookies().${cookiesMethodName}()`;
2107
- return;
2108
- }
2109
- const headersMethodName = getCookiesOrHeadersMethodName(child, "headers");
2110
- if (headersMethodName) {
2111
- sideEffectDescription = `headers().${headersMethodName}()`;
2796
+ const cookieMethodName = getCookieMutationMethodName(child, locallyScopedCookieBindings);
2797
+ if (cookieMethodName) {
2798
+ sideEffectDescription = `cookies().${cookieMethodName}()`;
2112
2799
  return;
2113
2800
  }
2114
2801
  if (isMutatingFetchCall(child) && isNodeOfType(child, "CallExpression")) {
@@ -2117,13 +2804,9 @@ const findSideEffect = (node) => {
2117
2804
  const methodProperty = optionsArgument.properties.find(isMutatingMethodProperty);
2118
2805
  if (!methodProperty || !isNodeOfType(methodProperty, "Property") || !isNodeOfType(methodProperty.value, "Literal")) return;
2119
2806
  sideEffectDescription = `fetch() with method ${methodProperty.value.value}`;
2120
- } else if (isMutatingDbCall(child) && isNodeOfType(child, "CallExpression")) {
2121
- if (!isNodeOfType(child.callee, "MemberExpression")) return;
2122
- if (!isNodeOfType(child.callee.property, "Identifier")) return;
2123
- const methodName = child.callee.property.name;
2124
- const objectName = isNodeOfType(child.callee.object, "Identifier") ? child.callee.object.name : null;
2125
- sideEffectDescription = objectName ? `${objectName}.${methodName}()` : `.${methodName}()`;
2807
+ return;
2126
2808
  }
2809
+ if (isMutatingDbCall(child, locallyScopedSafeBindings)) sideEffectDescription = getDbCallDescription(child);
2127
2810
  });
2128
2811
  return sideEffectDescription;
2129
2812
  };
@@ -2137,41 +2820,141 @@ const extractMutatingRouteSegment = (filename) => {
2137
2820
  }
2138
2821
  return null;
2139
2822
  };
2140
- const getExportedGetHandlerBody = (node) => {
2141
- if (!isNodeOfType(node, "ExportNamedDeclaration")) return null;
2823
+ const buildProgramBindingLookup = (programNode) => {
2824
+ const topLevelBindings = /* @__PURE__ */ new Map();
2825
+ if (!isNodeOfType(programNode, "Program")) return () => null;
2826
+ const collectFromStatements = (statements) => {
2827
+ for (const statement of statements) {
2828
+ if (isNodeOfType(statement, "VariableDeclaration")) {
2829
+ for (const declarator of statement.declarations ?? []) {
2830
+ if (!isNodeOfType(declarator.id, "Identifier")) continue;
2831
+ if (!declarator.init) continue;
2832
+ topLevelBindings.set(declarator.id.name, declarator.init);
2833
+ }
2834
+ continue;
2835
+ }
2836
+ if (isNodeOfType(statement, "FunctionDeclaration") && isNodeOfType(statement.id, "Identifier") && statement.body) {
2837
+ topLevelBindings.set(statement.id.name, statement);
2838
+ continue;
2839
+ }
2840
+ if (isNodeOfType(statement, "ExportNamedDeclaration") && statement.declaration) collectFromStatements([statement.declaration]);
2841
+ }
2842
+ };
2843
+ collectFromStatements(programNode.body ?? []);
2844
+ return (identifierName) => topLevelBindings.get(identifierName) ?? null;
2845
+ };
2846
+ const isExportedGetHandler = (node) => {
2847
+ if (!isNodeOfType(node, "ExportNamedDeclaration")) return false;
2142
2848
  const declaration = node.declaration;
2143
- if (!declaration) return null;
2144
- if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id?.name === "GET") return declaration.body;
2849
+ if (!declaration) return false;
2850
+ if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id?.name === "GET") return true;
2145
2851
  if (isNodeOfType(declaration, "VariableDeclaration")) {
2146
- for (const declarator of declaration.declarations ?? []) if (isNodeOfType(declarator?.id, "Identifier") && declarator.id.name === "GET" && declarator.init && (isNodeOfType(declarator.init, "ArrowFunctionExpression") || isNodeOfType(declarator.init, "FunctionExpression"))) return declarator.init.body;
2852
+ for (const declarator of declaration.declarations ?? []) if (isNodeOfType(declarator?.id, "Identifier") && declarator.id.name === "GET") return true;
2147
2853
  }
2854
+ return false;
2855
+ };
2856
+ const isGetMethodCall = (callExpression) => isNodeOfType(callExpression, "CallExpression") && isNodeOfType(callExpression.callee, "MemberExpression") && isNodeOfType(callExpression.callee.property, "Identifier") && callExpression.callee.property.name === "get";
2857
+ const isStringLikeNode = (node) => isNodeOfType(node, "Literal") && typeof node.value === "string" || isNodeOfType(node, "TemplateLiteral");
2858
+ const getHandlerCallbackBody = (callExpression) => {
2859
+ const callArguments = callExpression.arguments ?? [];
2860
+ if (callArguments.length < 2) return null;
2861
+ const routePatternArgument = callArguments[0];
2862
+ if (!isStringLikeNode(routePatternArgument)) return null;
2863
+ const handlerArgument = callArguments[callArguments.length - 1];
2864
+ if ((isNodeOfType(handlerArgument, "ArrowFunctionExpression") || isNodeOfType(handlerArgument, "FunctionExpression")) && handlerArgument.body) return handlerArgument.body;
2148
2865
  return null;
2149
2866
  };
2867
+ const collectChainedGetHandlerBodies = (initNode) => {
2868
+ const chainedBodies = [];
2869
+ let cursor = initNode;
2870
+ while (cursor && isNodeOfType(cursor, "CallExpression")) {
2871
+ if (isGetMethodCall(cursor)) {
2872
+ const body = getHandlerCallbackBody(cursor);
2873
+ if (body) chainedBodies.push(body);
2874
+ }
2875
+ cursor = isNodeOfType(cursor.callee, "MemberExpression") ? cursor.callee.object : null;
2876
+ }
2877
+ return chainedBodies;
2878
+ };
2879
+ const resolveBodiesFromExpression = (expression, resolveBinding, remainingDepth) => {
2880
+ if (remainingDepth <= 0) return [];
2881
+ if (isNodeOfType(expression, "ArrowFunctionExpression") || isNodeOfType(expression, "FunctionExpression") || isNodeOfType(expression, "FunctionDeclaration")) return expression.body ? [expression.body] : [];
2882
+ if (isNodeOfType(expression, "CallExpression")) {
2883
+ for (const callArgument of expression.arguments ?? []) {
2884
+ if (isNodeOfType(callArgument, "ArrowFunctionExpression") || isNodeOfType(callArgument, "FunctionExpression")) {
2885
+ if (callArgument.body) return [callArgument.body];
2886
+ }
2887
+ if (!isNodeOfType(callArgument, "Identifier")) continue;
2888
+ const argumentInit = resolveBinding(callArgument.name);
2889
+ if (!argumentInit) continue;
2890
+ const resolvedBodies = resolveBodiesFromExpression(argumentInit, resolveBinding, remainingDepth - 1);
2891
+ if (resolvedBodies.length > 0) return resolvedBodies;
2892
+ const chainedBodies = collectChainedGetHandlerBodies(argumentInit);
2893
+ if (chainedBodies.length > 0) return chainedBodies;
2894
+ }
2895
+ return [];
2896
+ }
2897
+ if (isNodeOfType(expression, "Identifier")) {
2898
+ const boundInit = resolveBinding(expression.name);
2899
+ if (!boundInit) return [];
2900
+ return resolveBodiesFromExpression(boundInit, resolveBinding, remainingDepth - 1);
2901
+ }
2902
+ return [];
2903
+ };
2904
+ const resolveGetHandlerBodies = (exportNode, resolveBinding) => {
2905
+ if (!isNodeOfType(exportNode, "ExportNamedDeclaration")) return [];
2906
+ const declaration = exportNode.declaration;
2907
+ if (!declaration) return [];
2908
+ if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id?.name === "GET") return declaration.body ? [declaration.body] : [];
2909
+ if (!isNodeOfType(declaration, "VariableDeclaration")) return [];
2910
+ for (const declarator of declaration.declarations ?? []) {
2911
+ if (!isNodeOfType(declarator.id, "Identifier") || declarator.id.name !== "GET") continue;
2912
+ if (!declarator.init) return [];
2913
+ return resolveBodiesFromExpression(declarator.init, resolveBinding, 3);
2914
+ }
2915
+ return [];
2916
+ };
2150
2917
  const nextjsNoSideEffectInGetHandler = defineRule({
2151
2918
  id: "nextjs-no-side-effect-in-get-handler",
2152
2919
  requires: ["nextjs"],
2153
2920
  severity: "error",
2154
2921
  category: "Security",
2155
2922
  recommendation: "Move the side effect to a POST handler and use a <form> or fetch with method POST — GET requests can be triggered by prefetching and are vulnerable to CSRF",
2156
- create: (context) => ({ ExportNamedDeclaration(node) {
2157
- const filename = context.getFilename?.() ?? "";
2158
- if (!ROUTE_HANDLER_FILE_PATTERN.test(filename)) return;
2159
- const handlerBody = getExportedGetHandlerBody(node);
2160
- if (!handlerBody) return;
2161
- const mutatingSegment = extractMutatingRouteSegment(filename);
2162
- if (mutatingSegment) {
2163
- context.report({
2164
- node,
2165
- message: `GET handler on "/${mutatingSegment}" route — use POST to prevent CSRF and unintended prefetch triggers`
2166
- });
2167
- return;
2168
- }
2169
- const sideEffect = findSideEffect(handlerBody);
2170
- if (sideEffect) context.report({
2171
- node,
2172
- message: `GET handler has side effects (${sideEffect}) — use POST to prevent CSRF and unintended prefetch triggers`
2173
- });
2174
- } })
2923
+ create: (context) => {
2924
+ let resolveBinding = () => null;
2925
+ return {
2926
+ Program(node) {
2927
+ resolveBinding = buildProgramBindingLookup(node);
2928
+ },
2929
+ ExportNamedDeclaration(node) {
2930
+ const filename = context.getFilename?.() ?? "";
2931
+ if (!ROUTE_HANDLER_FILE_PATTERN.test(filename)) return;
2932
+ if (CRON_ROUTE_PATTERN.test(filename)) return;
2933
+ if (!isExportedGetHandler(node)) return;
2934
+ const mutatingSegment = extractMutatingRouteSegment(filename);
2935
+ if (mutatingSegment) {
2936
+ context.report({
2937
+ node,
2938
+ message: `GET handler on "/${mutatingSegment}" route — use POST to prevent CSRF and unintended prefetch triggers`
2939
+ });
2940
+ return;
2941
+ }
2942
+ const handlerBodies = resolveGetHandlerBodies(node, resolveBinding);
2943
+ for (const handlerBody of handlerBodies) {
2944
+ const sideEffect = findSideEffect(handlerBody, {
2945
+ locallyScopedSafeBindings: collectLocallyScopedSafeBindings(handlerBody),
2946
+ locallyScopedCookieBindings: collectLocallyScopedCookieBindings(handlerBody)
2947
+ });
2948
+ if (!sideEffect) continue;
2949
+ context.report({
2950
+ node,
2951
+ message: `GET handler has side effects (${sideEffect}) — use POST to prevent CSRF and unintended prefetch triggers`
2952
+ });
2953
+ return;
2954
+ }
2955
+ }
2956
+ };
2957
+ }
2175
2958
  });
2176
2959
  //#endregion
2177
2960
  //#region src/plugin/utils/get-imported-name.ts
@@ -2946,44 +3729,16 @@ const noDerivedStateEffect = defineRule({
2946
3729
  } })
2947
3730
  });
2948
3731
  //#endregion
2949
- //#region src/plugin/utils/collect-pattern-names.ts
2950
- const collectPatternNames = (pattern, into) => {
2951
- if (!pattern) return;
2952
- if (isNodeOfType(pattern, "Identifier")) {
2953
- into.add(pattern.name);
2954
- return;
2955
- }
2956
- if (isNodeOfType(pattern, "AssignmentPattern")) {
2957
- collectPatternNames(pattern.left, into);
2958
- return;
2959
- }
2960
- if (isNodeOfType(pattern, "RestElement")) {
2961
- collectPatternNames(pattern.argument, into);
2962
- return;
2963
- }
2964
- if (isNodeOfType(pattern, "ArrayPattern")) {
2965
- for (const element of pattern.elements ?? []) collectPatternNames(element, into);
2966
- return;
2967
- }
2968
- if (isNodeOfType(pattern, "ObjectPattern")) for (const property of pattern.properties ?? []) {
2969
- if (isNodeOfType(property, "RestElement")) {
2970
- collectPatternNames(property.argument, into);
2971
- continue;
2972
- }
2973
- if (isNodeOfType(property, "Property")) collectPatternNames(property.value, into);
2974
- }
2975
- };
2976
- //#endregion
2977
3732
  //#region src/plugin/utils/create-component-prop-stack-tracker.ts
2978
3733
  const extractDestructuredPropNames = (params) => {
2979
3734
  const propNames = /* @__PURE__ */ new Set();
2980
3735
  for (const param of params) collectPatternNames(param, propNames);
2981
3736
  return propNames;
2982
3737
  };
2983
- const isFunctionNode = (node) => Boolean(node) && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression"));
3738
+ const isFunctionNode$1 = (node) => Boolean(node) && (isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression"));
2984
3739
  const getInlineFunctionNode = (node) => {
2985
3740
  if (!node) return null;
2986
- if (isFunctionNode(node)) return node;
3741
+ if (isFunctionNode$1(node)) return node;
2987
3742
  if (!isNodeOfType(node, "CallExpression")) return null;
2988
3743
  for (const argument of node.arguments ?? []) {
2989
3744
  const inlineFunctionNode = getInlineFunctionNode(argument);
@@ -2994,7 +3749,7 @@ const getInlineFunctionNode = (node) => {
2994
3749
  const getNearestComponentFunction = (node) => {
2995
3750
  let cursor = node.parent ?? null;
2996
3751
  while (cursor) {
2997
- if (isFunctionNode(cursor)) return cursor;
3752
+ if (isFunctionNode$1(cursor)) return cursor;
2998
3753
  cursor = cursor.parent ?? null;
2999
3754
  }
3000
3755
  return null;
@@ -3172,11 +3927,11 @@ const collectFunctionLocalBindings = (functionNode) => {
3172
3927
  }
3173
3928
  return localBindings;
3174
3929
  };
3175
- const isFunctionLikeNode = (node) => isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression");
3930
+ const isFunctionLikeNode$1 = (node) => isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression");
3176
3931
  const walkComponentRespectingShadows = (node, shadowedStateNames, visit) => {
3177
3932
  if (!node || typeof node !== "object") return;
3178
3933
  let nextShadowedStateNames = shadowedStateNames;
3179
- if (isFunctionLikeNode(node)) {
3934
+ if (isFunctionLikeNode$1(node)) {
3180
3935
  const localBindings = collectFunctionLocalBindings(node);
3181
3936
  if (localBindings.size > 0) {
3182
3937
  const merged = new Set(shadowedStateNames);
@@ -3472,26 +4227,6 @@ const noEffectChain = defineRule({
3472
4227
  }
3473
4228
  });
3474
4229
  //#endregion
3475
- //#region src/plugin/utils/are-expressions-structurally-equal.ts
3476
- const areExpressionsStructurallyEqual = (a, b) => {
3477
- if (!a || !b) return a === b;
3478
- if (a.type !== b.type) return false;
3479
- if (isNodeOfType(a, "Identifier") && isNodeOfType(b, "Identifier")) return a.name === b.name;
3480
- if (isNodeOfType(a, "Literal") && isNodeOfType(b, "Literal")) return a.value === b.value;
3481
- if (isNodeOfType(a, "MemberExpression") && isNodeOfType(b, "MemberExpression")) {
3482
- if (a.computed !== b.computed) return false;
3483
- return areExpressionsStructurallyEqual(a.object, b.object) && areExpressionsStructurallyEqual(a.property, b.property);
3484
- }
3485
- if (isNodeOfType(a, "CallExpression") && isNodeOfType(b, "CallExpression")) {
3486
- if (!areExpressionsStructurallyEqual(a.callee, b.callee)) return false;
3487
- const argumentsA = a.arguments ?? [];
3488
- const argumentsB = b.arguments ?? [];
3489
- if (argumentsA.length !== argumentsB.length) return false;
3490
- return argumentsA.every((argument, index) => areExpressionsStructurallyEqual(argument, argumentsB[index]));
3491
- }
3492
- return false;
3493
- };
3494
- //#endregion
3495
4230
  //#region src/plugin/rules/state-and-effects/utils/find-triggered-side-effect-callee-name.ts
3496
4231
  const findTriggeredSideEffectCalleeName = (consequentNode) => {
3497
4232
  let foundCalleeName = null;
@@ -5425,8 +6160,44 @@ const noPolymorphicChildren = defineRule({
5425
6160
  } })
5426
6161
  });
5427
6162
  //#endregion
6163
+ //#region src/plugin/utils/get-react-doctor-setting.ts
6164
+ const readReactDoctorSettingsBag = (settings) => {
6165
+ const reactDoctorSettings = settings?.["react-doctor"];
6166
+ if (typeof reactDoctorSettings !== "object" || reactDoctorSettings === null || Array.isArray(reactDoctorSettings)) return null;
6167
+ return reactDoctorSettings;
6168
+ };
6169
+ const readOwnPropertyValue = (bag, settingName) => Object.getOwnPropertyDescriptor(bag, settingName)?.value;
6170
+ const getReactDoctorStringSetting = (settings, settingName) => {
6171
+ const bag = readReactDoctorSettingsBag(settings);
6172
+ if (!bag) return void 0;
6173
+ const settingValue = readOwnPropertyValue(bag, settingName);
6174
+ return typeof settingValue === "string" ? settingValue : void 0;
6175
+ };
6176
+ const getReactDoctorStringArraySetting = (settings, settingName) => {
6177
+ const bag = readReactDoctorSettingsBag(settings);
6178
+ if (!bag) return [];
6179
+ const settingValue = readOwnPropertyValue(bag, settingName);
6180
+ if (!Array.isArray(settingValue)) return [];
6181
+ return settingValue.filter((entry) => typeof entry === "string" && entry.length > 0);
6182
+ };
6183
+ //#endregion
5428
6184
  //#region src/plugin/rules/correctness/no-prevent-default.ts
5429
6185
  const PREVENT_DEFAULT_ELEMENTS = new Map([["form", ["onSubmit"]], ["a", ["onClick"]]]);
6186
+ const SERVER_CAPABLE_FRAMEWORKS = new Set([
6187
+ "nextjs",
6188
+ "tanstack-start",
6189
+ "remix"
6190
+ ]);
6191
+ const CLIENT_ONLY_FRAMEWORKS = new Set([
6192
+ "vite",
6193
+ "cra",
6194
+ "gatsby",
6195
+ "react-native",
6196
+ "expo"
6197
+ ]);
6198
+ const FORM_MESSAGE_SERVER_CAPABLE = "preventDefault() on <form> onSubmit — form won't work without JavaScript. Use a server action (`<form action={serverAction}>`) for progressive enhancement";
6199
+ const FORM_MESSAGE_GENERIC = "preventDefault() on <form> onSubmit — form won't work without JavaScript. Consider a form action for progressive enhancement";
6200
+ const ANCHOR_MESSAGE = "preventDefault() on <a> onClick — use a <button> or routing component instead";
5430
6201
  const containsPreventDefaultCall = (node) => {
5431
6202
  let didFindPreventDefault = false;
5432
6203
  walkAst(node, (child) => {
@@ -5435,32 +6206,35 @@ const containsPreventDefaultCall = (node) => {
5435
6206
  });
5436
6207
  return didFindPreventDefault;
5437
6208
  };
5438
- const buildPreventDefaultMessage = (elementName) => {
5439
- if (elementName === "form") return "preventDefault() on <form> onSubmit — form won't work without JavaScript. Consider using a server action for progressive enhancement";
5440
- return "preventDefault() on <a> onClick — use a <button> or routing component instead";
5441
- };
6209
+ const selectFormMessage = (framework) => framework !== void 0 && SERVER_CAPABLE_FRAMEWORKS.has(framework) ? FORM_MESSAGE_SERVER_CAPABLE : FORM_MESSAGE_GENERIC;
5442
6210
  const noPreventDefault = defineRule({
5443
6211
  id: "no-prevent-default",
5444
6212
  severity: "warn",
5445
- recommendation: "Use `<form action={serverAction}>` (works without JS) or `<button>` instead of `<a>` with preventDefault",
5446
- create: (context) => ({ JSXOpeningElement(node) {
5447
- const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
5448
- if (!elementName) return;
5449
- const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
5450
- if (!targetEventProps) return;
5451
- for (const targetEventProp of targetEventProps) {
5452
- const eventAttribute = findJsxAttribute(node.attributes ?? [], targetEventProp);
5453
- if (!eventAttribute?.value || !isNodeOfType(eventAttribute.value, "JSXExpressionContainer")) continue;
5454
- const expression = eventAttribute.value.expression;
5455
- if (!isNodeOfType(expression, "ArrowFunctionExpression") && !isNodeOfType(expression, "FunctionExpression")) continue;
5456
- if (!containsPreventDefaultCall(expression)) continue;
5457
- context.report({
5458
- node,
5459
- message: buildPreventDefaultMessage(elementName)
5460
- });
5461
- return;
5462
- }
5463
- } })
6213
+ recommendation: "Use `<form action>` (works without JS) where your framework supports it, or use a `<button>` instead of `<a>` with preventDefault",
6214
+ create: (context) => {
6215
+ const framework = getReactDoctorStringSetting(context.settings, "framework");
6216
+ const isClientOnlyFramework = framework !== void 0 && CLIENT_ONLY_FRAMEWORKS.has(framework);
6217
+ const formMessage = selectFormMessage(framework);
6218
+ return { JSXOpeningElement(node) {
6219
+ const elementName = isNodeOfType(node.name, "JSXIdentifier") ? node.name.name : null;
6220
+ if (!elementName) return;
6221
+ const targetEventProps = PREVENT_DEFAULT_ELEMENTS.get(elementName);
6222
+ if (!targetEventProps) return;
6223
+ if (elementName === "form" && isClientOnlyFramework) return;
6224
+ for (const targetEventProp of targetEventProps) {
6225
+ const eventAttribute = findJsxAttribute(node.attributes ?? [], targetEventProp);
6226
+ if (!eventAttribute?.value || !isNodeOfType(eventAttribute.value, "JSXExpressionContainer")) continue;
6227
+ const expression = eventAttribute.value.expression;
6228
+ if (!isNodeOfType(expression, "ArrowFunctionExpression") && !isNodeOfType(expression, "FunctionExpression")) continue;
6229
+ if (!containsPreventDefaultCall(expression)) continue;
6230
+ context.report({
6231
+ node,
6232
+ message: elementName === "form" ? formMessage : ANCHOR_MESSAGE
6233
+ });
6234
+ return;
6235
+ }
6236
+ } };
6237
+ }
5464
6238
  });
5465
6239
  //#endregion
5466
6240
  //#region src/plugin/rules/state-and-effects/no-prop-callback-in-effect.ts
@@ -5713,6 +6487,8 @@ const AUTH_FUNCTION_NAMES = new Set([
5713
6487
  "getAuth",
5714
6488
  "validateSession"
5715
6489
  ]);
6490
+ const GENERIC_AUTH_METHOD_NAMES = new Set(["getUser"]);
6491
+ const AUTH_OBJECT_PATTERN = /(?:^|[._])(?:auth|authn|authz|clerk|session|jwt|firebase|supabase|nextauth|kinde|workos|stytch|descope|cognito|propelauth|lucia)/i;
5716
6492
  const SECRET_PATTERNS = [
5717
6493
  /^sk_live_/,
5718
6494
  /^sk_test_/,
@@ -5725,6 +6501,63 @@ const SECRET_PATTERNS = [
5725
6501
  /^sk-[a-zA-Z0-9]{32,}$/
5726
6502
  ];
5727
6503
  const SECRET_VARIABLE_PATTERN = /(?:api_?key|secret|token|password|credential|auth)/i;
6504
+ const SECRET_TOOLING_FILE_PATTERN = /(?:^|\/)[^/]+\.config\.[cm]?[jt]s$/;
6505
+ const SECRET_TOOLING_RC_FILE_PATTERN = /(?:^|\/)(?:\.[a-z-]+rc|[a-z-]+\.rc)\.[cm]?[jt]s$/;
6506
+ const SECRET_TEST_FILE_PATTERN = /(?:^|\/)[^/]+\.(?:test|spec|stories|story|fixture|fixtures)\.[cm]?[jt]sx?$/;
6507
+ const SECRET_SERVER_FILE_SUFFIX_PATTERN = /(?:^|\/)[^/]+\.server\.[cm]?[jt]sx?$/;
6508
+ const SECRET_SERVER_ENTRY_FILE_PATTERN = /(?:^|\/)(?:middleware|route)\.[cm]?[jt]sx?$/;
6509
+ const SECRET_NEXT_PAGES_API_FILE_PATTERN = /(?:^|\/)pages\/api\/.+\.[cm]?[jt]sx?$/;
6510
+ const SECRET_CLIENT_FILE_SUFFIX_PATTERN = /(?:^|\/)[^/]+\.(?:client|browser|web)\.[cm]?[jt]sx?$/;
6511
+ const SECRET_CLIENT_ENTRY_FILE_PATTERN = /(?:^|\/)(?:src\/)?(?:main|index|[Aa]pp|client)\.[cm]?[jt]sx?$/;
6512
+ const SECRET_SERVER_DIRECTORY_NAMES = new Set([
6513
+ "backend",
6514
+ "functions",
6515
+ "lambdas",
6516
+ "lambda",
6517
+ "middleware",
6518
+ "server",
6519
+ "servers"
6520
+ ]);
6521
+ const SECRET_SERVER_SOURCE_ROOT_OWNER_NAMES = new Set([
6522
+ "api",
6523
+ "backend",
6524
+ "edge",
6525
+ "function",
6526
+ "functions",
6527
+ "lambda",
6528
+ "lambdas",
6529
+ "server",
6530
+ "servers",
6531
+ "worker",
6532
+ "workers"
6533
+ ]);
6534
+ const SECRET_TEST_DIRECTORY_NAMES = new Set([
6535
+ "__fixtures__",
6536
+ "__mocks__",
6537
+ "__tests__",
6538
+ "fixtures",
6539
+ "mocks",
6540
+ "test",
6541
+ "tests"
6542
+ ]);
6543
+ const SECRET_TOOLING_DIRECTORY_NAMES = new Set([
6544
+ "bin",
6545
+ "config",
6546
+ "configs",
6547
+ "script",
6548
+ "scripts",
6549
+ "tooling",
6550
+ "tools"
6551
+ ]);
6552
+ const SECRET_CLIENT_SOURCE_DIRECTORY_NAMES = new Set([
6553
+ "components",
6554
+ "features",
6555
+ "hooks",
6556
+ "pages",
6557
+ "ui",
6558
+ "views",
6559
+ "widgets"
6560
+ ]);
5728
6561
  const SECRET_FALSE_POSITIVE_SUFFIXES = new Set([
5729
6562
  "modal",
5730
6563
  "label",
@@ -5732,7 +6565,6 @@ const SECRET_FALSE_POSITIVE_SUFFIXES = new Set([
5732
6565
  "title",
5733
6566
  "name",
5734
6567
  "id",
5735
- "key",
5736
6568
  "url",
5737
6569
  "path",
5738
6570
  "route",
@@ -5742,6 +6574,7 @@ const SECRET_FALSE_POSITIVE_SUFFIXES = new Set([
5742
6574
  "column",
5743
6575
  "header",
5744
6576
  "placeholder",
6577
+ "prefix",
5745
6578
  "description",
5746
6579
  "type",
5747
6580
  "icon",
@@ -5786,93 +6619,280 @@ const SECRET_FALSE_POSITIVE_SUFFIXES = new Set([
5786
6619
  "constant"
5787
6620
  ]);
5788
6621
  //#endregion
5789
- //#region src/plugin/rules/security/no-secrets-in-client-code.ts
5790
- const noSecretsInClientCode = defineRule({
5791
- id: "no-secrets-in-client-code",
5792
- severity: "warn",
5793
- recommendation: "Move to server-side `process.env.SECRET_NAME`. Only `NEXT_PUBLIC_*` vars are safe for the client (and should not contain secrets)",
5794
- create: (context) => ({ VariableDeclarator(node) {
5795
- if (!isNodeOfType(node.id, "Identifier")) return;
5796
- if (!isNodeOfType(node.init, "Literal") || typeof node.init.value !== "string") return;
5797
- const variableName = node.id.name;
5798
- const literalValue = node.init.value;
5799
- const trailingSuffix = variableName.split("_").pop()?.toLowerCase() ?? "";
5800
- const isUiConstant = SECRET_FALSE_POSITIVE_SUFFIXES.has(trailingSuffix);
5801
- if (SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && literalValue.length > 24) {
5802
- context.report({
5803
- node,
5804
- message: `Possible hardcoded secret in "${variableName}" — use environment variables instead`
5805
- });
5806
- return;
5807
- }
5808
- if (SECRET_PATTERNS.some((pattern) => pattern.test(literalValue))) context.report({
5809
- node,
5810
- message: "Hardcoded secret detected use environment variables instead"
5811
- });
5812
- } })
5813
- });
5814
- //#endregion
5815
- //#region src/plugin/rules/state-and-effects/no-set-state-in-render.ts
5816
- const isUnconditionalSetterCallStatement = (statement, setterNames) => {
5817
- if (!isNodeOfType(statement, "ExpressionStatement")) return null;
5818
- const expression = statement.expression;
5819
- if (!isNodeOfType(expression, "CallExpression")) return null;
5820
- const callee = expression.callee;
5821
- if (!isNodeOfType(callee, "Identifier")) return null;
5822
- if (!setterNames.has(callee.name)) return null;
5823
- return expression;
6622
+ //#region src/plugin/utils/classify-secret-file-exposure.ts
6623
+ const SOURCE_FILE_EXTENSION_PATTERN = /\.[cm]?[jt]sx?$/;
6624
+ const CLIENT_SOURCE_FILE_EXTENSION_PATTERN = /\.[cm]?[jt]sx$/;
6625
+ const CLIENT_APP_DIRECTORY_FRAMEWORKS = new Set([
6626
+ "cra",
6627
+ "expo",
6628
+ "gatsby",
6629
+ "vite"
6630
+ ]);
6631
+ const isInsideDirectory = (pathSegments, directoryNames) => pathSegments.some((pathSegment) => directoryNames.has(pathSegment));
6632
+ const normalizeFilename = (filename) => filename.replaceAll("\\", "/");
6633
+ const normalizeDirectory = (directory) => normalizeFilename(directory).replace(/\/+$/, "");
6634
+ const getProjectRelativeFilename = (filename, rootDirectory) => {
6635
+ const normalizedFilename = normalizeFilename(filename);
6636
+ if (!rootDirectory) return normalizedFilename;
6637
+ const rootDirectoryPrefix = `${normalizeDirectory(rootDirectory)}/`;
6638
+ if (!normalizedFilename.startsWith(rootDirectoryPrefix)) return normalizedFilename;
6639
+ return normalizedFilename.slice(rootDirectoryPrefix.length);
6640
+ };
6641
+ const getClassifiablePathSegments = (pathSegments) => {
6642
+ const srcIndex = pathSegments.lastIndexOf("src");
6643
+ if (srcIndex === -1) return pathSegments;
6644
+ return pathSegments.slice(srcIndex + 1);
6645
+ };
6646
+ const isClientSourceFile = (normalizedFilename, pathSegments, classifiablePathSegments, options) => {
6647
+ if (!SOURCE_FILE_EXTENSION_PATTERN.test(normalizedFilename)) return false;
6648
+ if (!pathSegments.includes("src")) return false;
6649
+ if (classifiablePathSegments[0] === "app" && !CLIENT_APP_DIRECTORY_FRAMEWORKS.has(options.framework ?? "")) return false;
6650
+ if (CLIENT_SOURCE_FILE_EXTENSION_PATTERN.test(normalizedFilename)) return true;
6651
+ return classifiablePathSegments.some((pathSegment) => SECRET_CLIENT_SOURCE_DIRECTORY_NAMES.has(pathSegment));
6652
+ };
6653
+ const getSourceRootOwner = (pathSegments) => {
6654
+ const srcIndex = pathSegments.lastIndexOf("src");
6655
+ if (srcIndex <= 0) return null;
6656
+ return pathSegments[srcIndex - 1];
6657
+ };
6658
+ const isAppDirectoryClientSourceFile = (normalizedFilename, classifiablePathSegments, options) => {
6659
+ if (!SOURCE_FILE_EXTENSION_PATTERN.test(normalizedFilename)) return false;
6660
+ if (!CLIENT_APP_DIRECTORY_FRAMEWORKS.has(options.framework ?? "")) return false;
6661
+ return classifiablePathSegments.includes("app");
6662
+ };
6663
+ const isNextJsFramework = (options) => options.framework === "nextjs";
6664
+ const classifySecretFileExposure = (filename, options = {}) => {
6665
+ if (filename.length === 0) return "unknown";
6666
+ const normalizedFilename = getProjectRelativeFilename(filename, options.rootDirectory);
6667
+ const pathSegments = normalizedFilename.split("/");
6668
+ const classifiablePathSegments = getClassifiablePathSegments(pathSegments);
6669
+ const sourceRootOwner = getSourceRootOwner(pathSegments);
6670
+ if (SECRET_TEST_FILE_PATTERN.test(normalizedFilename)) return "test";
6671
+ if (isInsideDirectory(classifiablePathSegments, SECRET_TEST_DIRECTORY_NAMES)) return "test";
6672
+ if (SECRET_TOOLING_FILE_PATTERN.test(normalizedFilename)) return "tooling";
6673
+ if (SECRET_TOOLING_RC_FILE_PATTERN.test(normalizedFilename)) return "tooling";
6674
+ if (sourceRootOwner && SECRET_TOOLING_DIRECTORY_NAMES.has(sourceRootOwner)) return "tooling";
6675
+ if (isInsideDirectory(classifiablePathSegments, SECRET_TOOLING_DIRECTORY_NAMES)) return "tooling";
6676
+ if (SECRET_SERVER_FILE_SUFFIX_PATTERN.test(normalizedFilename)) return "server";
6677
+ if (options.hasUseServerDirective === true) return "server";
6678
+ if (options.hasUseClientDirective === true) return "client";
6679
+ if (SECRET_CLIENT_FILE_SUFFIX_PATTERN.test(normalizedFilename)) return "client";
6680
+ if (isNextJsFramework(options) && SECRET_SERVER_ENTRY_FILE_PATTERN.test(normalizedFilename)) return "server";
6681
+ if (isNextJsFramework(options) && SECRET_NEXT_PAGES_API_FILE_PATTERN.test(normalizedFilename)) return "server";
6682
+ if (sourceRootOwner && SECRET_SERVER_SOURCE_ROOT_OWNER_NAMES.has(sourceRootOwner)) return "server";
6683
+ if (isInsideDirectory(classifiablePathSegments, SECRET_SERVER_DIRECTORY_NAMES)) return "server";
6684
+ if (SECRET_CLIENT_ENTRY_FILE_PATTERN.test(normalizedFilename)) return "client";
6685
+ if (isAppDirectoryClientSourceFile(normalizedFilename, classifiablePathSegments, options)) return "client";
6686
+ if (isClientSourceFile(normalizedFilename, pathSegments, classifiablePathSegments, options)) return "client";
6687
+ return "unknown";
6688
+ };
6689
+ //#endregion
6690
+ //#region src/plugin/utils/get-identifier-trailing-word.ts
6691
+ const getIdentifierTrailingWord = (identifierName) => {
6692
+ return identifierName.match(/[A-Z]+(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|\d+/g)?.at(-1)?.toLowerCase() ?? identifierName.toLowerCase();
5824
6693
  };
5825
- const noSetStateInRender = defineRule({
5826
- id: "no-set-state-in-render",
5827
- severity: "warn",
5828
- recommendation: "Move the setter call into a `useEffect`, an event handler, or replace the state with a value computed during render. Calling a setter at render time triggers another render, which calls the setter again — an infinite loop",
5829
- create: (context) => {
5830
- const checkComponent = (componentBody) => {
5831
- if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
5832
- const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
5833
- if (setterNames.size === 0) return;
5834
- for (const statement of componentBody.body ?? []) {
5835
- const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
5836
- if (!setterCall) continue;
5837
- if (!isNodeOfType(setterCall, "CallExpression")) continue;
5838
- if (!isNodeOfType(setterCall.callee, "Identifier")) continue;
5839
- const setterIdentifierName = setterCall.callee.name;
5840
- context.report({
5841
- node: setterCall,
5842
- message: `${setterIdentifierName}() called unconditionally at the top of render — causes an infinite re-render loop. Move into a useEffect or an event handler. (To derive state from props, guard the call: \`if (prev !== prop) ${setterIdentifierName}(prop)\`)`
5843
- });
5844
- }
5845
- };
5846
- return {
5847
- FunctionDeclaration(node) {
5848
- if (!node.id?.name || !isUppercaseName(node.id.name)) return;
5849
- checkComponent(node.body);
5850
- },
5851
- VariableDeclarator(node) {
5852
- if (!isComponentAssignment(node)) return;
5853
- if (!isNodeOfType(node.init, "ArrowFunctionExpression") && !isNodeOfType(node.init, "FunctionExpression")) return;
5854
- checkComponent(node.init.body);
5855
- }
5856
- };
5857
- }
5858
- });
5859
6694
  //#endregion
5860
- //#region src/plugin/rules/design/no-side-tab-border.ts
5861
- const isNeutralBorderColor = (value) => {
5862
- const trimmed = value.trim().toLowerCase();
5863
- if ([
5864
- "gray",
5865
- "grey",
5866
- "silver",
5867
- "white",
5868
- "black",
5869
- "transparent",
5870
- "currentcolor"
5871
- ].includes(trimmed)) return true;
5872
- const parsed = parseColorToRgb(trimmed);
5873
- if (parsed) return !hasColorChroma(parsed);
5874
- return false;
5875
- };
6695
+ //#region src/plugin/constants/tanstack.ts
6696
+ const TANSTACK_ROUTE_FILE_PATTERN = /\/routes\//;
6697
+ const TANSTACK_ROOT_ROUTE_FILE_PATTERN = /__root\.(tsx?|jsx?)$/;
6698
+ const TANSTACK_ROUTE_PROPERTY_ORDER = [
6699
+ "params",
6700
+ "validateSearch",
6701
+ "loaderDeps",
6702
+ "search.middlewares",
6703
+ "ssr",
6704
+ "context",
6705
+ "beforeLoad",
6706
+ "loader",
6707
+ "onEnter",
6708
+ "onStay",
6709
+ "onLeave",
6710
+ "head",
6711
+ "scripts",
6712
+ "headers",
6713
+ "remountDeps"
6714
+ ];
6715
+ const TANSTACK_ROUTE_CREATION_FUNCTIONS = new Set([
6716
+ "createFileRoute",
6717
+ "createRoute",
6718
+ "createRootRoute",
6719
+ "createRootRouteWithContext"
6720
+ ]);
6721
+ const TANSTACK_SERVER_FN_NAMES = new Set(["createServerFn"]);
6722
+ const TANSTACK_MIDDLEWARE_METHOD_ORDER = [
6723
+ "middleware",
6724
+ "inputValidator",
6725
+ "client",
6726
+ "server",
6727
+ "handler"
6728
+ ];
6729
+ const TANSTACK_REDIRECT_FUNCTIONS = new Set(["redirect", "notFound"]);
6730
+ const TANSTACK_SERVER_FN_FILE_PATTERN = /\.functions(\.[jt]sx?)?$/;
6731
+ const TANSTACK_QUERY_HOOKS = new Set([
6732
+ "useQuery",
6733
+ "useInfiniteQuery",
6734
+ "useSuspenseQuery",
6735
+ "useSuspenseInfiniteQuery"
6736
+ ]);
6737
+ const TANSTACK_MUTATION_HOOKS = new Set(["useMutation"]);
6738
+ const QUERY_CACHE_UPDATE_METHODS = new Set([
6739
+ "invalidateQueries",
6740
+ "setQueryData",
6741
+ "setQueriesData",
6742
+ "resetQueries",
6743
+ "refetchQueries",
6744
+ "removeQueries",
6745
+ "cancelQueries",
6746
+ "clear"
6747
+ ]);
6748
+ //#endregion
6749
+ //#region src/plugin/utils/has-use-server-directive.ts
6750
+ const hasUseServerDirective = (node) => {
6751
+ if (!isNodeOfType(node, "FunctionDeclaration") && !isNodeOfType(node, "FunctionExpression") && !isNodeOfType(node, "ArrowFunctionExpression")) return false;
6752
+ if (!isNodeOfType(node.body, "BlockStatement")) return false;
6753
+ return Boolean(node.body.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && statement.directive === "use server"));
6754
+ };
6755
+ //#endregion
6756
+ //#region src/plugin/utils/is-inside-server-only-scope.ts
6757
+ const isFunctionNode = (node) => isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression");
6758
+ const isTanStackServerFnHandlerCall = (node) => {
6759
+ if (!isNodeOfType(node, "CallExpression")) return false;
6760
+ if (!isNodeOfType(node.callee, "MemberExpression")) return false;
6761
+ if (!isNodeOfType(node.callee.property, "Identifier")) return false;
6762
+ if (node.callee.property.name !== "handler") return false;
6763
+ let currentNode = node.callee.object;
6764
+ while (isNodeOfType(currentNode, "CallExpression")) {
6765
+ const calleeName = getCalleeName$1(currentNode);
6766
+ if (calleeName && TANSTACK_SERVER_FN_NAMES.has(calleeName)) return true;
6767
+ if (!isNodeOfType(currentNode.callee, "MemberExpression")) return false;
6768
+ currentNode = currentNode.callee.object;
6769
+ }
6770
+ return false;
6771
+ };
6772
+ const isTanStackServerFnHandler = (node) => {
6773
+ const parent = node.parent;
6774
+ if (!parent || !isNodeOfType(parent, "CallExpression")) return false;
6775
+ if (!parent.arguments?.some((argument) => argument === node)) return false;
6776
+ return isTanStackServerFnHandlerCall(parent);
6777
+ };
6778
+ const isInsideServerOnlyScope = (node) => {
6779
+ let currentNode = node.parent ?? null;
6780
+ while (currentNode) {
6781
+ if (isFunctionNode(currentNode)) {
6782
+ if (hasUseServerDirective(currentNode) || isTanStackServerFnHandler(currentNode)) return true;
6783
+ }
6784
+ currentNode = currentNode.parent ?? null;
6785
+ }
6786
+ return false;
6787
+ };
6788
+ //#endregion
6789
+ //#region src/plugin/rules/security/no-secrets-in-client-code.ts
6790
+ const noSecretsInClientCode = defineRule({
6791
+ id: "no-secrets-in-client-code",
6792
+ severity: "warn",
6793
+ recommendation: "Move secrets to server-only code. Public client environment variables are bundled into browser code and must not contain secrets",
6794
+ create: (context) => {
6795
+ const filename = context.getFilename?.() ?? "";
6796
+ const framework = getReactDoctorStringSetting(context.settings, "framework");
6797
+ const rootDirectory = getReactDoctorStringSetting(context.settings, "rootDirectory");
6798
+ let shouldUseVariableNameHeuristic = classifySecretFileExposure(filename, {
6799
+ framework,
6800
+ rootDirectory
6801
+ }) === "client";
6802
+ return {
6803
+ Program(programNode) {
6804
+ shouldUseVariableNameHeuristic = classifySecretFileExposure(filename, {
6805
+ framework,
6806
+ hasUseClientDirective: hasDirective(programNode, "use client"),
6807
+ hasUseServerDirective: hasDirective(programNode, "use server"),
6808
+ rootDirectory
6809
+ }) === "client";
6810
+ },
6811
+ VariableDeclarator(node) {
6812
+ if (!isNodeOfType(node.id, "Identifier")) return;
6813
+ if (!isNodeOfType(node.init, "Literal") || typeof node.init.value !== "string") return;
6814
+ const variableName = node.id.name;
6815
+ const literalValue = node.init.value;
6816
+ const isServerOnlyScope = isInsideServerOnlyScope(node);
6817
+ const trailingSuffix = getIdentifierTrailingWord(variableName);
6818
+ const isUiConstant = SECRET_FALSE_POSITIVE_SUFFIXES.has(trailingSuffix);
6819
+ if (shouldUseVariableNameHeuristic && !isServerOnlyScope && SECRET_VARIABLE_PATTERN.test(variableName) && !isUiConstant && literalValue.length > 24) {
6820
+ context.report({
6821
+ node,
6822
+ message: `Possible hardcoded secret in "${variableName}" — use environment variables instead`
6823
+ });
6824
+ return;
6825
+ }
6826
+ if (SECRET_PATTERNS.some((pattern) => pattern.test(literalValue))) context.report({
6827
+ node,
6828
+ message: "Hardcoded secret detected — use environment variables instead"
6829
+ });
6830
+ }
6831
+ };
6832
+ }
6833
+ });
6834
+ //#endregion
6835
+ //#region src/plugin/rules/state-and-effects/no-set-state-in-render.ts
6836
+ const isUnconditionalSetterCallStatement = (statement, setterNames) => {
6837
+ if (!isNodeOfType(statement, "ExpressionStatement")) return null;
6838
+ const expression = statement.expression;
6839
+ if (!isNodeOfType(expression, "CallExpression")) return null;
6840
+ const callee = expression.callee;
6841
+ if (!isNodeOfType(callee, "Identifier")) return null;
6842
+ if (!setterNames.has(callee.name)) return null;
6843
+ return expression;
6844
+ };
6845
+ const noSetStateInRender = defineRule({
6846
+ id: "no-set-state-in-render",
6847
+ severity: "warn",
6848
+ recommendation: "Move the setter call into a `useEffect`, an event handler, or replace the state with a value computed during render. Calling a setter at render time triggers another render, which calls the setter again — an infinite loop",
6849
+ create: (context) => {
6850
+ const checkComponent = (componentBody) => {
6851
+ if (!componentBody || !isNodeOfType(componentBody, "BlockStatement")) return;
6852
+ const setterNames = new Set(collectUseStateBindings(componentBody).map((binding) => binding.setterName));
6853
+ if (setterNames.size === 0) return;
6854
+ for (const statement of componentBody.body ?? []) {
6855
+ const setterCall = isUnconditionalSetterCallStatement(statement, setterNames);
6856
+ if (!setterCall) continue;
6857
+ if (!isNodeOfType(setterCall, "CallExpression")) continue;
6858
+ if (!isNodeOfType(setterCall.callee, "Identifier")) continue;
6859
+ const setterIdentifierName = setterCall.callee.name;
6860
+ context.report({
6861
+ node: setterCall,
6862
+ message: `${setterIdentifierName}() called unconditionally at the top of render — causes an infinite re-render loop. Move into a useEffect or an event handler. (To derive state from props, guard the call: \`if (prev !== prop) ${setterIdentifierName}(prop)\`)`
6863
+ });
6864
+ }
6865
+ };
6866
+ return {
6867
+ FunctionDeclaration(node) {
6868
+ if (!node.id?.name || !isUppercaseName(node.id.name)) return;
6869
+ checkComponent(node.body);
6870
+ },
6871
+ VariableDeclarator(node) {
6872
+ if (!isComponentAssignment(node)) return;
6873
+ if (!isNodeOfType(node.init, "ArrowFunctionExpression") && !isNodeOfType(node.init, "FunctionExpression")) return;
6874
+ checkComponent(node.init.body);
6875
+ }
6876
+ };
6877
+ }
6878
+ });
6879
+ //#endregion
6880
+ //#region src/plugin/rules/design/no-side-tab-border.ts
6881
+ const isNeutralBorderColor = (value) => {
6882
+ const trimmed = value.trim().toLowerCase();
6883
+ if ([
6884
+ "gray",
6885
+ "grey",
6886
+ "silver",
6887
+ "white",
6888
+ "black",
6889
+ "transparent",
6890
+ "currentcolor"
6891
+ ].includes(trimmed)) return true;
6892
+ const parsed = parseColorToRgb(trimmed);
6893
+ if (parsed) return !hasColorChroma(parsed);
6894
+ return false;
6895
+ };
5876
6896
  const extractBorderColorFromShorthand = (shorthandValue) => {
5877
6897
  const afterSolid = shorthandValue.match(/solid\s+(.+)$/i);
5878
6898
  if (!afterSolid) return null;
@@ -6557,60 +7577,6 @@ const preferUseReducer = defineRule({
6557
7577
  }
6558
7578
  });
6559
7579
  //#endregion
6560
- //#region src/plugin/constants/tanstack.ts
6561
- const TANSTACK_ROUTE_FILE_PATTERN = /\/routes\//;
6562
- const TANSTACK_ROOT_ROUTE_FILE_PATTERN = /__root\.(tsx?|jsx?)$/;
6563
- const TANSTACK_ROUTE_PROPERTY_ORDER = [
6564
- "params",
6565
- "validateSearch",
6566
- "loaderDeps",
6567
- "search.middlewares",
6568
- "ssr",
6569
- "context",
6570
- "beforeLoad",
6571
- "loader",
6572
- "onEnter",
6573
- "onStay",
6574
- "onLeave",
6575
- "head",
6576
- "scripts",
6577
- "headers",
6578
- "remountDeps"
6579
- ];
6580
- const TANSTACK_ROUTE_CREATION_FUNCTIONS = new Set([
6581
- "createFileRoute",
6582
- "createRoute",
6583
- "createRootRoute",
6584
- "createRootRouteWithContext"
6585
- ]);
6586
- const TANSTACK_SERVER_FN_NAMES = new Set(["createServerFn"]);
6587
- const TANSTACK_MIDDLEWARE_METHOD_ORDER = [
6588
- "middleware",
6589
- "inputValidator",
6590
- "client",
6591
- "server",
6592
- "handler"
6593
- ];
6594
- const TANSTACK_REDIRECT_FUNCTIONS = new Set(["redirect", "notFound"]);
6595
- const TANSTACK_SERVER_FN_FILE_PATTERN = /\.functions(\.[jt]sx?)?$/;
6596
- const TANSTACK_QUERY_HOOKS = new Set([
6597
- "useQuery",
6598
- "useInfiniteQuery",
6599
- "useSuspenseQuery",
6600
- "useSuspenseInfiniteQuery"
6601
- ]);
6602
- const TANSTACK_MUTATION_HOOKS = new Set(["useMutation"]);
6603
- const QUERY_CACHE_UPDATE_METHODS = new Set([
6604
- "invalidateQueries",
6605
- "setQueryData",
6606
- "setQueriesData",
6607
- "resetQueries",
6608
- "refetchQueries",
6609
- "removeQueries",
6610
- "cancelQueries",
6611
- "clear"
6612
- ]);
6613
- //#endregion
6614
7580
  //#region src/plugin/rules/tanstack-query/query-mutation-missing-invalidation.ts
6615
7581
  const queryMutationMissingInvalidation = defineRule({
6616
7582
  id: "query-mutation-missing-invalidation",
@@ -8130,6 +9096,118 @@ const rnNoNonNativeNavigator = defineRule({
8130
9096
  } })
8131
9097
  });
8132
9098
  //#endregion
9099
+ //#region src/plugin/utils/is-inside-platform-os-web-branch.ts
9100
+ const unwrapAccessor = (node) => {
9101
+ let current = node;
9102
+ while (current) {
9103
+ if (isNodeOfType(current, "TSNonNullExpression")) {
9104
+ current = current.expression;
9105
+ continue;
9106
+ }
9107
+ if (isNodeOfType(current, "ChainExpression")) {
9108
+ current = current.expression;
9109
+ continue;
9110
+ }
9111
+ if (isNodeOfType(current, "TSAsExpression") || isNodeOfType(current, "TSSatisfiesExpression")) {
9112
+ current = current.expression;
9113
+ continue;
9114
+ }
9115
+ return current;
9116
+ }
9117
+ return null;
9118
+ };
9119
+ const isPlatformOsMemberExpression = (node) => {
9120
+ const unwrapped = unwrapAccessor(node);
9121
+ if (!unwrapped || !isNodeOfType(unwrapped, "MemberExpression")) return false;
9122
+ if (unwrapped.computed) return false;
9123
+ const objectExpression = unwrapAccessor(unwrapped.object);
9124
+ if (!objectExpression || !isNodeOfType(objectExpression, "Identifier")) return false;
9125
+ if (objectExpression.name !== "Platform") return false;
9126
+ if (!isNodeOfType(unwrapped.property, "Identifier") || unwrapped.property.name !== "OS") return false;
9127
+ return true;
9128
+ };
9129
+ const isPlatformSelectCallee = (callee) => {
9130
+ const unwrapped = unwrapAccessor(callee);
9131
+ if (!unwrapped || !isNodeOfType(unwrapped, "MemberExpression")) return false;
9132
+ if (unwrapped.computed) return false;
9133
+ const objectExpression = unwrapAccessor(unwrapped.object);
9134
+ if (!objectExpression || !isNodeOfType(objectExpression, "Identifier")) return false;
9135
+ if (objectExpression.name !== "Platform") return false;
9136
+ if (!isNodeOfType(unwrapped.property, "Identifier") || unwrapped.property.name !== "select") return false;
9137
+ return true;
9138
+ };
9139
+ const isStringLiteralEqualTo = (node, expected) => {
9140
+ if (!node) return false;
9141
+ if (isNodeOfType(node, "Literal") && node.value === expected) return true;
9142
+ if (isNodeOfType(node, "TemplateLiteral") && node.quasis.length === 1) return node.quasis[0]?.value?.cooked === expected;
9143
+ return false;
9144
+ };
9145
+ const readStaticPropertyKeyName = (property) => {
9146
+ if (!isNodeOfType(property, "Property")) return null;
9147
+ if (property.computed) {
9148
+ if (isStringLiteralEqualTo(property.key, "web")) return "web";
9149
+ return null;
9150
+ }
9151
+ if (isNodeOfType(property.key, "Identifier")) return property.key.name;
9152
+ if (isNodeOfType(property.key, "Literal") && typeof property.key.value === "string") return property.key.value;
9153
+ return null;
9154
+ };
9155
+ const classifyPlatformOsBinaryTest = (testNode) => {
9156
+ if (!testNode || !isNodeOfType(testNode, "BinaryExpression")) return {
9157
+ isWebBranch: false,
9158
+ isNonWebBranch: false
9159
+ };
9160
+ if (testNode.operator !== "===" && testNode.operator !== "!==") return {
9161
+ isWebBranch: false,
9162
+ isNonWebBranch: false
9163
+ };
9164
+ const matchesLeft = isPlatformOsMemberExpression(testNode.left) && isStringLiteralEqualTo(testNode.right, "web");
9165
+ const matchesRight = isPlatformOsMemberExpression(testNode.right) && isStringLiteralEqualTo(testNode.left, "web");
9166
+ if (!matchesLeft && !matchesRight) return {
9167
+ isWebBranch: false,
9168
+ isNonWebBranch: false
9169
+ };
9170
+ return {
9171
+ isWebBranch: testNode.operator === "===",
9172
+ isNonWebBranch: testNode.operator === "!=="
9173
+ };
9174
+ };
9175
+ const isScopeBoundaryNode = (node) => isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression") || isNodeOfType(node, "Program");
9176
+ const isInsidePlatformOsWebBranch = (node) => {
9177
+ let child = node;
9178
+ let parent = node.parent;
9179
+ while (parent) {
9180
+ if (isNodeOfType(parent, "IfStatement")) {
9181
+ const classification = classifyPlatformOsBinaryTest(parent.test);
9182
+ if (classification.isWebBranch && parent.consequent === child) return true;
9183
+ if (classification.isNonWebBranch && parent.alternate === child) return true;
9184
+ } else if (isNodeOfType(parent, "ConditionalExpression")) {
9185
+ const classification = classifyPlatformOsBinaryTest(parent.test);
9186
+ if (classification.isWebBranch && parent.consequent === child) return true;
9187
+ if (classification.isNonWebBranch && parent.alternate === child) return true;
9188
+ } else if (isNodeOfType(parent, "LogicalExpression")) {
9189
+ const classification = classifyPlatformOsBinaryTest(parent.left);
9190
+ if (parent.right === child) {
9191
+ if (parent.operator === "&&" && classification.isWebBranch) return true;
9192
+ if (parent.operator === "||" && classification.isNonWebBranch) return true;
9193
+ }
9194
+ } else if (isNodeOfType(parent, "SwitchCase")) {
9195
+ const switchStatement = parent.parent;
9196
+ if (isNodeOfType(switchStatement, "SwitchStatement") && isPlatformOsMemberExpression(switchStatement.discriminant) && isStringLiteralEqualTo(parent.test, "web") && parent.test !== child) return true;
9197
+ } else if (isNodeOfType(parent, "Property")) {
9198
+ const objectExpression = parent.parent;
9199
+ if (isNodeOfType(objectExpression, "ObjectExpression")) {
9200
+ const callExpression = objectExpression.parent;
9201
+ if (isNodeOfType(callExpression, "CallExpression") && callExpression.arguments[0] === objectExpression && isPlatformSelectCallee(callExpression.callee) && readStaticPropertyKeyName(parent) === "web" && parent.value === child) return true;
9202
+ }
9203
+ }
9204
+ if (isScopeBoundaryNode(parent)) return false;
9205
+ child = parent;
9206
+ parent = parent.parent ?? null;
9207
+ }
9208
+ return false;
9209
+ };
9210
+ //#endregion
8133
9211
  //#region src/plugin/rules/react-native/rn-no-raw-text.ts
8134
9212
  const truncateText = (text) => text.length > 30 ? `${text.slice(0, 30)}...` : text;
8135
9213
  const isRawTextContent = (child) => {
@@ -8152,25 +9230,22 @@ const isTextHandlingComponent = (elementName) => {
8152
9230
  if (REACT_NATIVE_TEXT_COMPONENTS.has(elementName)) return true;
8153
9231
  return [...REACT_NATIVE_TEXT_COMPONENT_KEYWORDS].some((keyword) => elementName.includes(keyword));
8154
9232
  };
8155
- const WEB_FILE_EXTENSION_PATTERN = /\.web\.[jt]sx?$/;
8156
9233
  const rnNoRawText = defineRule({
8157
9234
  id: "rn-no-raw-text",
8158
9235
  requires: ["react-native"],
8159
9236
  severity: "error",
8160
9237
  recommendation: "Wrap text in a `<Text>` component: `<Text>{value}</Text>` — raw strings outside `<Text>` crash on React Native",
8161
9238
  create: (context) => {
8162
- let isWebOnlyFile = false;
8163
9239
  let isDomComponentFile = false;
8164
9240
  return {
8165
9241
  Program(programNode) {
8166
9242
  isDomComponentFile = hasDirective(programNode, "use dom");
8167
- const filename = context.getFilename?.() ?? "";
8168
- isWebOnlyFile = WEB_FILE_EXTENSION_PATTERN.test(filename);
8169
9243
  },
8170
9244
  JSXElement(node) {
8171
- if (isDomComponentFile || isWebOnlyFile) return;
9245
+ if (isDomComponentFile) return;
8172
9246
  const elementName = resolveJsxElementName(node.openingElement);
8173
9247
  if (elementName && isTextHandlingComponent(elementName)) return;
9248
+ if (isInsidePlatformOsWebBranch(node)) return;
8174
9249
  for (const child of node.children ?? []) {
8175
9250
  if (!isRawTextContent(child)) continue;
8176
9251
  context.report({
@@ -8504,13 +9579,6 @@ const rnStylePreferBoxShadow = defineRule({
8504
9579
  })
8505
9580
  });
8506
9581
  //#endregion
8507
- //#region src/plugin/utils/has-use-server-directive.ts
8508
- const hasUseServerDirective = (node) => {
8509
- if (!isNodeOfType(node, "FunctionDeclaration") && !isNodeOfType(node, "FunctionExpression") && !isNodeOfType(node, "ArrowFunctionExpression")) return false;
8510
- if (!isNodeOfType(node.body, "BlockStatement")) return false;
8511
- return Boolean(node.body.body?.some((statement) => isNodeOfType(statement, "ExpressionStatement") && statement.directive === "use server"));
8512
- };
8513
- //#endregion
8514
9582
  //#region src/plugin/rules/server/server-after-nonblocking.ts
8515
9583
  const CONSOLE_DEFERRABLE_METHODS = new Set([
8516
9584
  "log",
@@ -8581,38 +9649,134 @@ const serverAfterNonblocking = defineRule({
8581
9649
  });
8582
9650
  //#endregion
8583
9651
  //#region src/plugin/rules/server/server-auth-actions.ts
8584
- const containsAuthCheck = (statements) => {
9652
+ const isAsyncFunctionLikeNode = (node) => {
9653
+ if (!node) return false;
9654
+ if (!isNodeOfType(node, "FunctionDeclaration") && !isNodeOfType(node, "FunctionExpression") && !isNodeOfType(node, "ArrowFunctionExpression")) return false;
9655
+ return Boolean(node.async);
9656
+ };
9657
+ const unwrapTypeWrappedCallee = (node) => {
9658
+ let currentNode = node;
9659
+ while (currentNode) {
9660
+ if (isNodeOfType(currentNode, "TSAsExpression") || isNodeOfType(currentNode, "TSNonNullExpression") || isNodeOfType(currentNode, "TSTypeAssertion") || isNodeOfType(currentNode, "TSSatisfiesExpression") || isNodeOfType(currentNode, "TSInstantiationExpression")) {
9661
+ currentNode = currentNode.expression;
9662
+ continue;
9663
+ }
9664
+ if (isNodeOfType(currentNode, "ChainExpression")) {
9665
+ currentNode = currentNode.expression;
9666
+ continue;
9667
+ }
9668
+ return currentNode;
9669
+ }
9670
+ return null;
9671
+ };
9672
+ const buildDottedReceiverSource = (receiverNode) => {
9673
+ const unwrapped = unwrapTypeWrappedCallee(receiverNode);
9674
+ if (!unwrapped) return "";
9675
+ if (isNodeOfType(unwrapped, "Identifier")) return unwrapped.name;
9676
+ if (isNodeOfType(unwrapped, "ThisExpression")) return "this";
9677
+ if (isNodeOfType(unwrapped, "MemberExpression")) {
9678
+ const objectSource = buildDottedReceiverSource(unwrapped.object);
9679
+ const propertyName = isNodeOfType(unwrapped.property, "Identifier") ? unwrapped.property.name : "";
9680
+ if (!propertyName) return objectSource;
9681
+ return objectSource ? `${objectSource}.${propertyName}` : propertyName;
9682
+ }
9683
+ return "";
9684
+ };
9685
+ const isMemberCallAuthRelated = (receiverNode, methodName, genericMethodNames) => {
9686
+ if (!genericMethodNames.has(methodName)) return true;
9687
+ const receiverSource = buildDottedReceiverSource(receiverNode);
9688
+ return AUTH_OBJECT_PATTERN.test(receiverSource);
9689
+ };
9690
+ const getAuthCallName = (callExpression, allowedFunctionNames, genericMethodNames) => {
9691
+ const calleeNode = unwrapTypeWrappedCallee(callExpression.callee);
9692
+ if (!calleeNode) return null;
9693
+ if (isNodeOfType(calleeNode, "Identifier")) return allowedFunctionNames.has(calleeNode.name) ? calleeNode.name : null;
9694
+ if (isNodeOfType(calleeNode, "MemberExpression") && isNodeOfType(calleeNode.property, "Identifier")) {
9695
+ const methodName = calleeNode.property.name;
9696
+ if (!allowedFunctionNames.has(methodName)) return null;
9697
+ if (!isMemberCallAuthRelated(calleeNode.object, methodName, genericMethodNames)) return null;
9698
+ return methodName;
9699
+ }
9700
+ return null;
9701
+ };
9702
+ const isFunctionLikeNode = (node) => isNodeOfType(node, "FunctionDeclaration") || isNodeOfType(node, "FunctionExpression") || isNodeOfType(node, "ArrowFunctionExpression");
9703
+ const containsAuthCheck = (rootNodes, allowedFunctionNames, genericMethodNames) => {
8585
9704
  let foundAuthCall = false;
8586
- for (const statement of statements) walkAst(statement, (child) => {
9705
+ for (const rootNode of rootNodes) walkAst(rootNode, (child) => {
8587
9706
  if (foundAuthCall) return;
8588
- let callNode = null;
8589
- if (isNodeOfType(child, "CallExpression")) callNode = child;
8590
- else if (isNodeOfType(child, "AwaitExpression") && isNodeOfType(child.argument, "CallExpression")) callNode = child.argument;
8591
- if (isNodeOfType(callNode?.callee, "Identifier") && AUTH_FUNCTION_NAMES.has(callNode.callee.name)) foundAuthCall = true;
9707
+ if (isFunctionLikeNode(child)) return false;
9708
+ if (!isNodeOfType(child, "CallExpression")) return;
9709
+ if (getAuthCallName(child, allowedFunctionNames, genericMethodNames)) foundAuthCall = true;
8592
9710
  });
8593
9711
  return foundAuthCall;
8594
9712
  };
9713
+ const getAuthScanRoots = (functionNode) => {
9714
+ const bodyNode = functionNode.body;
9715
+ if (!bodyNode) return [];
9716
+ if (isNodeOfType(bodyNode, "BlockStatement")) return (bodyNode.body ?? []).slice(0, 10);
9717
+ return [bodyNode];
9718
+ };
9719
+ const inspectServerAction = (candidate, fileHasUseServerDirective, allowedFunctionNames, context) => {
9720
+ if (!(fileHasUseServerDirective || hasUseServerDirective(candidate.functionNode))) return;
9721
+ if (containsAuthCheck(getAuthScanRoots(candidate.functionNode), allowedFunctionNames, GENERIC_AUTH_METHOD_NAMES)) return;
9722
+ context.report({
9723
+ node: candidate.reportNode,
9724
+ message: `Server action "${candidate.displayName}" — add auth check (auth(), getSession(), etc.) at the top`
9725
+ });
9726
+ };
9727
+ const collectCandidatesFromVariableDeclaration = (variableDeclaration) => {
9728
+ const candidates = [];
9729
+ for (const declarator of variableDeclaration.declarations ?? []) {
9730
+ if (!isAsyncFunctionLikeNode(declarator.init)) continue;
9731
+ const bindingNode = isNodeOfType(declarator.id, "Identifier") ? declarator.id : null;
9732
+ candidates.push({
9733
+ functionNode: declarator.init,
9734
+ displayName: bindingNode?.name ?? "anonymous",
9735
+ reportNode: bindingNode ?? declarator
9736
+ });
9737
+ }
9738
+ return candidates;
9739
+ };
9740
+ const getCandidateFromDefaultDeclaration = (node) => {
9741
+ const declaration = node.declaration;
9742
+ if (!isAsyncFunctionLikeNode(declaration)) return null;
9743
+ const functionId = (isNodeOfType(declaration, "FunctionDeclaration") || isNodeOfType(declaration, "FunctionExpression")) && declaration.id ? declaration.id : null;
9744
+ return {
9745
+ functionNode: declaration,
9746
+ displayName: functionId?.name ?? "default",
9747
+ reportNode: functionId ?? node
9748
+ };
9749
+ };
8595
9750
  const serverAuthActions = defineRule({
8596
9751
  id: "server-auth-actions",
8597
9752
  severity: "error",
8598
9753
  recommendation: "Add `const session = await auth()` at the top and throw/redirect if unauthorized before any data access",
8599
9754
  create: (context) => {
8600
9755
  let fileHasUseServerDirective = false;
9756
+ const customAuthFunctionNames = getReactDoctorStringArraySetting(context.settings, "serverAuthFunctionNames");
9757
+ const allowedFunctionNames = customAuthFunctionNames.length > 0 ? new Set([...AUTH_FUNCTION_NAMES, ...customAuthFunctionNames]) : AUTH_FUNCTION_NAMES;
9758
+ const inspect = (candidate) => inspectServerAction(candidate, fileHasUseServerDirective, allowedFunctionNames, context);
8601
9759
  return {
8602
9760
  Program(programNode) {
8603
9761
  fileHasUseServerDirective = hasDirective(programNode, "use server");
8604
9762
  },
8605
9763
  ExportNamedDeclaration(node) {
8606
9764
  const declaration = node.declaration;
8607
- if (!isNodeOfType(declaration, "FunctionDeclaration") || !declaration?.async) return;
8608
- if (!(fileHasUseServerDirective || hasUseServerDirective(declaration))) return;
8609
- if (!containsAuthCheck((declaration.body?.body ?? []).slice(0, 10))) {
8610
- const functionName = declaration.id?.name ?? "anonymous";
8611
- context.report({
8612
- node: declaration.id ?? node,
8613
- message: `Server action "${functionName}" — add auth check (auth(), getSession(), etc.) at the top`
9765
+ if (!declaration) return;
9766
+ if (isAsyncFunctionLikeNode(declaration)) {
9767
+ if (!isNodeOfType(declaration, "FunctionDeclaration")) return;
9768
+ inspect({
9769
+ functionNode: declaration,
9770
+ displayName: declaration.id?.name ?? "anonymous",
9771
+ reportNode: declaration.id ?? node
8614
9772
  });
9773
+ return;
8615
9774
  }
9775
+ if (isNodeOfType(declaration, "VariableDeclaration")) for (const candidate of collectCandidatesFromVariableDeclaration(declaration)) inspect(candidate);
9776
+ },
9777
+ ExportDefaultDeclaration(node) {
9778
+ const candidate = getCandidateFromDefaultDeclaration(node);
9779
+ if (candidate) inspect(candidate);
8616
9780
  }
8617
9781
  };
8618
9782
  }
@@ -9005,7 +10169,13 @@ const tanstackStartGetMutation = defineRule({
9005
10169
  if (chainInfo.specifiedMethod && MUTATING_HTTP_METHODS.has(chainInfo.specifiedMethod.toUpperCase())) return;
9006
10170
  const handlerFunction = node.arguments?.[0];
9007
10171
  if (!handlerFunction) return;
9008
- const sideEffect = findSideEffect(handlerFunction);
10172
+ if (!isNodeOfType(handlerFunction, "ArrowFunctionExpression") && !isNodeOfType(handlerFunction, "FunctionExpression")) return;
10173
+ const handlerBody = handlerFunction.body;
10174
+ if (!handlerBody) return;
10175
+ const sideEffect = findSideEffect(handlerBody, {
10176
+ locallyScopedSafeBindings: collectLocallyScopedSafeBindings(handlerBody),
10177
+ locallyScopedCookieBindings: collectLocallyScopedCookieBindings(handlerBody)
10178
+ });
9009
10179
  if (sideEffect) context.report({
9010
10180
  node,
9011
10181
  message: `GET server function has side effects (${sideEffect}) — use createServerFn({ method: 'POST' }) for mutations`
@@ -9447,906 +10617,1065 @@ const useLazyMotion = defineRule({
9447
10617
  } })
9448
10618
  });
9449
10619
  //#endregion
10620
+ //#region src/plugin/rule-registry.ts
10621
+ const ruleRegistry = {
10622
+ "advanced-event-handler-refs": {
10623
+ ...advancedEventHandlerRefs,
10624
+ framework: "global",
10625
+ category: "Performance"
10626
+ },
10627
+ "async-await-in-loop": {
10628
+ ...asyncAwaitInLoop,
10629
+ framework: "global",
10630
+ category: "Performance"
10631
+ },
10632
+ "async-defer-await": {
10633
+ ...asyncDeferAwait,
10634
+ framework: "global",
10635
+ category: "Performance"
10636
+ },
10637
+ "async-parallel": {
10638
+ ...asyncParallel,
10639
+ framework: "global",
10640
+ category: "Performance"
10641
+ },
10642
+ "client-localstorage-no-version": {
10643
+ ...clientLocalstorageNoVersion,
10644
+ framework: "global",
10645
+ category: "Correctness"
10646
+ },
10647
+ "client-passive-event-listeners": {
10648
+ ...clientPassiveEventListeners,
10649
+ framework: "global",
10650
+ category: "Performance"
10651
+ },
10652
+ "design-no-bold-heading": {
10653
+ ...noBoldHeading,
10654
+ framework: "global",
10655
+ category: "Architecture"
10656
+ },
10657
+ "design-no-default-tailwind-palette": {
10658
+ ...noDefaultTailwindPalette,
10659
+ framework: "global",
10660
+ category: "Architecture"
10661
+ },
10662
+ "design-no-em-dash-in-jsx-text": {
10663
+ ...noEmDashInJsxText,
10664
+ framework: "global",
10665
+ category: "Architecture"
10666
+ },
10667
+ "design-no-redundant-padding-axes": {
10668
+ ...noRedundantPaddingAxes,
10669
+ framework: "global",
10670
+ category: "Architecture"
10671
+ },
10672
+ "design-no-redundant-size-axes": {
10673
+ ...noRedundantSizeAxes,
10674
+ framework: "global",
10675
+ category: "Architecture"
10676
+ },
10677
+ "design-no-space-on-flex-children": {
10678
+ ...noSpaceOnFlexChildren,
10679
+ framework: "global",
10680
+ category: "Architecture"
10681
+ },
10682
+ "design-no-three-period-ellipsis": {
10683
+ ...noThreePeriodEllipsis,
10684
+ framework: "global",
10685
+ category: "Architecture"
10686
+ },
10687
+ "design-no-vague-button-label": {
10688
+ ...noVagueButtonLabel,
10689
+ framework: "global",
10690
+ category: "Accessibility"
10691
+ },
10692
+ "effect-needs-cleanup": {
10693
+ ...effectNeedsCleanup,
10694
+ framework: "global",
10695
+ category: "State & Effects"
10696
+ },
10697
+ "js-batch-dom-css": {
10698
+ ...jsBatchDomCss,
10699
+ framework: "global",
10700
+ category: "Performance"
10701
+ },
10702
+ "js-cache-property-access": {
10703
+ ...jsCachePropertyAccess,
10704
+ framework: "global",
10705
+ category: "Performance"
10706
+ },
10707
+ "js-cache-storage": {
10708
+ ...jsCacheStorage,
10709
+ framework: "global",
10710
+ category: "Performance"
10711
+ },
10712
+ "js-combine-iterations": {
10713
+ ...jsCombineIterations,
10714
+ framework: "global",
10715
+ category: "Performance"
10716
+ },
10717
+ "js-early-exit": {
10718
+ ...jsEarlyExit,
10719
+ framework: "global",
10720
+ category: "Performance"
10721
+ },
10722
+ "js-flatmap-filter": {
10723
+ ...jsFlatmapFilter,
10724
+ framework: "global",
10725
+ category: "Performance"
10726
+ },
10727
+ "js-hoist-intl": {
10728
+ ...jsHoistIntl,
10729
+ framework: "global",
10730
+ category: "Performance"
10731
+ },
10732
+ "js-hoist-regexp": {
10733
+ ...jsHoistRegexp,
10734
+ framework: "global",
10735
+ category: "Performance"
10736
+ },
10737
+ "js-index-maps": {
10738
+ ...jsIndexMaps,
10739
+ framework: "global",
10740
+ category: "Performance"
10741
+ },
10742
+ "js-length-check-first": {
10743
+ ...jsLengthCheckFirst,
10744
+ framework: "global",
10745
+ category: "Performance"
10746
+ },
10747
+ "js-min-max-loop": {
10748
+ ...jsMinMaxLoop,
10749
+ framework: "global",
10750
+ category: "Performance"
10751
+ },
10752
+ "js-set-map-lookups": {
10753
+ ...jsSetMapLookups,
10754
+ framework: "global",
10755
+ category: "Performance"
10756
+ },
10757
+ "js-tosorted-immutable": {
10758
+ ...jsTosortedImmutable,
10759
+ framework: "global",
10760
+ category: "Performance"
10761
+ },
10762
+ "nextjs-async-client-component": {
10763
+ ...nextjsAsyncClientComponent,
10764
+ framework: "nextjs",
10765
+ category: "Next.js"
10766
+ },
10767
+ "nextjs-image-missing-sizes": {
10768
+ ...nextjsImageMissingSizes,
10769
+ framework: "nextjs",
10770
+ category: "Next.js"
10771
+ },
10772
+ "nextjs-inline-script-missing-id": {
10773
+ ...nextjsInlineScriptMissingId,
10774
+ framework: "nextjs",
10775
+ category: "Next.js"
10776
+ },
10777
+ "nextjs-missing-metadata": {
10778
+ ...nextjsMissingMetadata,
10779
+ framework: "nextjs",
10780
+ category: "Next.js"
10781
+ },
10782
+ "nextjs-no-a-element": {
10783
+ ...nextjsNoAElement,
10784
+ framework: "nextjs",
10785
+ category: "Next.js"
10786
+ },
10787
+ "nextjs-no-client-fetch-for-server-data": {
10788
+ ...nextjsNoClientFetchForServerData,
10789
+ framework: "nextjs",
10790
+ category: "Next.js"
10791
+ },
10792
+ "nextjs-no-client-side-redirect": {
10793
+ ...nextjsNoClientSideRedirect,
10794
+ framework: "nextjs",
10795
+ category: "Next.js"
10796
+ },
10797
+ "nextjs-no-css-link": {
10798
+ ...nextjsNoCssLink,
10799
+ framework: "nextjs",
10800
+ category: "Next.js"
10801
+ },
10802
+ "nextjs-no-font-link": {
10803
+ ...nextjsNoFontLink,
10804
+ framework: "nextjs",
10805
+ category: "Next.js"
10806
+ },
10807
+ "nextjs-no-head-import": {
10808
+ ...nextjsNoHeadImport,
10809
+ framework: "nextjs",
10810
+ category: "Next.js"
10811
+ },
10812
+ "nextjs-no-img-element": {
10813
+ ...nextjsNoImgElement,
10814
+ framework: "nextjs",
10815
+ category: "Next.js"
10816
+ },
10817
+ "nextjs-no-native-script": {
10818
+ ...nextjsNoNativeScript,
10819
+ framework: "nextjs",
10820
+ category: "Next.js"
10821
+ },
10822
+ "nextjs-no-polyfill-script": {
10823
+ ...nextjsNoPolyfillScript,
10824
+ framework: "nextjs",
10825
+ category: "Next.js"
10826
+ },
10827
+ "nextjs-no-redirect-in-try-catch": {
10828
+ ...nextjsNoRedirectInTryCatch,
10829
+ framework: "nextjs",
10830
+ category: "Next.js"
10831
+ },
10832
+ "nextjs-no-side-effect-in-get-handler": {
10833
+ ...nextjsNoSideEffectInGetHandler,
10834
+ framework: "nextjs",
10835
+ category: "Security"
10836
+ },
10837
+ "nextjs-no-use-search-params-without-suspense": {
10838
+ ...nextjsNoUseSearchParamsWithoutSuspense,
10839
+ framework: "nextjs",
10840
+ category: "Next.js"
10841
+ },
10842
+ "no-array-index-as-key": {
10843
+ ...noArrayIndexAsKey,
10844
+ framework: "global",
10845
+ category: "Correctness"
10846
+ },
10847
+ "no-barrel-import": {
10848
+ ...noBarrelImport,
10849
+ framework: "global",
10850
+ category: "Bundle Size"
10851
+ },
10852
+ "no-cascading-set-state": {
10853
+ ...noCascadingSetState,
10854
+ framework: "global",
10855
+ category: "State & Effects"
10856
+ },
10857
+ "no-dark-mode-glow": {
10858
+ ...noDarkModeGlow,
10859
+ framework: "global",
10860
+ category: "Architecture"
10861
+ },
10862
+ "no-default-props": {
10863
+ ...noDefaultProps,
10864
+ framework: "global",
10865
+ category: "Architecture"
10866
+ },
10867
+ "no-derived-state-effect": {
10868
+ ...noDerivedStateEffect,
10869
+ framework: "global",
10870
+ category: "State & Effects"
10871
+ },
10872
+ "no-derived-useState": {
10873
+ ...noDerivedUseState,
10874
+ framework: "global",
10875
+ category: "State & Effects"
10876
+ },
10877
+ "no-direct-state-mutation": {
10878
+ ...noDirectStateMutation,
10879
+ framework: "global",
10880
+ category: "State & Effects"
10881
+ },
10882
+ "no-disabled-zoom": {
10883
+ ...noDisabledZoom,
10884
+ framework: "global",
10885
+ category: "Accessibility"
10886
+ },
10887
+ "no-document-start-view-transition": {
10888
+ ...noDocumentStartViewTransition,
10889
+ framework: "global",
10890
+ category: "Correctness"
10891
+ },
10892
+ "no-dynamic-import-path": {
10893
+ ...noDynamicImportPath,
10894
+ framework: "global",
10895
+ category: "Bundle Size"
10896
+ },
10897
+ "no-effect-chain": {
10898
+ ...noEffectChain,
10899
+ framework: "global",
10900
+ category: "State & Effects"
10901
+ },
10902
+ "no-effect-event-handler": {
10903
+ ...noEffectEventHandler,
10904
+ framework: "global",
10905
+ category: "State & Effects"
10906
+ },
10907
+ "no-effect-event-in-deps": {
10908
+ ...noEffectEventInDeps,
10909
+ framework: "global",
10910
+ category: "State & Effects"
10911
+ },
10912
+ "no-eval": {
10913
+ ...noEval,
10914
+ framework: "global",
10915
+ category: "Security"
10916
+ },
10917
+ "no-event-trigger-state": {
10918
+ ...noEventTriggerState,
10919
+ framework: "global",
10920
+ category: "State & Effects"
10921
+ },
10922
+ "no-fetch-in-effect": {
10923
+ ...noFetchInEffect,
10924
+ framework: "global",
10925
+ category: "State & Effects"
10926
+ },
10927
+ "no-flush-sync": {
10928
+ ...noFlushSync,
10929
+ framework: "global",
10930
+ category: "Performance"
10931
+ },
10932
+ "no-full-lodash-import": {
10933
+ ...noFullLodashImport,
10934
+ framework: "global",
10935
+ category: "Bundle Size"
10936
+ },
10937
+ "no-generic-handler-names": {
10938
+ ...noGenericHandlerNames,
10939
+ framework: "global",
10940
+ category: "Architecture"
10941
+ },
10942
+ "no-giant-component": {
10943
+ ...noGiantComponent,
10944
+ framework: "global",
10945
+ category: "Architecture"
10946
+ },
10947
+ "no-global-css-variable-animation": {
10948
+ ...noGlobalCssVariableAnimation,
10949
+ framework: "global",
10950
+ category: "Performance"
10951
+ },
10952
+ "no-gradient-text": {
10953
+ ...noGradientText,
10954
+ framework: "global",
10955
+ category: "Architecture"
10956
+ },
10957
+ "no-gray-on-colored-background": {
10958
+ ...noGrayOnColoredBackground,
10959
+ framework: "global",
10960
+ category: "Accessibility"
10961
+ },
10962
+ "no-inline-bounce-easing": {
10963
+ ...noInlineBounceEasing,
10964
+ framework: "global",
10965
+ category: "Performance"
10966
+ },
10967
+ "no-inline-exhaustive-style": {
10968
+ ...noInlineExhaustiveStyle,
10969
+ framework: "global",
10970
+ category: "Architecture"
10971
+ },
10972
+ "no-inline-prop-on-memo-component": {
10973
+ ...noInlinePropOnMemoComponent,
10974
+ framework: "global",
10975
+ category: "Performance"
10976
+ },
10977
+ "no-justified-text": {
10978
+ ...noJustifiedText,
10979
+ framework: "global",
10980
+ category: "Accessibility"
10981
+ },
10982
+ "no-large-animated-blur": {
10983
+ ...noLargeAnimatedBlur,
10984
+ framework: "global",
10985
+ category: "Performance"
10986
+ },
10987
+ "no-layout-property-animation": {
10988
+ ...noLayoutPropertyAnimation,
10989
+ framework: "global",
10990
+ category: "Performance"
10991
+ },
10992
+ "no-layout-transition-inline": {
10993
+ ...noLayoutTransitionInline,
10994
+ framework: "global",
10995
+ category: "Performance"
10996
+ },
10997
+ "no-legacy-class-lifecycles": {
10998
+ ...noLegacyClassLifecycles,
10999
+ framework: "global",
11000
+ category: "Correctness"
11001
+ },
11002
+ "no-legacy-context-api": {
11003
+ ...noLegacyContextApi,
11004
+ framework: "global",
11005
+ category: "Correctness"
11006
+ },
11007
+ "no-long-transition-duration": {
11008
+ ...noLongTransitionDuration,
11009
+ framework: "global",
11010
+ category: "Performance"
11011
+ },
11012
+ "no-many-boolean-props": {
11013
+ ...noManyBooleanProps,
11014
+ framework: "global",
11015
+ category: "Architecture"
11016
+ },
11017
+ "no-mirror-prop-effect": {
11018
+ ...noMirrorPropEffect,
11019
+ framework: "global",
11020
+ category: "State & Effects"
11021
+ },
11022
+ "no-moment": {
11023
+ ...noMoment,
11024
+ framework: "global",
11025
+ category: "Bundle Size"
11026
+ },
11027
+ "no-mutable-in-deps": {
11028
+ ...noMutableInDeps,
11029
+ framework: "global",
11030
+ category: "State & Effects"
11031
+ },
11032
+ "no-nested-component-definition": {
11033
+ ...noNestedComponentDefinition,
11034
+ framework: "global",
11035
+ category: "Correctness"
11036
+ },
11037
+ "no-outline-none": {
11038
+ ...noOutlineNone,
11039
+ framework: "global",
11040
+ category: "Accessibility"
11041
+ },
11042
+ "no-permanent-will-change": {
11043
+ ...noPermanentWillChange,
11044
+ framework: "global",
11045
+ category: "Performance"
11046
+ },
11047
+ "no-polymorphic-children": {
11048
+ ...noPolymorphicChildren,
11049
+ framework: "global",
11050
+ category: "Architecture"
11051
+ },
11052
+ "no-prevent-default": {
11053
+ ...noPreventDefault,
11054
+ framework: "global",
11055
+ category: "Correctness"
11056
+ },
11057
+ "no-prop-callback-in-effect": {
11058
+ ...noPropCallbackInEffect,
11059
+ framework: "global",
11060
+ category: "State & Effects"
11061
+ },
11062
+ "no-pure-black-background": {
11063
+ ...noPureBlackBackground,
11064
+ framework: "global",
11065
+ category: "Architecture"
11066
+ },
11067
+ "no-react-dom-deprecated-apis": {
11068
+ ...noReactDomDeprecatedApis,
11069
+ framework: "global",
11070
+ category: "Architecture"
11071
+ },
11072
+ "no-react19-deprecated-apis": {
11073
+ ...noReact19DeprecatedApis,
11074
+ framework: "global",
11075
+ category: "Architecture"
11076
+ },
11077
+ "no-render-in-render": {
11078
+ ...noRenderInRender,
11079
+ framework: "global",
11080
+ category: "Architecture"
11081
+ },
11082
+ "no-render-prop-children": {
11083
+ ...noRenderPropChildren,
11084
+ framework: "global",
11085
+ category: "Architecture"
11086
+ },
11087
+ "no-scale-from-zero": {
11088
+ ...noScaleFromZero,
11089
+ framework: "global",
11090
+ category: "Performance"
11091
+ },
11092
+ "no-secrets-in-client-code": {
11093
+ ...noSecretsInClientCode,
11094
+ framework: "global",
11095
+ category: "Security"
11096
+ },
11097
+ "no-set-state-in-render": {
11098
+ ...noSetStateInRender,
11099
+ framework: "global",
11100
+ category: "State & Effects"
11101
+ },
11102
+ "no-side-tab-border": {
11103
+ ...noSideTabBorder,
11104
+ framework: "global",
11105
+ category: "Architecture"
11106
+ },
11107
+ "no-tiny-text": {
11108
+ ...noTinyText,
11109
+ framework: "global",
11110
+ category: "Accessibility"
11111
+ },
11112
+ "no-transition-all": {
11113
+ ...noTransitionAll,
11114
+ framework: "global",
11115
+ category: "Performance"
11116
+ },
11117
+ "no-uncontrolled-input": {
11118
+ ...noUncontrolledInput,
11119
+ framework: "global",
11120
+ category: "Correctness"
11121
+ },
11122
+ "no-undeferred-third-party": {
11123
+ ...noUndeferredThirdParty,
11124
+ framework: "global",
11125
+ category: "Bundle Size"
11126
+ },
11127
+ "no-usememo-simple-expression": {
11128
+ ...noUsememoSimpleExpression,
11129
+ framework: "global",
11130
+ category: "Performance"
11131
+ },
11132
+ "no-wide-letter-spacing": {
11133
+ ...noWideLetterSpacing,
11134
+ framework: "global",
11135
+ category: "Architecture"
11136
+ },
11137
+ "no-z-index-9999": {
11138
+ ...noZIndex9999,
11139
+ framework: "global",
11140
+ category: "Architecture"
11141
+ },
11142
+ "prefer-dynamic-import": {
11143
+ ...preferDynamicImport,
11144
+ framework: "global",
11145
+ category: "Bundle Size"
11146
+ },
11147
+ "prefer-use-effect-event": {
11148
+ ...preferUseEffectEvent,
11149
+ framework: "global",
11150
+ category: "State & Effects"
11151
+ },
11152
+ "prefer-use-sync-external-store": {
11153
+ ...preferUseSyncExternalStore,
11154
+ framework: "global",
11155
+ category: "State & Effects"
11156
+ },
11157
+ "prefer-useReducer": {
11158
+ ...preferUseReducer,
11159
+ framework: "global",
11160
+ category: "State & Effects"
11161
+ },
11162
+ "query-mutation-missing-invalidation": {
11163
+ ...queryMutationMissingInvalidation,
11164
+ framework: "tanstack-query",
11165
+ category: "TanStack Query"
11166
+ },
11167
+ "query-no-query-in-effect": {
11168
+ ...queryNoQueryInEffect,
11169
+ framework: "tanstack-query",
11170
+ category: "TanStack Query"
11171
+ },
11172
+ "query-no-rest-destructuring": {
11173
+ ...queryNoRestDestructuring,
11174
+ framework: "tanstack-query",
11175
+ category: "TanStack Query"
11176
+ },
11177
+ "query-no-usequery-for-mutation": {
11178
+ ...queryNoUseQueryForMutation,
11179
+ framework: "tanstack-query",
11180
+ category: "TanStack Query"
11181
+ },
11182
+ "query-no-void-query-fn": {
11183
+ ...queryNoVoidQueryFn,
11184
+ framework: "tanstack-query",
11185
+ category: "TanStack Query"
11186
+ },
11187
+ "query-stable-query-client": {
11188
+ ...queryStableQueryClient,
11189
+ framework: "tanstack-query",
11190
+ category: "TanStack Query"
11191
+ },
11192
+ "react-compiler-destructure-method": {
11193
+ ...reactCompilerDestructureMethod,
11194
+ framework: "global",
11195
+ category: "Architecture"
11196
+ },
11197
+ "rendering-animate-svg-wrapper": {
11198
+ ...renderingAnimateSvgWrapper,
11199
+ framework: "global",
11200
+ category: "Performance"
11201
+ },
11202
+ "rendering-conditional-render": {
11203
+ ...renderingConditionalRender,
11204
+ framework: "global",
11205
+ category: "Correctness"
11206
+ },
11207
+ "rendering-hoist-jsx": {
11208
+ ...renderingHoistJsx,
11209
+ framework: "global",
11210
+ category: "Performance"
11211
+ },
11212
+ "rendering-hydration-mismatch-time": {
11213
+ ...renderingHydrationMismatchTime,
11214
+ framework: "global",
11215
+ category: "Correctness"
11216
+ },
11217
+ "rendering-hydration-no-flicker": {
11218
+ ...renderingHydrationNoFlicker,
11219
+ framework: "global",
11220
+ category: "Performance"
11221
+ },
11222
+ "rendering-script-defer-async": {
11223
+ ...renderingScriptDeferAsync,
11224
+ framework: "global",
11225
+ category: "Performance"
11226
+ },
11227
+ "rendering-svg-precision": {
11228
+ ...renderingSvgPrecision,
11229
+ framework: "global",
11230
+ category: "Performance"
11231
+ },
11232
+ "rendering-usetransition-loading": {
11233
+ ...renderingUsetransitionLoading,
11234
+ framework: "global",
11235
+ category: "Performance"
11236
+ },
11237
+ "rerender-defer-reads-hook": {
11238
+ ...rerenderDeferReadsHook,
11239
+ framework: "global",
11240
+ category: "Performance"
11241
+ },
11242
+ "rerender-dependencies": {
11243
+ ...rerenderDependencies,
11244
+ framework: "global",
11245
+ category: "State & Effects"
11246
+ },
11247
+ "rerender-derived-state-from-hook": {
11248
+ ...rerenderDerivedStateFromHook,
11249
+ framework: "global",
11250
+ category: "Performance"
11251
+ },
11252
+ "rerender-functional-setstate": {
11253
+ ...rerenderFunctionalSetstate,
11254
+ framework: "global",
11255
+ category: "Performance"
11256
+ },
11257
+ "rerender-lazy-state-init": {
11258
+ ...rerenderLazyStateInit,
11259
+ framework: "global",
11260
+ category: "Performance"
11261
+ },
11262
+ "rerender-memo-before-early-return": {
11263
+ ...rerenderMemoBeforeEarlyReturn,
11264
+ framework: "global",
11265
+ category: "Performance"
11266
+ },
11267
+ "rerender-memo-with-default-value": {
11268
+ ...rerenderMemoWithDefaultValue,
11269
+ framework: "global",
11270
+ category: "Performance"
11271
+ },
11272
+ "rerender-state-only-in-handlers": {
11273
+ ...rerenderStateOnlyInHandlers,
11274
+ framework: "global",
11275
+ category: "Performance"
11276
+ },
11277
+ "rerender-transitions-scroll": {
11278
+ ...rerenderTransitionsScroll,
11279
+ framework: "global",
11280
+ category: "Performance"
11281
+ },
11282
+ "rn-animate-layout-property": {
11283
+ ...rnAnimateLayoutProperty,
11284
+ framework: "react-native",
11285
+ category: "React Native"
11286
+ },
11287
+ "rn-animation-reaction-as-derived": {
11288
+ ...rnAnimationReactionAsDerived,
11289
+ framework: "react-native",
11290
+ category: "React Native"
11291
+ },
11292
+ "rn-bottom-sheet-prefer-native": {
11293
+ ...rnBottomSheetPreferNative,
11294
+ framework: "react-native",
11295
+ category: "React Native"
11296
+ },
11297
+ "rn-list-callback-per-row": {
11298
+ ...rnListCallbackPerRow,
11299
+ framework: "react-native",
11300
+ category: "React Native"
11301
+ },
11302
+ "rn-list-data-mapped": {
11303
+ ...rnListDataMapped,
11304
+ framework: "react-native",
11305
+ category: "React Native"
11306
+ },
11307
+ "rn-list-recyclable-without-types": {
11308
+ ...rnListRecyclableWithoutTypes,
11309
+ framework: "react-native",
11310
+ category: "React Native"
11311
+ },
11312
+ "rn-no-deprecated-modules": {
11313
+ ...rnNoDeprecatedModules,
11314
+ framework: "react-native",
11315
+ category: "React Native"
11316
+ },
11317
+ "rn-no-dimensions-get": {
11318
+ ...rnNoDimensionsGet,
11319
+ framework: "react-native",
11320
+ category: "React Native"
11321
+ },
11322
+ "rn-no-inline-flatlist-renderitem": {
11323
+ ...rnNoInlineFlatlistRenderitem,
11324
+ framework: "react-native",
11325
+ category: "React Native"
11326
+ },
11327
+ "rn-no-inline-object-in-list-item": {
11328
+ ...rnNoInlineObjectInListItem,
11329
+ framework: "react-native",
11330
+ category: "React Native"
11331
+ },
11332
+ "rn-no-legacy-expo-packages": {
11333
+ ...rnNoLegacyExpoPackages,
11334
+ framework: "react-native",
11335
+ category: "React Native"
11336
+ },
11337
+ "rn-no-legacy-shadow-styles": {
11338
+ ...rnNoLegacyShadowStyles,
11339
+ framework: "react-native",
11340
+ category: "React Native"
11341
+ },
11342
+ "rn-no-non-native-navigator": {
11343
+ ...rnNoNonNativeNavigator,
11344
+ framework: "react-native",
11345
+ category: "React Native"
11346
+ },
11347
+ "rn-no-raw-text": {
11348
+ ...rnNoRawText,
11349
+ framework: "react-native",
11350
+ category: "React Native"
11351
+ },
11352
+ "rn-no-scroll-state": {
11353
+ ...rnNoScrollState,
11354
+ framework: "react-native",
11355
+ category: "React Native"
11356
+ },
11357
+ "rn-no-scrollview-mapped-list": {
11358
+ ...rnNoScrollviewMappedList,
11359
+ framework: "react-native",
11360
+ category: "React Native"
11361
+ },
11362
+ "rn-no-single-element-style-array": {
11363
+ ...rnNoSingleElementStyleArray,
11364
+ framework: "react-native",
11365
+ category: "React Native"
11366
+ },
11367
+ "rn-prefer-content-inset-adjustment": {
11368
+ ...rnPreferContentInsetAdjustment,
11369
+ framework: "react-native",
11370
+ category: "React Native"
11371
+ },
11372
+ "rn-prefer-expo-image": {
11373
+ ...rnPreferExpoImage,
11374
+ framework: "react-native",
11375
+ category: "React Native"
11376
+ },
11377
+ "rn-prefer-pressable": {
11378
+ ...rnPreferPressable,
11379
+ framework: "react-native",
11380
+ category: "React Native"
11381
+ },
11382
+ "rn-prefer-reanimated": {
11383
+ ...rnPreferReanimated,
11384
+ framework: "react-native",
11385
+ category: "React Native"
11386
+ },
11387
+ "rn-pressable-shared-value-mutation": {
11388
+ ...rnPressableSharedValueMutation,
11389
+ framework: "react-native",
11390
+ category: "React Native"
11391
+ },
11392
+ "rn-scrollview-dynamic-padding": {
11393
+ ...rnScrollviewDynamicPadding,
11394
+ framework: "react-native",
11395
+ category: "React Native"
11396
+ },
11397
+ "rn-style-prefer-boxshadow": {
11398
+ ...rnStylePreferBoxShadow,
11399
+ framework: "react-native",
11400
+ category: "React Native"
11401
+ },
11402
+ "server-after-nonblocking": {
11403
+ ...serverAfterNonblocking,
11404
+ framework: "global",
11405
+ category: "Server"
11406
+ },
11407
+ "server-auth-actions": {
11408
+ ...serverAuthActions,
11409
+ framework: "global",
11410
+ category: "Server"
11411
+ },
11412
+ "server-cache-with-object-literal": {
11413
+ ...serverCacheWithObjectLiteral,
11414
+ framework: "global",
11415
+ category: "Server"
11416
+ },
11417
+ "server-dedup-props": {
11418
+ ...serverDedupProps,
11419
+ framework: "global",
11420
+ category: "Server"
11421
+ },
11422
+ "server-fetch-without-revalidate": {
11423
+ ...serverFetchWithoutRevalidate,
11424
+ framework: "global",
11425
+ category: "Server"
11426
+ },
11427
+ "server-hoist-static-io": {
11428
+ ...serverHoistStaticIo,
11429
+ framework: "global",
11430
+ category: "Server"
11431
+ },
11432
+ "server-no-mutable-module-state": {
11433
+ ...serverNoMutableModuleState,
11434
+ framework: "global",
11435
+ category: "Server"
11436
+ },
11437
+ "server-sequential-independent-await": {
11438
+ ...serverSequentialIndependentAwait,
11439
+ framework: "global",
11440
+ category: "Server"
11441
+ },
11442
+ "tanstack-start-get-mutation": {
11443
+ ...tanstackStartGetMutation,
11444
+ framework: "tanstack-start",
11445
+ category: "Security"
11446
+ },
11447
+ "tanstack-start-loader-parallel-fetch": {
11448
+ ...tanstackStartLoaderParallelFetch,
11449
+ framework: "tanstack-start",
11450
+ category: "Performance"
11451
+ },
11452
+ "tanstack-start-missing-head-content": {
11453
+ ...tanstackStartMissingHeadContent,
11454
+ framework: "tanstack-start",
11455
+ category: "TanStack Start"
11456
+ },
11457
+ "tanstack-start-no-anchor-element": {
11458
+ ...tanstackStartNoAnchorElement,
11459
+ framework: "tanstack-start",
11460
+ category: "TanStack Start"
11461
+ },
11462
+ "tanstack-start-no-direct-fetch-in-loader": {
11463
+ ...tanstackStartNoDirectFetchInLoader,
11464
+ framework: "tanstack-start",
11465
+ category: "TanStack Start"
11466
+ },
11467
+ "tanstack-start-no-dynamic-server-fn-import": {
11468
+ ...tanstackStartNoDynamicServerFnImport,
11469
+ framework: "tanstack-start",
11470
+ category: "TanStack Start"
11471
+ },
11472
+ "tanstack-start-no-navigate-in-render": {
11473
+ ...tanstackStartNoNavigateInRender,
11474
+ framework: "tanstack-start",
11475
+ category: "TanStack Start"
11476
+ },
11477
+ "tanstack-start-no-secrets-in-loader": {
11478
+ ...tanstackStartNoSecretsInLoader,
11479
+ framework: "tanstack-start",
11480
+ category: "Security"
11481
+ },
11482
+ "tanstack-start-no-use-server-in-handler": {
11483
+ ...tanstackStartNoUseServerInHandler,
11484
+ framework: "tanstack-start",
11485
+ category: "TanStack Start"
11486
+ },
11487
+ "tanstack-start-no-useeffect-fetch": {
11488
+ ...tanstackStartNoUseEffectFetch,
11489
+ framework: "tanstack-start",
11490
+ category: "TanStack Start"
11491
+ },
11492
+ "tanstack-start-redirect-in-try-catch": {
11493
+ ...tanstackStartRedirectInTryCatch,
11494
+ framework: "tanstack-start",
11495
+ category: "TanStack Start"
11496
+ },
11497
+ "tanstack-start-route-property-order": {
11498
+ ...tanstackStartRoutePropertyOrder,
11499
+ framework: "tanstack-start",
11500
+ category: "TanStack Start"
11501
+ },
11502
+ "tanstack-start-server-fn-method-order": {
11503
+ ...tanstackStartServerFnMethodOrder,
11504
+ framework: "tanstack-start",
11505
+ category: "TanStack Start"
11506
+ },
11507
+ "tanstack-start-server-fn-validate-input": {
11508
+ ...tanstackStartServerFnValidateInput,
11509
+ framework: "tanstack-start",
11510
+ category: "TanStack Start"
11511
+ },
11512
+ "use-lazy-motion": {
11513
+ ...useLazyMotion,
11514
+ framework: "global",
11515
+ category: "Bundle Size"
11516
+ }
11517
+ };
11518
+ //#endregion
11519
+ //#region ../types/dist/index.js
11520
+ const REACT_NATIVE_DEPENDENCY_NAMES = new Set([
11521
+ "react-native",
11522
+ "react-native-tvos",
11523
+ "expo",
11524
+ "expo-router",
11525
+ "@expo/cli",
11526
+ "@expo/metro-config",
11527
+ "@expo/metro-runtime",
11528
+ "react-native-windows",
11529
+ "react-native-macos"
11530
+ ]);
11531
+ const REACT_NATIVE_DEPENDENCY_PREFIXES = ["@react-native/", "@react-native-"];
11532
+ const isReactNativeDependencyName = (dependencyName) => {
11533
+ if (REACT_NATIVE_DEPENDENCY_NAMES.has(dependencyName)) return true;
11534
+ for (const prefix of REACT_NATIVE_DEPENDENCY_PREFIXES) if (dependencyName.startsWith(prefix)) return true;
11535
+ return false;
11536
+ };
11537
+ //#endregion
11538
+ //#region src/plugin/utils/classify-package-platform.ts
11539
+ const WEB_FRAMEWORK_DEPENDENCY_NAMES = new Set([
11540
+ "next",
11541
+ "vite",
11542
+ "react-scripts",
11543
+ "gatsby",
11544
+ "@remix-run/react",
11545
+ "@remix-run/node",
11546
+ "@docusaurus/core",
11547
+ "@docusaurus/preset-classic",
11548
+ "@storybook/react",
11549
+ "@storybook/react-vite",
11550
+ "@storybook/react-webpack5",
11551
+ "@storybook/nextjs",
11552
+ "@storybook/web-components",
11553
+ "storybook",
11554
+ "react-dom",
11555
+ "@vitejs/plugin-react",
11556
+ "@vitejs/plugin-react-swc"
11557
+ ]);
11558
+ const cachedPlatformByPackageDirectory = /* @__PURE__ */ new Map();
11559
+ const cachedPackageDirectoryByFilename = /* @__PURE__ */ new Map();
11560
+ const findNearestPackageDirectory = (filename) => {
11561
+ if (!filename) return null;
11562
+ const fromCache = cachedPackageDirectoryByFilename.get(filename);
11563
+ if (fromCache !== void 0) return fromCache;
11564
+ let currentDirectory = path.dirname(filename);
11565
+ while (true) {
11566
+ const candidatePackageJsonPath = path.join(currentDirectory, "package.json");
11567
+ let hasPackageJson = false;
11568
+ try {
11569
+ hasPackageJson = fs.statSync(candidatePackageJsonPath).isFile();
11570
+ } catch {
11571
+ hasPackageJson = false;
11572
+ }
11573
+ if (hasPackageJson) {
11574
+ cachedPackageDirectoryByFilename.set(filename, currentDirectory);
11575
+ return currentDirectory;
11576
+ }
11577
+ const parentDirectory = path.dirname(currentDirectory);
11578
+ if (parentDirectory === currentDirectory) {
11579
+ cachedPackageDirectoryByFilename.set(filename, null);
11580
+ return null;
11581
+ }
11582
+ currentDirectory = parentDirectory;
11583
+ }
11584
+ };
11585
+ const readPackageJsonSafe = (packageJsonPath) => {
11586
+ let rawContents;
11587
+ try {
11588
+ rawContents = fs.readFileSync(packageJsonPath, "utf-8");
11589
+ } catch {
11590
+ return null;
11591
+ }
11592
+ try {
11593
+ const parsed = JSON.parse(rawContents);
11594
+ if (typeof parsed === "object" && parsed !== null) return parsed;
11595
+ return null;
11596
+ } catch {
11597
+ return null;
11598
+ }
11599
+ };
11600
+ const DEPENDENCY_SECTION_NAMES = [
11601
+ "dependencies",
11602
+ "devDependencies",
11603
+ "peerDependencies",
11604
+ "optionalDependencies"
11605
+ ];
11606
+ const iterateDependencyNames = function* (packageJson) {
11607
+ for (const sectionName of DEPENDENCY_SECTION_NAMES) {
11608
+ const section = packageJson[sectionName];
11609
+ if (!section) continue;
11610
+ for (const dependencyName of Object.keys(section)) yield dependencyName;
11611
+ }
11612
+ };
11613
+ const isReactNativeAware = (packageJson) => {
11614
+ if (typeof packageJson["react-native"] === "string") return true;
11615
+ for (const dependencyName of iterateDependencyNames(packageJson)) if (isReactNativeDependencyName(dependencyName)) return true;
11616
+ return false;
11617
+ };
11618
+ const isWebFrameworkOnly = (packageJson) => {
11619
+ for (const dependencyName of iterateDependencyNames(packageJson)) if (WEB_FRAMEWORK_DEPENDENCY_NAMES.has(dependencyName)) return true;
11620
+ return false;
11621
+ };
11622
+ const classifyPackagePlatform = (filename) => {
11623
+ const packageDirectory = findNearestPackageDirectory(filename);
11624
+ if (!packageDirectory) return "unknown";
11625
+ const cached = cachedPlatformByPackageDirectory.get(packageDirectory);
11626
+ if (cached !== void 0) return cached;
11627
+ const packageJson = readPackageJsonSafe(path.join(packageDirectory, "package.json"));
11628
+ if (!packageJson) {
11629
+ cachedPlatformByPackageDirectory.set(packageDirectory, "unknown");
11630
+ return "unknown";
11631
+ }
11632
+ let result;
11633
+ if (isReactNativeAware(packageJson)) result = "react-native";
11634
+ else if (isWebFrameworkOnly(packageJson)) result = "web";
11635
+ else result = "unknown";
11636
+ cachedPlatformByPackageDirectory.set(packageDirectory, result);
11637
+ return result;
11638
+ };
11639
+ //#endregion
11640
+ //#region src/plugin/utils/is-react-native-file.ts
11641
+ const WEB_FILE_EXTENSION_PATTERN = /\.web\.[cm]?[jt]sx?$/;
11642
+ const NATIVE_FILE_EXTENSION_PATTERN = /\.(?:ios|android|native)\.[cm]?[jt]sx?$/;
11643
+ const isReactNativeFileActive = (context) => {
11644
+ const filename = context.getFilename?.();
11645
+ if (!filename) return true;
11646
+ if (NATIVE_FILE_EXTENSION_PATTERN.test(filename)) return true;
11647
+ if (WEB_FILE_EXTENSION_PATTERN.test(filename)) return false;
11648
+ const packagePlatform = classifyPackagePlatform(filename);
11649
+ if (packagePlatform === "web") return false;
11650
+ if (packagePlatform === "react-native") return true;
11651
+ const framework = getReactDoctorStringSetting(context.settings, "framework");
11652
+ if (framework === "react-native" || framework === "expo") return true;
11653
+ if (framework === "nextjs" || framework === "vite" || framework === "cra" || framework === "remix" || framework === "gatsby" || framework === "tanstack-start") return false;
11654
+ return true;
11655
+ };
11656
+ //#endregion
11657
+ //#region src/plugin/utils/wrap-react-native-rule.ts
11658
+ const EMPTY_VISITORS = {};
11659
+ const wrapReactNativeRule = (rule) => {
11660
+ const innerCreate = rule.create.bind(rule);
11661
+ return {
11662
+ ...rule,
11663
+ create: (context) => {
11664
+ if (!isReactNativeFileActive(context)) return EMPTY_VISITORS;
11665
+ return innerCreate(context);
11666
+ }
11667
+ };
11668
+ };
11669
+ //#endregion
9450
11670
  //#region src/plugin/react-doctor-plugin.ts
11671
+ const applyFrameworkRuleWrappers = (registry) => {
11672
+ const wrapped = {};
11673
+ for (const [ruleId, rule] of Object.entries(registry)) wrapped[ruleId] = rule.framework === "react-native" ? wrapReactNativeRule(rule) : rule;
11674
+ return wrapped;
11675
+ };
9451
11676
  const plugin = {
9452
11677
  meta: { name: "react-doctor" },
9453
- rules: {
9454
- "advanced-event-handler-refs": {
9455
- ...advancedEventHandlerRefs,
9456
- framework: "global",
9457
- category: "Performance"
9458
- },
9459
- "async-await-in-loop": {
9460
- ...asyncAwaitInLoop,
9461
- framework: "global",
9462
- category: "Performance"
9463
- },
9464
- "async-defer-await": {
9465
- ...asyncDeferAwait,
9466
- framework: "global",
9467
- category: "Performance"
9468
- },
9469
- "async-parallel": {
9470
- ...asyncParallel,
9471
- framework: "global",
9472
- category: "Performance"
9473
- },
9474
- "client-localstorage-no-version": {
9475
- ...clientLocalstorageNoVersion,
9476
- framework: "global",
9477
- category: "Correctness"
9478
- },
9479
- "client-passive-event-listeners": {
9480
- ...clientPassiveEventListeners,
9481
- framework: "global",
9482
- category: "Performance"
9483
- },
9484
- "design-no-bold-heading": {
9485
- ...noBoldHeading,
9486
- framework: "global",
9487
- category: "Architecture"
9488
- },
9489
- "design-no-default-tailwind-palette": {
9490
- ...noDefaultTailwindPalette,
9491
- framework: "global",
9492
- category: "Architecture"
9493
- },
9494
- "design-no-em-dash-in-jsx-text": {
9495
- ...noEmDashInJsxText,
9496
- framework: "global",
9497
- category: "Architecture"
9498
- },
9499
- "design-no-redundant-padding-axes": {
9500
- ...noRedundantPaddingAxes,
9501
- framework: "global",
9502
- category: "Architecture"
9503
- },
9504
- "design-no-redundant-size-axes": {
9505
- ...noRedundantSizeAxes,
9506
- framework: "global",
9507
- category: "Architecture"
9508
- },
9509
- "design-no-space-on-flex-children": {
9510
- ...noSpaceOnFlexChildren,
9511
- framework: "global",
9512
- category: "Architecture"
9513
- },
9514
- "design-no-three-period-ellipsis": {
9515
- ...noThreePeriodEllipsis,
9516
- framework: "global",
9517
- category: "Architecture"
9518
- },
9519
- "design-no-vague-button-label": {
9520
- ...noVagueButtonLabel,
9521
- framework: "global",
9522
- category: "Accessibility"
9523
- },
9524
- "effect-needs-cleanup": {
9525
- ...effectNeedsCleanup,
9526
- framework: "global",
9527
- category: "State & Effects"
9528
- },
9529
- "js-batch-dom-css": {
9530
- ...jsBatchDomCss,
9531
- framework: "global",
9532
- category: "Performance"
9533
- },
9534
- "js-cache-property-access": {
9535
- ...jsCachePropertyAccess,
9536
- framework: "global",
9537
- category: "Performance"
9538
- },
9539
- "js-cache-storage": {
9540
- ...jsCacheStorage,
9541
- framework: "global",
9542
- category: "Performance"
9543
- },
9544
- "js-combine-iterations": {
9545
- ...jsCombineIterations,
9546
- framework: "global",
9547
- category: "Performance"
9548
- },
9549
- "js-early-exit": {
9550
- ...jsEarlyExit,
9551
- framework: "global",
9552
- category: "Performance"
9553
- },
9554
- "js-flatmap-filter": {
9555
- ...jsFlatmapFilter,
9556
- framework: "global",
9557
- category: "Performance"
9558
- },
9559
- "js-hoist-intl": {
9560
- ...jsHoistIntl,
9561
- framework: "global",
9562
- category: "Performance"
9563
- },
9564
- "js-hoist-regexp": {
9565
- ...jsHoistRegexp,
9566
- framework: "global",
9567
- category: "Performance"
9568
- },
9569
- "js-index-maps": {
9570
- ...jsIndexMaps,
9571
- framework: "global",
9572
- category: "Performance"
9573
- },
9574
- "js-length-check-first": {
9575
- ...jsLengthCheckFirst,
9576
- framework: "global",
9577
- category: "Performance"
9578
- },
9579
- "js-min-max-loop": {
9580
- ...jsMinMaxLoop,
9581
- framework: "global",
9582
- category: "Performance"
9583
- },
9584
- "js-set-map-lookups": {
9585
- ...jsSetMapLookups,
9586
- framework: "global",
9587
- category: "Performance"
9588
- },
9589
- "js-tosorted-immutable": {
9590
- ...jsTosortedImmutable,
9591
- framework: "global",
9592
- category: "Performance"
9593
- },
9594
- "nextjs-async-client-component": {
9595
- ...nextjsAsyncClientComponent,
9596
- framework: "nextjs",
9597
- category: "Next.js"
9598
- },
9599
- "nextjs-image-missing-sizes": {
9600
- ...nextjsImageMissingSizes,
9601
- framework: "nextjs",
9602
- category: "Next.js"
9603
- },
9604
- "nextjs-inline-script-missing-id": {
9605
- ...nextjsInlineScriptMissingId,
9606
- framework: "nextjs",
9607
- category: "Next.js"
9608
- },
9609
- "nextjs-missing-metadata": {
9610
- ...nextjsMissingMetadata,
9611
- framework: "nextjs",
9612
- category: "Next.js"
9613
- },
9614
- "nextjs-no-a-element": {
9615
- ...nextjsNoAElement,
9616
- framework: "nextjs",
9617
- category: "Next.js"
9618
- },
9619
- "nextjs-no-client-fetch-for-server-data": {
9620
- ...nextjsNoClientFetchForServerData,
9621
- framework: "nextjs",
9622
- category: "Next.js"
9623
- },
9624
- "nextjs-no-client-side-redirect": {
9625
- ...nextjsNoClientSideRedirect,
9626
- framework: "nextjs",
9627
- category: "Next.js"
9628
- },
9629
- "nextjs-no-css-link": {
9630
- ...nextjsNoCssLink,
9631
- framework: "nextjs",
9632
- category: "Next.js"
9633
- },
9634
- "nextjs-no-font-link": {
9635
- ...nextjsNoFontLink,
9636
- framework: "nextjs",
9637
- category: "Next.js"
9638
- },
9639
- "nextjs-no-head-import": {
9640
- ...nextjsNoHeadImport,
9641
- framework: "nextjs",
9642
- category: "Next.js"
9643
- },
9644
- "nextjs-no-img-element": {
9645
- ...nextjsNoImgElement,
9646
- framework: "nextjs",
9647
- category: "Next.js"
9648
- },
9649
- "nextjs-no-native-script": {
9650
- ...nextjsNoNativeScript,
9651
- framework: "nextjs",
9652
- category: "Next.js"
9653
- },
9654
- "nextjs-no-polyfill-script": {
9655
- ...nextjsNoPolyfillScript,
9656
- framework: "nextjs",
9657
- category: "Next.js"
9658
- },
9659
- "nextjs-no-redirect-in-try-catch": {
9660
- ...nextjsNoRedirectInTryCatch,
9661
- framework: "nextjs",
9662
- category: "Next.js"
9663
- },
9664
- "nextjs-no-side-effect-in-get-handler": {
9665
- ...nextjsNoSideEffectInGetHandler,
9666
- framework: "nextjs",
9667
- category: "Security"
9668
- },
9669
- "nextjs-no-use-search-params-without-suspense": {
9670
- ...nextjsNoUseSearchParamsWithoutSuspense,
9671
- framework: "nextjs",
9672
- category: "Next.js"
9673
- },
9674
- "no-array-index-as-key": {
9675
- ...noArrayIndexAsKey,
9676
- framework: "global",
9677
- category: "Correctness"
9678
- },
9679
- "no-barrel-import": {
9680
- ...noBarrelImport,
9681
- framework: "global",
9682
- category: "Bundle Size"
9683
- },
9684
- "no-cascading-set-state": {
9685
- ...noCascadingSetState,
9686
- framework: "global",
9687
- category: "State & Effects"
9688
- },
9689
- "no-dark-mode-glow": {
9690
- ...noDarkModeGlow,
9691
- framework: "global",
9692
- category: "Architecture"
9693
- },
9694
- "no-default-props": {
9695
- ...noDefaultProps,
9696
- framework: "global",
9697
- category: "Architecture"
9698
- },
9699
- "no-derived-state-effect": {
9700
- ...noDerivedStateEffect,
9701
- framework: "global",
9702
- category: "State & Effects"
9703
- },
9704
- "no-derived-useState": {
9705
- ...noDerivedUseState,
9706
- framework: "global",
9707
- category: "State & Effects"
9708
- },
9709
- "no-direct-state-mutation": {
9710
- ...noDirectStateMutation,
9711
- framework: "global",
9712
- category: "State & Effects"
9713
- },
9714
- "no-disabled-zoom": {
9715
- ...noDisabledZoom,
9716
- framework: "global",
9717
- category: "Accessibility"
9718
- },
9719
- "no-document-start-view-transition": {
9720
- ...noDocumentStartViewTransition,
9721
- framework: "global",
9722
- category: "Correctness"
9723
- },
9724
- "no-dynamic-import-path": {
9725
- ...noDynamicImportPath,
9726
- framework: "global",
9727
- category: "Bundle Size"
9728
- },
9729
- "no-effect-chain": {
9730
- ...noEffectChain,
9731
- framework: "global",
9732
- category: "State & Effects"
9733
- },
9734
- "no-effect-event-handler": {
9735
- ...noEffectEventHandler,
9736
- framework: "global",
9737
- category: "State & Effects"
9738
- },
9739
- "no-effect-event-in-deps": {
9740
- ...noEffectEventInDeps,
9741
- framework: "global",
9742
- category: "State & Effects"
9743
- },
9744
- "no-eval": {
9745
- ...noEval,
9746
- framework: "global",
9747
- category: "Security"
9748
- },
9749
- "no-event-trigger-state": {
9750
- ...noEventTriggerState,
9751
- framework: "global",
9752
- category: "State & Effects"
9753
- },
9754
- "no-fetch-in-effect": {
9755
- ...noFetchInEffect,
9756
- framework: "global",
9757
- category: "State & Effects"
9758
- },
9759
- "no-flush-sync": {
9760
- ...noFlushSync,
9761
- framework: "global",
9762
- category: "Performance"
9763
- },
9764
- "no-full-lodash-import": {
9765
- ...noFullLodashImport,
9766
- framework: "global",
9767
- category: "Bundle Size"
9768
- },
9769
- "no-generic-handler-names": {
9770
- ...noGenericHandlerNames,
9771
- framework: "global",
9772
- category: "Architecture"
9773
- },
9774
- "no-giant-component": {
9775
- ...noGiantComponent,
9776
- framework: "global",
9777
- category: "Architecture"
9778
- },
9779
- "no-global-css-variable-animation": {
9780
- ...noGlobalCssVariableAnimation,
9781
- framework: "global",
9782
- category: "Performance"
9783
- },
9784
- "no-gradient-text": {
9785
- ...noGradientText,
9786
- framework: "global",
9787
- category: "Architecture"
9788
- },
9789
- "no-gray-on-colored-background": {
9790
- ...noGrayOnColoredBackground,
9791
- framework: "global",
9792
- category: "Accessibility"
9793
- },
9794
- "no-inline-bounce-easing": {
9795
- ...noInlineBounceEasing,
9796
- framework: "global",
9797
- category: "Performance"
9798
- },
9799
- "no-inline-exhaustive-style": {
9800
- ...noInlineExhaustiveStyle,
9801
- framework: "global",
9802
- category: "Architecture"
9803
- },
9804
- "no-inline-prop-on-memo-component": {
9805
- ...noInlinePropOnMemoComponent,
9806
- framework: "global",
9807
- category: "Performance"
9808
- },
9809
- "no-justified-text": {
9810
- ...noJustifiedText,
9811
- framework: "global",
9812
- category: "Accessibility"
9813
- },
9814
- "no-large-animated-blur": {
9815
- ...noLargeAnimatedBlur,
9816
- framework: "global",
9817
- category: "Performance"
9818
- },
9819
- "no-layout-property-animation": {
9820
- ...noLayoutPropertyAnimation,
9821
- framework: "global",
9822
- category: "Performance"
9823
- },
9824
- "no-layout-transition-inline": {
9825
- ...noLayoutTransitionInline,
9826
- framework: "global",
9827
- category: "Performance"
9828
- },
9829
- "no-legacy-class-lifecycles": {
9830
- ...noLegacyClassLifecycles,
9831
- framework: "global",
9832
- category: "Correctness"
9833
- },
9834
- "no-legacy-context-api": {
9835
- ...noLegacyContextApi,
9836
- framework: "global",
9837
- category: "Correctness"
9838
- },
9839
- "no-long-transition-duration": {
9840
- ...noLongTransitionDuration,
9841
- framework: "global",
9842
- category: "Performance"
9843
- },
9844
- "no-many-boolean-props": {
9845
- ...noManyBooleanProps,
9846
- framework: "global",
9847
- category: "Architecture"
9848
- },
9849
- "no-mirror-prop-effect": {
9850
- ...noMirrorPropEffect,
9851
- framework: "global",
9852
- category: "State & Effects"
9853
- },
9854
- "no-moment": {
9855
- ...noMoment,
9856
- framework: "global",
9857
- category: "Bundle Size"
9858
- },
9859
- "no-mutable-in-deps": {
9860
- ...noMutableInDeps,
9861
- framework: "global",
9862
- category: "State & Effects"
9863
- },
9864
- "no-nested-component-definition": {
9865
- ...noNestedComponentDefinition,
9866
- framework: "global",
9867
- category: "Correctness"
9868
- },
9869
- "no-outline-none": {
9870
- ...noOutlineNone,
9871
- framework: "global",
9872
- category: "Accessibility"
9873
- },
9874
- "no-permanent-will-change": {
9875
- ...noPermanentWillChange,
9876
- framework: "global",
9877
- category: "Performance"
9878
- },
9879
- "no-polymorphic-children": {
9880
- ...noPolymorphicChildren,
9881
- framework: "global",
9882
- category: "Architecture"
9883
- },
9884
- "no-prevent-default": {
9885
- ...noPreventDefault,
9886
- framework: "global",
9887
- category: "Correctness"
9888
- },
9889
- "no-prop-callback-in-effect": {
9890
- ...noPropCallbackInEffect,
9891
- framework: "global",
9892
- category: "State & Effects"
9893
- },
9894
- "no-pure-black-background": {
9895
- ...noPureBlackBackground,
9896
- framework: "global",
9897
- category: "Architecture"
9898
- },
9899
- "no-react-dom-deprecated-apis": {
9900
- ...noReactDomDeprecatedApis,
9901
- framework: "global",
9902
- category: "Architecture"
9903
- },
9904
- "no-react19-deprecated-apis": {
9905
- ...noReact19DeprecatedApis,
9906
- framework: "global",
9907
- category: "Architecture"
9908
- },
9909
- "no-render-in-render": {
9910
- ...noRenderInRender,
9911
- framework: "global",
9912
- category: "Architecture"
9913
- },
9914
- "no-render-prop-children": {
9915
- ...noRenderPropChildren,
9916
- framework: "global",
9917
- category: "Architecture"
9918
- },
9919
- "no-scale-from-zero": {
9920
- ...noScaleFromZero,
9921
- framework: "global",
9922
- category: "Performance"
9923
- },
9924
- "no-secrets-in-client-code": {
9925
- ...noSecretsInClientCode,
9926
- framework: "global",
9927
- category: "Security"
9928
- },
9929
- "no-set-state-in-render": {
9930
- ...noSetStateInRender,
9931
- framework: "global",
9932
- category: "State & Effects"
9933
- },
9934
- "no-side-tab-border": {
9935
- ...noSideTabBorder,
9936
- framework: "global",
9937
- category: "Architecture"
9938
- },
9939
- "no-tiny-text": {
9940
- ...noTinyText,
9941
- framework: "global",
9942
- category: "Accessibility"
9943
- },
9944
- "no-transition-all": {
9945
- ...noTransitionAll,
9946
- framework: "global",
9947
- category: "Performance"
9948
- },
9949
- "no-uncontrolled-input": {
9950
- ...noUncontrolledInput,
9951
- framework: "global",
9952
- category: "Correctness"
9953
- },
9954
- "no-undeferred-third-party": {
9955
- ...noUndeferredThirdParty,
9956
- framework: "global",
9957
- category: "Bundle Size"
9958
- },
9959
- "no-usememo-simple-expression": {
9960
- ...noUsememoSimpleExpression,
9961
- framework: "global",
9962
- category: "Performance"
9963
- },
9964
- "no-wide-letter-spacing": {
9965
- ...noWideLetterSpacing,
9966
- framework: "global",
9967
- category: "Architecture"
9968
- },
9969
- "no-z-index-9999": {
9970
- ...noZIndex9999,
9971
- framework: "global",
9972
- category: "Architecture"
9973
- },
9974
- "prefer-dynamic-import": {
9975
- ...preferDynamicImport,
9976
- framework: "global",
9977
- category: "Bundle Size"
9978
- },
9979
- "prefer-use-effect-event": {
9980
- ...preferUseEffectEvent,
9981
- framework: "global",
9982
- category: "State & Effects"
9983
- },
9984
- "prefer-use-sync-external-store": {
9985
- ...preferUseSyncExternalStore,
9986
- framework: "global",
9987
- category: "State & Effects"
9988
- },
9989
- "prefer-useReducer": {
9990
- ...preferUseReducer,
9991
- framework: "global",
9992
- category: "State & Effects"
9993
- },
9994
- "query-mutation-missing-invalidation": {
9995
- ...queryMutationMissingInvalidation,
9996
- framework: "tanstack-query",
9997
- category: "TanStack Query"
9998
- },
9999
- "query-no-query-in-effect": {
10000
- ...queryNoQueryInEffect,
10001
- framework: "tanstack-query",
10002
- category: "TanStack Query"
10003
- },
10004
- "query-no-rest-destructuring": {
10005
- ...queryNoRestDestructuring,
10006
- framework: "tanstack-query",
10007
- category: "TanStack Query"
10008
- },
10009
- "query-no-usequery-for-mutation": {
10010
- ...queryNoUseQueryForMutation,
10011
- framework: "tanstack-query",
10012
- category: "TanStack Query"
10013
- },
10014
- "query-no-void-query-fn": {
10015
- ...queryNoVoidQueryFn,
10016
- framework: "tanstack-query",
10017
- category: "TanStack Query"
10018
- },
10019
- "query-stable-query-client": {
10020
- ...queryStableQueryClient,
10021
- framework: "tanstack-query",
10022
- category: "TanStack Query"
10023
- },
10024
- "react-compiler-destructure-method": {
10025
- ...reactCompilerDestructureMethod,
10026
- framework: "global",
10027
- category: "Architecture"
10028
- },
10029
- "rendering-animate-svg-wrapper": {
10030
- ...renderingAnimateSvgWrapper,
10031
- framework: "global",
10032
- category: "Performance"
10033
- },
10034
- "rendering-conditional-render": {
10035
- ...renderingConditionalRender,
10036
- framework: "global",
10037
- category: "Correctness"
10038
- },
10039
- "rendering-hoist-jsx": {
10040
- ...renderingHoistJsx,
10041
- framework: "global",
10042
- category: "Performance"
10043
- },
10044
- "rendering-hydration-mismatch-time": {
10045
- ...renderingHydrationMismatchTime,
10046
- framework: "global",
10047
- category: "Correctness"
10048
- },
10049
- "rendering-hydration-no-flicker": {
10050
- ...renderingHydrationNoFlicker,
10051
- framework: "global",
10052
- category: "Performance"
10053
- },
10054
- "rendering-script-defer-async": {
10055
- ...renderingScriptDeferAsync,
10056
- framework: "global",
10057
- category: "Performance"
10058
- },
10059
- "rendering-svg-precision": {
10060
- ...renderingSvgPrecision,
10061
- framework: "global",
10062
- category: "Performance"
10063
- },
10064
- "rendering-usetransition-loading": {
10065
- ...renderingUsetransitionLoading,
10066
- framework: "global",
10067
- category: "Performance"
10068
- },
10069
- "rerender-defer-reads-hook": {
10070
- ...rerenderDeferReadsHook,
10071
- framework: "global",
10072
- category: "Performance"
10073
- },
10074
- "rerender-dependencies": {
10075
- ...rerenderDependencies,
10076
- framework: "global",
10077
- category: "State & Effects"
10078
- },
10079
- "rerender-derived-state-from-hook": {
10080
- ...rerenderDerivedStateFromHook,
10081
- framework: "global",
10082
- category: "Performance"
10083
- },
10084
- "rerender-functional-setstate": {
10085
- ...rerenderFunctionalSetstate,
10086
- framework: "global",
10087
- category: "Performance"
10088
- },
10089
- "rerender-lazy-state-init": {
10090
- ...rerenderLazyStateInit,
10091
- framework: "global",
10092
- category: "Performance"
10093
- },
10094
- "rerender-memo-before-early-return": {
10095
- ...rerenderMemoBeforeEarlyReturn,
10096
- framework: "global",
10097
- category: "Performance"
10098
- },
10099
- "rerender-memo-with-default-value": {
10100
- ...rerenderMemoWithDefaultValue,
10101
- framework: "global",
10102
- category: "Performance"
10103
- },
10104
- "rerender-state-only-in-handlers": {
10105
- ...rerenderStateOnlyInHandlers,
10106
- framework: "global",
10107
- category: "Performance"
10108
- },
10109
- "rerender-transitions-scroll": {
10110
- ...rerenderTransitionsScroll,
10111
- framework: "global",
10112
- category: "Performance"
10113
- },
10114
- "rn-animate-layout-property": {
10115
- ...rnAnimateLayoutProperty,
10116
- framework: "react-native",
10117
- category: "React Native"
10118
- },
10119
- "rn-animation-reaction-as-derived": {
10120
- ...rnAnimationReactionAsDerived,
10121
- framework: "react-native",
10122
- category: "React Native"
10123
- },
10124
- "rn-bottom-sheet-prefer-native": {
10125
- ...rnBottomSheetPreferNative,
10126
- framework: "react-native",
10127
- category: "React Native"
10128
- },
10129
- "rn-list-callback-per-row": {
10130
- ...rnListCallbackPerRow,
10131
- framework: "react-native",
10132
- category: "React Native"
10133
- },
10134
- "rn-list-data-mapped": {
10135
- ...rnListDataMapped,
10136
- framework: "react-native",
10137
- category: "React Native"
10138
- },
10139
- "rn-list-recyclable-without-types": {
10140
- ...rnListRecyclableWithoutTypes,
10141
- framework: "react-native",
10142
- category: "React Native"
10143
- },
10144
- "rn-no-deprecated-modules": {
10145
- ...rnNoDeprecatedModules,
10146
- framework: "react-native",
10147
- category: "React Native"
10148
- },
10149
- "rn-no-dimensions-get": {
10150
- ...rnNoDimensionsGet,
10151
- framework: "react-native",
10152
- category: "React Native"
10153
- },
10154
- "rn-no-inline-flatlist-renderitem": {
10155
- ...rnNoInlineFlatlistRenderitem,
10156
- framework: "react-native",
10157
- category: "React Native"
10158
- },
10159
- "rn-no-inline-object-in-list-item": {
10160
- ...rnNoInlineObjectInListItem,
10161
- framework: "react-native",
10162
- category: "React Native"
10163
- },
10164
- "rn-no-legacy-expo-packages": {
10165
- ...rnNoLegacyExpoPackages,
10166
- framework: "react-native",
10167
- category: "React Native"
10168
- },
10169
- "rn-no-legacy-shadow-styles": {
10170
- ...rnNoLegacyShadowStyles,
10171
- framework: "react-native",
10172
- category: "React Native"
10173
- },
10174
- "rn-no-non-native-navigator": {
10175
- ...rnNoNonNativeNavigator,
10176
- framework: "react-native",
10177
- category: "React Native"
10178
- },
10179
- "rn-no-raw-text": {
10180
- ...rnNoRawText,
10181
- framework: "react-native",
10182
- category: "React Native"
10183
- },
10184
- "rn-no-scroll-state": {
10185
- ...rnNoScrollState,
10186
- framework: "react-native",
10187
- category: "React Native"
10188
- },
10189
- "rn-no-scrollview-mapped-list": {
10190
- ...rnNoScrollviewMappedList,
10191
- framework: "react-native",
10192
- category: "React Native"
10193
- },
10194
- "rn-no-single-element-style-array": {
10195
- ...rnNoSingleElementStyleArray,
10196
- framework: "react-native",
10197
- category: "React Native"
10198
- },
10199
- "rn-prefer-content-inset-adjustment": {
10200
- ...rnPreferContentInsetAdjustment,
10201
- framework: "react-native",
10202
- category: "React Native"
10203
- },
10204
- "rn-prefer-expo-image": {
10205
- ...rnPreferExpoImage,
10206
- framework: "react-native",
10207
- category: "React Native"
10208
- },
10209
- "rn-prefer-pressable": {
10210
- ...rnPreferPressable,
10211
- framework: "react-native",
10212
- category: "React Native"
10213
- },
10214
- "rn-prefer-reanimated": {
10215
- ...rnPreferReanimated,
10216
- framework: "react-native",
10217
- category: "React Native"
10218
- },
10219
- "rn-pressable-shared-value-mutation": {
10220
- ...rnPressableSharedValueMutation,
10221
- framework: "react-native",
10222
- category: "React Native"
10223
- },
10224
- "rn-scrollview-dynamic-padding": {
10225
- ...rnScrollviewDynamicPadding,
10226
- framework: "react-native",
10227
- category: "React Native"
10228
- },
10229
- "rn-style-prefer-boxshadow": {
10230
- ...rnStylePreferBoxShadow,
10231
- framework: "react-native",
10232
- category: "React Native"
10233
- },
10234
- "server-after-nonblocking": {
10235
- ...serverAfterNonblocking,
10236
- framework: "global",
10237
- category: "Server"
10238
- },
10239
- "server-auth-actions": {
10240
- ...serverAuthActions,
10241
- framework: "global",
10242
- category: "Server"
10243
- },
10244
- "server-cache-with-object-literal": {
10245
- ...serverCacheWithObjectLiteral,
10246
- framework: "global",
10247
- category: "Server"
10248
- },
10249
- "server-dedup-props": {
10250
- ...serverDedupProps,
10251
- framework: "global",
10252
- category: "Server"
10253
- },
10254
- "server-fetch-without-revalidate": {
10255
- ...serverFetchWithoutRevalidate,
10256
- framework: "global",
10257
- category: "Server"
10258
- },
10259
- "server-hoist-static-io": {
10260
- ...serverHoistStaticIo,
10261
- framework: "global",
10262
- category: "Server"
10263
- },
10264
- "server-no-mutable-module-state": {
10265
- ...serverNoMutableModuleState,
10266
- framework: "global",
10267
- category: "Server"
10268
- },
10269
- "server-sequential-independent-await": {
10270
- ...serverSequentialIndependentAwait,
10271
- framework: "global",
10272
- category: "Server"
10273
- },
10274
- "tanstack-start-get-mutation": {
10275
- ...tanstackStartGetMutation,
10276
- framework: "tanstack-start",
10277
- category: "Security"
10278
- },
10279
- "tanstack-start-loader-parallel-fetch": {
10280
- ...tanstackStartLoaderParallelFetch,
10281
- framework: "tanstack-start",
10282
- category: "Performance"
10283
- },
10284
- "tanstack-start-missing-head-content": {
10285
- ...tanstackStartMissingHeadContent,
10286
- framework: "tanstack-start",
10287
- category: "TanStack Start"
10288
- },
10289
- "tanstack-start-no-anchor-element": {
10290
- ...tanstackStartNoAnchorElement,
10291
- framework: "tanstack-start",
10292
- category: "TanStack Start"
10293
- },
10294
- "tanstack-start-no-direct-fetch-in-loader": {
10295
- ...tanstackStartNoDirectFetchInLoader,
10296
- framework: "tanstack-start",
10297
- category: "TanStack Start"
10298
- },
10299
- "tanstack-start-no-dynamic-server-fn-import": {
10300
- ...tanstackStartNoDynamicServerFnImport,
10301
- framework: "tanstack-start",
10302
- category: "TanStack Start"
10303
- },
10304
- "tanstack-start-no-navigate-in-render": {
10305
- ...tanstackStartNoNavigateInRender,
10306
- framework: "tanstack-start",
10307
- category: "TanStack Start"
10308
- },
10309
- "tanstack-start-no-secrets-in-loader": {
10310
- ...tanstackStartNoSecretsInLoader,
10311
- framework: "tanstack-start",
10312
- category: "Security"
10313
- },
10314
- "tanstack-start-no-use-server-in-handler": {
10315
- ...tanstackStartNoUseServerInHandler,
10316
- framework: "tanstack-start",
10317
- category: "TanStack Start"
10318
- },
10319
- "tanstack-start-no-useeffect-fetch": {
10320
- ...tanstackStartNoUseEffectFetch,
10321
- framework: "tanstack-start",
10322
- category: "TanStack Start"
10323
- },
10324
- "tanstack-start-redirect-in-try-catch": {
10325
- ...tanstackStartRedirectInTryCatch,
10326
- framework: "tanstack-start",
10327
- category: "TanStack Start"
10328
- },
10329
- "tanstack-start-route-property-order": {
10330
- ...tanstackStartRoutePropertyOrder,
10331
- framework: "tanstack-start",
10332
- category: "TanStack Start"
10333
- },
10334
- "tanstack-start-server-fn-method-order": {
10335
- ...tanstackStartServerFnMethodOrder,
10336
- framework: "tanstack-start",
10337
- category: "TanStack Start"
10338
- },
10339
- "tanstack-start-server-fn-validate-input": {
10340
- ...tanstackStartServerFnValidateInput,
10341
- framework: "tanstack-start",
10342
- category: "TanStack Start"
10343
- },
10344
- "use-lazy-motion": {
10345
- ...useLazyMotion,
10346
- framework: "global",
10347
- category: "Bundle Size"
10348
- }
10349
- }
11678
+ rules: applyFrameworkRuleWrappers(ruleRegistry)
10350
11679
  };
10351
11680
  //#endregion
10352
11681
  //#region src/rules-by-framework.ts