Skip to content

Commit 3c9c4e5

Browse files
committed
Merge branch 'main' into prefer-jest-globals
2 parents 1453eb8 + 541760c commit 3c9c4e5

File tree

3 files changed

+275
-287
lines changed

3 files changed

+275
-287
lines changed

src/rules/__tests__/prefer-importing-jest-globals.test.ts

+11-11
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
6767
`,
6868
parserOptions: { sourceType: 'module' },
6969
errors: [
70-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
70+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
7171
],
7272
},
7373
{
@@ -88,7 +88,7 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
8888
`,
8989
parserOptions: { sourceType: 'module' },
9090
errors: [
91-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
91+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
9292
],
9393
},
9494
{
@@ -109,7 +109,7 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
109109
`,
110110
parserOptions: { sourceType: 'module' },
111111
errors: [
112-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
112+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
113113
],
114114
},
115115
{
@@ -129,27 +129,27 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
129129
`,
130130
parserOptions: { sourceType: 'module' },
131131
errors: [
132-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
132+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
133133
],
134134
},
135135
{
136136
code: dedent`
137137
const { test } = require('@jest/globals');
138-
describe("suite", () => {
139-
test("foo");
138+
describe("suite", () => {
139+
test("foo");
140140
expect(true).toBeDefined();
141141
})
142142
`,
143143
output: dedent`
144144
const { test, describe, expect } = require('@jest/globals');
145-
describe("suite", () => {
146-
test("foo");
145+
describe("suite", () => {
146+
test("foo");
147147
expect(true).toBeDefined();
148148
})
149149
`,
150150
parserOptions: { sourceType: 'module' },
151151
errors: [
152-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
152+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
153153
],
154154
},
155155
{
@@ -160,15 +160,15 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
160160
});
161161
`,
162162
output: dedent`
163-
const { describe, test } = require('@jest/globals');
164163
const { pending } = require('actions');
164+
const { describe, test } = require('@jest/globals');
165165
describe('foo', () => {
166166
test.each(['hello', 'world'])("%s", (a) => {});
167167
});
168168
`,
169169
parserOptions: { sourceType: 'module' },
170170
errors: [
171-
{ endColumn: 4, column: 1, messageId: 'preferImportingJestGlobal' },
171+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
172172
],
173173
},
174174
],

src/rules/prefer-importing-jest-globals.ts

+127-123
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Literal } from 'estree';
2-
import { createRule, parseJestFnCall } from './utils';
2+
import { type ParsedJestFnCall, createRule, parseJestFnCall } from './utils';
33

44
const createFixerImports = (
55
usesImport: boolean,
@@ -30,7 +30,7 @@ export default createRule({
3030
defaultOptions: [],
3131
create(context) {
3232
const importedJestFunctions: string[] = [];
33-
const usedJestFunctions = new Set<string>();
33+
const usedJestFunctions: ParsedJestFnCall[] = [];
3434

3535
return {
3636
CallExpression(node) {
@@ -44,138 +44,142 @@ export default createRule({
4444
importedJestFunctions.push(jestFnCall.name);
4545
}
4646

47-
usedJestFunctions.add(jestFnCall.name);
47+
usedJestFunctions.push(jestFnCall);
4848
},
4949
'Program:exit'() {
50-
const jestFunctionsToImport = Array.from(usedJestFunctions).filter(
51-
jestFunction => !importedJestFunctions.includes(jestFunction),
50+
const jestFunctionsToReport = usedJestFunctions.filter(
51+
jestFunction => !importedJestFunctions.includes(jestFunction.name),
5252
);
5353

54-
if (jestFunctionsToImport.length > 0) {
55-
const node = context.getSourceCode().ast;
56-
const jestFunctionsToImportFormatted =
57-
jestFunctionsToImport.join(', ');
58-
59-
context.report({
60-
node,
61-
messageId: 'preferImportingJestGlobal',
62-
data: { jestFunctions: jestFunctionsToImportFormatted },
63-
fix(fixer) {
64-
const sourceCode = context.getSourceCode();
65-
const usesImport = sourceCode.ast.body.some(
66-
node => node.type === 'ImportDeclaration',
67-
);
68-
const [firstNode] = sourceCode.ast.body;
69-
70-
let firstNodeValue;
71-
72-
if (firstNode.type === 'ExpressionStatement') {
73-
const firstExpression = firstNode.expression as Literal;
74-
const { value } = firstExpression;
75-
76-
firstNodeValue = value;
77-
}
78-
79-
const useStrictDirectiveExists =
80-
firstNode.type === 'ExpressionStatement' &&
81-
firstNodeValue === 'use strict';
82-
83-
if (useStrictDirectiveExists) {
84-
return fixer.insertTextAfter(
85-
firstNode,
86-
`\n${createFixerImports(usesImport, jestFunctionsToImport)}`,
87-
);
88-
}
89-
90-
const importNode = sourceCode.ast.body.find(
91-
node =>
92-
node.type === 'ImportDeclaration' &&
93-
node.source.value === '@jest/globals',
54+
if (!jestFunctionsToReport.length) {
55+
return;
56+
}
57+
const jestFunctionsToImport = jestFunctionsToReport.map(
58+
jestFunction => jestFunction.name,
59+
);
60+
const reportingNode = jestFunctionsToReport[0].head.node;
61+
62+
const jestFunctionsToImportFormatted = jestFunctionsToImport.join(', ');
63+
64+
context.report({
65+
node: reportingNode,
66+
messageId: 'preferImportingJestGlobal',
67+
data: { jestFunctions: jestFunctionsToImportFormatted },
68+
fix(fixer) {
69+
const sourceCode = context.getSourceCode();
70+
const usesImport = sourceCode.ast.body.some(
71+
node => node.type === 'ImportDeclaration',
72+
);
73+
const [firstNode] = sourceCode.ast.body;
74+
75+
let firstNodeValue;
76+
77+
if (firstNode.type === 'ExpressionStatement') {
78+
const firstExpression = firstNode.expression as Literal;
79+
const { value } = firstExpression;
80+
81+
firstNodeValue = value;
82+
}
83+
84+
const useStrictDirectiveExists =
85+
firstNode.type === 'ExpressionStatement' &&
86+
firstNodeValue === 'use strict';
87+
88+
if (useStrictDirectiveExists) {
89+
return fixer.insertTextAfter(
90+
firstNode,
91+
`\n${createFixerImports(usesImport, jestFunctionsToImport)}`,
9492
);
95-
96-
if (importNode && importNode.type === 'ImportDeclaration') {
97-
const existingImports = importNode.specifiers.map(specifier => {
98-
/* istanbul ignore else */
99-
if (specifier.type === 'ImportSpecifier') {
100-
return specifier.imported?.name;
101-
}
102-
103-
// istanbul ignore next
104-
return null;
105-
});
106-
const allImports = [
107-
...new Set([
108-
...existingImports.filter(
109-
(imp): imp is string => imp !== null,
110-
),
111-
...jestFunctionsToImport,
112-
]),
113-
];
114-
115-
return fixer.replaceText(
116-
importNode,
117-
createFixerImports(usesImport, allImports),
118-
);
119-
}
120-
121-
const requireNode = sourceCode.ast.body.find(
122-
node =>
123-
node.type === 'VariableDeclaration' &&
124-
node.declarations.some(
125-
declaration =>
126-
declaration.init &&
127-
(declaration.init as any).callee &&
128-
(declaration.init as any).callee.name === 'require' &&
129-
(declaration.init as any).arguments?.[0]?.type ===
130-
'Literal' &&
131-
(declaration.init as any).arguments?.[0]?.value ===
132-
'@jest/globals',
93+
}
94+
95+
const importNode = sourceCode.ast.body.find(
96+
node =>
97+
node.type === 'ImportDeclaration' &&
98+
node.source.value === '@jest/globals',
99+
);
100+
101+
if (importNode && importNode.type === 'ImportDeclaration') {
102+
const existingImports = importNode.specifiers.map(specifier => {
103+
/* istanbul ignore else */
104+
if (specifier.type === 'ImportSpecifier') {
105+
return specifier.imported?.name;
106+
}
107+
108+
// istanbul ignore next
109+
return null;
110+
});
111+
const allImports = [
112+
...new Set([
113+
...existingImports.filter(
114+
(imp): imp is string => imp !== null,
133115
),
134-
);
116+
...jestFunctionsToImport,
117+
]),
118+
];
135119

136-
if (requireNode && requireNode.type === 'VariableDeclaration') {
137-
const existingImports =
138-
requireNode.declarations[0]?.id.type === 'ObjectPattern'
139-
? requireNode.declarations[0]?.id.properties?.map(
140-
property => {
120+
return fixer.replaceText(
121+
importNode,
122+
createFixerImports(usesImport, allImports),
123+
);
124+
}
125+
126+
const requireNode = sourceCode.ast.body.find(
127+
node =>
128+
node.type === 'VariableDeclaration' &&
129+
node.declarations.some(
130+
declaration =>
131+
declaration.init &&
132+
(declaration.init as any).callee &&
133+
(declaration.init as any).callee.name === 'require' &&
134+
(declaration.init as any).arguments?.[0]?.type ===
135+
'Literal' &&
136+
(declaration.init as any).arguments?.[0]?.value ===
137+
'@jest/globals',
138+
),
139+
);
140+
141+
if (requireNode && requireNode.type === 'VariableDeclaration') {
142+
const existingImports =
143+
requireNode.declarations[0]?.id.type === 'ObjectPattern'
144+
? requireNode.declarations[0]?.id.properties?.map(
145+
property => {
146+
/* istanbul ignore else */
147+
if (property.type === 'Property') {
141148
/* istanbul ignore else */
142-
if (property.type === 'Property') {
143-
/* istanbul ignore else */
144-
if (property.key.type === 'Identifier') {
145-
return property.key.name;
146-
}
149+
if (property.key.type === 'Identifier') {
150+
return property.key.name;
147151
}
152+
}
153+
154+
// istanbul ignore next
155+
return null;
156+
},
157+
) ||
158+
// istanbul ignore next
159+
[]
160+
: // istanbul ignore next
161+
[];
162+
const allImports = [
163+
...new Set([
164+
...existingImports.filter(
165+
(imp): imp is string => imp !== null,
166+
),
167+
...jestFunctionsToImport,
168+
]),
169+
];
148170

149-
// istanbul ignore next
150-
return null;
151-
},
152-
) ||
153-
// istanbul ignore next
154-
[]
155-
: // istanbul ignore next
156-
[];
157-
const allImports = [
158-
...new Set([
159-
...existingImports.filter(
160-
(imp): imp is string => imp !== null,
161-
),
162-
...jestFunctionsToImport,
163-
]),
164-
];
165-
166-
return fixer.replaceText(
167-
requireNode,
168-
`${createFixerImports(usesImport, allImports)}`,
169-
);
170-
}
171-
172-
return fixer.insertTextBefore(
173-
node,
174-
`${createFixerImports(usesImport, jestFunctionsToImport)}\n`,
171+
return fixer.replaceText(
172+
requireNode,
173+
`${createFixerImports(usesImport, allImports)}`,
175174
);
176-
},
177-
});
178-
}
175+
}
176+
177+
return fixer.insertTextBefore(
178+
reportingNode,
179+
`${createFixerImports(usesImport, jestFunctionsToImport)}\n`,
180+
);
181+
},
182+
});
179183
},
180184
};
181185
},

0 commit comments

Comments
 (0)