Skip to content

Commit b097245

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

File tree

4 files changed

+332
-33
lines changed

4 files changed

+332
-33
lines changed

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

+1-1
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

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

+96-32
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 ?

Diff for: packages/eslint-plugin/src/util/astUtils.ts

+95
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)