4
4
TSESLint ,
5
5
TSESTree ,
6
6
} from '@typescript-eslint/experimental-utils' ;
7
+ import { PatternVisitor } from '@typescript-eslint/scope-manager' ;
7
8
import * as ts from 'typescript' ;
8
9
import * as util from '../util' ;
9
10
@@ -95,13 +96,23 @@ type MetaSelectorsString = keyof typeof MetaSelectors;
95
96
type IndividualAndMetaSelectorsString = SelectorsString | MetaSelectorsString ;
96
97
97
98
enum Modifiers {
99
+ // const variable
98
100
const = 1 << 0 ,
101
+ // readonly members
99
102
readonly = 1 << 1 ,
103
+ // static members
100
104
static = 1 << 2 ,
105
+ // member accessibility
101
106
public = 1 << 3 ,
102
107
protected = 1 << 4 ,
103
108
private = 1 << 5 ,
104
109
abstract = 1 << 6 ,
110
+ // destructured variable
111
+ destructured = 1 << 7 ,
112
+ // variables declared in the top-level scope
113
+ global = 1 << 8 ,
114
+ // things that are exported
115
+ exported = 1 << 9 ,
105
116
}
106
117
type ModifiersString = keyof typeof Modifiers ;
107
118
@@ -324,8 +335,13 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
324
335
...selectorSchema ( 'default' , false , util . getEnumNames ( Modifiers ) ) ,
325
336
326
337
...selectorSchema ( 'variableLike' , false ) ,
327
- ...selectorSchema ( 'variable' , true , [ 'const' ] ) ,
328
- ...selectorSchema ( 'function' , false ) ,
338
+ ...selectorSchema ( 'variable' , true , [
339
+ 'const' ,
340
+ 'destructured' ,
341
+ 'global' ,
342
+ 'exported' ,
343
+ ] ) ,
344
+ ...selectorSchema ( 'function' , false , [ 'global' , 'exported' ] ) ,
329
345
...selectorSchema ( 'parameter' , true ) ,
330
346
331
347
...selectorSchema ( 'memberLike' , false , [
@@ -412,11 +428,11 @@ const SCHEMA: JSONSchema.JSONSchema4 = {
412
428
] ) ,
413
429
...selectorSchema ( 'enumMember' , false ) ,
414
430
415
- ...selectorSchema ( 'typeLike' , false , [ 'abstract' ] ) ,
416
- ...selectorSchema ( 'class' , false , [ 'abstract' ] ) ,
417
- ...selectorSchema ( 'interface' , false ) ,
418
- ...selectorSchema ( 'typeAlias' , false ) ,
419
- ...selectorSchema ( 'enum' , false ) ,
431
+ ...selectorSchema ( 'typeLike' , false , [ 'abstract' , 'exported' ] ) ,
432
+ ...selectorSchema ( 'class' , false , [ 'abstract' , 'exported' ] ) ,
433
+ ...selectorSchema ( 'interface' , false , [ 'exported' ] ) ,
434
+ ...selectorSchema ( 'typeAlias' , false , [ 'exported' ] ) ,
435
+ ...selectorSchema ( 'enum' , false , [ 'exported' ] ) ,
420
436
...selectorSchema ( 'typeParameter' , false ) ,
421
437
] ,
422
438
} ,
@@ -550,22 +566,40 @@ export default util.createRule<Options, MessageIds>({
550
566
if ( ! validator ) {
551
567
return ;
552
568
}
569
+ const identifiers = getIdentifiersFromPattern ( node . id ) ;
553
570
554
- const identifiers : TSESTree . Identifier [ ] = [ ] ;
555
- getIdentifiersFromPattern ( node . id , identifiers ) ;
556
-
557
- const modifiers = new Set < Modifiers > ( ) ;
571
+ const baseModifiers = new Set < Modifiers > ( ) ;
558
572
const parent = node . parent ;
559
- if (
560
- parent &&
561
- parent . type === AST_NODE_TYPES . VariableDeclaration &&
562
- parent . kind === 'const'
563
- ) {
564
- modifiers . add ( Modifiers . const ) ;
573
+ if ( parent ?. type === AST_NODE_TYPES . VariableDeclaration ) {
574
+ if ( parent . kind === 'const' ) {
575
+ baseModifiers . add ( Modifiers . const ) ;
576
+ }
577
+ if ( isGlobal ( context . getScope ( ) ) ) {
578
+ baseModifiers . add ( Modifiers . global ) ;
579
+ }
565
580
}
566
581
567
- identifiers . forEach ( i => {
568
- validator ( i , modifiers ) ;
582
+ identifiers . forEach ( id => {
583
+ const modifiers = new Set ( baseModifiers ) ;
584
+ if (
585
+ // `const { x }`
586
+ // does not match `const { x: y }`
587
+ ( id . parent ?. type === AST_NODE_TYPES . Property &&
588
+ id . parent . shorthand ) ||
589
+ // `const { x = 2 }`
590
+ // does not match const `{ x: y = 2 }`
591
+ ( id . parent ?. type === AST_NODE_TYPES . AssignmentPattern &&
592
+ id . parent . parent ?. type === AST_NODE_TYPES . Property &&
593
+ id . parent . parent . shorthand )
594
+ ) {
595
+ modifiers . add ( Modifiers . destructured ) ;
596
+ }
597
+
598
+ if ( isExported ( parent , id . name , context . getScope ( ) ) ) {
599
+ modifiers . add ( Modifiers . exported ) ;
600
+ }
601
+
602
+ validator ( id , modifiers ) ;
569
603
} ) ;
570
604
} ,
571
605
@@ -584,7 +618,17 @@ export default util.createRule<Options, MessageIds>({
584
618
return ;
585
619
}
586
620
587
- validator ( node . id ) ;
621
+ const modifiers = new Set < Modifiers > ( ) ;
622
+ // functions create their own nested scope
623
+ const scope = context . getScope ( ) . upper ;
624
+ if ( isGlobal ( scope ) ) {
625
+ modifiers . add ( Modifiers . global ) ;
626
+ }
627
+ if ( isExported ( node , node . id . name , scope ) ) {
628
+ modifiers . add ( Modifiers . exported ) ;
629
+ }
630
+
631
+ validator ( node . id , modifiers ) ;
588
632
} ,
589
633
590
634
// #endregion function
@@ -608,8 +652,7 @@ export default util.createRule<Options, MessageIds>({
608
652
return ;
609
653
}
610
654
611
- const identifiers : TSESTree . Identifier [ ] = [ ] ;
612
- getIdentifiersFromPattern ( param , identifiers ) ;
655
+ const identifiers = getIdentifiersFromPattern ( param ) ;
613
656
614
657
identifiers . forEach ( i => {
615
658
validator ( i ) ;
@@ -629,8 +672,7 @@ export default util.createRule<Options, MessageIds>({
629
672
630
673
const modifiers = getMemberModifiers ( node ) ;
631
674
632
- const identifiers : TSESTree . Identifier [ ] = [ ] ;
633
- getIdentifiersFromPattern ( node . parameter , identifiers ) ;
675
+ const identifiers = getIdentifiersFromPattern ( node . parameter ) ;
634
676
635
677
identifiers . forEach ( i => {
636
678
validator ( i , modifiers ) ;
@@ -765,6 +807,11 @@ export default util.createRule<Options, MessageIds>({
765
807
modifiers . add ( Modifiers . abstract ) ;
766
808
}
767
809
810
+ // classes create their own nested scope
811
+ if ( isExported ( node , id . name , context . getScope ( ) . upper ) ) {
812
+ modifiers . add ( Modifiers . exported ) ;
813
+ }
814
+
768
815
validator ( id , modifiers ) ;
769
816
} ,
770
817
@@ -778,7 +825,12 @@ export default util.createRule<Options, MessageIds>({
778
825
return ;
779
826
}
780
827
781
- validator ( node . id ) ;
828
+ const modifiers = new Set < Modifiers > ( ) ;
829
+ if ( isExported ( node , node . id . name , context . getScope ( ) ) ) {
830
+ modifiers . add ( Modifiers . exported ) ;
831
+ }
832
+
833
+ validator ( node . id , modifiers ) ;
782
834
} ,
783
835
784
836
// #endregion interface
@@ -791,7 +843,12 @@ export default util.createRule<Options, MessageIds>({
791
843
return ;
792
844
}
793
845
794
- validator ( node . id ) ;
846
+ const modifiers = new Set < Modifiers > ( ) ;
847
+ if ( isExported ( node , node . id . name , context . getScope ( ) ) ) {
848
+ modifiers . add ( Modifiers . exported ) ;
849
+ }
850
+
851
+ validator ( node . id , modifiers ) ;
795
852
} ,
796
853
797
854
// #endregion typeAlias
@@ -804,7 +861,13 @@ export default util.createRule<Options, MessageIds>({
804
861
return ;
805
862
}
806
863
807
- validator ( node . id ) ;
864
+ const modifiers = new Set < Modifiers > ( ) ;
865
+ // enums create their own nested scope
866
+ if ( isExported ( node , node . id . name , context . getScope ( ) . upper ) ) {
867
+ modifiers . add ( Modifiers . exported ) ;
868
+ }
869
+
870
+ validator ( node . id , modifiers ) ;
808
871
} ,
809
872
810
873
// #endregion enum
@@ -829,55 +892,54 @@ export default util.createRule<Options, MessageIds>({
829
892
830
893
function getIdentifiersFromPattern (
831
894
pattern : TSESTree . DestructuringPattern ,
832
- identifiers : TSESTree . Identifier [ ] ,
833
- ) : void {
834
- switch ( pattern . type ) {
835
- case AST_NODE_TYPES . Identifier :
836
- identifiers . push ( pattern ) ;
837
- break ;
838
-
839
- case AST_NODE_TYPES . ArrayPattern :
840
- pattern . elements . forEach ( element => {
841
- if ( element !== null ) {
842
- getIdentifiersFromPattern ( element , identifiers ) ;
843
- }
844
- } ) ;
845
- break ;
846
-
847
- case AST_NODE_TYPES . ObjectPattern :
848
- pattern . properties . forEach ( property => {
849
- if ( property . type === AST_NODE_TYPES . RestElement ) {
850
- getIdentifiersFromPattern ( property , identifiers ) ;
851
- } else {
852
- // this is a bit weird, but it's because ESTree doesn't have a new node type
853
- // for object destructuring properties - it just reuses Property...
854
- // https://github.com/estree/estree/blob/9ae284b71130d53226e7153b42f01bf819e6e657/es2015.md#L206-L211
855
- // However, the parser guarantees this is safe (and there is error handling)
856
- getIdentifiersFromPattern (
857
- property . value as TSESTree . DestructuringPattern ,
858
- identifiers ,
859
- ) ;
860
- }
861
- } ) ;
862
- break ;
895
+ ) : TSESTree . Identifier [ ] {
896
+ const identifiers : TSESTree . Identifier [ ] = [ ] ;
897
+ const visitor = new PatternVisitor ( { } , pattern , id => identifiers . push ( id ) ) ;
898
+ visitor . visit ( pattern ) ;
899
+ return identifiers ;
900
+ }
863
901
864
- case AST_NODE_TYPES . RestElement :
865
- getIdentifiersFromPattern ( pattern . argument , identifiers ) ;
866
- break ;
902
+ function isExported (
903
+ node : TSESTree . Node | undefined ,
904
+ name : string ,
905
+ scope : TSESLint . Scope . Scope | null ,
906
+ ) : boolean {
907
+ if (
908
+ node ?. parent ?. type === AST_NODE_TYPES . ExportDefaultDeclaration ||
909
+ node ?. parent ?. type === AST_NODE_TYPES . ExportNamedDeclaration
910
+ ) {
911
+ return true ;
912
+ }
867
913
868
- case AST_NODE_TYPES . AssignmentPattern :
869
- getIdentifiersFromPattern ( pattern . left , identifiers ) ;
870
- break ;
914
+ if ( scope == null ) {
915
+ return false ;
916
+ }
871
917
872
- case AST_NODE_TYPES . MemberExpression :
873
- // ignore member expressions, as the everything must already be defined
874
- break ;
918
+ const variable = scope . set . get ( name ) ;
919
+ if ( variable ) {
920
+ for ( const ref of variable . references ) {
921
+ const refParent = ref . identifier . parent ;
922
+ if (
923
+ refParent ?. type === AST_NODE_TYPES . ExportDefaultDeclaration ||
924
+ refParent ?. type === AST_NODE_TYPES . ExportSpecifier
925
+ ) {
926
+ return true ;
927
+ }
928
+ }
929
+ }
930
+
931
+ return false ;
932
+ }
875
933
876
- default :
877
- // https://github.com/typescript-eslint/typescript-eslint/issues/1282
878
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
879
- throw new Error ( `Unexpected pattern type ${ pattern ! . type } ` ) ;
934
+ function isGlobal ( scope : TSESLint . Scope . Scope | null ) : boolean {
935
+ if ( scope == null ) {
936
+ return false ;
880
937
}
938
+
939
+ return (
940
+ scope . type === TSESLint . Scope . ScopeType . global ||
941
+ scope . type === TSESLint . Scope . ScopeType . module
942
+ ) ;
881
943
}
882
944
883
945
type ValidatorFunction = (
0 commit comments