Skip to content

Commit c06ca95

Browse files
committed
Merge branch 'jest-community:main' into prefer-jest-globals
2 parents 15c1f71 + ec205cd commit c06ca95

File tree

4 files changed

+301
-55
lines changed

4 files changed

+301
-55
lines changed

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

+115-4
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,18 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
1515
valid: [
1616
{
1717
code: dedent`
18+
// with import
1819
import { test, expect } from '@jest/globals';
19-
20+
test('should pass', () => {
21+
expect(true).toBeDefined();
22+
});
23+
`,
24+
parserOptions: { sourceType: 'module' },
25+
},
26+
{
27+
code: dedent`
28+
// with require
29+
const { test, expect } = require('@jest/globals');
2030
test('should pass', () => {
2131
expect(true).toBeDefined();
2232
});
@@ -26,15 +36,13 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
2636
{
2737
code: dedent`
2838
import { it as itChecks } from '@jest/globals';
29-
3039
itChecks("foo");
3140
`,
3241
parserOptions: { sourceType: 'module' },
3342
},
3443
{
3544
code: dedent`
3645
const { test } = require('@jest/globals');
37-
3846
test("foo");
3947
`,
4048
parserOptions: { sourceType: 'module' },
@@ -43,13 +51,97 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
4351
invalid: [
4452
{
4553
code: dedent`
54+
#!/usr/bin/env node
55+
describe("suite", () => {
56+
test("foo");
57+
expect(true).toBeDefined();
58+
})
59+
`,
60+
output: dedent`
61+
#!/usr/bin/env node
62+
const { describe, test, expect } = require('@jest/globals');
63+
describe("suite", () => {
64+
test("foo");
65+
expect(true).toBeDefined();
66+
})
67+
`,
68+
parserOptions: { sourceType: 'module' },
69+
errors: [
70+
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
71+
],
72+
},
73+
{
74+
code: dedent`
75+
// with comment above
76+
describe("suite", () => {
77+
test("foo");
78+
expect(true).toBeDefined();
79+
})
80+
`,
81+
output: dedent`
82+
// with comment above
83+
const { describe, test, expect } = require('@jest/globals');
84+
describe("suite", () => {
85+
test("foo");
86+
expect(true).toBeDefined();
87+
})
88+
`,
89+
parserOptions: { sourceType: 'module' },
90+
errors: [
91+
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
92+
],
93+
},
94+
{
95+
code: dedent`
96+
'use strict';
97+
describe("suite", () => {
98+
test("foo");
99+
expect(true).toBeDefined();
100+
})
101+
`,
102+
output: dedent`
103+
'use strict';
104+
const { describe, test, expect } = require('@jest/globals');
105+
describe("suite", () => {
106+
test("foo");
107+
expect(true).toBeDefined();
108+
})
109+
`,
110+
parserOptions: { sourceType: 'module' },
111+
errors: [
112+
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
113+
],
114+
},
115+
{
116+
code: dedent`
117+
import { test } from '@jest/globals';
118+
describe("suite", () => {
119+
test("foo");
120+
expect(true).toBeDefined();
121+
})
122+
`,
123+
output: dedent`
124+
import { test, describe, expect } from '@jest/globals';
125+
describe("suite", () => {
126+
test("foo");
127+
expect(true).toBeDefined();
128+
})
129+
`,
130+
parserOptions: { sourceType: 'module' },
131+
errors: [
132+
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
133+
],
134+
},
135+
{
136+
code: dedent`
137+
const { test } = require('@jest/globals');
46138
describe("suite", () => {
47139
test("foo");
48140
expect(true).toBeDefined();
49141
})
50142
`,
51143
output: dedent`
52-
import { describe, test, expect } from '@jest/globals';
144+
const { test, describe, expect } = require('@jest/globals');
53145
describe("suite", () => {
54146
test("foo");
55147
expect(true).toBeDefined();
@@ -60,5 +152,24 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
60152
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
61153
],
62154
},
155+
{
156+
code: dedent`
157+
const { pending } = require('actions');
158+
describe('foo', () => {
159+
test.each(['hello', 'world'])("%s", (a) => {});
160+
});
161+
`,
162+
output: dedent`
163+
const { describe, test } = require('@jest/globals');
164+
const { pending } = require('actions');
165+
describe('foo', () => {
166+
test.each(['hello', 'world'])("%s", (a) => {});
167+
});
168+
`,
169+
parserOptions: { sourceType: 'module' },
170+
errors: [
171+
{ endColumn: 4, column: 1, messageId: 'preferImportingJestGlobal' },
172+
],
173+
},
63174
],
64175
});

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

+121-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
1+
import type { Literal } from 'estree';
12
import globalsJson from '../globals.json';
23
import { createRule, parseJestFnCall } from './utils';
34

5+
const createFixerImports = (
6+
usesImport: boolean,
7+
functionsToImport: string[],
8+
) => {
9+
const allImportsFormatted = functionsToImport.filter(Boolean).join(', ');
10+
11+
return usesImport
12+
? `import { ${allImportsFormatted} } from '@jest/globals';`
13+
: `const { ${allImportsFormatted} } = require('@jest/globals');`;
14+
};
15+
416
export default createRule({
517
name: __filename,
618
meta: {
@@ -53,9 +65,117 @@ export default createRule({
5365
messageId: 'preferImportingJestGlobal',
5466
data: { jestFunctions: jestFunctionsToImportFormatted },
5567
fix(fixer) {
68+
const sourceCode = context.getSourceCode();
69+
const usesImport = sourceCode.ast.body.some(
70+
node => node.type === 'ImportDeclaration',
71+
);
72+
const [firstNode] = sourceCode.ast.body;
73+
74+
let firstNodeValue;
75+
76+
if (firstNode.type === 'ExpressionStatement') {
77+
const firstExpression = firstNode.expression as Literal;
78+
const { value } = firstExpression;
79+
80+
firstNodeValue = value;
81+
}
82+
83+
const useStrictDirectiveExists =
84+
firstNode.type === 'ExpressionStatement' &&
85+
firstNodeValue === 'use strict';
86+
87+
if (useStrictDirectiveExists) {
88+
return fixer.insertTextAfter(
89+
firstNode,
90+
`\n${createFixerImports(usesImport, jestFunctionsToImport)}`,
91+
);
92+
}
93+
94+
const importNode = sourceCode.ast.body.find(
95+
node =>
96+
node.type === 'ImportDeclaration' &&
97+
node.source.value === '@jest/globals',
98+
);
99+
100+
if (importNode && importNode.type === 'ImportDeclaration') {
101+
const existingImports = importNode.specifiers.map(specifier => {
102+
/* istanbul ignore else */
103+
if (specifier.type === 'ImportSpecifier') {
104+
return specifier.imported?.name;
105+
}
106+
107+
// istanbul ignore next
108+
return null;
109+
});
110+
const allImports = [
111+
...new Set([
112+
...existingImports.filter(
113+
(imp): imp is string => imp !== null,
114+
),
115+
...jestFunctionsToImport,
116+
]),
117+
];
118+
119+
return fixer.replaceText(
120+
importNode,
121+
createFixerImports(usesImport, allImports),
122+
);
123+
}
124+
125+
const requireNode = sourceCode.ast.body.find(
126+
node =>
127+
node.type === 'VariableDeclaration' &&
128+
node.declarations.some(
129+
declaration =>
130+
declaration.init &&
131+
(declaration.init as any).callee &&
132+
(declaration.init as any).callee.name === 'require' &&
133+
(declaration.init as any).arguments?.[0]?.type ===
134+
'Literal' &&
135+
(declaration.init as any).arguments?.[0]?.value ===
136+
'@jest/globals',
137+
),
138+
);
139+
140+
if (requireNode && requireNode.type === 'VariableDeclaration') {
141+
const existingImports =
142+
requireNode.declarations[0]?.id.type === 'ObjectPattern'
143+
? requireNode.declarations[0]?.id.properties?.map(
144+
property => {
145+
/* istanbul ignore else */
146+
if (property.type === 'Property') {
147+
/* istanbul ignore else */
148+
if (property.key.type === 'Identifier') {
149+
return property.key.name;
150+
}
151+
}
152+
153+
// istanbul ignore next
154+
return null;
155+
},
156+
) ||
157+
// istanbul ignore next
158+
[]
159+
: // istanbul ignore next
160+
[];
161+
const allImports = [
162+
...new Set([
163+
...existingImports.filter(
164+
(imp): imp is string => imp !== null,
165+
),
166+
...jestFunctionsToImport,
167+
]),
168+
];
169+
170+
return fixer.replaceText(
171+
requireNode,
172+
`${createFixerImports(usesImport, allImports)}`,
173+
);
174+
}
175+
56176
return fixer.insertTextBefore(
57177
node,
58-
`import { ${jestFunctionsToImportFormatted} } from '@jest/globals';\n`,
178+
`${createFixerImports(usesImport, jestFunctionsToImport)}\n`,
59179
);
60180
},
61181
});

tsconfig.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@
1616
"resolveJsonModule": true,
1717
"isolatedModules": true,
1818
"skipLibCheck": false,
19-
"forceConsistentCasingInFileNames": true
19+
"forceConsistentCasingInFileNames": true,
2020
},
2121
"files": ["eslint-remote-tester.config.ts"],
2222
"include": ["src/**/*", "tools/**/*"],
23-
"exclude": ["src/rules/__tests__/fixtures/**/*"]
23+
"exclude": ["src/rules/__tests__/fixtures/**/*"],
2424
}

0 commit comments

Comments
 (0)