oxlint-plugin-react-doctor 0.2.0-beta.4 → 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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2595 -1266
- 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/
|
|
476
|
-
const
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
651
|
+
if (isEarlyExitStatement(consequent)) return true;
|
|
487
652
|
if (!isNodeOfType(consequent, "BlockStatement")) return false;
|
|
488
|
-
for (const inner of consequent.body ?? []) if (
|
|
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
|
|
499
|
-
if (!
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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:
|
|
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
|
-
|
|
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
|
|
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
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
if (
|
|
612
|
-
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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) =>
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
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
|
|
1494
|
-
if (
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
if (
|
|
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
|
|
2075
|
-
if (
|
|
2076
|
-
|
|
2077
|
-
|
|
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
|
|
2082
|
-
if (!isNodeOfType(node, "CallExpression")
|
|
2083
|
-
|
|
2084
|
-
|
|
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
|
|
2094
|
-
if (!
|
|
2095
|
-
|
|
2096
|
-
if (!isNodeOfType(
|
|
2097
|
-
if (
|
|
2098
|
-
return
|
|
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
|
|
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
|
|
2105
|
-
if (
|
|
2106
|
-
sideEffectDescription = `cookies().${
|
|
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
|
-
|
|
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
|
|
2141
|
-
|
|
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
|
|
2144
|
-
if (isNodeOfType(declaration, "FunctionDeclaration") && declaration.id?.name === "GET") return
|
|
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"
|
|
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) =>
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
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
|
|
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
|
|
5446
|
-
create: (context) =>
|
|
5447
|
-
const
|
|
5448
|
-
|
|
5449
|
-
const
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
if (
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5460
|
-
|
|
5461
|
-
|
|
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/
|
|
5790
|
-
const
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5804
|
-
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
if (
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
if (
|
|
5823
|
-
return
|
|
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/
|
|
5861
|
-
const
|
|
5862
|
-
|
|
5863
|
-
|
|
5864
|
-
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
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
|
|
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
|
|
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
|
|
9705
|
+
for (const rootNode of rootNodes) walkAst(rootNode, (child) => {
|
|
8587
9706
|
if (foundAuthCall) return;
|
|
8588
|
-
|
|
8589
|
-
if (isNodeOfType(child, "CallExpression"))
|
|
8590
|
-
|
|
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 (!
|
|
8608
|
-
if (
|
|
8609
|
-
|
|
8610
|
-
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
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
|
-
|
|
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
|