eslint-plugin-jest-formatting 0.1.0 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc +5 -5
- package/CHANGELOG.md +28 -6
- package/README.md +29 -9
- package/docs/rules/padding-around-after-all-blocks.md +25 -0
- package/docs/rules/padding-around-after-each-blocks.md +29 -0
- package/docs/rules/padding-around-all.md +13 -0
- package/docs/rules/padding-around-before-all-blocks.md +29 -0
- package/docs/rules/padding-around-before-each-blocks.md +29 -0
- package/docs/rules/padding-around-describe-blocks.md +33 -0
- package/docs/rules/padding-around-expect-groups.md +35 -0
- package/docs/rules/padding-around-test-blocks.md +39 -0
- package/lib/ast-utils.js +40 -0
- package/lib/index.js +238 -38
- package/lib/rules/padding.js +130 -158
- package/package.json +4 -2
- package/src/ast-utils.ts +78 -0
- package/src/index.ts +282 -43
- package/src/rules/padding.ts +261 -252
- package/tests/catfood.js +39 -0
- package/tests/dogfood.spec.js +16 -1
- package/tests/lib/ast-utils.spec.ts +94 -0
- package/tests/lib/rules/padding-around-after-all-blocks.spec.js +114 -0
- package/tests/lib/rules/padding-around-after-each-blocks.spec.js +112 -0
- package/tests/lib/rules/padding-around-before-all-blocks.spec.js +114 -0
- package/tests/lib/rules/padding-around-before-each-blocks.spec.js +114 -0
- package/tests/lib/rules/padding-around-describe-blocks.spec.js +152 -0
- package/tests/lib/rules/padding-around-expect-groups.spec.js +154 -0
- package/tests/lib/rules/padding-around-test-blocks.spec.js +161 -0
- package/tests/lib/rules/padding-before-after-all-blocks.spec.js +1 -7
- package/tests/lib/rules/padding-before-after-each-blocks.spec.js +1 -7
- package/tests/lib/rules/padding-before-before-all-blocks.spec.js +1 -7
- package/tests/lib/rules/padding-before-before-each-blocks.spec.js +1 -7
- package/tests/lib/rules/padding-before-describe-blocks.spec.js +1 -7
- package/tests/lib/rules/padding-before-expect-statements.spec.js +1 -7
- package/tests/lib/rules/padding-before-test-blocks.spec.js +1 -7
- package/tsconfig.json +1 -1
- package/yarn.lock +12 -10
- package/lib/utils.js +0 -15
- package/src/utils.ts +0 -40
package/src/rules/padding.ts
CHANGED
|
@@ -1,115 +1,142 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
* Based on eslint/padding-line-between-statements by Toru Nagashima
|
|
4
|
-
* See: https://github.com/eslint/eslint/blob/master/lib/rules/padding-line-between-statements.js
|
|
2
|
+
* Require/fix newlines around jest functions
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Based on eslint/padding-line-between-statements by Toru Nagashima
|
|
5
|
+
* See: https://github.com/eslint/eslint/blob/master/lib/rules/padding-line-between-statements.js
|
|
6
|
+
*
|
|
7
|
+
* Some helpers borrowed from eslint ast-utils by Gyandeep Singh
|
|
8
|
+
* See: https://github.com/eslint/eslint/blob/master/lib/rules/utils/ast-utils.js
|
|
9
9
|
*/
|
|
10
|
+
|
|
10
11
|
import { AST, Rule, SourceCode } from 'eslint';
|
|
11
12
|
import { Node } from 'estree';
|
|
13
|
+
import * as astUtils from '../ast-utils';
|
|
14
|
+
|
|
15
|
+
// Statement types we'll respond to
|
|
16
|
+
export const enum StatementType {
|
|
17
|
+
Any,
|
|
18
|
+
AfterAllToken,
|
|
19
|
+
AfterEachToken,
|
|
20
|
+
BeforeAllToken,
|
|
21
|
+
BeforeEachToken,
|
|
22
|
+
DescribeToken,
|
|
23
|
+
ExpectToken,
|
|
24
|
+
FdescribeToken,
|
|
25
|
+
FitToken,
|
|
26
|
+
ItToken,
|
|
27
|
+
TestToken,
|
|
28
|
+
XdescribeToken,
|
|
29
|
+
XitToken,
|
|
30
|
+
XtestToken,
|
|
31
|
+
}
|
|
12
32
|
|
|
13
|
-
type
|
|
14
|
-
[test: string]: (node: Node, sourceCode: SourceCode) => boolean;
|
|
15
|
-
};
|
|
33
|
+
type StatementTypes = StatementType | StatementType[];
|
|
16
34
|
|
|
17
|
-
type
|
|
18
|
-
[verify: string]: (
|
|
19
|
-
context: Rule.RuleContext,
|
|
20
|
-
prevNode: Node,
|
|
21
|
-
nextNode: Node,
|
|
22
|
-
paddingLines: AST.Token[][],
|
|
23
|
-
) => void;
|
|
24
|
-
};
|
|
35
|
+
type StatementTester = (node: Node, sourceCode: SourceCode) => boolean;
|
|
25
36
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
]);
|
|
37
|
+
// Padding type to apply between statements
|
|
38
|
+
export const enum PaddingType {
|
|
39
|
+
Any,
|
|
40
|
+
Always,
|
|
41
|
+
}
|
|
32
42
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
43
|
+
type PaddingTester = (
|
|
44
|
+
prevNode: Node,
|
|
45
|
+
nextNode: Node,
|
|
46
|
+
paddingContext: PaddingContext,
|
|
47
|
+
) => void;
|
|
48
|
+
|
|
49
|
+
// A configuration object for padding type and the two statement types
|
|
50
|
+
export interface Config {
|
|
51
|
+
paddingType: PaddingType;
|
|
52
|
+
prevStatementType: StatementTypes;
|
|
53
|
+
nextStatementType: StatementTypes;
|
|
54
|
+
}
|
|
37
55
|
|
|
38
|
-
|
|
39
|
-
|
|
56
|
+
// Tracks position in scope and prevNode. Used to compare current and prev node
|
|
57
|
+
// and then to walk back up to the parent scope or down into the next one.
|
|
58
|
+
// And so on...
|
|
59
|
+
interface Scope {
|
|
60
|
+
upper: Scope | null;
|
|
61
|
+
prevNode: Node | null;
|
|
62
|
+
}
|
|
40
63
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
64
|
+
interface ScopeInfo {
|
|
65
|
+
prevNode: Node | null;
|
|
66
|
+
enter: () => void;
|
|
67
|
+
exit: () => void;
|
|
68
|
+
}
|
|
44
69
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
test: (node: Node, sourceCode: SourceCode): boolean => {
|
|
51
|
-
const token = sourceCode.getFirstToken(node);
|
|
52
|
-
|
|
53
|
-
return (
|
|
54
|
-
node.type === 'ExpressionStatement' &&
|
|
55
|
-
token.type === 'Identifier' &&
|
|
56
|
-
token.value === name
|
|
57
|
-
);
|
|
58
|
-
},
|
|
59
|
-
};
|
|
70
|
+
interface PaddingContext {
|
|
71
|
+
ruleContext: Rule.RuleContext;
|
|
72
|
+
sourceCode: SourceCode;
|
|
73
|
+
scopeInfo: ScopeInfo;
|
|
74
|
+
configs: Config[];
|
|
60
75
|
}
|
|
61
76
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
* For example:
|
|
67
|
-
*
|
|
68
|
-
* foo()
|
|
69
|
-
* ;[1, 2, 3].forEach(bar)
|
|
70
|
-
*/
|
|
71
|
-
function getActualLastToken(sourceCode: SourceCode, node: Node): AST.Token {
|
|
72
|
-
const semiToken = sourceCode.getLastToken(node);
|
|
73
|
-
const prevToken = sourceCode.getTokenBefore(semiToken);
|
|
74
|
-
const nextToken = sourceCode.getTokenAfter(semiToken);
|
|
75
|
-
const isSemicolonLessStyle = Boolean(
|
|
76
|
-
prevToken &&
|
|
77
|
-
nextToken &&
|
|
78
|
-
prevToken.range[0] >= node.range[0] &&
|
|
79
|
-
isSemicolonToken(semiToken) &&
|
|
80
|
-
semiToken.loc.start.line !== prevToken.loc.end.line &&
|
|
81
|
-
semiToken.loc.end.line === nextToken.loc.start.line,
|
|
82
|
-
);
|
|
77
|
+
// Creates a StatementTester to test an ExpressionStatement's first token name
|
|
78
|
+
const createTokenTester = (tokenName: string): StatementTester => {
|
|
79
|
+
return (node: Node, sourceCode: SourceCode): boolean => {
|
|
80
|
+
const token = sourceCode.getFirstToken(node);
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
82
|
+
return (
|
|
83
|
+
node.type === 'ExpressionStatement' &&
|
|
84
|
+
token.type === 'Identifier' &&
|
|
85
|
+
token.value === tokenName
|
|
86
|
+
);
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// A mapping of StatementType to StatementTester for... testing statements
|
|
91
|
+
const statementTesters: { [T in StatementType]: StatementTester } = {
|
|
92
|
+
[StatementType.Any]: () => true,
|
|
93
|
+
[StatementType.AfterAllToken]: createTokenTester('afterAll'),
|
|
94
|
+
[StatementType.AfterEachToken]: createTokenTester('afterEach'),
|
|
95
|
+
[StatementType.BeforeAllToken]: createTokenTester('beforeAll'),
|
|
96
|
+
[StatementType.BeforeEachToken]: createTokenTester('beforeEach'),
|
|
97
|
+
[StatementType.DescribeToken]: createTokenTester('describe'),
|
|
98
|
+
[StatementType.ExpectToken]: createTokenTester('expect'),
|
|
99
|
+
[StatementType.FdescribeToken]: createTokenTester('fdescribe'),
|
|
100
|
+
[StatementType.FitToken]: createTokenTester('fit'),
|
|
101
|
+
[StatementType.ItToken]: createTokenTester('it'),
|
|
102
|
+
[StatementType.TestToken]: createTokenTester('test'),
|
|
103
|
+
[StatementType.XdescribeToken]: createTokenTester('xdescribe'),
|
|
104
|
+
[StatementType.XitToken]: createTokenTester('xit'),
|
|
105
|
+
[StatementType.XtestToken]: createTokenTester('xtest'),
|
|
106
|
+
};
|
|
86
107
|
|
|
87
108
|
/**
|
|
88
|
-
* Check and report statements for `
|
|
109
|
+
* Check and report statements for `PaddingType.Always` configuration.
|
|
89
110
|
* This autofix inserts a blank line between the given 2 statements.
|
|
90
111
|
* If the `prevNode` has trailing comments, it inserts a blank line after the
|
|
91
112
|
* trailing comments.
|
|
92
113
|
*/
|
|
93
|
-
|
|
94
|
-
context: Rule.RuleContext,
|
|
114
|
+
const paddingAlwaysTester = (
|
|
95
115
|
prevNode: Node,
|
|
96
116
|
nextNode: Node,
|
|
97
|
-
|
|
98
|
-
): void {
|
|
117
|
+
paddingContext: PaddingContext,
|
|
118
|
+
): void => {
|
|
119
|
+
const { sourceCode, ruleContext } = paddingContext;
|
|
120
|
+
const paddingLines = astUtils.getPaddingLineSequences(
|
|
121
|
+
prevNode,
|
|
122
|
+
nextNode,
|
|
123
|
+
sourceCode,
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
// We've got some padding lines. Great.
|
|
99
127
|
if (paddingLines.length > 0) {
|
|
100
128
|
return;
|
|
101
129
|
}
|
|
102
130
|
|
|
103
|
-
|
|
131
|
+
// Missing padding line
|
|
132
|
+
ruleContext.report({
|
|
104
133
|
node: nextNode,
|
|
105
134
|
message: 'Expected blank line before this statement.',
|
|
106
135
|
fix(fixer: Rule.RuleFixer): Rule.Fix {
|
|
107
|
-
|
|
108
|
-
let prevToken = getActualLastToken(sourceCode, prevNode);
|
|
136
|
+
let prevToken = astUtils.getActualLastToken(sourceCode, prevNode);
|
|
109
137
|
const nextToken =
|
|
110
138
|
sourceCode.getFirstTokenBetween(prevToken, nextNode, {
|
|
111
139
|
includeComments: true,
|
|
112
|
-
|
|
113
140
|
/**
|
|
114
141
|
* Skip the trailing comments of the previous node.
|
|
115
142
|
* This inserts a blank line after the last trailing comment.
|
|
@@ -128,7 +155,7 @@ function verifyForAlways(
|
|
|
128
155
|
* bar();
|
|
129
156
|
*/
|
|
130
157
|
filter(token: AST.Token): boolean {
|
|
131
|
-
if (
|
|
158
|
+
if (astUtils.areTokensOnSameLine(prevToken, token)) {
|
|
132
159
|
prevToken = token;
|
|
133
160
|
return false;
|
|
134
161
|
}
|
|
@@ -137,203 +164,185 @@ function verifyForAlways(
|
|
|
137
164
|
},
|
|
138
165
|
}) || nextNode;
|
|
139
166
|
|
|
140
|
-
const insertText =
|
|
167
|
+
const insertText = astUtils.areTokensOnSameLine(prevToken, nextToken)
|
|
141
168
|
? '\n\n'
|
|
142
169
|
: '\n';
|
|
143
170
|
|
|
144
171
|
return fixer.insertTextAfter(prevToken, insertText);
|
|
145
172
|
},
|
|
146
173
|
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/**
|
|
150
|
-
* Types of blank lines.
|
|
151
|
-
* `any` and `always` are defined.
|
|
152
|
-
* Those have `verify` method to check and report statements.
|
|
153
|
-
*/
|
|
154
|
-
const PaddingTypes = {
|
|
155
|
-
any: { verify: () => {} },
|
|
156
|
-
always: { verify: verifyForAlways },
|
|
157
174
|
};
|
|
158
175
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const StatementTypes = {
|
|
164
|
-
'*': { test: () => true },
|
|
165
|
-
afterAll: newJestTokenTester('afterAll'),
|
|
166
|
-
afterEach: newJestTokenTester('afterEach'),
|
|
167
|
-
beforeAll: newJestTokenTester('beforeAll'),
|
|
168
|
-
beforeEach: newJestTokenTester('beforeEach'),
|
|
169
|
-
describe: newJestTokenTester('describe'),
|
|
170
|
-
expect: newJestTokenTester('expect'),
|
|
171
|
-
it: newJestTokenTester('it'),
|
|
172
|
-
test: newJestTokenTester('test'),
|
|
176
|
+
// A mapping of PaddingType to PaddingTester
|
|
177
|
+
const paddingTesters: { [T in PaddingType]: PaddingTester } = {
|
|
178
|
+
[PaddingType.Any]: () => true,
|
|
179
|
+
[PaddingType.Always]: paddingAlwaysTester,
|
|
173
180
|
};
|
|
174
181
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
182
|
+
const createScopeInfo = (): ScopeInfo => {
|
|
183
|
+
return (() => {
|
|
184
|
+
let scope: Scope = null;
|
|
178
185
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
schema: {
|
|
183
|
-
definitions: {
|
|
184
|
-
paddingType: {
|
|
185
|
-
enum: Object.keys(PaddingTypes),
|
|
186
|
-
},
|
|
187
|
-
statementType: {
|
|
188
|
-
anyOf: [
|
|
189
|
-
{ enum: Object.keys(StatementTypes) },
|
|
190
|
-
{
|
|
191
|
-
type: 'array',
|
|
192
|
-
items: { enum: Object.keys(StatementTypes) },
|
|
193
|
-
minItems: 1,
|
|
194
|
-
uniqueItems: true,
|
|
195
|
-
additionalItems: false,
|
|
196
|
-
},
|
|
197
|
-
],
|
|
198
|
-
},
|
|
186
|
+
return {
|
|
187
|
+
get prevNode() {
|
|
188
|
+
return scope.prevNode;
|
|
199
189
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
type: 'object',
|
|
203
|
-
properties: {
|
|
204
|
-
blankLine: { $ref: '#/definitions/paddingType' },
|
|
205
|
-
prev: { $ref: '#/definitions/statementType' },
|
|
206
|
-
next: { $ref: '#/definitions/statementType' },
|
|
207
|
-
},
|
|
208
|
-
additionalProperties: false,
|
|
209
|
-
required: ['blankLine', 'prev', 'next'],
|
|
190
|
+
set prevNode(node) {
|
|
191
|
+
scope.prevNode = node;
|
|
210
192
|
},
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
const sourceCode = context.getSourceCode();
|
|
222
|
-
const configureList = context.options || [];
|
|
223
|
-
let scopeInfo = null;
|
|
224
|
-
|
|
225
|
-
function enterScope(): void {
|
|
226
|
-
scopeInfo = { upper: scopeInfo, prevNode: null };
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
function exitScope(): void {
|
|
230
|
-
scopeInfo = scopeInfo.upper;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Checks whether the given node matches the given type.
|
|
235
|
-
*/
|
|
236
|
-
function match(node: Node, type: string | string[]): boolean {
|
|
237
|
-
let innerStatementNode = node;
|
|
238
|
-
|
|
239
|
-
while (innerStatementNode.type === 'LabeledStatement') {
|
|
240
|
-
innerStatementNode = innerStatementNode.body;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (Array.isArray(type)) {
|
|
244
|
-
return type.some(match.bind(null, innerStatementNode));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
return StatementTypes[type].test(innerStatementNode, sourceCode);
|
|
248
|
-
}
|
|
193
|
+
enter() {
|
|
194
|
+
scope = { upper: scope, prevNode: null };
|
|
195
|
+
},
|
|
196
|
+
exit() {
|
|
197
|
+
scope = scope.upper;
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
})();
|
|
201
|
+
};
|
|
249
202
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
203
|
+
/**
|
|
204
|
+
* Check whether the given node matches the statement type
|
|
205
|
+
*/
|
|
206
|
+
const nodeMatchesType = (
|
|
207
|
+
node: Node,
|
|
208
|
+
statementType: StatementTypes,
|
|
209
|
+
paddingContext: PaddingContext,
|
|
210
|
+
): boolean => {
|
|
211
|
+
let innerStatementNode = node;
|
|
212
|
+
const { sourceCode } = paddingContext;
|
|
213
|
+
|
|
214
|
+
// Dig into LabeledStatement body until it's not that anymore
|
|
215
|
+
while (innerStatementNode.type === 'LabeledStatement') {
|
|
216
|
+
innerStatementNode = innerStatementNode.body;
|
|
217
|
+
}
|
|
256
218
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
219
|
+
// If it's an array recursively check if any of the statement types match
|
|
220
|
+
// the node
|
|
221
|
+
if (Array.isArray(statementType)) {
|
|
222
|
+
return statementType.some(type =>
|
|
223
|
+
nodeMatchesType(innerStatementNode, type, paddingContext),
|
|
224
|
+
);
|
|
225
|
+
}
|
|
261
226
|
|
|
262
|
-
|
|
263
|
-
|
|
227
|
+
return statementTesters[statementType](innerStatementNode, sourceCode);
|
|
228
|
+
};
|
|
264
229
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
return
|
|
230
|
+
/**
|
|
231
|
+
* Executes matching padding tester for last matched padding config for given
|
|
232
|
+
* nodes
|
|
233
|
+
*/
|
|
234
|
+
const testPadding = (
|
|
235
|
+
prevNode: Node,
|
|
236
|
+
nextNode: Node,
|
|
237
|
+
paddingContext: PaddingContext,
|
|
238
|
+
): void => {
|
|
239
|
+
const { configs } = paddingContext;
|
|
240
|
+
|
|
241
|
+
const testType = (type: PaddingType) =>
|
|
242
|
+
paddingTesters[type](prevNode, nextNode, paddingContext);
|
|
243
|
+
|
|
244
|
+
for (let i = configs.length - 1; i >= 0; --i) {
|
|
245
|
+
const {
|
|
246
|
+
prevStatementType: prevType,
|
|
247
|
+
nextStatementType: nextType,
|
|
248
|
+
paddingType,
|
|
249
|
+
} = configs[i];
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
nodeMatchesType(prevNode, prevType, paddingContext) &&
|
|
253
|
+
nodeMatchesType(nextNode, nextType, paddingContext)
|
|
254
|
+
) {
|
|
255
|
+
return testType(paddingType);
|
|
291
256
|
}
|
|
257
|
+
}
|
|
292
258
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
const parentType = node.parent.type;
|
|
298
|
-
const validParent = STATEMENT_LIST_PARENTS.has(parentType);
|
|
299
|
-
|
|
300
|
-
if (!validParent) {
|
|
301
|
-
return;
|
|
302
|
-
}
|
|
259
|
+
// There were no matching padding rules for the prevNode, nextNode,
|
|
260
|
+
// paddingType combination... so we'll use PaddingType.Any which is always ok
|
|
261
|
+
return testType(PaddingType.Any);
|
|
262
|
+
};
|
|
303
263
|
|
|
304
|
-
|
|
305
|
-
|
|
264
|
+
/**
|
|
265
|
+
* Verify padding lines between the given node and the previous node.
|
|
266
|
+
*/
|
|
267
|
+
const verifyNode = (node: Node, paddingContext: PaddingContext): void => {
|
|
268
|
+
const { scopeInfo } = paddingContext;
|
|
269
|
+
|
|
270
|
+
// NOTE: ESLint types use ESTree which provides a Node type, however
|
|
271
|
+
// ESTree.Node doesn't support the parent property which is added by
|
|
272
|
+
// ESLint during traversal. Our best bet is to ignore the property access
|
|
273
|
+
// here as it's the only place that it's checked.
|
|
274
|
+
// @ts-ignore
|
|
275
|
+
if (!astUtils.isValidParent(node.parent.type)) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
306
278
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const paddingLines = getPaddingLineSequences(prevNode, node);
|
|
279
|
+
if (scopeInfo.prevNode) {
|
|
280
|
+
testPadding(scopeInfo.prevNode, node, paddingContext);
|
|
281
|
+
}
|
|
311
282
|
|
|
312
|
-
|
|
313
|
-
|
|
283
|
+
scopeInfo.prevNode = node;
|
|
284
|
+
};
|
|
314
285
|
|
|
315
|
-
|
|
316
|
-
|
|
286
|
+
/**
|
|
287
|
+
* Creates an ESLint rule for a given set of padding Config objects.
|
|
288
|
+
*
|
|
289
|
+
* The algorithm is approximately this:
|
|
290
|
+
*
|
|
291
|
+
* For each 'scope' in the program
|
|
292
|
+
* - Enter the scope (store the parent scope and previous node)
|
|
293
|
+
* - For each statement in the scope
|
|
294
|
+
* - Check the current node and previous node against the Config objects
|
|
295
|
+
* - If the current node and previous node match a Config, check the padding.
|
|
296
|
+
* Otherwise, ignore it.
|
|
297
|
+
* - If the padding is missing (and required), report and fix
|
|
298
|
+
* - Store the current node as the previous
|
|
299
|
+
* - Repeat
|
|
300
|
+
* - Exit scope (return to parent scope and clear previous node)
|
|
301
|
+
*
|
|
302
|
+
* The items we're looking for with this rule are ExpressionStatement nodes
|
|
303
|
+
* where the first token is an Identifier with a name matching one of the Jest
|
|
304
|
+
* functions. It's not foolproof, of course, but it's probably good enough for
|
|
305
|
+
* almost all cases.
|
|
306
|
+
*
|
|
307
|
+
* The Config objects specify a padding type, a previous statement type, and a
|
|
308
|
+
* next statement type. Wildcard statement types and padding types are
|
|
309
|
+
* supported. The current node and previous node are checked against the
|
|
310
|
+
* statement types. If they match then the specified padding type is
|
|
311
|
+
* tested/enforced.
|
|
312
|
+
*
|
|
313
|
+
* See src/index.ts for examples of Config usage.
|
|
314
|
+
*/
|
|
315
|
+
export const createRule = (
|
|
316
|
+
configs: Config[],
|
|
317
|
+
deprecated: boolean = false,
|
|
318
|
+
): Rule.RuleModule => ({
|
|
319
|
+
meta: {
|
|
320
|
+
fixable: 'whitespace',
|
|
321
|
+
deprecated,
|
|
322
|
+
},
|
|
323
|
+
create(context: Rule.RuleContext) {
|
|
324
|
+
const paddingContext = {
|
|
325
|
+
ruleContext: context,
|
|
326
|
+
sourceCode: context.getSourceCode(),
|
|
327
|
+
scopeInfo: createScopeInfo(),
|
|
328
|
+
configs,
|
|
329
|
+
};
|
|
317
330
|
|
|
318
|
-
|
|
319
|
-
* Verify padding lines between the given node and the previous node.
|
|
320
|
-
* Then process to enter to new scope.
|
|
321
|
-
*/
|
|
322
|
-
function verifyThenEnterScope(node: Node): void {
|
|
323
|
-
verify(node);
|
|
324
|
-
enterScope();
|
|
325
|
-
}
|
|
331
|
+
const { scopeInfo } = paddingContext;
|
|
326
332
|
|
|
327
333
|
return {
|
|
328
|
-
Program:
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
'
|
|
332
|
-
|
|
333
|
-
'SwitchStatement:exit':
|
|
334
|
-
':statement':
|
|
335
|
-
SwitchCase:
|
|
336
|
-
|
|
334
|
+
Program: scopeInfo.enter,
|
|
335
|
+
'Program:exit': scopeInfo.enter,
|
|
336
|
+
BlockStatement: scopeInfo.enter,
|
|
337
|
+
'BlockStatement:exit': scopeInfo.exit,
|
|
338
|
+
SwitchStatement: scopeInfo.enter,
|
|
339
|
+
'SwitchStatement:exit': scopeInfo.exit,
|
|
340
|
+
':statement': (node: Node) => verifyNode(node, paddingContext),
|
|
341
|
+
SwitchCase: (node: Node) => {
|
|
342
|
+
verifyNode(node, paddingContext);
|
|
343
|
+
scopeInfo.enter();
|
|
344
|
+
},
|
|
345
|
+
'SwitchCase:exit': scopeInfo.exit,
|
|
337
346
|
};
|
|
338
347
|
},
|
|
339
|
-
};
|
|
348
|
+
});
|
package/tests/catfood.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file should be IGNORED by jest-formatting due to the `overrides.files`
|
|
3
|
+
* value in `recommended` and `strict` config. This file does not match the
|
|
4
|
+
* globs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
beforeAll(() => {});
|
|
8
|
+
beforeEach(() => {});
|
|
9
|
+
afterAll(() => {});
|
|
10
|
+
afterEach(() => {});
|
|
11
|
+
describe('no padding on top', () => {
|
|
12
|
+
it('has no padding above', () => {
|
|
13
|
+
expect(true);
|
|
14
|
+
});
|
|
15
|
+
expect(true);
|
|
16
|
+
expect(1);
|
|
17
|
+
xit('has padding above', () => {
|
|
18
|
+
expect(true);
|
|
19
|
+
});
|
|
20
|
+
it('also has padding above but not below', () => {
|
|
21
|
+
expect(true);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
xdescribe('padding above', () => {
|
|
25
|
+
it('has no padding above or below', () => {
|
|
26
|
+
expect(true);
|
|
27
|
+
});
|
|
28
|
+
it('has no padding above or below', () => {
|
|
29
|
+
expect(true);
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('padding above', () => {
|
|
33
|
+
it('has no padding above or below', () => {
|
|
34
|
+
expect(true);
|
|
35
|
+
});
|
|
36
|
+
it('has no padding above or below', () => {
|
|
37
|
+
expect(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
package/tests/dogfood.spec.js
CHANGED
|
@@ -1,15 +1,30 @@
|
|
|
1
|
+
beforeAll(() => {});
|
|
2
|
+
|
|
3
|
+
beforeEach(() => {});
|
|
4
|
+
|
|
5
|
+
afterAll(() => {});
|
|
6
|
+
|
|
7
|
+
afterEach(() => {});
|
|
8
|
+
|
|
1
9
|
describe('no padding on top', () => {
|
|
2
10
|
it('has no padding above', () => {
|
|
3
11
|
expect(true);
|
|
4
12
|
});
|
|
5
13
|
|
|
14
|
+
expect(true);
|
|
15
|
+
expect(1);
|
|
16
|
+
|
|
6
17
|
it('has padding above', () => {
|
|
7
18
|
expect(true);
|
|
8
19
|
});
|
|
20
|
+
|
|
21
|
+
it('also has padding above but not below', () => {
|
|
22
|
+
expect(true);
|
|
23
|
+
});
|
|
9
24
|
});
|
|
10
25
|
|
|
11
26
|
describe('padding above', () => {
|
|
12
|
-
it('has no padding above', () => {
|
|
27
|
+
it('has no padding above or below', () => {
|
|
13
28
|
expect(true);
|
|
14
29
|
});
|
|
15
30
|
});
|