1
1
import {
2
2
AST_NODE_TYPES ,
3
- TSESLint ,
4
3
TSESTree ,
5
4
} from '@typescript-eslint/experimental-utils' ;
6
5
import * as util from '../util' ;
@@ -30,6 +29,14 @@ type Options = [
30
29
] ;
31
30
type MessageIds = 'noTypeAlias' | 'noCompositionAlias' ;
32
31
32
+ type CompositionType =
33
+ | AST_NODE_TYPES . TSUnionType
34
+ | AST_NODE_TYPES . TSIntersectionType ;
35
+ interface TypeWithLabel {
36
+ node : TSESTree . Node ;
37
+ compositionType : CompositionType | null ;
38
+ }
39
+
33
40
export default util . createRule < Options , MessageIds > ( {
34
41
name : 'no-type-alias' ,
35
42
meta : {
@@ -106,24 +113,13 @@ export default util.createRule<Options, MessageIds>({
106
113
'in-intersections' ,
107
114
'in-unions-and-intersections' ,
108
115
] ;
109
- const aliasTypes = [
116
+ const aliasTypes = new Set ( [
110
117
AST_NODE_TYPES . TSArrayType ,
111
118
AST_NODE_TYPES . TSTypeReference ,
112
119
AST_NODE_TYPES . TSLiteralType ,
113
120
AST_NODE_TYPES . TSTypeQuery ,
114
- ] ;
115
-
116
- type CompositionType = TSESTree . TSUnionType | TSESTree . TSIntersectionType ;
117
- /**
118
- * Determines if the given node is a union or an intersection.
119
- */
120
- function isComposition ( node : TSESTree . TypeNode ) : node is CompositionType {
121
- return (
122
- node &&
123
- ( node . type === AST_NODE_TYPES . TSUnionType ||
124
- node . type === AST_NODE_TYPES . TSIntersectionType )
125
- ) ;
126
- }
121
+ AST_NODE_TYPES . TSIndexedAccessType ,
122
+ ] ) ;
127
123
128
124
/**
129
125
* Determines if the composition type is supported by the allowed flags.
@@ -133,7 +129,7 @@ export default util.createRule<Options, MessageIds>({
133
129
*/
134
130
function isSupportedComposition (
135
131
isTopLevel : boolean ,
136
- compositionType : string | undefined ,
132
+ compositionType : CompositionType | null ,
137
133
allowed : string ,
138
134
) : boolean {
139
135
return (
@@ -146,43 +142,6 @@ export default util.createRule<Options, MessageIds>({
146
142
) ;
147
143
}
148
144
149
- /**
150
- * Determines if the given node is an alias type (keywords, arrays, type references and constants).
151
- * @param node the node to be evaluated.
152
- */
153
- function isAlias (
154
- node : TSESTree . Node ,
155
- ) : boolean /* not worth enumerating the ~25 individual types here */ {
156
- return (
157
- node &&
158
- ( / K e y w o r d $ / . test ( node . type ) || aliasTypes . indexOf ( node . type ) > - 1 )
159
- ) ;
160
- }
161
-
162
- /**
163
- * Determines if the given node is a callback type.
164
- * @param node the node to be evaluated.
165
- */
166
- function isCallback ( node : TSESTree . Node ) : node is TSESTree . TSFunctionType {
167
- return node && node . type === AST_NODE_TYPES . TSFunctionType ;
168
- }
169
-
170
- /**
171
- * Determines if the given node is a literal type (objects).
172
- * @param node the node to be evaluated.
173
- */
174
- function isLiteral ( node : TSESTree . Node ) : node is TSESTree . TSTypeLiteral {
175
- return node && node . type === AST_NODE_TYPES . TSTypeLiteral ;
176
- }
177
-
178
- /**
179
- * Determines if the given node is a mapped type.
180
- * @param node the node to be evaluated.
181
- */
182
- function isMappedType ( node : TSESTree . Node ) : node is TSESTree . TSMappedType {
183
- return node && node . type === AST_NODE_TYPES . TSMappedType ;
184
- }
185
-
186
145
/**
187
146
* Gets the message to be displayed based on the node type and whether the node is a top level declaration.
188
147
* @param node the location
@@ -191,108 +150,134 @@ export default util.createRule<Options, MessageIds>({
191
150
* @param isRoot a flag indicating we are dealing with the top level declaration.
192
151
* @param type the kind of type alias being validated.
193
152
*/
194
- function getMessage (
153
+ function reportError (
195
154
node : TSESTree . Node ,
196
- compositionType : string | undefined ,
155
+ compositionType : CompositionType | null ,
197
156
isRoot : boolean ,
198
- type ? : string ,
199
- ) : TSESLint . ReportDescriptor < MessageIds > {
157
+ type : string ,
158
+ ) : void {
200
159
if ( isRoot ) {
201
- return {
160
+ return context . report ( {
202
161
node,
203
162
messageId : 'noTypeAlias' ,
204
163
data : {
205
- alias : type || 'aliases' ,
164
+ alias : type . toLowerCase ( ) ,
206
165
} ,
207
- } ;
166
+ } ) ;
208
167
}
209
168
210
- return {
169
+ return context . report ( {
211
170
node,
212
171
messageId : 'noCompositionAlias' ,
213
172
data : {
214
173
compositionType :
215
174
compositionType === AST_NODE_TYPES . TSUnionType
216
175
? 'union'
217
176
: 'intersection' ,
218
- typeName : util . upperCaseFirst ( type ! ) ,
177
+ typeName : type ,
219
178
} ,
220
- } ;
179
+ } ) ;
221
180
}
222
181
223
182
/**
224
183
* Validates the node looking for aliases, callbacks and literals.
225
184
* @param node the node to be validated.
226
- * @param isTopLevel a flag indicating this is the top level node.
227
- * @param compositionType the type of composition this alias is part of (undefined if not
185
+ * @param compositionType the type of composition this alias is part of (null if not
228
186
* part of a composition)
187
+ * @param isTopLevel a flag indicating this is the top level node.
229
188
*/
230
189
function validateTypeAliases (
231
- node : TSESTree . Node ,
232
- isTopLevel : boolean ,
233
- compositionType ?: string ,
190
+ type : TypeWithLabel ,
191
+ isTopLevel : boolean = false ,
234
192
) : void {
235
- if ( isCallback ( node ) ) {
193
+ if ( type . node . type === AST_NODE_TYPES . TSFunctionType ) {
194
+ // callback
236
195
if ( allowCallbacks === 'never' ) {
237
- context . report (
238
- getMessage ( node , compositionType , isTopLevel , 'callbacks' ) ,
239
- ) ;
196
+ reportError ( type . node , type . compositionType , isTopLevel , 'Callbacks' ) ;
240
197
}
241
- } else if ( isLiteral ( node ) ) {
198
+ } else if ( type . node . type === AST_NODE_TYPES . TSTypeLiteral ) {
199
+ // literal object type
242
200
if (
243
201
allowLiterals === 'never' ||
244
- ! isSupportedComposition ( isTopLevel , compositionType , allowLiterals ! )
202
+ ! isSupportedComposition (
203
+ isTopLevel ,
204
+ type . compositionType ,
205
+ allowLiterals ! ,
206
+ )
245
207
) {
246
- context . report (
247
- getMessage ( node , compositionType , isTopLevel , 'literals' ) ,
248
- ) ;
208
+ reportError ( type . node , type . compositionType , isTopLevel , 'Literals' ) ;
249
209
}
250
- } else if ( isMappedType ( node ) ) {
210
+ } else if ( type . node . type === AST_NODE_TYPES . TSMappedType ) {
211
+ // mapped type
251
212
if (
252
213
allowMappedTypes === 'never' ||
253
214
! isSupportedComposition (
254
215
isTopLevel ,
255
- compositionType ,
216
+ type . compositionType ,
256
217
allowMappedTypes ! ,
257
218
)
258
219
) {
259
- context . report (
260
- getMessage ( node , compositionType , isTopLevel , 'mapped types' ) ,
220
+ reportError (
221
+ type . node ,
222
+ type . compositionType ,
223
+ isTopLevel ,
224
+ 'Mapped types' ,
261
225
) ;
262
226
}
263
- } else if ( isAlias ( node ) ) {
227
+ } else if (
228
+ / K e y w o r d $ / . test ( type . node . type ) ||
229
+ aliasTypes . has ( type . node . type )
230
+ ) {
231
+ // alias / keyword
264
232
if (
265
233
allowAliases === 'never' ||
266
- ! isSupportedComposition ( isTopLevel , compositionType , allowAliases ! )
234
+ ! isSupportedComposition (
235
+ isTopLevel ,
236
+ type . compositionType ,
237
+ allowAliases ! ,
238
+ )
267
239
) {
268
- context . report (
269
- getMessage ( node , compositionType , isTopLevel , 'aliases' ) ,
270
- ) ;
240
+ reportError ( type . node , type . compositionType , isTopLevel , 'Aliases' ) ;
271
241
}
272
242
} else {
273
- context . report ( getMessage ( node , compositionType , isTopLevel ) ) ;
243
+ // unhandled type - shouldn't happen
244
+ reportError ( type . node , type . compositionType , isTopLevel , 'Unhandled' ) ;
274
245
}
275
246
}
276
247
277
248
/**
278
- * Validates compositions (unions and/or intersections).
249
+ * Flatten the given type into an array of its dependencies
279
250
*/
280
- function validateCompositions ( node : CompositionType ) : void {
281
- node . types . forEach ( type => {
282
- if ( isComposition ( type ) ) {
283
- validateCompositions ( type ) ;
284
- } else {
285
- validateTypeAliases ( type , false , node . type ) ;
286
- }
287
- } ) ;
251
+ function getTypes (
252
+ node : TSESTree . Node ,
253
+ compositionType : CompositionType | null = null ,
254
+ ) : TypeWithLabel [ ] {
255
+ if (
256
+ node . type === AST_NODE_TYPES . TSUnionType ||
257
+ node . type === AST_NODE_TYPES . TSIntersectionType
258
+ ) {
259
+ return node . types . reduce < TypeWithLabel [ ] > ( ( acc , type ) => {
260
+ acc . push ( ...getTypes ( type , node . type ) ) ;
261
+ return acc ;
262
+ } , [ ] ) ;
263
+ }
264
+ if ( node . type === AST_NODE_TYPES . TSParenthesizedType ) {
265
+ return getTypes ( node . typeAnnotation , compositionType ) ;
266
+ }
267
+ return [ { node, compositionType } ] ;
288
268
}
289
269
290
270
return {
291
271
TSTypeAliasDeclaration ( node ) {
292
- if ( isComposition ( node . typeAnnotation ) ) {
293
- validateCompositions ( node . typeAnnotation ) ;
272
+ const types = getTypes ( node . typeAnnotation ) ;
273
+ if ( types . length === 1 ) {
274
+ // is a top level type annotation
275
+ validateTypeAliases ( types [ 0 ] , true ) ;
294
276
} else {
295
- validateTypeAliases ( node . typeAnnotation , true ) ;
277
+ // is a composition type
278
+ types . forEach ( type => {
279
+ validateTypeAliases ( type ) ;
280
+ } ) ;
296
281
}
297
282
} ,
298
283
} ;
0 commit comments