1 | 'use strict';
|
2 |
|
3 | const path = require('path');
|
4 | const { version } = require('../package.json');
|
5 |
|
6 | const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest';
|
7 |
|
8 | const expectCase = node =>
|
9 | node.callee.name === 'expect' &&
|
10 | node.arguments.length === 1 &&
|
11 | node.parent &&
|
12 | node.parent.type === 'MemberExpression' &&
|
13 | node.parent.parent;
|
14 |
|
15 | const expectNotCase = node =>
|
16 | expectCase(node) &&
|
17 | node.parent.parent.type === 'MemberExpression' &&
|
18 | methodName(node) === 'not';
|
19 |
|
20 | const expectResolveCase = node =>
|
21 | expectCase(node) &&
|
22 | node.parent.parent.type === 'MemberExpression' &&
|
23 | methodName(node) === 'resolve';
|
24 |
|
25 | const expectRejectCase = node =>
|
26 | expectCase(node) &&
|
27 | node.parent.parent.type === 'MemberExpression' &&
|
28 | methodName(node) === 'reject';
|
29 |
|
30 | const expectToBeCase = (node, arg) =>
|
31 | !(expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) &&
|
32 | expectCase(node) &&
|
33 | methodName(node) === 'toBe' &&
|
34 | argument(node) &&
|
35 | ((argument(node).type === 'Literal' &&
|
36 | argument(node).value === null &&
|
37 | arg === null) ||
|
38 | (argument(node).name === 'undefined' && arg === undefined));
|
39 |
|
40 | const expectNotToBeCase = (node, arg) =>
|
41 | expectNotCase(node) &&
|
42 | methodName2(node) === 'toBe' &&
|
43 | argument2(node) &&
|
44 | ((argument2(node).type === 'Literal' &&
|
45 | argument2(node).value === null &&
|
46 | arg === null) ||
|
47 | (argument2(node).name === 'undefined' && arg === undefined));
|
48 |
|
49 | const expectToEqualCase = (node, arg) =>
|
50 | !(expectNotCase(node) || expectResolveCase(node) || expectRejectCase(node)) &&
|
51 | expectCase(node) &&
|
52 | methodName(node) === 'toEqual' &&
|
53 | argument(node) &&
|
54 | ((argument(node).type === 'Literal' &&
|
55 | argument(node).value === null &&
|
56 | arg === null) ||
|
57 | (argument(node).name === 'undefined' && arg === undefined));
|
58 |
|
59 | const expectNotToEqualCase = (node, arg) =>
|
60 | expectNotCase(node) &&
|
61 | methodName2(node) === 'toEqual' &&
|
62 | argument2(node) &&
|
63 | ((argument2(node).type === 'Literal' &&
|
64 | argument2(node).value === null &&
|
65 | arg === null) ||
|
66 | (argument2(node).name === 'undefined' && arg === undefined));
|
67 |
|
68 | const method = node => node.parent.property;
|
69 |
|
70 | const method2 = node => node.parent.parent.property;
|
71 |
|
72 | const methodName = node => method(node).name;
|
73 |
|
74 | const methodName2 = node => method2(node).name;
|
75 |
|
76 | const argument = node =>
|
77 | node.parent.parent.arguments && node.parent.parent.arguments[0];
|
78 |
|
79 | const argument2 = node =>
|
80 | node.parent.parent.parent.arguments && node.parent.parent.parent.arguments[0];
|
81 |
|
82 | const describeAliases = Object.assign(Object.create(null), {
|
83 | describe: true,
|
84 | 'describe.only': true,
|
85 | 'describe.skip': true,
|
86 | fdescribe: true,
|
87 | xdescribe: true,
|
88 | });
|
89 |
|
90 | const testCaseNames = Object.assign(Object.create(null), {
|
91 | fit: true,
|
92 | it: true,
|
93 | 'it.only': true,
|
94 | 'it.skip': true,
|
95 | test: true,
|
96 | 'test.only': true,
|
97 | 'test.skip': true,
|
98 | xit: true,
|
99 | xtest: true,
|
100 | });
|
101 |
|
102 | const getNodeName = node => {
|
103 | function joinNames(a, b) {
|
104 | return a && b ? `${a}.${b}` : null;
|
105 | }
|
106 |
|
107 | switch (node && node.type) {
|
108 | case 'Identifier':
|
109 | return node.name;
|
110 | case 'Literal':
|
111 | return node.value;
|
112 | case 'TemplateLiteral':
|
113 | if (node.expressions.length === 0) return node.quasis[0].value.cooked;
|
114 | break;
|
115 | case 'MemberExpression':
|
116 | return joinNames(getNodeName(node.object), getNodeName(node.property));
|
117 | }
|
118 |
|
119 | return null;
|
120 | };
|
121 |
|
122 | const isTestCase = node =>
|
123 | node &&
|
124 | node.type === 'CallExpression' &&
|
125 | testCaseNames[getNodeName(node.callee)];
|
126 |
|
127 | const isDescribe = node =>
|
128 | node.type === 'CallExpression' && describeAliases[getNodeName(node.callee)];
|
129 |
|
130 | const isFunction = node =>
|
131 | node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression';
|
132 |
|
133 | const isString = node =>
|
134 | (node.type === 'Literal' && typeof node.value === 'string') ||
|
135 | isTemplateLiteral(node);
|
136 |
|
137 | const isTemplateLiteral = node => node.type === 'TemplateLiteral';
|
138 |
|
139 | const hasExpressions = node => node.expressions && node.expressions.length > 0;
|
140 |
|
141 | const getStringValue = arg =>
|
142 | isTemplateLiteral(arg) ? arg.quasis[0].value.raw : arg.value;
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 |
|
151 |
|
152 | const getDocsUrl = filename => {
|
153 | const ruleName = path.basename(filename, '.js');
|
154 |
|
155 | return `${REPO_URL}/blob/v${version}/docs/rules/${ruleName}.md`;
|
156 | };
|
157 |
|
158 | const collectReferences = scope => {
|
159 | const locals = new Set();
|
160 | const unresolved = new Set();
|
161 |
|
162 | let currentScope = scope;
|
163 |
|
164 | while (currentScope !== null) {
|
165 | for (const ref of currentScope.variables) {
|
166 | const isReferenceDefined = ref.defs.some(def => {
|
167 | return def.type !== 'ImplicitGlobalVariable';
|
168 | });
|
169 |
|
170 | if (isReferenceDefined) {
|
171 | locals.add(ref.name);
|
172 | }
|
173 | }
|
174 |
|
175 | for (const ref of currentScope.through) {
|
176 | unresolved.add(ref.identifier.name);
|
177 | }
|
178 |
|
179 | currentScope = currentScope.upper;
|
180 | }
|
181 |
|
182 | return { locals, unresolved };
|
183 | };
|
184 |
|
185 | const scopeHasLocalReference = (scope, referenceName) => {
|
186 | const references = collectReferences(scope);
|
187 | return (
|
188 |
|
189 | references.locals.has(referenceName) ||
|
190 |
|
191 |
|
192 | !references.unresolved.has(referenceName)
|
193 | );
|
194 | };
|
195 |
|
196 | function composeFixers(node) {
|
197 | return (...fixers) => {
|
198 | return fixerApi => {
|
199 | return fixers.reduce((all, fixer) => [...all, fixer(node, fixerApi)], []);
|
200 | };
|
201 | };
|
202 | }
|
203 |
|
204 | module.exports = {
|
205 | method,
|
206 | method2,
|
207 | argument,
|
208 | argument2,
|
209 | expectCase,
|
210 | expectNotCase,
|
211 | expectResolveCase,
|
212 | expectRejectCase,
|
213 | expectToBeCase,
|
214 | expectNotToBeCase,
|
215 | expectToEqualCase,
|
216 | expectNotToEqualCase,
|
217 | getNodeName,
|
218 | getStringValue,
|
219 | isDescribe,
|
220 | isFunction,
|
221 | isTemplateLiteral,
|
222 | isTestCase,
|
223 | isString,
|
224 | hasExpressions,
|
225 | getDocsUrl,
|
226 | scopeHasLocalReference,
|
227 | composeFixers,
|
228 | };
|