@@ -44,29 +44,36 @@ export default util.createRule<Options, MessageId>({
44
44
const service = util . getParserServices ( context ) ;
45
45
const typeChecker = service . program . getTypeChecker ( ) ;
46
46
47
- type BaseType =
48
- | 'string'
49
- | 'number'
50
- | 'bigint'
51
- | 'boolean'
52
- | 'null'
53
- | 'undefined'
54
- | 'other' ;
55
-
56
- const allowedTypes : BaseType [ ] = [
57
- 'string' ,
58
- ...( options . allowNumber ? ( [ 'number' , 'bigint' ] as const ) : [ ] ) ,
59
- ...( options . allowBoolean ? ( [ 'boolean' ] as const ) : [ ] ) ,
60
- ...( options . allowNullable ? ( [ 'null' , 'undefined' ] as const ) : [ ] ) ,
61
- ] ;
62
-
63
- function isAllowedType ( types : BaseType [ ] ) : boolean {
64
- for ( const type of types ) {
65
- if ( ! allowedTypes . includes ( type ) ) {
66
- return false ;
67
- }
47
+ function isUnderlyingTypePrimitive ( type : ts . Type ) : boolean {
48
+ if ( util . isTypeFlagSet ( type , ts . TypeFlags . StringLike ) ) {
49
+ return true ;
50
+ }
51
+
52
+ if (
53
+ util . isTypeFlagSet (
54
+ type ,
55
+ ts . TypeFlags . NumberLike | ts . TypeFlags . BigIntLike ,
56
+ ) &&
57
+ options . allowNumber
58
+ ) {
59
+ return true ;
68
60
}
69
- return true ;
61
+
62
+ if (
63
+ util . isTypeFlagSet ( type , ts . TypeFlags . BooleanLike ) &&
64
+ options . allowBoolean
65
+ ) {
66
+ return true ;
67
+ }
68
+
69
+ if (
70
+ util . isTypeFlagSet ( type , ts . TypeFlags . Null | ts . TypeFlags . Undefined ) &&
71
+ options . allowNullable
72
+ ) {
73
+ return true ;
74
+ }
75
+
76
+ return false ;
70
77
}
71
78
72
79
return {
@@ -76,75 +83,52 @@ export default util.createRule<Options, MessageId>({
76
83
return ;
77
84
}
78
85
79
- for ( const expr of node . expressions ) {
80
- const type = getNodeType ( expr ) ;
81
- if ( ! isAllowedType ( type ) ) {
86
+ for ( const expression of node . expressions ) {
87
+ if (
88
+ ! isUnderlyingExpressionTypeConfirmingTo (
89
+ expression ,
90
+ isUnderlyingTypePrimitive ,
91
+ )
92
+ ) {
82
93
context . report ( {
83
- node : expr ,
94
+ node : expression ,
84
95
messageId : 'invalidType' ,
85
96
} ) ;
86
97
}
87
98
}
88
99
} ,
89
100
} ;
90
101
91
- /**
92
- * Helper function to get base type of node
93
- * @param node the node to be evaluated.
94
- */
95
- function getNodeType ( node : TSESTree . Expression ) : BaseType [ ] {
96
- const tsNode = service . esTreeNodeToTSNodeMap . get ( node ) ;
97
- const type = typeChecker . getTypeAtLocation ( tsNode ) ;
102
+ function isUnderlyingExpressionTypeConfirmingTo (
103
+ expression : TSESTree . Expression ,
104
+ predicate : ( underlyingType : ts . Type ) => boolean ,
105
+ ) : boolean {
106
+ const expressionType = getExpressionNodeType ( expression ) ;
98
107
99
- return getBaseType ( type ) ;
100
- }
108
+ return rec (
109
+ // "Extracts" generic constraint, indexed access and conditional types:
110
+ typeChecker . getBaseConstraintOfType ( expressionType ) ?? expressionType ,
111
+ ) ;
101
112
102
- function getBaseType ( type : ts . Type ) : BaseType [ ] {
103
- const constraint = type . getConstraint ( ) ;
104
- if (
105
- constraint &&
106
- // for generic types with union constraints, it will return itself
107
- constraint !== type
108
- ) {
109
- return getBaseType ( constraint ) ;
110
- }
111
-
112
- if ( type . isStringLiteral ( ) ) {
113
- return [ 'string' ] ;
114
- }
115
- if ( type . isNumberLiteral ( ) ) {
116
- return [ 'number' ] ;
117
- }
118
- if ( type . flags & ts . TypeFlags . BigIntLiteral ) {
119
- return [ 'bigint' ] ;
120
- }
121
- if ( type . flags & ts . TypeFlags . BooleanLiteral ) {
122
- return [ 'boolean' ] ;
123
- }
124
- if ( type . flags & ts . TypeFlags . Null ) {
125
- return [ 'null' ] ;
126
- }
127
- if ( type . flags & ts . TypeFlags . Undefined ) {
128
- return [ 'undefined' ] ;
129
- }
113
+ function rec ( type : ts . Type ) : boolean {
114
+ if ( type . isUnion ( ) ) {
115
+ return type . types . every ( rec ) ;
116
+ }
130
117
131
- if ( type . isUnion ( ) ) {
132
- return type . types
133
- . map ( getBaseType )
134
- . reduce ( ( all , array ) => [ ...all , ...array ] , [ ] ) ;
135
- }
118
+ if ( type . isIntersection ( ) ) {
119
+ return type . types . some ( rec ) ;
120
+ }
136
121
137
- const stringType = typeChecker . typeToString ( type ) ;
138
- if (
139
- stringType === 'string' ||
140
- stringType === 'number' ||
141
- stringType === 'bigint' ||
142
- stringType === 'boolean'
143
- ) {
144
- return [ stringType ] ;
122
+ return predicate ( type ) ;
145
123
}
124
+ }
146
125
147
- return [ 'other' ] ;
126
+ /**
127
+ * Helper function to extract the TS type of an TSESTree expression.
128
+ */
129
+ function getExpressionNodeType ( node : TSESTree . Expression ) : ts . Type {
130
+ const tsNode = service . esTreeNodeToTSNodeMap . get ( node ) ;
131
+ return typeChecker . getTypeAtLocation ( tsNode ) ;
148
132
}
149
133
} ,
150
134
} ) ;
0 commit comments