Skip to content

Commit 6489293

Browse files
authored
fix(eslint-plugin): [no-type-alias] Fix parenthesized type handling (typescript-eslint#576)
1 parent 31d5bd4 commit 6489293

File tree

2 files changed

+88
-99
lines changed

2 files changed

+88
-99
lines changed

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

Lines changed: 84 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import {
22
AST_NODE_TYPES,
3-
TSESLint,
43
TSESTree,
54
} from '@typescript-eslint/experimental-utils';
65
import * as util from '../util';
@@ -30,6 +29,14 @@ type Options = [
3029
];
3130
type MessageIds = 'noTypeAlias' | 'noCompositionAlias';
3231

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+
3340
export default util.createRule<Options, MessageIds>({
3441
name: 'no-type-alias',
3542
meta: {
@@ -106,24 +113,13 @@ export default util.createRule<Options, MessageIds>({
106113
'in-intersections',
107114
'in-unions-and-intersections',
108115
];
109-
const aliasTypes = [
116+
const aliasTypes = new Set([
110117
AST_NODE_TYPES.TSArrayType,
111118
AST_NODE_TYPES.TSTypeReference,
112119
AST_NODE_TYPES.TSLiteralType,
113120
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+
]);
127123

128124
/**
129125
* Determines if the composition type is supported by the allowed flags.
@@ -133,7 +129,7 @@ export default util.createRule<Options, MessageIds>({
133129
*/
134130
function isSupportedComposition(
135131
isTopLevel: boolean,
136-
compositionType: string | undefined,
132+
compositionType: CompositionType | null,
137133
allowed: string,
138134
): boolean {
139135
return (
@@ -146,43 +142,6 @@ export default util.createRule<Options, MessageIds>({
146142
);
147143
}
148144

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-
(/Keyword$/.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-
186145
/**
187146
* Gets the message to be displayed based on the node type and whether the node is a top level declaration.
188147
* @param node the location
@@ -191,108 +150,134 @@ export default util.createRule<Options, MessageIds>({
191150
* @param isRoot a flag indicating we are dealing with the top level declaration.
192151
* @param type the kind of type alias being validated.
193152
*/
194-
function getMessage(
153+
function reportError(
195154
node: TSESTree.Node,
196-
compositionType: string | undefined,
155+
compositionType: CompositionType | null,
197156
isRoot: boolean,
198-
type?: string,
199-
): TSESLint.ReportDescriptor<MessageIds> {
157+
type: string,
158+
): void {
200159
if (isRoot) {
201-
return {
160+
return context.report({
202161
node,
203162
messageId: 'noTypeAlias',
204163
data: {
205-
alias: type || 'aliases',
164+
alias: type.toLowerCase(),
206165
},
207-
};
166+
});
208167
}
209168

210-
return {
169+
return context.report({
211170
node,
212171
messageId: 'noCompositionAlias',
213172
data: {
214173
compositionType:
215174
compositionType === AST_NODE_TYPES.TSUnionType
216175
? 'union'
217176
: 'intersection',
218-
typeName: util.upperCaseFirst(type!),
177+
typeName: type,
219178
},
220-
};
179+
});
221180
}
222181

223182
/**
224183
* Validates the node looking for aliases, callbacks and literals.
225184
* @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
228186
* part of a composition)
187+
* @param isTopLevel a flag indicating this is the top level node.
229188
*/
230189
function validateTypeAliases(
231-
node: TSESTree.Node,
232-
isTopLevel: boolean,
233-
compositionType?: string,
190+
type: TypeWithLabel,
191+
isTopLevel: boolean = false,
234192
): void {
235-
if (isCallback(node)) {
193+
if (type.node.type === AST_NODE_TYPES.TSFunctionType) {
194+
// callback
236195
if (allowCallbacks === 'never') {
237-
context.report(
238-
getMessage(node, compositionType, isTopLevel, 'callbacks'),
239-
);
196+
reportError(type.node, type.compositionType, isTopLevel, 'Callbacks');
240197
}
241-
} else if (isLiteral(node)) {
198+
} else if (type.node.type === AST_NODE_TYPES.TSTypeLiteral) {
199+
// literal object type
242200
if (
243201
allowLiterals === 'never' ||
244-
!isSupportedComposition(isTopLevel, compositionType, allowLiterals!)
202+
!isSupportedComposition(
203+
isTopLevel,
204+
type.compositionType,
205+
allowLiterals!,
206+
)
245207
) {
246-
context.report(
247-
getMessage(node, compositionType, isTopLevel, 'literals'),
248-
);
208+
reportError(type.node, type.compositionType, isTopLevel, 'Literals');
249209
}
250-
} else if (isMappedType(node)) {
210+
} else if (type.node.type === AST_NODE_TYPES.TSMappedType) {
211+
// mapped type
251212
if (
252213
allowMappedTypes === 'never' ||
253214
!isSupportedComposition(
254215
isTopLevel,
255-
compositionType,
216+
type.compositionType,
256217
allowMappedTypes!,
257218
)
258219
) {
259-
context.report(
260-
getMessage(node, compositionType, isTopLevel, 'mapped types'),
220+
reportError(
221+
type.node,
222+
type.compositionType,
223+
isTopLevel,
224+
'Mapped types',
261225
);
262226
}
263-
} else if (isAlias(node)) {
227+
} else if (
228+
/Keyword$/.test(type.node.type) ||
229+
aliasTypes.has(type.node.type)
230+
) {
231+
// alias / keyword
264232
if (
265233
allowAliases === 'never' ||
266-
!isSupportedComposition(isTopLevel, compositionType, allowAliases!)
234+
!isSupportedComposition(
235+
isTopLevel,
236+
type.compositionType,
237+
allowAliases!,
238+
)
267239
) {
268-
context.report(
269-
getMessage(node, compositionType, isTopLevel, 'aliases'),
270-
);
240+
reportError(type.node, type.compositionType, isTopLevel, 'Aliases');
271241
}
272242
} else {
273-
context.report(getMessage(node, compositionType, isTopLevel));
243+
// unhandled type - shouldn't happen
244+
reportError(type.node, type.compositionType, isTopLevel, 'Unhandled');
274245
}
275246
}
276247

277248
/**
278-
* Validates compositions (unions and/or intersections).
249+
* Flatten the given type into an array of its dependencies
279250
*/
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 }];
288268
}
289269

290270
return {
291271
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);
294276
} else {
295-
validateTypeAliases(node.typeAnnotation, true);
277+
// is a composition type
278+
types.forEach(type => {
279+
validateTypeAliases(type);
280+
});
296281
}
297282
},
298283
};

packages/eslint-plugin/tests/rules/no-type-alias.test.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ const ruleTester = new RuleTester({
77

88
ruleTester.run('no-type-alias', rule, {
99
valid: [
10+
{
11+
code: "type A = 'a' & ('b' | 'c');",
12+
options: [{ allowAliases: 'always' }],
13+
},
1014
{
1115
code: "type Foo = 'a';",
1216
options: [{ allowAliases: 'always' }],

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