Skip to content

Commit b097245

Browse files
authored
feat(eslint-plugin): additional annotation spacing rules for va… (typescript-eslint#1496)
1 parent 33e3e6f commit b097245

File tree

4 files changed

+332
-33
lines changed

4 files changed

+332
-33
lines changed

packages/eslint-plugin/docs/rules/type-annotation-spacing.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ This rule has an object option:
4141
- `"before": true`, (default for arrow) requires a space before the colon/arrow.
4242
- `"after": true`, (default) requires a space after the colon/arrow.
4343
- `"after": false`, disallows spaces after the colon/arrow.
44-
- `"overrides"`, overrides the default options for type annotations with `colon` (e.g. `const foo: string`) and function types with `arrow` (e.g. `type Foo = () => {}`).
44+
- `"overrides"`, overrides the default options for type annotations with `colon` (e.g. `const foo: string`) and function types with `arrow` (e.g. `type Foo = () => {}`). Additionally allows granular overrides for `variable` (`const foo: string`),`parameter` (`function foo(bar: string) {...}`),`property` (`interface Foo { bar: string }`) and `returnType` (`function foo(): string {...}`) annotations.
4545

4646
### defaults
4747

packages/eslint-plugin/src/rules/type-annotation-spacing.ts

Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,35 @@
11
import { TSESTree } from '@typescript-eslint/experimental-utils';
22
import * as util from '../util';
3+
import {
4+
isClassOrTypeElement,
5+
isFunction,
6+
isFunctionOrFunctionType,
7+
isIdentifier,
8+
isTSFunctionType,
9+
isVariableDeclarator,
10+
} from '../util';
311

4-
type Options = [
5-
{
6-
before?: boolean;
7-
after?: boolean;
8-
overrides?: {
9-
colon?: {
10-
before?: boolean;
11-
after?: boolean;
12-
};
13-
arrow?: {
14-
before?: boolean;
15-
after?: boolean;
16-
};
17-
};
18-
}?,
19-
];
12+
interface WhitespaceRule {
13+
readonly before?: boolean;
14+
readonly after?: boolean;
15+
}
16+
17+
interface WhitespaceOverride {
18+
readonly colon?: WhitespaceRule;
19+
readonly arrow?: WhitespaceRule;
20+
readonly variable?: WhitespaceRule;
21+
readonly property?: WhitespaceRule;
22+
readonly parameter?: WhitespaceRule;
23+
readonly returnType?: WhitespaceRule;
24+
}
25+
26+
interface Config extends WhitespaceRule {
27+
readonly overrides?: WhitespaceOverride;
28+
}
29+
30+
type WhitespaceRules = Required<WhitespaceOverride>;
31+
32+
type Options = [Config?];
2033
type MessageIds =
2134
| 'expectedSpaceAfter'
2235
| 'expectedSpaceBefore'
@@ -32,6 +45,67 @@ const definition = {
3245
additionalProperties: false,
3346
};
3447

48+
function createRules(options?: Config): WhitespaceRules {
49+
const globals = {
50+
...(options?.before !== undefined ? { before: options.before } : {}),
51+
...(options?.after !== undefined ? { after: options.after } : {}),
52+
};
53+
const override = options?.overrides ?? {};
54+
const colon = {
55+
...{ before: false, after: true },
56+
...globals,
57+
...override?.colon,
58+
};
59+
const arrow = {
60+
...{ before: true, after: true },
61+
...globals,
62+
...override?.arrow,
63+
};
64+
65+
return {
66+
colon: colon,
67+
arrow: arrow,
68+
variable: { ...colon, ...override?.variable },
69+
property: { ...colon, ...override?.property },
70+
parameter: { ...colon, ...override?.parameter },
71+
returnType: { ...colon, ...override?.returnType },
72+
};
73+
}
74+
75+
function getIdentifierRules(
76+
rules: WhitespaceRules,
77+
node: TSESTree.Node | undefined,
78+
): WhitespaceRule {
79+
const scope = node?.parent;
80+
81+
if (isVariableDeclarator(scope)) {
82+
return rules.variable;
83+
} else if (isFunctionOrFunctionType(scope)) {
84+
return rules.parameter;
85+
} else {
86+
return rules.colon;
87+
}
88+
}
89+
90+
function getRules(
91+
rules: WhitespaceRules,
92+
node: TSESTree.TypeNode,
93+
): WhitespaceRule {
94+
const scope = node?.parent?.parent;
95+
96+
if (isTSFunctionType(scope)) {
97+
return rules.arrow;
98+
} else if (isIdentifier(scope)) {
99+
return getIdentifierRules(rules, scope);
100+
} else if (isClassOrTypeElement(scope)) {
101+
return rules.property;
102+
} else if (isFunction(scope)) {
103+
return rules.returnType;
104+
} else {
105+
return rules.colon;
106+
}
107+
}
108+
35109
export default util.createRule<Options, MessageIds>({
36110
name: 'type-annotation-spacing',
37111
meta: {
@@ -59,6 +133,10 @@ export default util.createRule<Options, MessageIds>({
59133
properties: {
60134
colon: definition,
61135
arrow: definition,
136+
variable: definition,
137+
parameter: definition,
138+
property: definition,
139+
returnType: definition,
62140
},
63141
additionalProperties: false,
64142
},
@@ -76,20 +154,7 @@ export default util.createRule<Options, MessageIds>({
76154
const punctuators = [':', '=>'];
77155
const sourceCode = context.getSourceCode();
78156

79-
const overrides = options?.overrides ?? { colon: {}, arrow: {} };
80-
81-
const colonOptions = Object.assign(
82-
{},
83-
{ before: false, after: true },
84-
options,
85-
overrides.colon,
86-
);
87-
const arrowOptions = Object.assign(
88-
{},
89-
{ before: true, after: true },
90-
options,
91-
overrides.arrow,
92-
);
157+
const ruleSet = createRules(options);
93158

94159
/**
95160
* Checks if there's proper spacing around type annotations (no space
@@ -108,8 +173,7 @@ export default util.createRule<Options, MessageIds>({
108173
return;
109174
}
110175

111-
const before = type === ':' ? colonOptions.before : arrowOptions.before;
112-
const after = type === ':' ? colonOptions.after : arrowOptions.after;
176+
const { before, after } = getRules(ruleSet, typeAnnotation);
113177

114178
if (type === ':' && previousToken.value === '?') {
115179
// shift the start to the ?

packages/eslint-plugin/src/util/astUtils.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,95 @@ function isTypeAssertion(
8282
);
8383
}
8484

85+
function isVariableDeclarator(
86+
node: TSESTree.Node | undefined,
87+
): node is TSESTree.VariableDeclarator {
88+
return node?.type === AST_NODE_TYPES.VariableDeclarator;
89+
}
90+
91+
function isFunction(
92+
node: TSESTree.Node | undefined,
93+
): node is
94+
| TSESTree.ArrowFunctionExpression
95+
| TSESTree.FunctionDeclaration
96+
| TSESTree.FunctionExpression {
97+
if (!node) {
98+
return false;
99+
}
100+
101+
return [
102+
AST_NODE_TYPES.ArrowFunctionExpression,
103+
AST_NODE_TYPES.FunctionDeclaration,
104+
AST_NODE_TYPES.FunctionExpression,
105+
].includes(node.type);
106+
}
107+
108+
function isFunctionType(
109+
node: TSESTree.Node | undefined,
110+
): node is
111+
| TSESTree.TSCallSignatureDeclaration
112+
| TSESTree.TSConstructSignatureDeclaration
113+
| TSESTree.TSEmptyBodyFunctionExpression
114+
| TSESTree.TSFunctionType
115+
| TSESTree.TSMethodSignature {
116+
if (!node) {
117+
return false;
118+
}
119+
120+
return [
121+
AST_NODE_TYPES.TSCallSignatureDeclaration,
122+
AST_NODE_TYPES.TSConstructSignatureDeclaration,
123+
AST_NODE_TYPES.TSEmptyBodyFunctionExpression,
124+
AST_NODE_TYPES.TSFunctionType,
125+
AST_NODE_TYPES.TSMethodSignature,
126+
].includes(node.type);
127+
}
128+
129+
function isFunctionOrFunctionType(
130+
node: TSESTree.Node | undefined,
131+
): node is
132+
| TSESTree.ArrowFunctionExpression
133+
| TSESTree.FunctionDeclaration
134+
| TSESTree.FunctionExpression
135+
| TSESTree.TSCallSignatureDeclaration
136+
| TSESTree.TSConstructSignatureDeclaration
137+
| TSESTree.TSEmptyBodyFunctionExpression
138+
| TSESTree.TSFunctionType
139+
| TSESTree.TSMethodSignature {
140+
return isFunction(node) || isFunctionType(node);
141+
}
142+
143+
function isTSFunctionType(
144+
node: TSESTree.Node | undefined,
145+
): node is TSESTree.TSFunctionType {
146+
return node?.type === AST_NODE_TYPES.TSFunctionType;
147+
}
148+
149+
function isClassOrTypeElement(
150+
node: TSESTree.Node | undefined,
151+
): node is TSESTree.ClassElement | TSESTree.TypeElement {
152+
if (!node) {
153+
return false;
154+
}
155+
156+
return [
157+
// ClassElement
158+
AST_NODE_TYPES.ClassProperty,
159+
AST_NODE_TYPES.FunctionExpression,
160+
AST_NODE_TYPES.MethodDefinition,
161+
AST_NODE_TYPES.TSAbstractClassProperty,
162+
AST_NODE_TYPES.TSAbstractMethodDefinition,
163+
AST_NODE_TYPES.TSEmptyBodyFunctionExpression,
164+
AST_NODE_TYPES.TSIndexSignature,
165+
// TypeElement
166+
AST_NODE_TYPES.TSCallSignatureDeclaration,
167+
AST_NODE_TYPES.TSConstructSignatureDeclaration,
168+
// AST_NODE_TYPES.TSIndexSignature,
169+
AST_NODE_TYPES.TSMethodSignature,
170+
AST_NODE_TYPES.TSPropertySignature,
171+
].includes(node.type);
172+
}
173+
85174
/**
86175
* Checks if a node is a constructor method.
87176
*/
@@ -136,6 +225,10 @@ export {
136225
isAwaitExpression,
137226
isAwaitKeyword,
138227
isConstructor,
228+
isClassOrTypeElement,
229+
isFunction,
230+
isFunctionOrFunctionType,
231+
isFunctionType,
139232
isIdentifier,
140233
isLogicalOrOperator,
141234
isNonNullAssertionPunctuator,
@@ -145,6 +238,8 @@ export {
145238
isOptionalOptionalChain,
146239
isSetter,
147240
isTokenOnSameLine,
241+
isTSFunctionType,
148242
isTypeAssertion,
243+
isVariableDeclarator,
149244
LINEBREAK_MATCHER,
150245
};

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