@agilebot/eslint-plugin 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1142 -1142
- package/package.json +3 -3
package/dist/index.js
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
/**
|
2
|
-
* @license @agilebot/eslint-plugin v0.
|
2
|
+
* @license @agilebot/eslint-plugin v0.3.0
|
3
3
|
*
|
4
4
|
* Copyright (c) Agilebot, Inc. and its affiliates.
|
5
5
|
*
|
@@ -521,244 +521,524 @@ var noDefault = {
|
|
521
521
|
}
|
522
522
|
};
|
523
523
|
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
524
|
+
function getBasicIdentifier(node) {
|
525
|
+
if (node.type === 'Identifier') {
|
526
|
+
return node.name;
|
527
|
+
}
|
528
|
+
if (node.type === 'Literal') {
|
529
|
+
return node.value;
|
530
|
+
}
|
531
|
+
if (node.type === 'TemplateLiteral') {
|
532
|
+
if (node.expressions.length > 0) {
|
533
|
+
return null;
|
534
|
+
}
|
535
|
+
return node.quasis[0].value.raw;
|
536
|
+
}
|
537
|
+
return null;
|
538
|
+
}
|
539
|
+
function getBaseIdentifier(node) {
|
540
|
+
switch (node.type) {
|
541
|
+
case 'Identifier': {
|
542
|
+
return node;
|
543
|
+
}
|
544
|
+
case 'CallExpression': {
|
545
|
+
return getBaseIdentifier(node.callee);
|
546
|
+
}
|
547
|
+
case 'MemberExpression': {
|
548
|
+
return getBaseIdentifier(node.object);
|
549
|
+
}
|
550
|
+
}
|
551
|
+
return null;
|
552
|
+
}
|
553
|
+
function getStyesObj(node) {
|
554
|
+
const isMakeStyles = node.callee.name === 'makeStyles';
|
555
|
+
const isModernApi =
|
556
|
+
node.callee.type === 'MemberExpression' &&
|
557
|
+
node.callee.property.name === 'create' &&
|
558
|
+
getBaseIdentifier(node.callee.object) &&
|
559
|
+
getBaseIdentifier(node.callee.object).name === 'tss';
|
560
|
+
if (!isMakeStyles && !isModernApi) {
|
561
|
+
return;
|
562
|
+
}
|
563
|
+
const styles = (() => {
|
564
|
+
if (isMakeStyles) {
|
565
|
+
return node.parent.arguments[0];
|
566
|
+
}
|
567
|
+
if (isModernApi) {
|
568
|
+
return node.callee.parent.arguments[0];
|
569
|
+
}
|
570
|
+
})();
|
571
|
+
if (!styles) {
|
572
|
+
return;
|
573
|
+
}
|
574
|
+
switch (styles.type) {
|
575
|
+
case 'ObjectExpression':
|
576
|
+
return styles;
|
577
|
+
case 'ArrowFunctionExpression':
|
536
578
|
{
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
type: 'boolean'
|
553
|
-
},
|
554
|
-
{
|
555
|
-
type: 'array',
|
556
|
-
items: {
|
557
|
-
type: 'boolean'
|
558
|
-
}
|
559
|
-
},
|
560
|
-
{
|
561
|
-
type: 'object',
|
562
|
-
additionalProperties: {
|
563
|
-
type: 'boolean'
|
564
|
-
}
|
565
|
-
}
|
566
|
-
]
|
567
|
-
}
|
568
|
-
},
|
569
|
-
checkMemoizedVariableIsStatic: {
|
570
|
-
type: 'boolean'
|
579
|
+
const { body } = styles;
|
580
|
+
switch (body.type) {
|
581
|
+
case 'ObjectExpression':
|
582
|
+
return body;
|
583
|
+
case 'BlockStatement': {
|
584
|
+
let stylesObj;
|
585
|
+
body.body.forEach(bodyNode => {
|
586
|
+
if (
|
587
|
+
bodyNode.type === 'ReturnStatement' &&
|
588
|
+
bodyNode.argument.type === 'ObjectExpression'
|
589
|
+
) {
|
590
|
+
stylesObj = bodyNode.argument;
|
591
|
+
}
|
592
|
+
});
|
593
|
+
return stylesObj;
|
571
594
|
}
|
572
595
|
}
|
573
596
|
}
|
574
|
-
|
597
|
+
break;
|
598
|
+
}
|
599
|
+
}
|
600
|
+
|
601
|
+
var classNaming = {
|
602
|
+
meta: {
|
603
|
+
type: 'problem'
|
575
604
|
},
|
576
|
-
create(context) {
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
: undefined;
|
583
|
-
const enableDangerousAutofixThisMayCauseInfiniteLoops =
|
584
|
-
(context.options &&
|
585
|
-
context.options[0] &&
|
586
|
-
context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
587
|
-
false;
|
588
|
-
const staticHooks =
|
589
|
-
(context.options &&
|
590
|
-
context.options[0] &&
|
591
|
-
context.options[0].staticHooks) ||
|
592
|
-
{};
|
593
|
-
const checkMemoizedVariableIsStatic =
|
594
|
-
(context.options &&
|
595
|
-
context.options[0] &&
|
596
|
-
context.options[0].checkMemoizedVariableIsStatic) ||
|
597
|
-
false;
|
598
|
-
const options = {
|
599
|
-
additionalHooks,
|
600
|
-
enableDangerousAutofixThisMayCauseInfiniteLoops,
|
601
|
-
staticHooks,
|
602
|
-
checkMemoizedVariableIsStatic
|
603
|
-
};
|
604
|
-
function reportProblem(problem) {
|
605
|
-
if (
|
606
|
-
enableDangerousAutofixThisMayCauseInfiniteLoops &&
|
607
|
-
Array.isArray(problem.suggest) &&
|
608
|
-
problem.suggest.length > 0
|
609
|
-
) {
|
610
|
-
problem.fix = problem.suggest[0].fix;
|
611
|
-
}
|
612
|
-
context.report(problem);
|
613
|
-
}
|
614
|
-
const scopeManager = context.getSourceCode().scopeManager;
|
615
|
-
const setStateCallSites = new WeakMap();
|
616
|
-
const stateVariables = new WeakSet();
|
617
|
-
const stableKnownValueCache = new WeakMap();
|
618
|
-
const functionWithoutCapturedValueCache = new WeakMap();
|
619
|
-
function memoizeWithWeakMap(fn, map) {
|
620
|
-
return function (arg) {
|
621
|
-
if (map.has(arg)) {
|
622
|
-
return map.get(arg);
|
605
|
+
create: function rule(context) {
|
606
|
+
return {
|
607
|
+
CallExpression(node) {
|
608
|
+
const stylesObj = getStyesObj(node);
|
609
|
+
if (stylesObj === undefined) {
|
610
|
+
return;
|
623
611
|
}
|
624
|
-
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
`Put the async function inside:\n\n` +
|
642
|
-
'useEffect(() => {\n' +
|
643
|
-
' async function fetchData() {\n' +
|
644
|
-
' // You can await here\n' +
|
645
|
-
' const response = await MyAPI.getData(someId);\n' +
|
646
|
-
' // ...\n' +
|
647
|
-
' }\n' +
|
648
|
-
' fetchData();\n' +
|
649
|
-
`}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
|
650
|
-
'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching'
|
612
|
+
stylesObj.properties.forEach(property => {
|
613
|
+
if (property.computed) {
|
614
|
+
return;
|
615
|
+
}
|
616
|
+
if (
|
617
|
+
property.type === 'ExperimentalSpreadProperty' ||
|
618
|
+
property.type === 'SpreadElement'
|
619
|
+
) {
|
620
|
+
return;
|
621
|
+
}
|
622
|
+
const className = property.key.value || property.key.name;
|
623
|
+
if (!eslintUtils.isCamelCase(className)) {
|
624
|
+
context.report({
|
625
|
+
node: property,
|
626
|
+
message: `Class \`${className}\` must be camelCase in TSS.`
|
627
|
+
});
|
628
|
+
}
|
651
629
|
});
|
652
630
|
}
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
659
|
-
|
660
|
-
|
661
|
-
|
662
|
-
|
663
|
-
|
664
|
-
|
665
|
-
|
631
|
+
};
|
632
|
+
}
|
633
|
+
};
|
634
|
+
|
635
|
+
var noColorValue = {
|
636
|
+
meta: {
|
637
|
+
type: 'problem',
|
638
|
+
docs: {
|
639
|
+
description:
|
640
|
+
'Enforce the use of color variables instead of color codes within TSS'
|
641
|
+
}
|
642
|
+
},
|
643
|
+
create: function (context) {
|
644
|
+
const parserOptions = context.parserOptions;
|
645
|
+
if (!parserOptions || !parserOptions.project) {
|
646
|
+
return {};
|
647
|
+
}
|
648
|
+
return {
|
649
|
+
CallExpression(node) {
|
650
|
+
const stylesObj = getStyesObj(node);
|
651
|
+
if (!stylesObj) {
|
666
652
|
return;
|
667
653
|
}
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
if (!isArray(resolved.defs)) {
|
681
|
-
return false;
|
654
|
+
function checkColorLiteral(value) {
|
655
|
+
if (value.type === 'Literal' && typeof value.value === 'string') {
|
656
|
+
const colorCodePattern =
|
657
|
+
/#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|rgb\?\(\s*(\d{1,3}\s*,\s*){2}\d{1,3}(?:\s*,\s*\d*(?:\.\d+)?)?\s*\)/g;
|
658
|
+
const isColorCode = colorCodePattern.test(value.value);
|
659
|
+
if (isColorCode) {
|
660
|
+
context.report({
|
661
|
+
node: value,
|
662
|
+
message: 'Use color variables instead of color codes in TSS.'
|
663
|
+
});
|
664
|
+
}
|
665
|
+
}
|
682
666
|
}
|
683
|
-
|
684
|
-
|
685
|
-
|
667
|
+
function loopStylesObj(obj) {
|
668
|
+
if (obj && obj.type === 'ObjectExpression') {
|
669
|
+
obj.properties.forEach(property => {
|
670
|
+
if (property.type === 'Property' && property.value) {
|
671
|
+
if (property.value.type === 'ObjectExpression') {
|
672
|
+
loopStylesObj(property.value);
|
673
|
+
} else {
|
674
|
+
checkColorLiteral(property.value);
|
675
|
+
}
|
676
|
+
}
|
677
|
+
});
|
678
|
+
}
|
686
679
|
}
|
687
|
-
|
688
|
-
|
680
|
+
loopStylesObj(stylesObj);
|
681
|
+
}
|
682
|
+
};
|
683
|
+
}
|
684
|
+
};
|
685
|
+
|
686
|
+
var unusedClasses = {
|
687
|
+
meta: {
|
688
|
+
type: 'problem'
|
689
|
+
},
|
690
|
+
create: function rule(context) {
|
691
|
+
const usedClasses = {};
|
692
|
+
const definedClasses = {};
|
693
|
+
return {
|
694
|
+
CallExpression(node) {
|
695
|
+
const stylesObj = getStyesObj(node);
|
696
|
+
if (stylesObj === undefined) {
|
697
|
+
return;
|
689
698
|
}
|
690
|
-
|
691
|
-
|
692
|
-
|
699
|
+
stylesObj.properties.forEach(property => {
|
700
|
+
if (property.computed) {
|
701
|
+
return;
|
702
|
+
}
|
703
|
+
if (
|
704
|
+
property.type === 'ExperimentalSpreadProperty' ||
|
705
|
+
property.type === 'SpreadElement'
|
706
|
+
) {
|
707
|
+
return;
|
708
|
+
}
|
709
|
+
definedClasses[property.key.value || property.key.name] = property;
|
710
|
+
});
|
711
|
+
},
|
712
|
+
MemberExpression(node) {
|
713
|
+
if (
|
714
|
+
node.object.type === 'Identifier' &&
|
715
|
+
node.object.name === 'classes'
|
716
|
+
) {
|
717
|
+
const whichClass = getBasicIdentifier(node.property);
|
718
|
+
if (whichClass) {
|
719
|
+
usedClasses[whichClass] = true;
|
720
|
+
}
|
721
|
+
return;
|
693
722
|
}
|
694
|
-
|
695
|
-
|
723
|
+
const classIdentifier = getBasicIdentifier(node.property);
|
724
|
+
if (!classIdentifier) {
|
725
|
+
return;
|
696
726
|
}
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
}
|
727
|
+
if (classIdentifier !== 'classes') {
|
728
|
+
return;
|
729
|
+
}
|
730
|
+
const { parent } = node;
|
731
|
+
if (parent.type !== 'MemberExpression') {
|
732
|
+
return;
|
704
733
|
}
|
705
734
|
if (
|
706
|
-
|
707
|
-
|
708
|
-
(typeof init.value === 'string' ||
|
709
|
-
typeof init.value === 'number' ||
|
710
|
-
init.value == null)
|
735
|
+
node.object.object &&
|
736
|
+
node.object.object.type !== 'ThisExpression'
|
711
737
|
) {
|
712
|
-
return
|
738
|
+
return;
|
713
739
|
}
|
714
|
-
|
715
|
-
|
740
|
+
const propsIdentifier = getBasicIdentifier(parent.object);
|
741
|
+
if (propsIdentifier && propsIdentifier !== 'props') {
|
742
|
+
return;
|
716
743
|
}
|
717
|
-
|
744
|
+
if (!propsIdentifier && parent.object.type !== 'MemberExpression') {
|
745
|
+
return;
|
746
|
+
}
|
747
|
+
if (parent.parent.type === 'MemberExpression') {
|
748
|
+
return;
|
749
|
+
}
|
750
|
+
const parentClassIdentifier = getBasicIdentifier(parent.property);
|
751
|
+
if (parentClassIdentifier) {
|
752
|
+
usedClasses[parentClassIdentifier] = true;
|
753
|
+
}
|
754
|
+
},
|
755
|
+
'Program:exit': () => {
|
756
|
+
Object.keys(definedClasses).forEach(definedClassKey => {
|
757
|
+
if (!usedClasses[definedClassKey]) {
|
758
|
+
context.report({
|
759
|
+
node: definedClasses[definedClassKey],
|
760
|
+
message: `Class \`${definedClassKey}\` is unused`
|
761
|
+
});
|
762
|
+
}
|
763
|
+
});
|
764
|
+
}
|
765
|
+
};
|
766
|
+
}
|
767
|
+
};
|
768
|
+
|
769
|
+
var noUnnecessaryTemplateLiterals = {
|
770
|
+
meta: {
|
771
|
+
type: 'problem',
|
772
|
+
docs: {
|
773
|
+
description: 'Check if a template string contains only one ${}',
|
774
|
+
recommended: true
|
775
|
+
},
|
776
|
+
fixable: 'code',
|
777
|
+
schema: []
|
778
|
+
},
|
779
|
+
create(context) {
|
780
|
+
return {
|
781
|
+
TemplateLiteral(node) {
|
782
|
+
const code = context.sourceCode.getText(node);
|
718
783
|
if (
|
719
|
-
|
720
|
-
|
721
|
-
|
722
|
-
!callee.computed
|
784
|
+
code.startsWith('`${') &&
|
785
|
+
code.endsWith('}`') &&
|
786
|
+
code.split('${').length === 2
|
723
787
|
) {
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
788
|
+
context.report({
|
789
|
+
node,
|
790
|
+
message: 'Unnecessary template string with only one ${}.',
|
791
|
+
fix(fixer) {
|
792
|
+
return fixer.replaceText(
|
793
|
+
node,
|
794
|
+
code.substring(3, code.length - 2)
|
795
|
+
);
|
796
|
+
}
|
797
|
+
});
|
728
798
|
}
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
799
|
+
}
|
800
|
+
};
|
801
|
+
}
|
802
|
+
};
|
803
|
+
|
804
|
+
var betterExhaustiveDeps = {
|
805
|
+
meta: {
|
806
|
+
type: 'suggestion',
|
807
|
+
docs: {
|
808
|
+
description:
|
809
|
+
'verifies the list of dependencies for Hooks like useEffect and similar',
|
810
|
+
recommended: true,
|
811
|
+
url: 'https://github.com/facebook/react/issues/14920'
|
812
|
+
},
|
813
|
+
fixable: 'code',
|
814
|
+
hasSuggestions: true,
|
815
|
+
schema: [
|
816
|
+
{
|
817
|
+
type: 'object',
|
818
|
+
additionalProperties: false,
|
819
|
+
enableDangerousAutofixThisMayCauseInfiniteLoops: false,
|
820
|
+
properties: {
|
821
|
+
additionalHooks: {
|
822
|
+
type: 'string'
|
823
|
+
},
|
824
|
+
enableDangerousAutofixThisMayCauseInfiniteLoops: {
|
825
|
+
type: 'boolean'
|
826
|
+
},
|
827
|
+
staticHooks: {
|
828
|
+
type: 'object',
|
829
|
+
additionalProperties: {
|
830
|
+
oneOf: [
|
831
|
+
{
|
832
|
+
type: 'boolean'
|
833
|
+
},
|
834
|
+
{
|
835
|
+
type: 'array',
|
836
|
+
items: {
|
837
|
+
type: 'boolean'
|
746
838
|
}
|
747
|
-
|
748
|
-
|
839
|
+
},
|
840
|
+
{
|
841
|
+
type: 'object',
|
842
|
+
additionalProperties: {
|
843
|
+
type: 'boolean'
|
749
844
|
}
|
750
|
-
setStateCallSites.set(reference.identifier, id.elements[0]);
|
751
|
-
}
|
752
|
-
}
|
753
|
-
return true;
|
754
|
-
} else if (id.elements[0] === resolved.identifiers[0]) {
|
755
|
-
if (name === 'useState') {
|
756
|
-
const references = resolved.references;
|
757
|
-
for (const reference of references) {
|
758
|
-
stateVariables.add(reference.identifier);
|
759
845
|
}
|
760
|
-
|
761
|
-
|
846
|
+
]
|
847
|
+
}
|
848
|
+
},
|
849
|
+
checkMemoizedVariableIsStatic: {
|
850
|
+
type: 'boolean'
|
851
|
+
}
|
852
|
+
}
|
853
|
+
}
|
854
|
+
]
|
855
|
+
},
|
856
|
+
create(context) {
|
857
|
+
const additionalHooks =
|
858
|
+
context.options &&
|
859
|
+
context.options[0] &&
|
860
|
+
context.options[0].additionalHooks
|
861
|
+
? new RegExp(context.options[0].additionalHooks)
|
862
|
+
: undefined;
|
863
|
+
const enableDangerousAutofixThisMayCauseInfiniteLoops =
|
864
|
+
(context.options &&
|
865
|
+
context.options[0] &&
|
866
|
+
context.options[0].enableDangerousAutofixThisMayCauseInfiniteLoops) ||
|
867
|
+
false;
|
868
|
+
const staticHooks =
|
869
|
+
(context.options &&
|
870
|
+
context.options[0] &&
|
871
|
+
context.options[0].staticHooks) ||
|
872
|
+
{};
|
873
|
+
const checkMemoizedVariableIsStatic =
|
874
|
+
(context.options &&
|
875
|
+
context.options[0] &&
|
876
|
+
context.options[0].checkMemoizedVariableIsStatic) ||
|
877
|
+
false;
|
878
|
+
const options = {
|
879
|
+
additionalHooks,
|
880
|
+
enableDangerousAutofixThisMayCauseInfiniteLoops,
|
881
|
+
staticHooks,
|
882
|
+
checkMemoizedVariableIsStatic
|
883
|
+
};
|
884
|
+
function reportProblem(problem) {
|
885
|
+
if (
|
886
|
+
enableDangerousAutofixThisMayCauseInfiniteLoops &&
|
887
|
+
Array.isArray(problem.suggest) &&
|
888
|
+
problem.suggest.length > 0
|
889
|
+
) {
|
890
|
+
problem.fix = problem.suggest[0].fix;
|
891
|
+
}
|
892
|
+
context.report(problem);
|
893
|
+
}
|
894
|
+
const scopeManager = context.getSourceCode().scopeManager;
|
895
|
+
const setStateCallSites = new WeakMap();
|
896
|
+
const stateVariables = new WeakSet();
|
897
|
+
const stableKnownValueCache = new WeakMap();
|
898
|
+
const functionWithoutCapturedValueCache = new WeakMap();
|
899
|
+
function memoizeWithWeakMap(fn, map) {
|
900
|
+
return function (arg) {
|
901
|
+
if (map.has(arg)) {
|
902
|
+
return map.get(arg);
|
903
|
+
}
|
904
|
+
const result = fn(arg);
|
905
|
+
map.set(arg, result);
|
906
|
+
return result;
|
907
|
+
};
|
908
|
+
}
|
909
|
+
function visitFunctionWithDependencies(
|
910
|
+
node,
|
911
|
+
declaredDependenciesNode,
|
912
|
+
reactiveHook,
|
913
|
+
reactiveHookName,
|
914
|
+
isEffect
|
915
|
+
) {
|
916
|
+
if (isEffect && node.async) {
|
917
|
+
reportProblem({
|
918
|
+
node: node,
|
919
|
+
message:
|
920
|
+
`Effect callbacks are synchronous to prevent race conditions. ` +
|
921
|
+
`Put the async function inside:\n\n` +
|
922
|
+
'useEffect(() => {\n' +
|
923
|
+
' async function fetchData() {\n' +
|
924
|
+
' // You can await here\n' +
|
925
|
+
' const response = await MyAPI.getData(someId);\n' +
|
926
|
+
' // ...\n' +
|
927
|
+
' }\n' +
|
928
|
+
' fetchData();\n' +
|
929
|
+
`}, [someId]); // Or [] if effect doesn't need props or state\n\n` +
|
930
|
+
'Learn more about data fetching with Hooks: https://reactjs.org/link/hooks-data-fetching'
|
931
|
+
});
|
932
|
+
}
|
933
|
+
const scope = scopeManager.acquire(node);
|
934
|
+
const pureScopes = new Set();
|
935
|
+
let componentScope = null;
|
936
|
+
{
|
937
|
+
let currentScope = scope.upper;
|
938
|
+
while (currentScope) {
|
939
|
+
pureScopes.add(currentScope);
|
940
|
+
if (currentScope.type === 'function') {
|
941
|
+
break;
|
942
|
+
}
|
943
|
+
currentScope = currentScope.upper;
|
944
|
+
}
|
945
|
+
if (!currentScope) {
|
946
|
+
return;
|
947
|
+
}
|
948
|
+
componentScope = currentScope;
|
949
|
+
}
|
950
|
+
const isArray = Array.isArray;
|
951
|
+
const memoizedIsStableKnownHookValue = memoizeWithWeakMap(
|
952
|
+
isStableKnownHookValue,
|
953
|
+
stableKnownValueCache
|
954
|
+
);
|
955
|
+
const memoizedIsFunctionWithoutCapturedValues = memoizeWithWeakMap(
|
956
|
+
isFunctionWithoutCapturedValues,
|
957
|
+
functionWithoutCapturedValueCache
|
958
|
+
);
|
959
|
+
function isStableKnownHookValue(resolved) {
|
960
|
+
if (!isArray(resolved.defs)) {
|
961
|
+
return false;
|
962
|
+
}
|
963
|
+
const def = resolved.defs[0];
|
964
|
+
if (def == null) {
|
965
|
+
return false;
|
966
|
+
}
|
967
|
+
if (def.node.type !== 'VariableDeclarator') {
|
968
|
+
return false;
|
969
|
+
}
|
970
|
+
let init = def.node.init;
|
971
|
+
if (init == null) {
|
972
|
+
return false;
|
973
|
+
}
|
974
|
+
while (init.type === 'TSAsExpression') {
|
975
|
+
init = init.expression;
|
976
|
+
}
|
977
|
+
let declaration = def.node.parent;
|
978
|
+
if (declaration == null) {
|
979
|
+
fastFindReferenceWithParent(componentScope.block, def.node.id);
|
980
|
+
declaration = def.node.parent;
|
981
|
+
if (declaration == null) {
|
982
|
+
return false;
|
983
|
+
}
|
984
|
+
}
|
985
|
+
if (
|
986
|
+
declaration.kind === 'const' &&
|
987
|
+
init.type === 'Literal' &&
|
988
|
+
(typeof init.value === 'string' ||
|
989
|
+
typeof init.value === 'number' ||
|
990
|
+
init.value == null)
|
991
|
+
) {
|
992
|
+
return true;
|
993
|
+
}
|
994
|
+
if (init.type !== 'CallExpression') {
|
995
|
+
return false;
|
996
|
+
}
|
997
|
+
let callee = init.callee;
|
998
|
+
if (
|
999
|
+
callee.type === 'MemberExpression' &&
|
1000
|
+
callee.object.name === 'React' &&
|
1001
|
+
callee.property != null &&
|
1002
|
+
!callee.computed
|
1003
|
+
) {
|
1004
|
+
callee = callee.property;
|
1005
|
+
}
|
1006
|
+
if (callee.type !== 'Identifier') {
|
1007
|
+
return false;
|
1008
|
+
}
|
1009
|
+
const id = def.node.id;
|
1010
|
+
const { name } = callee;
|
1011
|
+
if (name === 'useRef' && id.type === 'Identifier') {
|
1012
|
+
return true;
|
1013
|
+
} else if (name === 'useState' || name === 'useReducer') {
|
1014
|
+
if (
|
1015
|
+
id.type === 'ArrayPattern' &&
|
1016
|
+
id.elements.length === 2 &&
|
1017
|
+
isArray(resolved.identifiers)
|
1018
|
+
) {
|
1019
|
+
if (id.elements[1] === resolved.identifiers[0]) {
|
1020
|
+
if (name === 'useState') {
|
1021
|
+
const references = resolved.references;
|
1022
|
+
let writeCount = 0;
|
1023
|
+
for (const reference of references) {
|
1024
|
+
if (reference.isWrite()) {
|
1025
|
+
writeCount++;
|
1026
|
+
}
|
1027
|
+
if (writeCount > 1) {
|
1028
|
+
return false;
|
1029
|
+
}
|
1030
|
+
setStateCallSites.set(reference.identifier, id.elements[0]);
|
1031
|
+
}
|
1032
|
+
}
|
1033
|
+
return true;
|
1034
|
+
} else if (id.elements[0] === resolved.identifiers[0]) {
|
1035
|
+
if (name === 'useState') {
|
1036
|
+
const references = resolved.references;
|
1037
|
+
for (const reference of references) {
|
1038
|
+
stateVariables.add(reference.identifier);
|
1039
|
+
}
|
1040
|
+
}
|
1041
|
+
return false;
|
762
1042
|
}
|
763
1043
|
}
|
764
1044
|
} else if (name === 'useTransition') {
|
@@ -1531,1003 +1811,723 @@ var betterExhaustiveDeps = {
|
|
1531
1811
|
reportProblem({
|
1532
1812
|
node: reactiveHook,
|
1533
1813
|
message:
|
1534
|
-
`React Hook ${reactiveHookName} requires an effect callback. ` +
|
1535
|
-
`Did you forget to pass a callback to the hook?`
|
1536
|
-
});
|
1537
|
-
return;
|
1538
|
-
}
|
1539
|
-
if (!declaredDependenciesNode && !isEffect) {
|
1540
|
-
if (
|
1541
|
-
reactiveHookName === 'useMemo' ||
|
1542
|
-
reactiveHookName === 'useCallback'
|
1543
|
-
) {
|
1544
|
-
reportProblem({
|
1545
|
-
node: reactiveHook,
|
1546
|
-
message:
|
1547
|
-
`React Hook ${reactiveHookName} does nothing when called with ` +
|
1548
|
-
`only one argument. Did you forget to pass an array of ` +
|
1549
|
-
`dependencies?`
|
1550
|
-
});
|
1551
|
-
}
|
1552
|
-
return;
|
1553
|
-
}
|
1554
|
-
switch (callback.type) {
|
1555
|
-
case 'FunctionExpression':
|
1556
|
-
case 'ArrowFunctionExpression':
|
1557
|
-
visitFunctionWithDependencies(
|
1558
|
-
callback,
|
1559
|
-
declaredDependenciesNode,
|
1560
|
-
reactiveHook,
|
1561
|
-
reactiveHookName,
|
1562
|
-
isEffect
|
1563
|
-
);
|
1564
|
-
return;
|
1565
|
-
case 'Identifier':
|
1566
|
-
if (!declaredDependenciesNode) {
|
1567
|
-
return;
|
1568
|
-
}
|
1569
|
-
if (
|
1570
|
-
declaredDependenciesNode.elements &&
|
1571
|
-
declaredDependenciesNode.elements.some(
|
1572
|
-
el => el && el.type === 'Identifier' && el.name === callback.name
|
1573
|
-
)
|
1574
|
-
) {
|
1575
|
-
return;
|
1576
|
-
}
|
1577
|
-
const variable = context.getScope().set.get(callback.name);
|
1578
|
-
if (variable == null || variable.defs == null) {
|
1579
|
-
return;
|
1580
|
-
}
|
1581
|
-
const def = variable.defs[0];
|
1582
|
-
if (!def || !def.node) {
|
1583
|
-
break;
|
1584
|
-
}
|
1585
|
-
if (def.type !== 'Variable' && def.type !== 'FunctionName') {
|
1586
|
-
break;
|
1587
|
-
}
|
1588
|
-
switch (def.node.type) {
|
1589
|
-
case 'FunctionDeclaration':
|
1590
|
-
visitFunctionWithDependencies(
|
1591
|
-
def.node,
|
1592
|
-
declaredDependenciesNode,
|
1593
|
-
reactiveHook,
|
1594
|
-
reactiveHookName,
|
1595
|
-
isEffect
|
1596
|
-
);
|
1597
|
-
return;
|
1598
|
-
case 'VariableDeclarator':
|
1599
|
-
const init = def.node.init;
|
1600
|
-
if (!init) {
|
1601
|
-
break;
|
1602
|
-
}
|
1603
|
-
switch (init.type) {
|
1604
|
-
case 'ArrowFunctionExpression':
|
1605
|
-
case 'FunctionExpression':
|
1606
|
-
visitFunctionWithDependencies(
|
1607
|
-
init,
|
1608
|
-
declaredDependenciesNode,
|
1609
|
-
reactiveHook,
|
1610
|
-
reactiveHookName,
|
1611
|
-
isEffect
|
1612
|
-
);
|
1613
|
-
return;
|
1614
|
-
}
|
1615
|
-
break;
|
1616
|
-
}
|
1617
|
-
break;
|
1618
|
-
default:
|
1619
|
-
reportProblem({
|
1620
|
-
node: reactiveHook,
|
1621
|
-
message:
|
1622
|
-
`React Hook ${reactiveHookName} received a function whose dependencies ` +
|
1623
|
-
`are unknown. Pass an inline function instead.`
|
1624
|
-
});
|
1625
|
-
return;
|
1626
|
-
}
|
1627
|
-
reportProblem({
|
1628
|
-
node: reactiveHook,
|
1629
|
-
message:
|
1630
|
-
`React Hook ${reactiveHookName} has a missing dependency: '${callback.name}'. ` +
|
1631
|
-
`Either include it or remove the dependency array.`,
|
1632
|
-
suggest: [
|
1633
|
-
{
|
1634
|
-
desc: `Update the dependencies array to be: [${callback.name}]`,
|
1635
|
-
fix(fixer) {
|
1636
|
-
return fixer.replaceText(
|
1637
|
-
declaredDependenciesNode,
|
1638
|
-
`[${callback.name}]`
|
1639
|
-
);
|
1640
|
-
}
|
1641
|
-
}
|
1642
|
-
]
|
1643
|
-
});
|
1644
|
-
}
|
1645
|
-
return {
|
1646
|
-
CallExpression: visitCallExpression
|
1647
|
-
};
|
1648
|
-
}
|
1649
|
-
};
|
1650
|
-
function collectRecommendations({
|
1651
|
-
dependencies,
|
1652
|
-
declaredDependencies,
|
1653
|
-
stableDependencies,
|
1654
|
-
externalDependencies,
|
1655
|
-
isEffect
|
1656
|
-
}) {
|
1657
|
-
const depTree = createDepTree();
|
1658
|
-
function createDepTree() {
|
1659
|
-
return {
|
1660
|
-
isUsed: false,
|
1661
|
-
isSatisfiedRecursively: false,
|
1662
|
-
isSubtreeUsed: false,
|
1663
|
-
children: new Map()
|
1664
|
-
};
|
1665
|
-
}
|
1666
|
-
dependencies.forEach((_, key) => {
|
1667
|
-
const node = getOrCreateNodeByPath(depTree, key);
|
1668
|
-
node.isUsed = true;
|
1669
|
-
markAllParentsByPath(depTree, key, parent => {
|
1670
|
-
parent.isSubtreeUsed = true;
|
1671
|
-
});
|
1672
|
-
});
|
1673
|
-
declaredDependencies.forEach(({ key }) => {
|
1674
|
-
const node = getOrCreateNodeByPath(depTree, key);
|
1675
|
-
node.isSatisfiedRecursively = true;
|
1676
|
-
});
|
1677
|
-
stableDependencies.forEach(key => {
|
1678
|
-
const node = getOrCreateNodeByPath(depTree, key);
|
1679
|
-
node.isSatisfiedRecursively = true;
|
1680
|
-
});
|
1681
|
-
function getOrCreateNodeByPath(rootNode, path) {
|
1682
|
-
const keys = path.split('.');
|
1683
|
-
let node = rootNode;
|
1684
|
-
for (const key of keys) {
|
1685
|
-
let child = node.children.get(key);
|
1686
|
-
if (!child) {
|
1687
|
-
child = createDepTree();
|
1688
|
-
node.children.set(key, child);
|
1689
|
-
}
|
1690
|
-
node = child;
|
1691
|
-
}
|
1692
|
-
return node;
|
1693
|
-
}
|
1694
|
-
function markAllParentsByPath(rootNode, path, fn) {
|
1695
|
-
const keys = path.split('.');
|
1696
|
-
let node = rootNode;
|
1697
|
-
for (const key of keys) {
|
1698
|
-
const child = node.children.get(key);
|
1699
|
-
if (!child) {
|
1700
|
-
return;
|
1701
|
-
}
|
1702
|
-
fn(child);
|
1703
|
-
node = child;
|
1704
|
-
}
|
1705
|
-
}
|
1706
|
-
const missingDependencies = new Set();
|
1707
|
-
const satisfyingDependencies = new Set();
|
1708
|
-
scanTreeRecursively(
|
1709
|
-
depTree,
|
1710
|
-
missingDependencies,
|
1711
|
-
satisfyingDependencies,
|
1712
|
-
key => key
|
1713
|
-
);
|
1714
|
-
function scanTreeRecursively(node, missingPaths, satisfyingPaths, keyToPath) {
|
1715
|
-
node.children.forEach((child, key) => {
|
1716
|
-
const path = keyToPath(key);
|
1717
|
-
if (child.isSatisfiedRecursively) {
|
1718
|
-
if (child.isSubtreeUsed) {
|
1719
|
-
satisfyingPaths.add(path);
|
1720
|
-
}
|
1721
|
-
return;
|
1722
|
-
}
|
1723
|
-
if (child.isUsed) {
|
1724
|
-
missingPaths.add(path);
|
1725
|
-
return;
|
1726
|
-
}
|
1727
|
-
scanTreeRecursively(
|
1728
|
-
child,
|
1729
|
-
missingPaths,
|
1730
|
-
satisfyingPaths,
|
1731
|
-
childKey => path + '.' + childKey
|
1732
|
-
);
|
1733
|
-
});
|
1734
|
-
}
|
1735
|
-
const suggestedDependencies = [];
|
1736
|
-
const unnecessaryDependencies = new Set();
|
1737
|
-
const duplicateDependencies = new Set();
|
1738
|
-
declaredDependencies.forEach(({ key }) => {
|
1739
|
-
if (satisfyingDependencies.has(key)) {
|
1740
|
-
if (!suggestedDependencies.includes(key)) {
|
1741
|
-
suggestedDependencies.push(key);
|
1742
|
-
} else {
|
1743
|
-
duplicateDependencies.add(key);
|
1744
|
-
}
|
1745
|
-
} else if (
|
1746
|
-
isEffect &&
|
1747
|
-
!key.endsWith('.current') &&
|
1748
|
-
!externalDependencies.has(key)
|
1749
|
-
) {
|
1750
|
-
if (!suggestedDependencies.includes(key)) {
|
1751
|
-
suggestedDependencies.push(key);
|
1752
|
-
}
|
1753
|
-
} else {
|
1754
|
-
unnecessaryDependencies.add(key);
|
1755
|
-
}
|
1756
|
-
});
|
1757
|
-
missingDependencies.forEach(key => {
|
1758
|
-
suggestedDependencies.push(key);
|
1759
|
-
});
|
1760
|
-
return {
|
1761
|
-
suggestedDependencies,
|
1762
|
-
unnecessaryDependencies,
|
1763
|
-
duplicateDependencies,
|
1764
|
-
missingDependencies
|
1765
|
-
};
|
1766
|
-
}
|
1767
|
-
function getConstructionExpressionType(node) {
|
1768
|
-
switch (node.type) {
|
1769
|
-
case 'ObjectExpression':
|
1770
|
-
return 'object';
|
1771
|
-
case 'ArrayExpression':
|
1772
|
-
return 'array';
|
1773
|
-
case 'ArrowFunctionExpression':
|
1774
|
-
case 'FunctionExpression':
|
1775
|
-
return 'function';
|
1776
|
-
case 'ClassExpression':
|
1777
|
-
return 'class';
|
1778
|
-
case 'ConditionalExpression':
|
1779
|
-
if (
|
1780
|
-
getConstructionExpressionType(node.consequent) != null ||
|
1781
|
-
getConstructionExpressionType(node.alternate) != null
|
1782
|
-
) {
|
1783
|
-
return 'conditional';
|
1784
|
-
}
|
1785
|
-
return null;
|
1786
|
-
case 'LogicalExpression':
|
1787
|
-
if (
|
1788
|
-
getConstructionExpressionType(node.left) != null ||
|
1789
|
-
getConstructionExpressionType(node.right) != null
|
1790
|
-
) {
|
1791
|
-
return 'logical expression';
|
1792
|
-
}
|
1793
|
-
return null;
|
1794
|
-
case 'JSXFragment':
|
1795
|
-
return 'JSX fragment';
|
1796
|
-
case 'JSXElement':
|
1797
|
-
return 'JSX element';
|
1798
|
-
case 'AssignmentExpression':
|
1799
|
-
if (getConstructionExpressionType(node.right) != null) {
|
1800
|
-
return 'assignment expression';
|
1801
|
-
}
|
1802
|
-
return null;
|
1803
|
-
case 'NewExpression':
|
1804
|
-
return 'object construction';
|
1805
|
-
case 'Literal':
|
1806
|
-
if (node.value instanceof RegExp) {
|
1807
|
-
return 'regular expression';
|
1808
|
-
}
|
1809
|
-
return null;
|
1810
|
-
case 'TypeCastExpression':
|
1811
|
-
return getConstructionExpressionType(node.expression);
|
1812
|
-
case 'TSAsExpression':
|
1813
|
-
return getConstructionExpressionType(node.expression);
|
1814
|
-
}
|
1815
|
-
return null;
|
1816
|
-
}
|
1817
|
-
function scanForConstructions({
|
1818
|
-
declaredDependencies,
|
1819
|
-
declaredDependenciesNode,
|
1820
|
-
componentScope,
|
1821
|
-
scope
|
1822
|
-
}) {
|
1823
|
-
const constructions = declaredDependencies
|
1824
|
-
.map(({ key }) => {
|
1825
|
-
const ref = componentScope.variables.find(v => v.name === key);
|
1826
|
-
if (ref == null) {
|
1827
|
-
return null;
|
1828
|
-
}
|
1829
|
-
const node = ref.defs[0];
|
1830
|
-
if (node == null) {
|
1831
|
-
return null;
|
1832
|
-
}
|
1833
|
-
if (
|
1834
|
-
node.type === 'Variable' &&
|
1835
|
-
node.node.type === 'VariableDeclarator' &&
|
1836
|
-
node.node.id.type === 'Identifier' &&
|
1837
|
-
node.node.init != null
|
1838
|
-
) {
|
1839
|
-
const constantExpressionType = getConstructionExpressionType(
|
1840
|
-
node.node.init
|
1841
|
-
);
|
1842
|
-
if (constantExpressionType != null) {
|
1843
|
-
return [ref, constantExpressionType];
|
1844
|
-
}
|
1845
|
-
}
|
1846
|
-
if (
|
1847
|
-
node.type === 'FunctionName' &&
|
1848
|
-
node.node.type === 'FunctionDeclaration'
|
1849
|
-
) {
|
1850
|
-
return [ref, 'function'];
|
1851
|
-
}
|
1852
|
-
if (node.type === 'ClassName' && node.node.type === 'ClassDeclaration') {
|
1853
|
-
return [ref, 'class'];
|
1854
|
-
}
|
1855
|
-
return null;
|
1856
|
-
})
|
1857
|
-
.filter(Boolean);
|
1858
|
-
function isUsedOutsideOfHook(ref) {
|
1859
|
-
let foundWriteExpr = false;
|
1860
|
-
for (let i = 0; i < ref.references.length; i++) {
|
1861
|
-
const reference = ref.references[i];
|
1862
|
-
if (reference.writeExpr) {
|
1863
|
-
if (foundWriteExpr) {
|
1864
|
-
return true;
|
1865
|
-
}
|
1866
|
-
foundWriteExpr = true;
|
1867
|
-
continue;
|
1868
|
-
}
|
1869
|
-
let currentScope = reference.from;
|
1870
|
-
while (currentScope !== scope && currentScope != null) {
|
1871
|
-
currentScope = currentScope.upper;
|
1872
|
-
}
|
1873
|
-
if (
|
1874
|
-
currentScope !== scope &&
|
1875
|
-
!isAncestorNodeOf(declaredDependenciesNode, reference.identifier)
|
1876
|
-
) {
|
1877
|
-
return true;
|
1878
|
-
}
|
1879
|
-
}
|
1880
|
-
return false;
|
1881
|
-
}
|
1882
|
-
return constructions.map(([ref, depType]) => ({
|
1883
|
-
construction: ref.defs[0],
|
1884
|
-
depType,
|
1885
|
-
isUsedOutsideOfHook: isUsedOutsideOfHook(ref)
|
1886
|
-
}));
|
1887
|
-
}
|
1888
|
-
function getDependency(node) {
|
1889
|
-
if (
|
1890
|
-
(node.parent.type === 'MemberExpression' ||
|
1891
|
-
node.parent.type === 'OptionalMemberExpression') &&
|
1892
|
-
node.parent.object === node &&
|
1893
|
-
node.parent.property.name !== 'current' &&
|
1894
|
-
!node.parent.computed &&
|
1895
|
-
!(
|
1896
|
-
node.parent.parent != null &&
|
1897
|
-
(node.parent.parent.type === 'CallExpression' ||
|
1898
|
-
node.parent.parent.type === 'OptionalCallExpression') &&
|
1899
|
-
node.parent.parent.callee === node.parent
|
1900
|
-
)
|
1901
|
-
) {
|
1902
|
-
return getDependency(node.parent);
|
1903
|
-
} else if (
|
1904
|
-
node.type === 'MemberExpression' &&
|
1905
|
-
node.parent &&
|
1906
|
-
node.parent.type === 'AssignmentExpression' &&
|
1907
|
-
node.parent.left === node
|
1908
|
-
) {
|
1909
|
-
return node.object;
|
1910
|
-
}
|
1911
|
-
return node;
|
1912
|
-
}
|
1913
|
-
function markNode(node, optionalChains, result) {
|
1914
|
-
if (optionalChains) {
|
1915
|
-
if (node.optional) {
|
1916
|
-
if (!optionalChains.has(result)) {
|
1917
|
-
optionalChains.set(result, true);
|
1918
|
-
}
|
1919
|
-
} else {
|
1920
|
-
optionalChains.set(result, false);
|
1921
|
-
}
|
1922
|
-
}
|
1923
|
-
}
|
1924
|
-
function analyzePropertyChain(node, optionalChains) {
|
1925
|
-
if (node.type === 'Identifier' || node.type === 'JSXIdentifier') {
|
1926
|
-
const result = node.name;
|
1927
|
-
if (optionalChains) {
|
1928
|
-
optionalChains.set(result, false);
|
1929
|
-
}
|
1930
|
-
return result;
|
1931
|
-
} else if (node.type === 'MemberExpression' && !node.computed) {
|
1932
|
-
const object = analyzePropertyChain(node.object, optionalChains);
|
1933
|
-
const property = analyzePropertyChain(node.property, null);
|
1934
|
-
const result = `${object}.${property}`;
|
1935
|
-
markNode(node, optionalChains, result);
|
1936
|
-
return result;
|
1937
|
-
} else if (node.type === 'OptionalMemberExpression' && !node.computed) {
|
1938
|
-
const object = analyzePropertyChain(node.object, optionalChains);
|
1939
|
-
const property = analyzePropertyChain(node.property, null);
|
1940
|
-
const result = `${object}.${property}`;
|
1941
|
-
markNode(node, optionalChains, result);
|
1942
|
-
return result;
|
1943
|
-
} else if (node.type === 'ChainExpression' && !node.computed) {
|
1944
|
-
const expression = node.expression;
|
1945
|
-
if (expression.type === 'CallExpression') {
|
1946
|
-
throw new Error(`Unsupported node type: ${expression.type}`);
|
1947
|
-
}
|
1948
|
-
const object = analyzePropertyChain(expression.object, optionalChains);
|
1949
|
-
const property = analyzePropertyChain(expression.property, null);
|
1950
|
-
const result = `${object}.${property}`;
|
1951
|
-
markNode(expression, optionalChains, result);
|
1952
|
-
return result;
|
1953
|
-
}
|
1954
|
-
throw new Error(`Unsupported node type: ${node.type}`);
|
1955
|
-
}
|
1956
|
-
function getNodeWithoutReactNamespace(node) {
|
1957
|
-
if (
|
1958
|
-
node.type === 'MemberExpression' &&
|
1959
|
-
node.object.type === 'Identifier' &&
|
1960
|
-
node.object.name === 'React' &&
|
1961
|
-
node.property.type === 'Identifier' &&
|
1962
|
-
!node.computed
|
1963
|
-
) {
|
1964
|
-
return node.property;
|
1965
|
-
}
|
1966
|
-
return node;
|
1967
|
-
}
|
1968
|
-
function getReactiveHookCallbackIndex(calleeNode, options) {
|
1969
|
-
const node = getNodeWithoutReactNamespace(calleeNode);
|
1970
|
-
if (node.type !== 'Identifier') {
|
1971
|
-
return -1;
|
1972
|
-
}
|
1973
|
-
switch (node.name) {
|
1974
|
-
case 'useEffect':
|
1975
|
-
case 'useLayoutEffect':
|
1976
|
-
case 'useCallback':
|
1977
|
-
case 'useMemo':
|
1978
|
-
return 0;
|
1979
|
-
case 'useImperativeHandle':
|
1980
|
-
return 1;
|
1981
|
-
default:
|
1982
|
-
if (node === calleeNode && options && options.additionalHooks) {
|
1983
|
-
let name;
|
1984
|
-
try {
|
1985
|
-
name = analyzePropertyChain(node, null);
|
1986
|
-
} catch (err) {
|
1987
|
-
if (/Unsupported node type/.test(err.message)) {
|
1988
|
-
return 0;
|
1989
|
-
}
|
1990
|
-
throw err;
|
1991
|
-
}
|
1992
|
-
return options.additionalHooks.test(name) ? 0 : -1;
|
1993
|
-
}
|
1994
|
-
return -1;
|
1995
|
-
}
|
1996
|
-
}
|
1997
|
-
function fastFindReferenceWithParent(start, target) {
|
1998
|
-
const queue = [start];
|
1999
|
-
let item = null;
|
2000
|
-
while (queue.length > 0) {
|
2001
|
-
item = queue.shift();
|
2002
|
-
if (isSameIdentifier(item, target)) {
|
2003
|
-
return item;
|
2004
|
-
}
|
2005
|
-
if (!isAncestorNodeOf(item, target)) {
|
2006
|
-
continue;
|
2007
|
-
}
|
2008
|
-
for (const [key, value] of Object.entries(item)) {
|
2009
|
-
if (key === 'parent') {
|
2010
|
-
continue;
|
2011
|
-
}
|
2012
|
-
if (isNodeLike(value)) {
|
2013
|
-
value.parent = item;
|
2014
|
-
queue.push(value);
|
2015
|
-
} else if (Array.isArray(value)) {
|
2016
|
-
value.forEach(val => {
|
2017
|
-
if (isNodeLike(val)) {
|
2018
|
-
val.parent = item;
|
2019
|
-
queue.push(val);
|
2020
|
-
}
|
2021
|
-
});
|
2022
|
-
}
|
2023
|
-
}
|
2024
|
-
}
|
2025
|
-
return null;
|
2026
|
-
}
|
2027
|
-
function joinEnglish(arr) {
|
2028
|
-
let s = '';
|
2029
|
-
for (let i = 0; i < arr.length; i++) {
|
2030
|
-
s += arr[i];
|
2031
|
-
if (i === 0 && arr.length === 2) {
|
2032
|
-
s += ' and ';
|
2033
|
-
} else if (i === arr.length - 2 && arr.length > 2) {
|
2034
|
-
s += ', and ';
|
2035
|
-
} else if (i < arr.length - 1) {
|
2036
|
-
s += ', ';
|
2037
|
-
}
|
2038
|
-
}
|
2039
|
-
return s;
|
2040
|
-
}
|
2041
|
-
function isNodeLike(val) {
|
2042
|
-
return (
|
2043
|
-
typeof val === 'object' &&
|
2044
|
-
val != null &&
|
2045
|
-
!Array.isArray(val) &&
|
2046
|
-
typeof val.type === 'string'
|
2047
|
-
);
|
2048
|
-
}
|
2049
|
-
function isSameIdentifier(a, b) {
|
2050
|
-
return (
|
2051
|
-
(a.type === 'Identifier' || a.type === 'JSXIdentifier') &&
|
2052
|
-
a.type === b.type &&
|
2053
|
-
a.name === b.name &&
|
2054
|
-
a.range[0] === b.range[0] &&
|
2055
|
-
a.range[1] === b.range[1]
|
2056
|
-
);
|
2057
|
-
}
|
2058
|
-
function isAncestorNodeOf(a, b) {
|
2059
|
-
return a.range[0] <= b.range[0] && a.range[1] >= b.range[1];
|
2060
|
-
}
|
2061
|
-
|
2062
|
-
const Components = require('eslint-plugin-react/lib/util/Components');
|
2063
|
-
var hookUseRef = {
|
2064
|
-
meta: {
|
2065
|
-
docs: {
|
2066
|
-
description: 'Ensure naming of useRef hook value.',
|
2067
|
-
recommended: false
|
2068
|
-
},
|
2069
|
-
schema: [],
|
2070
|
-
type: 'suggestion',
|
2071
|
-
hasSuggestions: true
|
2072
|
-
},
|
2073
|
-
create: Components.detect((context, component, util) => ({
|
2074
|
-
CallExpression(node) {
|
2075
|
-
const isImmediateReturn =
|
2076
|
-
node.parent && node.parent.type === 'ReturnStatement';
|
2077
|
-
if (isImmediateReturn || !util.isReactHookCall(node, ['useRef'])) {
|
2078
|
-
return;
|
2079
|
-
}
|
2080
|
-
if (node.parent.id.type !== 'Identifier') {
|
2081
|
-
return;
|
2082
|
-
}
|
2083
|
-
const variable = node.parent.id.name;
|
2084
|
-
if (!variable.endsWith('Ref')) {
|
2085
|
-
context.report({
|
2086
|
-
node: node,
|
2087
|
-
message: 'useRef call is not end with "Ref"'
|
1814
|
+
`React Hook ${reactiveHookName} requires an effect callback. ` +
|
1815
|
+
`Did you forget to pass a callback to the hook?`
|
2088
1816
|
});
|
1817
|
+
return;
|
2089
1818
|
}
|
2090
|
-
|
2091
|
-
}))
|
2092
|
-
};
|
2093
|
-
|
2094
|
-
function* updateImportStatement(context, fixer, key) {
|
2095
|
-
const sourceCode = context.sourceCode;
|
2096
|
-
const importNode = sourceCode.ast.body.find(
|
2097
|
-
node => node.type === 'ImportDeclaration' && node.source.value === 'react'
|
2098
|
-
);
|
2099
|
-
if (!importNode) {
|
2100
|
-
yield fixer.insertTextBefore(
|
2101
|
-
sourceCode.ast.body[0],
|
2102
|
-
`import { ${key} } from 'react';\n`
|
2103
|
-
);
|
2104
|
-
return;
|
2105
|
-
}
|
2106
|
-
if (
|
2107
|
-
importNode.specifiers.length === 1 &&
|
2108
|
-
importNode.specifiers[0].type === 'ImportDefaultSpecifier'
|
2109
|
-
) {
|
2110
|
-
yield fixer.insertTextAfter(importNode.specifiers[0], `, { ${key} }`);
|
2111
|
-
return;
|
2112
|
-
}
|
2113
|
-
const alreadyImportedKeys = importNode.specifiers
|
2114
|
-
.filter(specifier => specifier.type === 'ImportSpecifier')
|
2115
|
-
.map(specifier => specifier.imported.name);
|
2116
|
-
if (alreadyImportedKeys.includes(key)) {
|
2117
|
-
return;
|
2118
|
-
}
|
2119
|
-
yield fixer.insertTextAfter([...importNode.specifiers].pop(), `, ${key}`);
|
2120
|
-
}
|
2121
|
-
var preferNamedPropertyAccess = utils.ESLintUtils.RuleCreator.withoutDocs({
|
2122
|
-
defaultOptions: [],
|
2123
|
-
meta: {
|
2124
|
-
type: 'layout',
|
2125
|
-
fixable: 'code',
|
2126
|
-
docs: {
|
2127
|
-
description:
|
2128
|
-
'Enforce importing each member of React namespace separately instead of accessing them through React namespace'
|
2129
|
-
},
|
2130
|
-
messages: {
|
2131
|
-
illegalReactPropertyAccess:
|
2132
|
-
'Illegal React property access: {{name}}. Use named import instead.'
|
2133
|
-
},
|
2134
|
-
schema: []
|
2135
|
-
},
|
2136
|
-
create(context) {
|
2137
|
-
return {
|
2138
|
-
TSQualifiedName(node) {
|
1819
|
+
if (!declaredDependenciesNode && !isEffect) {
|
2139
1820
|
if (
|
2140
|
-
|
2141
|
-
|
1821
|
+
reactiveHookName === 'useMemo' ||
|
1822
|
+
reactiveHookName === 'useCallback'
|
2142
1823
|
) {
|
2143
|
-
|
1824
|
+
reportProblem({
|
1825
|
+
node: reactiveHook,
|
1826
|
+
message:
|
1827
|
+
`React Hook ${reactiveHookName} does nothing when called with ` +
|
1828
|
+
`only one argument. Did you forget to pass an array of ` +
|
1829
|
+
`dependencies?`
|
1830
|
+
});
|
2144
1831
|
}
|
2145
|
-
|
2146
|
-
|
2147
|
-
|
2148
|
-
|
2149
|
-
|
2150
|
-
|
2151
|
-
|
2152
|
-
|
2153
|
-
|
2154
|
-
|
2155
|
-
|
2156
|
-
|
2157
|
-
MemberExpression(node) {
|
2158
|
-
if (node.object.name !== 'React') {
|
1832
|
+
return;
|
1833
|
+
}
|
1834
|
+
switch (callback.type) {
|
1835
|
+
case 'FunctionExpression':
|
1836
|
+
case 'ArrowFunctionExpression':
|
1837
|
+
visitFunctionWithDependencies(
|
1838
|
+
callback,
|
1839
|
+
declaredDependenciesNode,
|
1840
|
+
reactiveHook,
|
1841
|
+
reactiveHookName,
|
1842
|
+
isEffect
|
1843
|
+
);
|
2159
1844
|
return;
|
2160
|
-
|
2161
|
-
|
2162
|
-
|
2163
|
-
messageId: 'illegalReactPropertyAccess',
|
2164
|
-
data: {
|
2165
|
-
name: node.property.name
|
2166
|
-
},
|
2167
|
-
*fix(fixer) {
|
2168
|
-
yield fixer.replaceText(node, node.property.name);
|
2169
|
-
yield* updateImportStatement(context, fixer, node.property.name);
|
1845
|
+
case 'Identifier':
|
1846
|
+
if (!declaredDependenciesNode) {
|
1847
|
+
return;
|
2170
1848
|
}
|
2171
|
-
|
1849
|
+
if (
|
1850
|
+
declaredDependenciesNode.elements &&
|
1851
|
+
declaredDependenciesNode.elements.some(
|
1852
|
+
el => el && el.type === 'Identifier' && el.name === callback.name
|
1853
|
+
)
|
1854
|
+
) {
|
1855
|
+
return;
|
1856
|
+
}
|
1857
|
+
const variable = context.getScope().set.get(callback.name);
|
1858
|
+
if (variable == null || variable.defs == null) {
|
1859
|
+
return;
|
1860
|
+
}
|
1861
|
+
const def = variable.defs[0];
|
1862
|
+
if (!def || !def.node) {
|
1863
|
+
break;
|
1864
|
+
}
|
1865
|
+
if (def.type !== 'Variable' && def.type !== 'FunctionName') {
|
1866
|
+
break;
|
1867
|
+
}
|
1868
|
+
switch (def.node.type) {
|
1869
|
+
case 'FunctionDeclaration':
|
1870
|
+
visitFunctionWithDependencies(
|
1871
|
+
def.node,
|
1872
|
+
declaredDependenciesNode,
|
1873
|
+
reactiveHook,
|
1874
|
+
reactiveHookName,
|
1875
|
+
isEffect
|
1876
|
+
);
|
1877
|
+
return;
|
1878
|
+
case 'VariableDeclarator':
|
1879
|
+
const init = def.node.init;
|
1880
|
+
if (!init) {
|
1881
|
+
break;
|
1882
|
+
}
|
1883
|
+
switch (init.type) {
|
1884
|
+
case 'ArrowFunctionExpression':
|
1885
|
+
case 'FunctionExpression':
|
1886
|
+
visitFunctionWithDependencies(
|
1887
|
+
init,
|
1888
|
+
declaredDependenciesNode,
|
1889
|
+
reactiveHook,
|
1890
|
+
reactiveHookName,
|
1891
|
+
isEffect
|
1892
|
+
);
|
1893
|
+
return;
|
1894
|
+
}
|
1895
|
+
break;
|
1896
|
+
}
|
1897
|
+
break;
|
1898
|
+
default:
|
1899
|
+
reportProblem({
|
1900
|
+
node: reactiveHook,
|
1901
|
+
message:
|
1902
|
+
`React Hook ${reactiveHookName} received a function whose dependencies ` +
|
1903
|
+
`are unknown. Pass an inline function instead.`
|
1904
|
+
});
|
1905
|
+
return;
|
2172
1906
|
}
|
1907
|
+
reportProblem({
|
1908
|
+
node: reactiveHook,
|
1909
|
+
message:
|
1910
|
+
`React Hook ${reactiveHookName} has a missing dependency: '${callback.name}'. ` +
|
1911
|
+
`Either include it or remove the dependency array.`,
|
1912
|
+
suggest: [
|
1913
|
+
{
|
1914
|
+
desc: `Update the dependencies array to be: [${callback.name}]`,
|
1915
|
+
fix(fixer) {
|
1916
|
+
return fixer.replaceText(
|
1917
|
+
declaredDependenciesNode,
|
1918
|
+
`[${callback.name}]`
|
1919
|
+
);
|
1920
|
+
}
|
1921
|
+
}
|
1922
|
+
]
|
1923
|
+
});
|
1924
|
+
}
|
1925
|
+
return {
|
1926
|
+
CallExpression: visitCallExpression
|
2173
1927
|
};
|
2174
1928
|
}
|
2175
|
-
}
|
2176
|
-
|
2177
|
-
|
2178
|
-
|
2179
|
-
|
2180
|
-
|
2181
|
-
|
2182
|
-
|
2183
|
-
|
2184
|
-
|
2185
|
-
|
2186
|
-
|
2187
|
-
|
2188
|
-
|
2189
|
-
|
2190
|
-
|
2191
|
-
|
2192
|
-
|
2193
|
-
|
2194
|
-
|
2195
|
-
|
2196
|
-
|
2197
|
-
|
1929
|
+
};
|
1930
|
+
function collectRecommendations({
|
1931
|
+
dependencies,
|
1932
|
+
declaredDependencies,
|
1933
|
+
stableDependencies,
|
1934
|
+
externalDependencies,
|
1935
|
+
isEffect
|
1936
|
+
}) {
|
1937
|
+
const depTree = createDepTree();
|
1938
|
+
function createDepTree() {
|
1939
|
+
return {
|
1940
|
+
isUsed: false,
|
1941
|
+
isSatisfiedRecursively: false,
|
1942
|
+
isSubtreeUsed: false,
|
1943
|
+
children: new Map()
|
1944
|
+
};
|
1945
|
+
}
|
1946
|
+
dependencies.forEach((_, key) => {
|
1947
|
+
const node = getOrCreateNodeByPath(depTree, key);
|
1948
|
+
node.isUsed = true;
|
1949
|
+
markAllParentsByPath(depTree, key, parent => {
|
1950
|
+
parent.isSubtreeUsed = true;
|
1951
|
+
});
|
1952
|
+
});
|
1953
|
+
declaredDependencies.forEach(({ key }) => {
|
1954
|
+
const node = getOrCreateNodeByPath(depTree, key);
|
1955
|
+
node.isSatisfiedRecursively = true;
|
1956
|
+
});
|
1957
|
+
stableDependencies.forEach(key => {
|
1958
|
+
const node = getOrCreateNodeByPath(depTree, key);
|
1959
|
+
node.isSatisfiedRecursively = true;
|
1960
|
+
});
|
1961
|
+
function getOrCreateNodeByPath(rootNode, path) {
|
1962
|
+
const keys = path.split('.');
|
1963
|
+
let node = rootNode;
|
1964
|
+
for (const key of keys) {
|
1965
|
+
let child = node.children.get(key);
|
1966
|
+
if (!child) {
|
1967
|
+
child = createDepTree();
|
1968
|
+
node.children.set(key, child);
|
1969
|
+
}
|
1970
|
+
node = child;
|
1971
|
+
}
|
1972
|
+
return node;
|
1973
|
+
}
|
1974
|
+
function markAllParentsByPath(rootNode, path, fn) {
|
1975
|
+
const keys = path.split('.');
|
1976
|
+
let node = rootNode;
|
1977
|
+
for (const key of keys) {
|
1978
|
+
const child = node.children.get(key);
|
1979
|
+
if (!child) {
|
1980
|
+
return;
|
2198
1981
|
}
|
2199
|
-
|
2200
|
-
|
2201
|
-
|
2202
|
-
|
2203
|
-
|
2204
|
-
|
2205
|
-
|
2206
|
-
|
2207
|
-
|
2208
|
-
|
2209
|
-
|
2210
|
-
|
2211
|
-
|
2212
|
-
|
2213
|
-
|
2214
|
-
) {
|
1982
|
+
fn(child);
|
1983
|
+
node = child;
|
1984
|
+
}
|
1985
|
+
}
|
1986
|
+
const missingDependencies = new Set();
|
1987
|
+
const satisfyingDependencies = new Set();
|
1988
|
+
scanTreeRecursively(
|
1989
|
+
depTree,
|
1990
|
+
missingDependencies,
|
1991
|
+
satisfyingDependencies,
|
1992
|
+
key => key
|
1993
|
+
);
|
1994
|
+
function scanTreeRecursively(node, missingPaths, satisfyingPaths, keyToPath) {
|
1995
|
+
node.children.forEach((child, key) => {
|
1996
|
+
const path = keyToPath(key);
|
1997
|
+
if (child.isSatisfiedRecursively) {
|
1998
|
+
if (child.isSubtreeUsed) {
|
1999
|
+
satisfyingPaths.add(path);
|
2000
|
+
}
|
2215
2001
|
return;
|
2216
2002
|
}
|
2217
|
-
if (
|
2003
|
+
if (child.isUsed) {
|
2004
|
+
missingPaths.add(path);
|
2218
2005
|
return;
|
2219
2006
|
}
|
2220
|
-
|
2221
|
-
|
2222
|
-
|
2223
|
-
|
2224
|
-
|
2225
|
-
|
2007
|
+
scanTreeRecursively(
|
2008
|
+
child,
|
2009
|
+
missingPaths,
|
2010
|
+
satisfyingPaths,
|
2011
|
+
childKey => path + '.' + childKey
|
2012
|
+
);
|
2013
|
+
});
|
2014
|
+
}
|
2015
|
+
const suggestedDependencies = [];
|
2016
|
+
const unnecessaryDependencies = new Set();
|
2017
|
+
const duplicateDependencies = new Set();
|
2018
|
+
declaredDependencies.forEach(({ key }) => {
|
2019
|
+
if (satisfyingDependencies.has(key)) {
|
2020
|
+
if (!suggestedDependencies.includes(key)) {
|
2021
|
+
suggestedDependencies.push(key);
|
2022
|
+
} else {
|
2023
|
+
duplicateDependencies.add(key);
|
2024
|
+
}
|
2025
|
+
} else if (
|
2026
|
+
isEffect &&
|
2027
|
+
!key.endsWith('.current') &&
|
2028
|
+
!externalDependencies.has(key)
|
2029
|
+
) {
|
2030
|
+
if (!suggestedDependencies.includes(key)) {
|
2031
|
+
suggestedDependencies.push(key);
|
2226
2032
|
}
|
2033
|
+
} else {
|
2034
|
+
unnecessaryDependencies.add(key);
|
2227
2035
|
}
|
2228
|
-
|
2229
|
-
|
2036
|
+
});
|
2037
|
+
missingDependencies.forEach(key => {
|
2038
|
+
suggestedDependencies.push(key);
|
2039
|
+
});
|
2040
|
+
return {
|
2041
|
+
suggestedDependencies,
|
2042
|
+
unnecessaryDependencies,
|
2043
|
+
duplicateDependencies,
|
2044
|
+
missingDependencies
|
2045
|
+
};
|
2046
|
+
}
|
2047
|
+
function getConstructionExpressionType(node) {
|
2048
|
+
switch (node.type) {
|
2049
|
+
case 'ObjectExpression':
|
2050
|
+
return 'object';
|
2051
|
+
case 'ArrayExpression':
|
2052
|
+
return 'array';
|
2053
|
+
case 'ArrowFunctionExpression':
|
2054
|
+
case 'FunctionExpression':
|
2055
|
+
return 'function';
|
2056
|
+
case 'ClassExpression':
|
2057
|
+
return 'class';
|
2058
|
+
case 'ConditionalExpression':
|
2230
2059
|
if (
|
2231
|
-
|
2060
|
+
getConstructionExpressionType(node.consequent) != null ||
|
2061
|
+
getConstructionExpressionType(node.alternate) != null
|
2232
2062
|
) {
|
2233
|
-
return;
|
2063
|
+
return 'conditional';
|
2234
2064
|
}
|
2235
|
-
|
2236
|
-
|
2065
|
+
return null;
|
2066
|
+
case 'LogicalExpression':
|
2067
|
+
if (
|
2068
|
+
getConstructionExpressionType(node.left) != null ||
|
2069
|
+
getConstructionExpressionType(node.right) != null
|
2070
|
+
) {
|
2071
|
+
return 'logical expression';
|
2237
2072
|
}
|
2238
|
-
|
2239
|
-
|
2240
|
-
|
2241
|
-
|
2242
|
-
|
2243
|
-
|
2073
|
+
return null;
|
2074
|
+
case 'JSXFragment':
|
2075
|
+
return 'JSX fragment';
|
2076
|
+
case 'JSXElement':
|
2077
|
+
return 'JSX element';
|
2078
|
+
case 'AssignmentExpression':
|
2079
|
+
if (getConstructionExpressionType(node.right) != null) {
|
2080
|
+
return 'assignment expression';
|
2244
2081
|
}
|
2245
|
-
|
2246
|
-
|
2247
|
-
|
2248
|
-
|
2249
|
-
|
2082
|
+
return null;
|
2083
|
+
case 'NewExpression':
|
2084
|
+
return 'object construction';
|
2085
|
+
case 'Literal':
|
2086
|
+
if (node.value instanceof RegExp) {
|
2087
|
+
return 'regular expression';
|
2250
2088
|
}
|
2251
|
-
|
2089
|
+
return null;
|
2090
|
+
case 'TypeCastExpression':
|
2091
|
+
return getConstructionExpressionType(node.expression);
|
2092
|
+
case 'TSAsExpression':
|
2093
|
+
return getConstructionExpressionType(node.expression);
|
2252
2094
|
}
|
2253
|
-
|
2254
|
-
|
2255
|
-
|
2256
|
-
|
2257
|
-
|
2258
|
-
|
2259
|
-
|
2260
|
-
|
2261
|
-
|
2262
|
-
|
2263
|
-
|
2264
|
-
|
2265
|
-
|
2266
|
-
|
2267
|
-
|
2268
|
-
|
2269
|
-
|
2270
|
-
|
2271
|
-
|
2272
|
-
|
2273
|
-
|
2274
|
-
|
2275
|
-
|
2276
|
-
|
2277
|
-
|
2278
|
-
|
2279
|
-
|
2280
|
-
|
2281
|
-
|
2282
|
-
}
|
2283
|
-
});
|
2095
|
+
return null;
|
2096
|
+
}
|
2097
|
+
function scanForConstructions({
|
2098
|
+
declaredDependencies,
|
2099
|
+
declaredDependenciesNode,
|
2100
|
+
componentScope,
|
2101
|
+
scope
|
2102
|
+
}) {
|
2103
|
+
const constructions = declaredDependencies
|
2104
|
+
.map(({ key }) => {
|
2105
|
+
const ref = componentScope.variables.find(v => v.name === key);
|
2106
|
+
if (ref == null) {
|
2107
|
+
return null;
|
2108
|
+
}
|
2109
|
+
const node = ref.defs[0];
|
2110
|
+
if (node == null) {
|
2111
|
+
return null;
|
2112
|
+
}
|
2113
|
+
if (
|
2114
|
+
node.type === 'Variable' &&
|
2115
|
+
node.node.type === 'VariableDeclarator' &&
|
2116
|
+
node.node.id.type === 'Identifier' &&
|
2117
|
+
node.node.init != null
|
2118
|
+
) {
|
2119
|
+
const constantExpressionType = getConstructionExpressionType(
|
2120
|
+
node.node.init
|
2121
|
+
);
|
2122
|
+
if (constantExpressionType != null) {
|
2123
|
+
return [ref, constantExpressionType];
|
2284
2124
|
}
|
2285
2125
|
}
|
2286
|
-
|
2287
|
-
|
2288
|
-
|
2289
|
-
|
2290
|
-
|
2291
|
-
|
2292
|
-
|
2126
|
+
if (
|
2127
|
+
node.type === 'FunctionName' &&
|
2128
|
+
node.node.type === 'FunctionDeclaration'
|
2129
|
+
) {
|
2130
|
+
return [ref, 'function'];
|
2131
|
+
}
|
2132
|
+
if (node.type === 'ClassName' && node.node.type === 'ClassDeclaration') {
|
2133
|
+
return [ref, 'class'];
|
2134
|
+
}
|
2135
|
+
return null;
|
2136
|
+
})
|
2137
|
+
.filter(Boolean);
|
2138
|
+
function isUsedOutsideOfHook(ref) {
|
2139
|
+
let foundWriteExpr = false;
|
2140
|
+
for (let i = 0; i < ref.references.length; i++) {
|
2141
|
+
const reference = ref.references[i];
|
2142
|
+
if (reference.writeExpr) {
|
2143
|
+
if (foundWriteExpr) {
|
2144
|
+
return true;
|
2145
|
+
}
|
2146
|
+
foundWriteExpr = true;
|
2147
|
+
continue;
|
2148
|
+
}
|
2149
|
+
let currentScope = reference.from;
|
2150
|
+
while (currentScope !== scope && currentScope != null) {
|
2151
|
+
currentScope = currentScope.upper;
|
2152
|
+
}
|
2153
|
+
if (
|
2154
|
+
currentScope !== scope &&
|
2155
|
+
!isAncestorNodeOf(declaredDependenciesNode, reference.identifier)
|
2156
|
+
) {
|
2157
|
+
return true;
|
2158
|
+
}
|
2159
|
+
}
|
2160
|
+
return false;
|
2293
2161
|
}
|
2294
|
-
|
2295
|
-
|
2162
|
+
return constructions.map(([ref, depType]) => ({
|
2163
|
+
construction: ref.defs[0],
|
2164
|
+
depType,
|
2165
|
+
isUsedOutsideOfHook: isUsedOutsideOfHook(ref)
|
2166
|
+
}));
|
2167
|
+
}
|
2168
|
+
function getDependency(node) {
|
2169
|
+
if (
|
2170
|
+
(node.parent.type === 'MemberExpression' ||
|
2171
|
+
node.parent.type === 'OptionalMemberExpression') &&
|
2172
|
+
node.parent.object === node &&
|
2173
|
+
node.parent.property.name !== 'current' &&
|
2174
|
+
!node.parent.computed &&
|
2175
|
+
!(
|
2176
|
+
node.parent.parent != null &&
|
2177
|
+
(node.parent.parent.type === 'CallExpression' ||
|
2178
|
+
node.parent.parent.type === 'OptionalCallExpression') &&
|
2179
|
+
node.parent.parent.callee === node.parent
|
2180
|
+
)
|
2181
|
+
) {
|
2182
|
+
return getDependency(node.parent);
|
2183
|
+
} else if (
|
2184
|
+
node.type === 'MemberExpression' &&
|
2185
|
+
node.parent &&
|
2186
|
+
node.parent.type === 'AssignmentExpression' &&
|
2187
|
+
node.parent.left === node
|
2188
|
+
) {
|
2189
|
+
return node.object;
|
2296
2190
|
}
|
2297
|
-
|
2298
|
-
|
2299
|
-
|
2191
|
+
return node;
|
2192
|
+
}
|
2193
|
+
function markNode(node, optionalChains, result) {
|
2194
|
+
if (optionalChains) {
|
2195
|
+
if (node.optional) {
|
2196
|
+
if (!optionalChains.has(result)) {
|
2197
|
+
optionalChains.set(result, true);
|
2198
|
+
}
|
2199
|
+
} else {
|
2200
|
+
optionalChains.set(result, false);
|
2300
2201
|
}
|
2301
|
-
return node.quasis[0].value.raw;
|
2302
2202
|
}
|
2303
|
-
return null;
|
2304
2203
|
}
|
2305
|
-
function
|
2306
|
-
|
2307
|
-
|
2308
|
-
|
2309
|
-
|
2310
|
-
case 'CallExpression': {
|
2311
|
-
return getBaseIdentifier(node.callee);
|
2204
|
+
function analyzePropertyChain(node, optionalChains) {
|
2205
|
+
if (node.type === 'Identifier' || node.type === 'JSXIdentifier') {
|
2206
|
+
const result = node.name;
|
2207
|
+
if (optionalChains) {
|
2208
|
+
optionalChains.set(result, false);
|
2312
2209
|
}
|
2313
|
-
|
2314
|
-
|
2210
|
+
return result;
|
2211
|
+
} else if (node.type === 'MemberExpression' && !node.computed) {
|
2212
|
+
const object = analyzePropertyChain(node.object, optionalChains);
|
2213
|
+
const property = analyzePropertyChain(node.property, null);
|
2214
|
+
const result = `${object}.${property}`;
|
2215
|
+
markNode(node, optionalChains, result);
|
2216
|
+
return result;
|
2217
|
+
} else if (node.type === 'OptionalMemberExpression' && !node.computed) {
|
2218
|
+
const object = analyzePropertyChain(node.object, optionalChains);
|
2219
|
+
const property = analyzePropertyChain(node.property, null);
|
2220
|
+
const result = `${object}.${property}`;
|
2221
|
+
markNode(node, optionalChains, result);
|
2222
|
+
return result;
|
2223
|
+
} else if (node.type === 'ChainExpression' && !node.computed) {
|
2224
|
+
const expression = node.expression;
|
2225
|
+
if (expression.type === 'CallExpression') {
|
2226
|
+
throw new Error(`Unsupported node type: ${expression.type}`);
|
2315
2227
|
}
|
2228
|
+
const object = analyzePropertyChain(expression.object, optionalChains);
|
2229
|
+
const property = analyzePropertyChain(expression.property, null);
|
2230
|
+
const result = `${object}.${property}`;
|
2231
|
+
markNode(expression, optionalChains, result);
|
2232
|
+
return result;
|
2316
2233
|
}
|
2317
|
-
|
2234
|
+
throw new Error(`Unsupported node type: ${node.type}`);
|
2318
2235
|
}
|
2319
|
-
function
|
2320
|
-
|
2321
|
-
|
2322
|
-
node.
|
2323
|
-
node.
|
2324
|
-
|
2325
|
-
|
2326
|
-
|
2327
|
-
return;
|
2236
|
+
function getNodeWithoutReactNamespace(node) {
|
2237
|
+
if (
|
2238
|
+
node.type === 'MemberExpression' &&
|
2239
|
+
node.object.type === 'Identifier' &&
|
2240
|
+
node.object.name === 'React' &&
|
2241
|
+
node.property.type === 'Identifier' &&
|
2242
|
+
!node.computed
|
2243
|
+
) {
|
2244
|
+
return node.property;
|
2328
2245
|
}
|
2329
|
-
|
2330
|
-
|
2331
|
-
|
2332
|
-
|
2333
|
-
|
2334
|
-
|
2335
|
-
}
|
2336
|
-
})();
|
2337
|
-
if (!styles) {
|
2338
|
-
return;
|
2246
|
+
return node;
|
2247
|
+
}
|
2248
|
+
function getReactiveHookCallbackIndex(calleeNode, options) {
|
2249
|
+
const node = getNodeWithoutReactNamespace(calleeNode);
|
2250
|
+
if (node.type !== 'Identifier') {
|
2251
|
+
return -1;
|
2339
2252
|
}
|
2340
|
-
switch (
|
2341
|
-
case '
|
2342
|
-
|
2343
|
-
case '
|
2344
|
-
|
2345
|
-
|
2346
|
-
|
2347
|
-
|
2348
|
-
|
2349
|
-
|
2350
|
-
|
2351
|
-
|
2352
|
-
|
2353
|
-
|
2354
|
-
|
2355
|
-
|
2356
|
-
stylesObj = bodyNode.argument;
|
2357
|
-
}
|
2358
|
-
});
|
2359
|
-
return stylesObj;
|
2253
|
+
switch (node.name) {
|
2254
|
+
case 'useEffect':
|
2255
|
+
case 'useLayoutEffect':
|
2256
|
+
case 'useCallback':
|
2257
|
+
case 'useMemo':
|
2258
|
+
return 0;
|
2259
|
+
case 'useImperativeHandle':
|
2260
|
+
return 1;
|
2261
|
+
default:
|
2262
|
+
if (node === calleeNode && options && options.additionalHooks) {
|
2263
|
+
let name;
|
2264
|
+
try {
|
2265
|
+
name = analyzePropertyChain(node, null);
|
2266
|
+
} catch (err) {
|
2267
|
+
if (/Unsupported node type/.test(err.message)) {
|
2268
|
+
return 0;
|
2360
2269
|
}
|
2270
|
+
throw err;
|
2361
2271
|
}
|
2272
|
+
return options.additionalHooks.test(name) ? 0 : -1;
|
2362
2273
|
}
|
2363
|
-
|
2274
|
+
return -1;
|
2275
|
+
}
|
2276
|
+
}
|
2277
|
+
function fastFindReferenceWithParent(start, target) {
|
2278
|
+
const queue = [start];
|
2279
|
+
let item = null;
|
2280
|
+
while (queue.length > 0) {
|
2281
|
+
item = queue.shift();
|
2282
|
+
if (isSameIdentifier(item, target)) {
|
2283
|
+
return item;
|
2284
|
+
}
|
2285
|
+
if (!isAncestorNodeOf(item, target)) {
|
2286
|
+
continue;
|
2287
|
+
}
|
2288
|
+
for (const [key, value] of Object.entries(item)) {
|
2289
|
+
if (key === 'parent') {
|
2290
|
+
continue;
|
2291
|
+
}
|
2292
|
+
if (isNodeLike(value)) {
|
2293
|
+
value.parent = item;
|
2294
|
+
queue.push(value);
|
2295
|
+
} else if (Array.isArray(value)) {
|
2296
|
+
value.forEach(val => {
|
2297
|
+
if (isNodeLike(val)) {
|
2298
|
+
val.parent = item;
|
2299
|
+
queue.push(val);
|
2300
|
+
}
|
2301
|
+
});
|
2302
|
+
}
|
2303
|
+
}
|
2304
|
+
}
|
2305
|
+
return null;
|
2306
|
+
}
|
2307
|
+
function joinEnglish(arr) {
|
2308
|
+
let s = '';
|
2309
|
+
for (let i = 0; i < arr.length; i++) {
|
2310
|
+
s += arr[i];
|
2311
|
+
if (i === 0 && arr.length === 2) {
|
2312
|
+
s += ' and ';
|
2313
|
+
} else if (i === arr.length - 2 && arr.length > 2) {
|
2314
|
+
s += ', and ';
|
2315
|
+
} else if (i < arr.length - 1) {
|
2316
|
+
s += ', ';
|
2317
|
+
}
|
2364
2318
|
}
|
2319
|
+
return s;
|
2320
|
+
}
|
2321
|
+
function isNodeLike(val) {
|
2322
|
+
return (
|
2323
|
+
typeof val === 'object' &&
|
2324
|
+
val != null &&
|
2325
|
+
!Array.isArray(val) &&
|
2326
|
+
typeof val.type === 'string'
|
2327
|
+
);
|
2328
|
+
}
|
2329
|
+
function isSameIdentifier(a, b) {
|
2330
|
+
return (
|
2331
|
+
(a.type === 'Identifier' || a.type === 'JSXIdentifier') &&
|
2332
|
+
a.type === b.type &&
|
2333
|
+
a.name === b.name &&
|
2334
|
+
a.range[0] === b.range[0] &&
|
2335
|
+
a.range[1] === b.range[1]
|
2336
|
+
);
|
2337
|
+
}
|
2338
|
+
function isAncestorNodeOf(a, b) {
|
2339
|
+
return a.range[0] <= b.range[0] && a.range[1] >= b.range[1];
|
2365
2340
|
}
|
2366
2341
|
|
2367
|
-
|
2342
|
+
const Components = require('eslint-plugin-react/lib/util/Components');
|
2343
|
+
var hookUseRef = {
|
2368
2344
|
meta: {
|
2369
|
-
|
2345
|
+
docs: {
|
2346
|
+
description: 'Ensure naming of useRef hook value.',
|
2347
|
+
recommended: false
|
2348
|
+
},
|
2349
|
+
schema: [],
|
2350
|
+
type: 'suggestion',
|
2351
|
+
hasSuggestions: true
|
2370
2352
|
},
|
2371
|
-
create:
|
2372
|
-
|
2373
|
-
|
2374
|
-
|
2375
|
-
|
2376
|
-
|
2377
|
-
|
2378
|
-
|
2379
|
-
|
2380
|
-
|
2381
|
-
|
2382
|
-
|
2383
|
-
|
2384
|
-
|
2385
|
-
|
2386
|
-
return;
|
2387
|
-
}
|
2388
|
-
const className = property.key.value || property.key.name;
|
2389
|
-
if (!eslintUtils.isCamelCase(className)) {
|
2390
|
-
context.report({
|
2391
|
-
node: property,
|
2392
|
-
message: `Class \`${className}\` must be camelCase in TSS.`
|
2393
|
-
});
|
2394
|
-
}
|
2353
|
+
create: Components.detect((context, component, util) => ({
|
2354
|
+
CallExpression(node) {
|
2355
|
+
const isImmediateReturn =
|
2356
|
+
node.parent && node.parent.type === 'ReturnStatement';
|
2357
|
+
if (isImmediateReturn || !util.isReactHookCall(node, ['useRef'])) {
|
2358
|
+
return;
|
2359
|
+
}
|
2360
|
+
if (node.parent.id.type !== 'Identifier') {
|
2361
|
+
return;
|
2362
|
+
}
|
2363
|
+
const variable = node.parent.id.name;
|
2364
|
+
if (!variable.endsWith('Ref')) {
|
2365
|
+
context.report({
|
2366
|
+
node: node,
|
2367
|
+
message: 'useRef call is not end with "Ref"'
|
2395
2368
|
});
|
2396
2369
|
}
|
2397
|
-
}
|
2398
|
-
}
|
2370
|
+
}
|
2371
|
+
}))
|
2399
2372
|
};
|
2400
2373
|
|
2401
|
-
|
2374
|
+
function* updateImportStatement(context, fixer, key) {
|
2375
|
+
const sourceCode = context.sourceCode;
|
2376
|
+
const importNode = sourceCode.ast.body.find(
|
2377
|
+
node => node.type === 'ImportDeclaration' && node.source.value === 'react'
|
2378
|
+
);
|
2379
|
+
if (!importNode) {
|
2380
|
+
yield fixer.insertTextBefore(
|
2381
|
+
sourceCode.ast.body[0],
|
2382
|
+
`import { ${key} } from 'react';\n`
|
2383
|
+
);
|
2384
|
+
return;
|
2385
|
+
}
|
2386
|
+
if (
|
2387
|
+
importNode.specifiers.length === 1 &&
|
2388
|
+
importNode.specifiers[0].type === 'ImportDefaultSpecifier'
|
2389
|
+
) {
|
2390
|
+
yield fixer.insertTextAfter(importNode.specifiers[0], `, { ${key} }`);
|
2391
|
+
return;
|
2392
|
+
}
|
2393
|
+
const alreadyImportedKeys = importNode.specifiers
|
2394
|
+
.filter(specifier => specifier.type === 'ImportSpecifier')
|
2395
|
+
.map(specifier => specifier.imported.name);
|
2396
|
+
if (alreadyImportedKeys.includes(key)) {
|
2397
|
+
return;
|
2398
|
+
}
|
2399
|
+
yield fixer.insertTextAfter([...importNode.specifiers].pop(), `, ${key}`);
|
2400
|
+
}
|
2401
|
+
var preferNamedPropertyAccess = utils.ESLintUtils.RuleCreator.withoutDocs({
|
2402
|
+
defaultOptions: [],
|
2402
2403
|
meta: {
|
2403
|
-
type: '
|
2404
|
+
type: 'layout',
|
2405
|
+
fixable: 'code',
|
2404
2406
|
docs: {
|
2405
2407
|
description:
|
2406
|
-
'Enforce
|
2407
|
-
}
|
2408
|
+
'Enforce importing each member of React namespace separately instead of accessing them through React namespace'
|
2409
|
+
},
|
2410
|
+
messages: {
|
2411
|
+
illegalReactPropertyAccess:
|
2412
|
+
'Illegal React property access: {{name}}. Use named import instead.'
|
2413
|
+
},
|
2414
|
+
schema: []
|
2408
2415
|
},
|
2409
|
-
create
|
2410
|
-
const parserOptions = context.parserOptions;
|
2411
|
-
if (!parserOptions || !parserOptions.project) {
|
2412
|
-
return {};
|
2413
|
-
}
|
2416
|
+
create(context) {
|
2414
2417
|
return {
|
2415
|
-
|
2416
|
-
|
2417
|
-
|
2418
|
+
TSQualifiedName(node) {
|
2419
|
+
if (
|
2420
|
+
('name' in node.left && node.left.name !== 'React') ||
|
2421
|
+
('name' in node.right && node.right.name.endsWith('Event'))
|
2422
|
+
) {
|
2418
2423
|
return;
|
2419
2424
|
}
|
2420
|
-
|
2421
|
-
|
2422
|
-
|
2423
|
-
|
2424
|
-
|
2425
|
-
|
2426
|
-
|
2427
|
-
|
2428
|
-
|
2429
|
-
});
|
2430
|
-
}
|
2425
|
+
context.report({
|
2426
|
+
node,
|
2427
|
+
messageId: 'illegalReactPropertyAccess',
|
2428
|
+
data: {
|
2429
|
+
name: node.right.name
|
2430
|
+
},
|
2431
|
+
*fix(fixer) {
|
2432
|
+
yield fixer.replaceText(node, node.right.name);
|
2433
|
+
yield* updateImportStatement(context, fixer, node.right.name);
|
2431
2434
|
}
|
2435
|
+
});
|
2436
|
+
},
|
2437
|
+
MemberExpression(node) {
|
2438
|
+
if (node.object.name !== 'React') {
|
2439
|
+
return;
|
2432
2440
|
}
|
2433
|
-
|
2434
|
-
|
2435
|
-
|
2436
|
-
|
2437
|
-
|
2438
|
-
|
2439
|
-
|
2440
|
-
|
2441
|
-
|
2442
|
-
}
|
2443
|
-
});
|
2441
|
+
context.report({
|
2442
|
+
node,
|
2443
|
+
messageId: 'illegalReactPropertyAccess',
|
2444
|
+
data: {
|
2445
|
+
name: node.property.name
|
2446
|
+
},
|
2447
|
+
*fix(fixer) {
|
2448
|
+
yield fixer.replaceText(node, node.property.name);
|
2449
|
+
yield* updateImportStatement(context, fixer, node.property.name);
|
2444
2450
|
}
|
2445
|
-
}
|
2446
|
-
loopStylesObj(stylesObj);
|
2451
|
+
});
|
2447
2452
|
}
|
2448
2453
|
};
|
2449
2454
|
}
|
2450
|
-
};
|
2455
|
+
});
|
2451
2456
|
|
2452
|
-
var
|
2457
|
+
var preferSxProp = {
|
2453
2458
|
meta: {
|
2454
|
-
|
2455
|
-
|
2456
|
-
|
2457
|
-
|
2458
|
-
|
2459
|
-
|
2460
|
-
|
2461
|
-
|
2462
|
-
|
2463
|
-
|
2464
|
-
|
2465
|
-
|
2466
|
-
|
2467
|
-
|
2468
|
-
|
2469
|
-
|
2470
|
-
|
2471
|
-
property.type === 'SpreadElement'
|
2472
|
-
) {
|
2473
|
-
return;
|
2474
|
-
}
|
2475
|
-
definedClasses[property.key.value || property.key.name] = property;
|
2476
|
-
});
|
2477
|
-
},
|
2478
|
-
MemberExpression(node) {
|
2479
|
-
if (
|
2480
|
-
node.object.type === 'Identifier' &&
|
2481
|
-
node.object.name === 'classes'
|
2482
|
-
) {
|
2483
|
-
const whichClass = getBasicIdentifier(node.property);
|
2484
|
-
if (whichClass) {
|
2485
|
-
usedClasses[whichClass] = true;
|
2459
|
+
docs: {
|
2460
|
+
description: 'Prefer using sx prop instead of inline styles',
|
2461
|
+
category: 'Best Practices',
|
2462
|
+
recommended: false
|
2463
|
+
},
|
2464
|
+
messages: {
|
2465
|
+
preferSxProp:
|
2466
|
+
'Avoid using inline styles, use sx prop or tss-react or styled-component instead'
|
2467
|
+
},
|
2468
|
+
schema: [
|
2469
|
+
{
|
2470
|
+
type: 'object',
|
2471
|
+
properties: {
|
2472
|
+
allowedFor: {
|
2473
|
+
type: 'array',
|
2474
|
+
uniqueItems: true,
|
2475
|
+
items: { type: 'string' }
|
2486
2476
|
}
|
2487
|
-
return;
|
2488
|
-
}
|
2489
|
-
const classIdentifier = getBasicIdentifier(node.property);
|
2490
|
-
if (!classIdentifier) {
|
2491
|
-
return;
|
2492
|
-
}
|
2493
|
-
if (classIdentifier !== 'classes') {
|
2494
|
-
return;
|
2495
|
-
}
|
2496
|
-
const { parent } = node;
|
2497
|
-
if (parent.type !== 'MemberExpression') {
|
2498
|
-
return;
|
2499
|
-
}
|
2500
|
-
if (
|
2501
|
-
node.object.object &&
|
2502
|
-
node.object.object.type !== 'ThisExpression'
|
2503
|
-
) {
|
2504
|
-
return;
|
2505
|
-
}
|
2506
|
-
const propsIdentifier = getBasicIdentifier(parent.object);
|
2507
|
-
if (propsIdentifier && propsIdentifier !== 'props') {
|
2508
|
-
return;
|
2509
|
-
}
|
2510
|
-
if (!propsIdentifier && parent.object.type !== 'MemberExpression') {
|
2511
|
-
return;
|
2512
|
-
}
|
2513
|
-
if (parent.parent.type === 'MemberExpression') {
|
2514
|
-
return;
|
2515
2477
|
}
|
2516
|
-
|
2517
|
-
|
2518
|
-
|
2519
|
-
|
2520
|
-
|
2521
|
-
|
2522
|
-
|
2523
|
-
|
2524
|
-
|
2525
|
-
|
2526
|
-
|
2527
|
-
|
2528
|
-
|
2478
|
+
}
|
2479
|
+
]
|
2480
|
+
},
|
2481
|
+
create(context) {
|
2482
|
+
const configuration = context.options[0] || {};
|
2483
|
+
const allowedFor = configuration.allowedFor || [];
|
2484
|
+
function checkComponent(node) {
|
2485
|
+
const parentName = node.parent.name;
|
2486
|
+
const tag =
|
2487
|
+
parentName.name ||
|
2488
|
+
`${parentName.object.name}.${parentName.property.name}`;
|
2489
|
+
const componentName = parentName.name || parentName.property.name;
|
2490
|
+
if (
|
2491
|
+
componentName &&
|
2492
|
+
typeof componentName[0] === 'string' &&
|
2493
|
+
componentName[0] !== componentName[0].toUpperCase()
|
2494
|
+
) {
|
2495
|
+
return;
|
2496
|
+
}
|
2497
|
+
if (allowedFor.includes(tag)) {
|
2498
|
+
return;
|
2499
|
+
}
|
2500
|
+
const prop = node.name.name;
|
2501
|
+
if (prop === 'style') {
|
2502
|
+
context.report({
|
2503
|
+
node,
|
2504
|
+
messageId: 'preferSxProp'
|
2505
|
+
});
|
2506
|
+
}
|
2507
|
+
}
|
2508
|
+
function checkDOMNodes(node) {
|
2509
|
+
const tag = node.parent.name.name;
|
2510
|
+
if (
|
2511
|
+
!(tag && typeof tag === 'string' && tag[0] !== tag[0].toUpperCase())
|
2512
|
+
) {
|
2513
|
+
return;
|
2514
|
+
}
|
2515
|
+
if (allowedFor.includes(tag)) {
|
2516
|
+
return;
|
2517
|
+
}
|
2518
|
+
const prop = node.name.name;
|
2519
|
+
if (prop === 'style') {
|
2520
|
+
context.report({
|
2521
|
+
node,
|
2522
|
+
messageId: 'preferSxProp'
|
2529
2523
|
});
|
2530
2524
|
}
|
2525
|
+
}
|
2526
|
+
return {
|
2527
|
+
JSXAttribute(node) {
|
2528
|
+
checkComponent(node);
|
2529
|
+
checkDOMNodes(node);
|
2530
|
+
}
|
2531
2531
|
};
|
2532
2532
|
}
|
2533
2533
|
};
|