eslint-plugin-jest-formatting 1.0.0 → 2.0.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.
Files changed (33) hide show
  1. package/.circleci/config.yml +2 -2
  2. package/.eslintrc +15 -4
  3. package/CHANGELOG.md +28 -6
  4. package/README.md +21 -13
  5. package/docs/rules/padding-around-all.md +13 -0
  6. package/lib/ast-utils.js +41 -0
  7. package/lib/index.js +159 -76
  8. package/lib/rules/padding.js +131 -158
  9. package/package.json +20 -19
  10. package/src/ast-utils.ts +80 -0
  11. package/src/index.ts +166 -81
  12. package/src/rules/padding.ts +263 -252
  13. package/tests/catfood.js +41 -0
  14. package/tests/dogfood.spec.js +2 -0
  15. package/tests/lib/ast-utils.spec.ts +94 -0
  16. package/tests/lib/rules/padding-around-after-all-blocks.spec.js +3 -8
  17. package/tests/lib/rules/padding-around-after-each-blocks.spec.js +3 -8
  18. package/tests/lib/rules/padding-around-before-all-blocks.spec.js +3 -8
  19. package/tests/lib/rules/padding-around-before-each-blocks.spec.js +3 -8
  20. package/tests/lib/rules/padding-around-describe-blocks.spec.js +7 -12
  21. package/tests/lib/rules/padding-around-expect-groups.spec.js +3 -8
  22. package/tests/lib/rules/padding-around-test-blocks.spec.js +15 -12
  23. package/tsconfig.json +1 -1
  24. package/lib/utils.js +0 -15
  25. package/src/utils.ts +0 -40
  26. package/tests/lib/rules/padding-before-after-all-blocks.spec.js +0 -104
  27. package/tests/lib/rules/padding-before-after-each-blocks.spec.js +0 -104
  28. package/tests/lib/rules/padding-before-before-all-blocks.spec.js +0 -104
  29. package/tests/lib/rules/padding-before-before-each-blocks.spec.js +0 -104
  30. package/tests/lib/rules/padding-before-describe-blocks.spec.js +0 -150
  31. package/tests/lib/rules/padding-before-expect-statements.spec.js +0 -144
  32. package/tests/lib/rules/padding-before-test-blocks.spec.js +0 -159
  33. package/yarn.lock +0 -4232
@@ -1,190 +1,163 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const STATEMENT_LIST_PARENTS = new Set([
4
- 'Program',
5
- 'BlockStatement',
6
- 'SwitchCase',
7
- 'SwitchStatement',
8
- ]);
9
- const isTokenOnSameLine = (left, right) => left.loc.end.line === right.loc.start.line;
10
- const isSemicolonToken = (token) => token.value === ';' && token.type === 'Punctuator';
11
- const isTestFile = (filename) => filename.includes('test') || filename.includes('spec');
12
- function newJestTokenTester(name) {
13
- return {
14
- test: (node, sourceCode) => {
15
- const token = sourceCode.getFirstToken(node);
16
- return (node.type === 'ExpressionStatement' &&
17
- token.type === 'Identifier' &&
18
- token.value === name);
19
- },
3
+ exports.createRule = exports.PaddingType = exports.StatementType = void 0;
4
+ const astUtils = require("../ast-utils");
5
+ var StatementType;
6
+ (function (StatementType) {
7
+ StatementType[StatementType["Any"] = 0] = "Any";
8
+ StatementType[StatementType["AfterAllToken"] = 1] = "AfterAllToken";
9
+ StatementType[StatementType["AfterEachToken"] = 2] = "AfterEachToken";
10
+ StatementType[StatementType["BeforeAllToken"] = 3] = "BeforeAllToken";
11
+ StatementType[StatementType["BeforeEachToken"] = 4] = "BeforeEachToken";
12
+ StatementType[StatementType["DescribeToken"] = 5] = "DescribeToken";
13
+ StatementType[StatementType["ExpectToken"] = 6] = "ExpectToken";
14
+ StatementType[StatementType["FdescribeToken"] = 7] = "FdescribeToken";
15
+ StatementType[StatementType["FitToken"] = 8] = "FitToken";
16
+ StatementType[StatementType["ItToken"] = 9] = "ItToken";
17
+ StatementType[StatementType["TestToken"] = 10] = "TestToken";
18
+ StatementType[StatementType["XdescribeToken"] = 11] = "XdescribeToken";
19
+ StatementType[StatementType["XitToken"] = 12] = "XitToken";
20
+ StatementType[StatementType["XtestToken"] = 13] = "XtestToken";
21
+ })(StatementType = exports.StatementType || (exports.StatementType = {}));
22
+ var PaddingType;
23
+ (function (PaddingType) {
24
+ PaddingType[PaddingType["Any"] = 0] = "Any";
25
+ PaddingType[PaddingType["Always"] = 1] = "Always";
26
+ })(PaddingType = exports.PaddingType || (exports.PaddingType = {}));
27
+ const createTokenTester = (tokenName) => {
28
+ return (node, sourceCode) => {
29
+ const token = sourceCode.getFirstToken(node);
30
+ return (node.type === 'ExpressionStatement' &&
31
+ token.type === 'Identifier' &&
32
+ token.value === tokenName);
20
33
  };
21
- }
22
- function getActualLastToken(sourceCode, node) {
23
- const semiToken = sourceCode.getLastToken(node);
24
- const prevToken = sourceCode.getTokenBefore(semiToken);
25
- const nextToken = sourceCode.getTokenAfter(semiToken);
26
- const isSemicolonLessStyle = Boolean(prevToken &&
27
- nextToken &&
28
- prevToken.range[0] >= node.range[0] &&
29
- isSemicolonToken(semiToken) &&
30
- semiToken.loc.start.line !== prevToken.loc.end.line &&
31
- semiToken.loc.end.line === nextToken.loc.start.line);
32
- return isSemicolonLessStyle ? prevToken : semiToken;
33
- }
34
- function verifyForAlways(context, prevNode, nextNode, paddingLines) {
34
+ };
35
+ const statementTesters = {
36
+ [0]: () => true,
37
+ [1]: createTokenTester('afterAll'),
38
+ [2]: createTokenTester('afterEach'),
39
+ [3]: createTokenTester('beforeAll'),
40
+ [4]: createTokenTester('beforeEach'),
41
+ [5]: createTokenTester('describe'),
42
+ [6]: createTokenTester('expect'),
43
+ [7]: createTokenTester('fdescribe'),
44
+ [8]: createTokenTester('fit'),
45
+ [9]: createTokenTester('it'),
46
+ [10]: createTokenTester('test'),
47
+ [11]: createTokenTester('xdescribe'),
48
+ [12]: createTokenTester('xit'),
49
+ [13]: createTokenTester('xtest'),
50
+ };
51
+ const paddingAlwaysTester = (prevNode, nextNode, paddingContext) => {
52
+ const { sourceCode, ruleContext } = paddingContext;
53
+ const paddingLines = astUtils.getPaddingLineSequences(prevNode, nextNode, sourceCode);
35
54
  if (paddingLines.length > 0) {
36
55
  return;
37
56
  }
38
- context.report({
57
+ ruleContext.report({
39
58
  node: nextNode,
40
59
  message: 'Expected blank line before this statement.',
41
60
  fix(fixer) {
42
- const sourceCode = context.getSourceCode();
43
- let prevToken = getActualLastToken(sourceCode, prevNode);
61
+ let prevToken = astUtils.getActualLastToken(sourceCode, prevNode);
44
62
  const nextToken = sourceCode.getFirstTokenBetween(prevToken, nextNode, {
45
63
  includeComments: true,
46
64
  filter(token) {
47
- if (isTokenOnSameLine(prevToken, token)) {
65
+ if (astUtils.areTokensOnSameLine(prevToken, token)) {
48
66
  prevToken = token;
49
67
  return false;
50
68
  }
51
69
  return true;
52
70
  },
53
71
  }) || nextNode;
54
- const insertText = isTokenOnSameLine(prevToken, nextToken)
72
+ const insertText = astUtils.areTokensOnSameLine(prevToken, nextToken)
55
73
  ? '\n\n'
56
74
  : '\n';
57
75
  return fixer.insertTextAfter(prevToken, insertText);
58
76
  },
59
77
  });
60
- }
61
- const PaddingTypes = {
62
- any: { verify: () => { } },
63
- always: { verify: verifyForAlways },
64
78
  };
65
- const StatementTypes = {
66
- '*': { test: () => true },
67
- afterAll: newJestTokenTester('afterAll'),
68
- afterEach: newJestTokenTester('afterEach'),
69
- beforeAll: newJestTokenTester('beforeAll'),
70
- beforeEach: newJestTokenTester('beforeEach'),
71
- describe: newJestTokenTester('describe'),
72
- expect: newJestTokenTester('expect'),
73
- it: newJestTokenTester('it'),
74
- test: newJestTokenTester('test'),
79
+ const paddingTesters = {
80
+ [0]: () => true,
81
+ [1]: paddingAlwaysTester,
75
82
  };
76
- exports.default = {
77
- meta: {
78
- fixable: 'whitespace',
79
- schema: {
80
- definitions: {
81
- paddingType: {
82
- enum: Object.keys(PaddingTypes),
83
- },
84
- statementType: {
85
- anyOf: [
86
- { enum: Object.keys(StatementTypes) },
87
- {
88
- type: 'array',
89
- items: { enum: Object.keys(StatementTypes) },
90
- minItems: 1,
91
- uniqueItems: true,
92
- additionalItems: false,
93
- },
94
- ],
95
- },
83
+ const createScopeInfo = () => {
84
+ return (() => {
85
+ let scope = null;
86
+ return {
87
+ get prevNode() {
88
+ return scope.prevNode;
96
89
  },
97
- type: 'array',
98
- items: {
99
- type: 'object',
100
- properties: {
101
- blankLine: { $ref: '#/definitions/paddingType' },
102
- prev: { $ref: '#/definitions/statementType' },
103
- next: { $ref: '#/definitions/statementType' },
104
- },
105
- additionalProperties: false,
106
- required: ['blankLine', 'prev', 'next'],
90
+ set prevNode(node) {
91
+ scope.prevNode = node;
107
92
  },
108
- additionalItems: false,
109
- },
93
+ enter() {
94
+ scope = { upper: scope, prevNode: null };
95
+ },
96
+ exit() {
97
+ scope = scope.upper;
98
+ },
99
+ };
100
+ })();
101
+ };
102
+ const nodeMatchesType = (node, statementType, paddingContext) => {
103
+ let innerStatementNode = node;
104
+ const { sourceCode } = paddingContext;
105
+ while (innerStatementNode.type === 'LabeledStatement') {
106
+ innerStatementNode = innerStatementNode.body;
107
+ }
108
+ if (Array.isArray(statementType)) {
109
+ return statementType.some((type) => nodeMatchesType(innerStatementNode, type, paddingContext));
110
+ }
111
+ return statementTesters[statementType](innerStatementNode, sourceCode);
112
+ };
113
+ const testPadding = (prevNode, nextNode, paddingContext) => {
114
+ const { configs } = paddingContext;
115
+ const testType = (type) => paddingTesters[type](prevNode, nextNode, paddingContext);
116
+ for (let i = configs.length - 1; i >= 0; --i) {
117
+ const { prevStatementType: prevType, nextStatementType: nextType, paddingType, } = configs[i];
118
+ if (nodeMatchesType(prevNode, prevType, paddingContext) &&
119
+ nodeMatchesType(nextNode, nextType, paddingContext)) {
120
+ return testType(paddingType);
121
+ }
122
+ }
123
+ return testType(0);
124
+ };
125
+ const verifyNode = (node, paddingContext) => {
126
+ const { scopeInfo } = paddingContext;
127
+ if (!astUtils.isValidParent(node.parent.type)) {
128
+ return;
129
+ }
130
+ if (scopeInfo.prevNode) {
131
+ testPadding(scopeInfo.prevNode, node, paddingContext);
132
+ }
133
+ scopeInfo.prevNode = node;
134
+ };
135
+ exports.createRule = (configs, deprecated = false) => ({
136
+ meta: {
137
+ fixable: 'whitespace',
138
+ deprecated,
110
139
  },
111
140
  create(context) {
112
- const filename = context.getFilename();
113
- if (!isTestFile(filename)) {
114
- return {};
115
- }
116
- const sourceCode = context.getSourceCode();
117
- const configureList = context.options || [];
118
- let scopeInfo = null;
119
- function enterScope() {
120
- scopeInfo = { upper: scopeInfo, prevNode: null };
121
- }
122
- function exitScope() {
123
- scopeInfo = scopeInfo.upper;
124
- }
125
- function match(node, type) {
126
- let innerStatementNode = node;
127
- while (innerStatementNode.type === 'LabeledStatement') {
128
- innerStatementNode = innerStatementNode.body;
129
- }
130
- if (Array.isArray(type)) {
131
- return type.some(match.bind(null, innerStatementNode));
132
- }
133
- return StatementTypes[type].test(innerStatementNode, sourceCode);
134
- }
135
- function getPaddingType(prevNode, nextNode) {
136
- for (let i = configureList.length - 1; i >= 0; --i) {
137
- const { prev, next, blankLine } = configureList[i];
138
- if (match(prevNode, prev) && match(nextNode, next)) {
139
- return PaddingTypes[blankLine];
140
- }
141
- }
142
- return PaddingTypes.any;
143
- }
144
- function getPaddingLineSequences(prevNode, nextNode) {
145
- const pairs = [];
146
- let prevToken = getActualLastToken(sourceCode, prevNode);
147
- if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) {
148
- do {
149
- const token = sourceCode.getTokenAfter(prevToken, {
150
- includeComments: true,
151
- });
152
- if (token.loc.start.line - prevToken.loc.end.line >= 2) {
153
- pairs.push([prevToken, token]);
154
- }
155
- prevToken = token;
156
- } while (prevToken.range[0] < nextNode.range[0]);
157
- }
158
- return pairs;
159
- }
160
- function verify(node) {
161
- const parentType = node.parent.type;
162
- const validParent = STATEMENT_LIST_PARENTS.has(parentType);
163
- if (!validParent) {
164
- return;
165
- }
166
- const { prevNode } = scopeInfo;
167
- if (prevNode) {
168
- const type = getPaddingType(prevNode, node);
169
- const paddingLines = getPaddingLineSequences(prevNode, node);
170
- type.verify(context, prevNode, node, paddingLines);
171
- }
172
- scopeInfo.prevNode = node;
173
- }
174
- function verifyThenEnterScope(node) {
175
- verify(node);
176
- enterScope();
177
- }
141
+ const paddingContext = {
142
+ ruleContext: context,
143
+ sourceCode: context.getSourceCode(),
144
+ scopeInfo: createScopeInfo(),
145
+ configs,
146
+ };
147
+ const { scopeInfo } = paddingContext;
178
148
  return {
179
- Program: enterScope,
180
- BlockStatement: enterScope,
181
- SwitchStatement: enterScope,
182
- 'Program:exit': exitScope,
183
- 'BlockStatement:exit': exitScope,
184
- 'SwitchStatement:exit': exitScope,
185
- ':statement': verify,
186
- SwitchCase: verifyThenEnterScope,
187
- 'SwitchCase:exit': exitScope,
149
+ Program: scopeInfo.enter,
150
+ 'Program:exit': scopeInfo.enter,
151
+ BlockStatement: scopeInfo.enter,
152
+ 'BlockStatement:exit': scopeInfo.exit,
153
+ SwitchStatement: scopeInfo.enter,
154
+ 'SwitchStatement:exit': scopeInfo.exit,
155
+ ':statement': (node) => verifyNode(node, paddingContext),
156
+ SwitchCase: (node) => {
157
+ verifyNode(node, paddingContext);
158
+ scopeInfo.enter();
159
+ },
160
+ 'SwitchCase:exit': scopeInfo.exit,
188
161
  };
189
162
  },
190
- };
163
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-jest-formatting",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Formatting rules for jest tests",
5
5
  "keywords": [
6
6
  "eslint",
@@ -20,8 +20,8 @@
20
20
  "module": "src/index.ts",
21
21
  "scripts": {
22
22
  "pretty": "yarn prettier '**/*.*'",
23
- "lint": "yarn build && yarn link-plugin && yarn pretty --check && yarn eslint .",
24
- "format": "yarn build && yarn link-plugin && yarn pretty --write && yarn eslint --fix .",
23
+ "lint": "yarn build && yarn link-plugin && yarn pretty --check && yarn eslint --ext .ts,.js .",
24
+ "format": "yarn build && yarn link-plugin && yarn pretty --write && yarn eslint --fix --ext .ts,.js .",
25
25
  "build": "tsc",
26
26
  "test": "yarn build && jest",
27
27
  "link-plugin": "yarn link && yarn link eslint-plugin-jest-formatting"
@@ -30,25 +30,26 @@
30
30
  "eslint": ">=0.8.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@types/eslint": "4.16.6",
34
- "@types/estree": "0.0.39",
35
- "@types/jest": "24.0.11",
36
- "@types/node": "8.10.0",
37
- "@typescript-eslint/parser": "1.10.2",
38
- "eslint": "5.16.0",
39
- "eslint-config-airbnb-base": "13.1.0",
40
- "eslint-config-prettier": "5.0.0",
41
- "eslint-plugin-import": "2.14.0",
42
- "eslint-plugin-jest": "22.7.0",
33
+ "@types/eslint": "^7.2.0",
34
+ "@types/jest": "^26.0.0",
35
+ "@types/node": "^14.0.13",
36
+ "@typescript-eslint/eslint-plugin": "^3.4.0",
37
+ "@typescript-eslint/parser": "^3.4.0",
38
+ "eslint": "^7.3.1",
39
+ "eslint-config-airbnb-base": "^14.2.0",
40
+ "eslint-config-prettier": "^6.11.0",
41
+ "eslint-import-resolver-typescript": "^2.0.0",
42
+ "eslint-plugin-import": "^2.21.2",
43
+ "eslint-plugin-jest": "^23.17.1",
43
44
  "eslint-plugin-jest-formatting": "file:.",
44
- "eslint-plugin-prettier": "3.1.0",
45
- "jest": "24.8.0",
46
- "prettier": "1.18.2",
47
- "typescript": "3.4.1",
48
- "ts-jest": "24.0.2"
45
+ "eslint-plugin-prettier": "^3.1.4",
46
+ "jest": "^26.1.0",
47
+ "prettier": "2.0.5",
48
+ "typescript": "^3.9.5",
49
+ "ts-jest": "^26.1.1"
49
50
  },
50
51
  "engines": {
51
- "node": ">=8.10.0"
52
+ "node": "^10.12.0 || >=12.0.0"
52
53
  },
53
54
  "license": "MIT"
54
55
  }
@@ -0,0 +1,80 @@
1
+ import { AST, SourceCode } from 'eslint';
2
+ // This is because we are using @types/estree that are brought in with eslint
3
+ // eslint-disable-next-line import/no-extraneous-dependencies
4
+ import { Node } from 'estree';
5
+
6
+ export const isTokenASemicolon = (token: AST.Token): boolean =>
7
+ token.value === ';' && token.type === 'Punctuator';
8
+
9
+ export const areTokensOnSameLine = (
10
+ left: Node | AST.Token,
11
+ right: Node | AST.Token,
12
+ ): boolean => left.loc.end.line === right.loc.start.line;
13
+
14
+ // We'll only verify nodes with these parent types
15
+ const STATEMENT_LIST_PARENTS = new Set([
16
+ 'Program',
17
+ 'BlockStatement',
18
+ 'SwitchCase',
19
+ 'SwitchStatement',
20
+ ]);
21
+
22
+ export const isValidParent = (parentType: string): boolean => {
23
+ return STATEMENT_LIST_PARENTS.has(parentType);
24
+ };
25
+
26
+ /**
27
+ * Gets the actual last token.
28
+ *
29
+ * If a semicolon is semicolon-less style's semicolon, this ignores it.
30
+ * For example:
31
+ *
32
+ * foo()
33
+ * ;[1, 2, 3].forEach(bar)
34
+ */
35
+ export const getActualLastToken = (
36
+ sourceCode: SourceCode,
37
+ node: Node,
38
+ ): AST.Token => {
39
+ const semiToken = sourceCode.getLastToken(node);
40
+ const prevToken = sourceCode.getTokenBefore(semiToken);
41
+ const nextToken = sourceCode.getTokenAfter(semiToken);
42
+ const isSemicolonLessStyle = Boolean(
43
+ prevToken &&
44
+ nextToken &&
45
+ prevToken.range[0] >= node.range[0] &&
46
+ isTokenASemicolon(semiToken) &&
47
+ semiToken.loc.start.line !== prevToken.loc.end.line &&
48
+ semiToken.loc.end.line === nextToken.loc.start.line,
49
+ );
50
+
51
+ return isSemicolonLessStyle ? prevToken : semiToken;
52
+ };
53
+
54
+ /**
55
+ * Gets padding line sequences between the given 2 statements.
56
+ * Comments are separators of the padding line sequences.
57
+ */
58
+ export const getPaddingLineSequences = (
59
+ prevNode: Node,
60
+ nextNode: Node,
61
+ sourceCode: SourceCode,
62
+ ): AST.Token[][] => {
63
+ const pairs: AST.Token[][] = [];
64
+ const includeComments = true;
65
+ let prevToken = getActualLastToken(sourceCode, prevNode);
66
+
67
+ if (nextNode.loc.start.line - prevToken.loc.end.line >= 2) {
68
+ do {
69
+ const token = sourceCode.getTokenAfter(prevToken, { includeComments });
70
+
71
+ if (token.loc.start.line - prevToken.loc.end.line >= 2) {
72
+ pairs.push([prevToken, token]);
73
+ }
74
+
75
+ prevToken = token;
76
+ } while (prevToken.range[0] < nextNode.range[0]);
77
+ }
78
+
79
+ return pairs;
80
+ };