@@ -47,60 +47,135 @@ export default util.createRule<Options, MessageIds>({
47
47
} ,
48
48
] ,
49
49
create ( context , [ { ignoreParameters, ignoreProperties } ] ) {
50
+ function isFunctionCall ( init : TSESTree . Expression , callName : string ) {
51
+ return (
52
+ init . type === AST_NODE_TYPES . CallExpression &&
53
+ init . callee . type === AST_NODE_TYPES . Identifier &&
54
+ init . callee . name === callName
55
+ ) ;
56
+ }
57
+ function isLiteral ( init : TSESTree . Expression , typeName : string ) {
58
+ return (
59
+ init . type === AST_NODE_TYPES . Literal && typeof init . value === typeName
60
+ ) ;
61
+ }
62
+ function isIdentifier ( init : TSESTree . Expression , ...names : string [ ] ) {
63
+ return (
64
+ init . type === AST_NODE_TYPES . Identifier && names . includes ( init . name )
65
+ ) ;
66
+ }
67
+ function hasUnaryPrefix (
68
+ init : TSESTree . Expression ,
69
+ ...operators : string [ ]
70
+ ) : init is TSESTree . UnaryExpression {
71
+ return (
72
+ init . type === AST_NODE_TYPES . UnaryExpression &&
73
+ operators . includes ( init . operator )
74
+ ) ;
75
+ }
76
+
77
+ type Keywords =
78
+ | TSESTree . TSBigIntKeyword
79
+ | TSESTree . TSBooleanKeyword
80
+ | TSESTree . TSNumberKeyword
81
+ | TSESTree . TSNullKeyword
82
+ | TSESTree . TSStringKeyword
83
+ | TSESTree . TSSymbolKeyword
84
+ | TSESTree . TSUndefinedKeyword
85
+ | TSESTree . TSTypeReference ;
86
+ const keywordMap = {
87
+ [ AST_NODE_TYPES . TSBigIntKeyword ] : 'bigint' ,
88
+ [ AST_NODE_TYPES . TSBooleanKeyword ] : 'boolean' ,
89
+ [ AST_NODE_TYPES . TSNumberKeyword ] : 'number' ,
90
+ [ AST_NODE_TYPES . TSNullKeyword ] : 'null' ,
91
+ [ AST_NODE_TYPES . TSStringKeyword ] : 'string' ,
92
+ [ AST_NODE_TYPES . TSSymbolKeyword ] : 'symbol' ,
93
+ [ AST_NODE_TYPES . TSUndefinedKeyword ] : 'undefined' ,
94
+ } ;
95
+
50
96
/**
51
97
* Returns whether a node has an inferrable value or not
52
- * @param node the node to check
53
- * @param init the initializer
54
98
*/
55
99
function isInferrable (
56
- node : TSESTree . TSTypeAnnotation ,
100
+ annotation : TSESTree . TypeNode ,
57
101
init : TSESTree . Expression ,
58
- ) : boolean {
59
- if (
60
- node . type !== AST_NODE_TYPES . TSTypeAnnotation ||
61
- ! node . typeAnnotation
62
- ) {
63
- return false ;
64
- }
102
+ ) : annotation is Keywords {
103
+ switch ( annotation . type ) {
104
+ case AST_NODE_TYPES . TSBigIntKeyword : {
105
+ // note that bigint cannot have + prefixed to it
106
+ const unwrappedInit = hasUnaryPrefix ( init , '-' )
107
+ ? init . argument
108
+ : init ;
109
+
110
+ return (
111
+ isFunctionCall ( unwrappedInit , 'BigInt' ) ||
112
+ unwrappedInit . type === AST_NODE_TYPES . BigIntLiteral
113
+ ) ;
114
+ }
115
+
116
+ case AST_NODE_TYPES . TSBooleanKeyword :
117
+ return (
118
+ hasUnaryPrefix ( init , '!' ) ||
119
+ isFunctionCall ( init , 'Boolean' ) ||
120
+ isLiteral ( init , 'boolean' )
121
+ ) ;
65
122
66
- const annotation = node . typeAnnotation ;
123
+ case AST_NODE_TYPES . TSNumberKeyword : {
124
+ const unwrappedInit = hasUnaryPrefix ( init , '+' , '-' )
125
+ ? init . argument
126
+ : init ;
67
127
68
- if ( annotation . type === AST_NODE_TYPES . TSStringKeyword ) {
69
- if ( init . type === AST_NODE_TYPES . Literal ) {
70
- return typeof init . value === 'string' ;
128
+ return (
129
+ isIdentifier ( unwrappedInit , 'Infinity' , 'NaN' ) ||
130
+ isFunctionCall ( unwrappedInit , 'Number' ) ||
131
+ isLiteral ( unwrappedInit , 'number' )
132
+ ) ;
71
133
}
72
- return false ;
73
- }
74
134
75
- if ( annotation . type === AST_NODE_TYPES . TSBooleanKeyword ) {
76
- return init . type === AST_NODE_TYPES . Literal ;
77
- }
135
+ case AST_NODE_TYPES . TSNullKeyword :
136
+ return init . type === AST_NODE_TYPES . Literal && init . value === null ;
137
+
138
+ case AST_NODE_TYPES . TSStringKeyword :
139
+ return (
140
+ isFunctionCall ( init , 'String' ) ||
141
+ isLiteral ( init , 'string' ) ||
142
+ init . type === AST_NODE_TYPES . TemplateLiteral
143
+ ) ;
78
144
79
- if ( annotation . type === AST_NODE_TYPES . TSNumberKeyword ) {
80
- // Infinity is special
81
- if (
82
- ( init . type === AST_NODE_TYPES . UnaryExpression &&
83
- init . operator === '-' &&
84
- init . argument . type === AST_NODE_TYPES . Identifier &&
85
- init . argument . name === 'Infinity' ) ||
86
- ( init . type === AST_NODE_TYPES . Identifier && init . name === 'Infinity' )
87
- ) {
88
- return true ;
145
+ case AST_NODE_TYPES . TSSymbolKeyword :
146
+ return isFunctionCall ( init , 'Symbol' ) ;
147
+
148
+ case AST_NODE_TYPES . TSTypeReference : {
149
+ if (
150
+ annotation . typeName . type === AST_NODE_TYPES . Identifier &&
151
+ annotation . typeName . name === 'RegExp'
152
+ ) {
153
+ const isRegExpLiteral =
154
+ init . type === AST_NODE_TYPES . Literal &&
155
+ init . value instanceof RegExp ;
156
+ const isRegExpNewCall =
157
+ init . type === AST_NODE_TYPES . NewExpression &&
158
+ init . callee . type === 'Identifier' &&
159
+ init . callee . name === 'RegExp' ;
160
+ const isRegExpCall = isFunctionCall ( init , 'RegExp' ) ;
161
+
162
+ return isRegExpLiteral || isRegExpCall || isRegExpNewCall ;
163
+ }
164
+
165
+ return false ;
89
166
}
90
167
91
- return (
92
- init . type === AST_NODE_TYPES . Literal && typeof init . value === 'number'
93
- ) ;
168
+ case AST_NODE_TYPES . TSUndefinedKeyword :
169
+ return (
170
+ hasUnaryPrefix ( init , 'void' ) || isIdentifier ( init , 'undefined' )
171
+ ) ;
94
172
}
95
173
96
174
return false ;
97
175
}
98
176
99
177
/**
100
178
* Reports an inferrable type declaration, if any
101
- * @param node the node being visited
102
- * @param typeNode the type annotation node
103
- * @param initNode the initializer node
104
179
*/
105
180
function reportInferrableType (
106
181
node :
@@ -114,25 +189,15 @@ export default util.createRule<Options, MessageIds>({
114
189
return ;
115
190
}
116
191
117
- if ( ! isInferrable ( typeNode , initNode ) ) {
192
+ if ( ! isInferrable ( typeNode . typeAnnotation , initNode ) ) {
118
193
return ;
119
194
}
120
195
121
- let type = null ;
122
- if ( typeNode . typeAnnotation . type === AST_NODE_TYPES . TSBooleanKeyword ) {
123
- type = 'boolean' ;
124
- } else if (
125
- typeNode . typeAnnotation . type === AST_NODE_TYPES . TSNumberKeyword
126
- ) {
127
- type = 'number' ;
128
- } else if (
129
- typeNode . typeAnnotation . type === AST_NODE_TYPES . TSStringKeyword
130
- ) {
131
- type = 'string' ;
132
- } else {
133
- // shouldn't happen...
134
- return ;
135
- }
196
+ const type =
197
+ typeNode . typeAnnotation . type === AST_NODE_TYPES . TSTypeReference
198
+ ? // TODO - if we add more references
199
+ 'RegExp'
200
+ : keywordMap [ typeNode . typeAnnotation . type ] ;
136
201
137
202
context . report ( {
138
203
node,
0 commit comments