@@ -4,27 +4,27 @@ import {
4
4
} from '@typescript-eslint/experimental-utils' ;
5
5
import * as util from '../util' ;
6
6
7
+ type Values =
8
+ | 'always'
9
+ | 'never'
10
+ | 'in-unions'
11
+ | 'in-intersections'
12
+ | 'in-unions-and-intersections' ;
13
+ const enumValues : Values [ ] = [
14
+ 'always' ,
15
+ 'never' ,
16
+ 'in-unions' ,
17
+ 'in-intersections' ,
18
+ 'in-unions-and-intersections' ,
19
+ ] ;
20
+
7
21
type Options = [
8
22
{
9
- allowAliases ?:
10
- | 'always'
11
- | 'never'
12
- | 'in-unions'
13
- | 'in-intersections'
14
- | 'in-unions-and-intersections' ;
23
+ allowAliases ?: Values ;
15
24
allowCallbacks ?: 'always' | 'never' ;
16
- allowLiterals ?:
17
- | 'always'
18
- | 'never'
19
- | 'in-unions'
20
- | 'in-intersections'
21
- | 'in-unions-and-intersections' ;
22
- allowMappedTypes ?:
23
- | 'always'
24
- | 'never'
25
- | 'in-unions'
26
- | 'in-intersections'
27
- | 'in-unions-and-intersections' ;
25
+ allowLiterals ?: Values ;
26
+ allowMappedTypes ?: Values ;
27
+ allowTupleTypes ?: Values ;
28
28
} ,
29
29
] ;
30
30
type MessageIds = 'noTypeAlias' | 'noCompositionAlias' ;
@@ -57,34 +57,19 @@ export default util.createRule<Options, MessageIds>({
57
57
type : 'object' ,
58
58
properties : {
59
59
allowAliases : {
60
- enum : [
61
- 'always' ,
62
- 'never' ,
63
- 'in-unions' ,
64
- 'in-intersections' ,
65
- 'in-unions-and-intersections' ,
66
- ] ,
60
+ enum : enumValues ,
67
61
} ,
68
62
allowCallbacks : {
69
63
enum : [ 'always' , 'never' ] ,
70
64
} ,
71
65
allowLiterals : {
72
- enum : [
73
- 'always' ,
74
- 'never' ,
75
- 'in-unions' ,
76
- 'in-intersections' ,
77
- 'in-unions-and-intersections' ,
78
- ] ,
66
+ enum : enumValues ,
79
67
} ,
80
68
allowMappedTypes : {
81
- enum : [
82
- 'always' ,
83
- 'never' ,
84
- 'in-unions' ,
85
- 'in-intersections' ,
86
- 'in-unions-and-intersections' ,
87
- ] ,
69
+ enum : enumValues ,
70
+ } ,
71
+ allowTupleTypes : {
72
+ enum : enumValues ,
88
73
} ,
89
74
} ,
90
75
additionalProperties : false ,
@@ -97,11 +82,20 @@ export default util.createRule<Options, MessageIds>({
97
82
allowCallbacks : 'never' ,
98
83
allowLiterals : 'never' ,
99
84
allowMappedTypes : 'never' ,
85
+ allowTupleTypes : 'never' ,
100
86
} ,
101
87
] ,
102
88
create (
103
89
context ,
104
- [ { allowAliases, allowCallbacks, allowLiterals, allowMappedTypes } ] ,
90
+ [
91
+ {
92
+ allowAliases,
93
+ allowCallbacks,
94
+ allowLiterals,
95
+ allowMappedTypes,
96
+ allowTupleTypes,
97
+ } ,
98
+ ] ,
105
99
) {
106
100
const unions = [ 'always' , 'in-unions' , 'in-unions-and-intersections' ] ;
107
101
const intersections = [
@@ -180,6 +174,36 @@ export default util.createRule<Options, MessageIds>({
180
174
} ) ;
181
175
}
182
176
177
+ const isValidTupleType = ( type : TypeWithLabel ) => {
178
+ if ( type . node . type === AST_NODE_TYPES . TSTupleType ) {
179
+ return true ;
180
+ }
181
+ if ( type . node . type === AST_NODE_TYPES . TSTypeOperator ) {
182
+ if (
183
+ [ 'keyof' , 'readonly' ] . includes ( type . node . operator ) &&
184
+ type . node . typeAnnotation &&
185
+ type . node . typeAnnotation . type === AST_NODE_TYPES . TSTupleType
186
+ ) {
187
+ return true ;
188
+ }
189
+ }
190
+ return false ;
191
+ } ;
192
+
193
+ const checkAndReport = (
194
+ optionValue : Values ,
195
+ isTopLevel : boolean ,
196
+ type : TypeWithLabel ,
197
+ label : string ,
198
+ ) => {
199
+ if (
200
+ optionValue === 'never' ||
201
+ ! isSupportedComposition ( isTopLevel , type . compositionType , optionValue )
202
+ ) {
203
+ reportError ( type . node , type . compositionType , isTopLevel , label ) ;
204
+ }
205
+ } ;
206
+
183
207
/**
184
208
* Validates the node looking for aliases, callbacks and literals.
185
209
* @param node the node to be validated.
@@ -198,48 +222,19 @@ export default util.createRule<Options, MessageIds>({
198
222
}
199
223
} else if ( type . node . type === AST_NODE_TYPES . TSTypeLiteral ) {
200
224
// literal object type
201
- if (
202
- allowLiterals === 'never' ||
203
- ! isSupportedComposition (
204
- isTopLevel ,
205
- type . compositionType ,
206
- allowLiterals ! ,
207
- )
208
- ) {
209
- reportError ( type . node , type . compositionType , isTopLevel , 'Literals' ) ;
210
- }
225
+ checkAndReport ( allowLiterals ! , isTopLevel , type , 'Literals' ) ;
211
226
} else if ( type . node . type === AST_NODE_TYPES . TSMappedType ) {
212
227
// mapped type
213
- if (
214
- allowMappedTypes === 'never' ||
215
- ! isSupportedComposition (
216
- isTopLevel ,
217
- type . compositionType ,
218
- allowMappedTypes ! ,
219
- )
220
- ) {
221
- reportError (
222
- type . node ,
223
- type . compositionType ,
224
- isTopLevel ,
225
- 'Mapped types' ,
226
- ) ;
227
- }
228
+ checkAndReport ( allowMappedTypes ! , isTopLevel , type , 'Mapped types' ) ;
229
+ } else if ( isValidTupleType ( type ) ) {
230
+ // tuple types
231
+ checkAndReport ( allowTupleTypes ! , isTopLevel , type , 'Tuple Types' ) ;
228
232
} else if (
229
233
type . node . type . endsWith ( 'Keyword' ) ||
230
234
aliasTypes . has ( type . node . type )
231
235
) {
232
236
// alias / keyword
233
- if (
234
- allowAliases === 'never' ||
235
- ! isSupportedComposition (
236
- isTopLevel ,
237
- type . compositionType ,
238
- allowAliases ! ,
239
- )
240
- ) {
241
- reportError ( type . node , type . compositionType , isTopLevel , 'Aliases' ) ;
242
- }
237
+ checkAndReport ( allowAliases ! , isTopLevel , type , 'Aliases' ) ;
243
238
} else {
244
239
// unhandled type - shouldn't happen
245
240
reportError ( type . node , type . compositionType , isTopLevel , 'Unhandled' ) ;
0 commit comments