Skip to content

Commit c68e033

Browse files
ankeetmainiJamesHenry
authored andcommitted
feat(eslint-plugin): [no-type-alias] support tuples (typescript-eslint#775)
1 parent 14c6f80 commit c68e033

File tree

4 files changed

+408
-76
lines changed

4 files changed

+408
-76
lines changed

packages/eslint-plugin/docs/rules/no-type-alias.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ or more of the following you may pass an object with the options set as follows:
8686
- `allowCallbacks` set to `"always"` will allow you to use type aliases with callbacks (Defaults to `"never"`)
8787
- `allowLiterals` set to `"always"` will allow you to use type aliases with literal objects (Defaults to `"never"`)
8888
- `allowMappedTypes` set to `"always"` will allow you to use type aliases as mapping tools (Defaults to `"never"`)
89+
- `allowTupleTypes` set to `"always"` will allow you to use type aliases with tuples (Defaults to `"never"`)
8990

9091
### allowAliases
9192

@@ -453,6 +454,81 @@ type Foo<T, U> = { readonly [P in keyof T]: T[P] } &
453454
type Foo<T, U> = { [P in keyof T]?: T[P] } & { [P in keyof U]?: U[P] };
454455
```
455456

457+
### allowTupleTypes
458+
459+
This applies to tuple types (`type Foo = [number]`).
460+
461+
The setting accepts the following options:
462+
463+
- `"always"` or `"never"` to active or deactivate the feature.
464+
- `"in-unions"`, allows tuples in union statements, e.g. `type Foo = [string] | [string, string];`
465+
- `"in-intersections"`, allows tuples in intersection statements, e.g. `type Foo = [string] & [string, string];`
466+
- `"in-unions-and-intersections"`, allows tuples in union and/or intersection statements.
467+
468+
Examples of **correct** code for the `{ "allowTupleTypes": "always" }` options:
469+
470+
```ts
471+
type Foo = [number];
472+
473+
type Foo = [number] | [number, number];
474+
475+
type Foo = [number] & [number, number];
476+
477+
type Foo = [number] | [number, number] & [string, string];
478+
```
479+
480+
Examples of **incorrect** code for the `{ "allowTupleTypes": "in-unions" }` option:
481+
482+
```ts
483+
type Foo = [number];
484+
485+
type Foo = [number] & [number, number];
486+
487+
type Foo = [string] & [number];
488+
```
489+
490+
Examples of **correct** code for the `{ "allowTupleTypes": "in-unions" }` option:
491+
492+
```ts
493+
type Foo = [number] | [number, number];
494+
495+
type Foo = [string] | [number];
496+
```
497+
498+
Examples of **incorrect** code for the `{ "allowTupleTypes": "in-intersections" }` option:
499+
500+
```ts
501+
type Foo = [number];
502+
503+
type Foo = [number] | [number, number];
504+
505+
type Foo = [string] | [number];
506+
```
507+
508+
Examples of **correct** code for the `{ "allowTupleTypes": "in-intersections" }` option:
509+
510+
```ts
511+
type Foo = [number] & [number, number];
512+
513+
type Foo = [string] & [number];
514+
```
515+
516+
Examples of **incorrect** code for the `{ "allowTupleTypes": "in-unions-and-intersections" }` option:
517+
518+
```ts
519+
type Foo = [number];
520+
521+
type Foo = [string];
522+
```
523+
524+
Examples of **correct** code for the `{ "allowLiterals": "in-unions-and-intersections" }` option:
525+
526+
```ts
527+
type Foo = [number] & [number, number];
528+
529+
type Foo = [string] | [number];
530+
```
531+
456532
## When Not To Use It
457533

458534
When you can't express some shape with an interface or you need to use a union, tuple type,

packages/eslint-plugin/src/rules/no-type-alias.ts

Lines changed: 70 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,27 @@ import {
44
} from '@typescript-eslint/experimental-utils';
55
import * as util from '../util';
66

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+
721
type Options = [
822
{
9-
allowAliases?:
10-
| 'always'
11-
| 'never'
12-
| 'in-unions'
13-
| 'in-intersections'
14-
| 'in-unions-and-intersections';
23+
allowAliases?: Values;
1524
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;
2828
},
2929
];
3030
type MessageIds = 'noTypeAlias' | 'noCompositionAlias';
@@ -57,34 +57,19 @@ export default util.createRule<Options, MessageIds>({
5757
type: 'object',
5858
properties: {
5959
allowAliases: {
60-
enum: [
61-
'always',
62-
'never',
63-
'in-unions',
64-
'in-intersections',
65-
'in-unions-and-intersections',
66-
],
60+
enum: enumValues,
6761
},
6862
allowCallbacks: {
6963
enum: ['always', 'never'],
7064
},
7165
allowLiterals: {
72-
enum: [
73-
'always',
74-
'never',
75-
'in-unions',
76-
'in-intersections',
77-
'in-unions-and-intersections',
78-
],
66+
enum: enumValues,
7967
},
8068
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,
8873
},
8974
},
9075
additionalProperties: false,
@@ -97,11 +82,20 @@ export default util.createRule<Options, MessageIds>({
9782
allowCallbacks: 'never',
9883
allowLiterals: 'never',
9984
allowMappedTypes: 'never',
85+
allowTupleTypes: 'never',
10086
},
10187
],
10288
create(
10389
context,
104-
[{ allowAliases, allowCallbacks, allowLiterals, allowMappedTypes }],
90+
[
91+
{
92+
allowAliases,
93+
allowCallbacks,
94+
allowLiterals,
95+
allowMappedTypes,
96+
allowTupleTypes,
97+
},
98+
],
10599
) {
106100
const unions = ['always', 'in-unions', 'in-unions-and-intersections'];
107101
const intersections = [
@@ -180,6 +174,36 @@ export default util.createRule<Options, MessageIds>({
180174
});
181175
}
182176

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+
183207
/**
184208
* Validates the node looking for aliases, callbacks and literals.
185209
* @param node the node to be validated.
@@ -198,48 +222,19 @@ export default util.createRule<Options, MessageIds>({
198222
}
199223
} else if (type.node.type === AST_NODE_TYPES.TSTypeLiteral) {
200224
// 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');
211226
} else if (type.node.type === AST_NODE_TYPES.TSMappedType) {
212227
// 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');
228232
} else if (
229233
type.node.type.endsWith('Keyword') ||
230234
aliasTypes.has(type.node.type)
231235
) {
232236
// 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');
243238
} else {
244239
// unhandled type - shouldn't happen
245240
reportError(type.node, type.compositionType, isTopLevel, 'Unhandled');

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy