Skip to content

Commit 65c50fe

Browse files
MadeinFranceMaxime Gerbe
authored andcommitted
Merge branch 'main' into prefer-jest-globals
2 parents 1453eb8 + 541760c commit 65c50fe

File tree

3 files changed

+271
-294
lines changed

3 files changed

+271
-294
lines changed

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

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ const ruleTester = new TSESLint.RuleTester({
77
parser: espreeParser,
88
parserOptions: {
99
ecmaVersion: 2015,
10-
sourceType: 'module',
1110
},
1211
});
1312

@@ -31,7 +30,6 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
3130
expect(true).toBeDefined();
3231
});
3332
`,
34-
parserOptions: { sourceType: 'module' },
3533
},
3634
{
3735
code: dedent`
@@ -45,7 +43,6 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
4543
const { test } = require('@jest/globals');
4644
test("foo");
4745
`,
48-
parserOptions: { sourceType: 'module' },
4946
},
5047
],
5148
invalid: [
@@ -65,9 +62,9 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
6562
expect(true).toBeDefined();
6663
})
6764
`,
68-
parserOptions: { sourceType: 'module' },
65+
parserOptions: { sourceType: 'script' },
6966
errors: [
70-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
67+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
7168
],
7269
},
7370
{
@@ -86,9 +83,8 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
8683
expect(true).toBeDefined();
8784
})
8885
`,
89-
parserOptions: { sourceType: 'module' },
9086
errors: [
91-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
87+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
9288
],
9389
},
9490
{
@@ -107,9 +103,8 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
107103
expect(true).toBeDefined();
108104
})
109105
`,
110-
parserOptions: { sourceType: 'module' },
111106
errors: [
112-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
107+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
113108
],
114109
},
115110
{
@@ -129,27 +124,26 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
129124
`,
130125
parserOptions: { sourceType: 'module' },
131126
errors: [
132-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
127+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
133128
],
134129
},
135130
{
136131
code: dedent`
137132
const { test } = require('@jest/globals');
138-
describe("suite", () => {
139-
test("foo");
133+
describe("suite", () => {
134+
test("foo");
140135
expect(true).toBeDefined();
141136
})
142137
`,
143138
output: dedent`
144139
const { test, describe, expect } = require('@jest/globals');
145-
describe("suite", () => {
146-
test("foo");
140+
describe("suite", () => {
141+
test("foo");
147142
expect(true).toBeDefined();
148143
})
149144
`,
150-
parserOptions: { sourceType: 'module' },
151145
errors: [
152-
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
146+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
153147
],
154148
},
155149
{
@@ -160,15 +154,14 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
160154
});
161155
`,
162156
output: dedent`
163-
const { describe, test } = require('@jest/globals');
164157
const { pending } = require('actions');
158+
const { describe, test } = require('@jest/globals');
165159
describe('foo', () => {
166160
test.each(['hello', 'world'])("%s", (a) => {});
167161
});
168162
`,
169-
parserOptions: { sourceType: 'module' },
170163
errors: [
171-
{ endColumn: 4, column: 1, messageId: 'preferImportingJestGlobal' },
164+
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
172165
],
173166
},
174167
],

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

Lines changed: 122 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import type { Literal } from 'estree';
2-
import { createRule, parseJestFnCall } from './utils';
2+
import { type ParsedJestFnCall, createRule, parseJestFnCall } from './utils';
33

4-
const createFixerImports = (
5-
usesImport: boolean,
6-
functionsToImport: string[],
7-
) => {
4+
const createFixerImports = (isModule: boolean, functionsToImport: string[]) => {
85
const allImportsFormatted = functionsToImport.filter(Boolean).join(', ');
96

10-
return usesImport
7+
return isModule
118
? `import { ${allImportsFormatted} } from '@jest/globals';`
129
: `const { ${allImportsFormatted} } = require('@jest/globals');`;
1310
};
@@ -30,7 +27,7 @@ export default createRule({
3027
defaultOptions: [],
3128
create(context) {
3229
const importedJestFunctions: string[] = [];
33-
const usedJestFunctions = new Set<string>();
30+
const usedJestFunctions: ParsedJestFnCall[] = [];
3431

3532
return {
3633
CallExpression(node) {
@@ -44,138 +41,141 @@ export default createRule({
4441
importedJestFunctions.push(jestFnCall.name);
4542
}
4643

47-
usedJestFunctions.add(jestFnCall.name);
44+
usedJestFunctions.push(jestFnCall);
4845
},
4946
'Program:exit'() {
50-
const jestFunctionsToImport = Array.from(usedJestFunctions).filter(
51-
jestFunction => !importedJestFunctions.includes(jestFunction),
47+
const jestFunctionsToReport = usedJestFunctions.filter(
48+
jestFunction => !importedJestFunctions.includes(jestFunction.name),
5249
);
5350

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;
51+
if (!jestFunctionsToReport.length) {
52+
return;
53+
}
54+
const jestFunctionsToImport = jestFunctionsToReport.map(
55+
jestFunction => jestFunction.name,
56+
);
57+
const reportingNode = jestFunctionsToReport[0].head.node;
6958

70-
let firstNodeValue;
59+
const jestFunctionsToImportFormatted = jestFunctionsToImport.join(', ');
7160

72-
if (firstNode.type === 'ExpressionStatement') {
73-
const firstExpression = firstNode.expression as Literal;
74-
const { value } = firstExpression;
61+
const isModule = context.parserOptions.sourceType === 'module';
7562

76-
firstNodeValue = value;
77-
}
63+
context.report({
64+
node: reportingNode,
65+
messageId: 'preferImportingJestGlobal',
66+
data: { jestFunctions: jestFunctionsToImportFormatted },
67+
fix(fixer) {
68+
const sourceCode = context.getSourceCode();
69+
const [firstNode] = sourceCode.ast.body;
7870

79-
const useStrictDirectiveExists =
80-
firstNode.type === 'ExpressionStatement' &&
81-
firstNodeValue === 'use strict';
71+
let firstNodeValue;
8272

83-
if (useStrictDirectiveExists) {
84-
return fixer.insertTextAfter(
85-
firstNode,
86-
`\n${createFixerImports(usesImport, jestFunctionsToImport)}`,
87-
);
88-
}
73+
if (firstNode.type === 'ExpressionStatement') {
74+
const firstExpression = firstNode.expression as Literal;
75+
const { value } = firstExpression;
8976

90-
const importNode = sourceCode.ast.body.find(
91-
node =>
92-
node.type === 'ImportDeclaration' &&
93-
node.source.value === '@jest/globals',
94-
);
77+
firstNodeValue = value;
78+
}
9579

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',
133-
),
80+
const useStrictDirectiveExists =
81+
firstNode.type === 'ExpressionStatement' &&
82+
firstNodeValue === 'use strict';
83+
84+
if (useStrictDirectiveExists) {
85+
return fixer.insertTextAfter(
86+
firstNode,
87+
`\n${createFixerImports(isModule, jestFunctionsToImport)}`,
13488
);
89+
}
90+
91+
const importNode = sourceCode.ast.body.find(
92+
node =>
93+
node.type === 'ImportDeclaration' &&
94+
node.source.value === '@jest/globals',
95+
);
96+
97+
if (importNode && importNode.type === 'ImportDeclaration') {
98+
const existingImports = importNode.specifiers.map(specifier => {
99+
/* istanbul ignore else */
100+
if (specifier.type === 'ImportSpecifier') {
101+
return specifier.imported?.name;
102+
}
103+
104+
// istanbul ignore next
105+
return null;
106+
});
107+
const allImports = [
108+
...new Set([
109+
...existingImports.filter(
110+
(imp): imp is string => imp !== null,
111+
),
112+
...jestFunctionsToImport,
113+
]),
114+
];
135115

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

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`,
167+
return fixer.replaceText(
168+
requireNode,
169+
`${createFixerImports(isModule, allImports)}`,
175170
);
176-
},
177-
});
178-
}
171+
}
172+
173+
return fixer.insertTextBefore(
174+
reportingNode,
175+
`${createFixerImports(isModule, jestFunctionsToImport)}\n`,
176+
);
177+
},
178+
});
179179
},
180180
};
181181
},

0 commit comments

Comments
 (0)