Skip to content

Commit

Permalink
Merge branch 'main' into prefer-jest-globals
Browse files Browse the repository at this point in the history
  • Loading branch information
MadeinFrance committed Feb 2, 2024
2 parents 1453eb8 + 541760c commit 65c50fe
Show file tree
Hide file tree
Showing 3 changed files with 271 additions and 294 deletions.
31 changes: 12 additions & 19 deletions src/rules/__tests__/prefer-importing-jest-globals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const ruleTester = new TSESLint.RuleTester({
parser: espreeParser,
parserOptions: {
ecmaVersion: 2015,
sourceType: 'module',
},
});

Expand All @@ -31,7 +30,6 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
expect(true).toBeDefined();
});
`,
parserOptions: { sourceType: 'module' },
},
{
code: dedent`
Expand All @@ -45,7 +43,6 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
const { test } = require('@jest/globals');
test("foo");
`,
parserOptions: { sourceType: 'module' },
},
],
invalid: [
Expand All @@ -65,9 +62,9 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
expect(true).toBeDefined();
})
`,
parserOptions: { sourceType: 'module' },
parserOptions: { sourceType: 'script' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
Expand All @@ -86,9 +83,8 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
expect(true).toBeDefined();
})
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
Expand All @@ -107,9 +103,8 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
expect(true).toBeDefined();
})
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
Expand All @@ -129,27 +124,26 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
code: dedent`
const { test } = require('@jest/globals');
describe("suite", () => {
test("foo");
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
output: dedent`
const { test, describe, expect } = require('@jest/globals');
describe("suite", () => {
test("foo");
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
Expand All @@ -160,15 +154,14 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
});
`,
output: dedent`
const { describe, test } = require('@jest/globals');
const { pending } = require('actions');
const { describe, test } = require('@jest/globals');
describe('foo', () => {
test.each(['hello', 'world'])("%s", (a) => {});
});
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 4, column: 1, messageId: 'preferImportingJestGlobal' },
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
],
Expand Down
244 changes: 122 additions & 122 deletions src/rules/prefer-importing-jest-globals.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import type { Literal } from 'estree';
import { createRule, parseJestFnCall } from './utils';
import { type ParsedJestFnCall, createRule, parseJestFnCall } from './utils';

const createFixerImports = (
usesImport: boolean,
functionsToImport: string[],
) => {
const createFixerImports = (isModule: boolean, functionsToImport: string[]) => {
const allImportsFormatted = functionsToImport.filter(Boolean).join(', ');

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

return {
CallExpression(node) {
Expand All @@ -44,138 +41,141 @@ export default createRule({
importedJestFunctions.push(jestFnCall.name);
}

usedJestFunctions.add(jestFnCall.name);
usedJestFunctions.push(jestFnCall);
},
'Program:exit'() {
const jestFunctionsToImport = Array.from(usedJestFunctions).filter(
jestFunction => !importedJestFunctions.includes(jestFunction),
const jestFunctionsToReport = usedJestFunctions.filter(
jestFunction => !importedJestFunctions.includes(jestFunction.name),
);

if (jestFunctionsToImport.length > 0) {
const node = context.getSourceCode().ast;
const jestFunctionsToImportFormatted =
jestFunctionsToImport.join(', ');

context.report({
node,
messageId: 'preferImportingJestGlobal',
data: { jestFunctions: jestFunctionsToImportFormatted },
fix(fixer) {
const sourceCode = context.getSourceCode();
const usesImport = sourceCode.ast.body.some(
node => node.type === 'ImportDeclaration',
);
const [firstNode] = sourceCode.ast.body;
if (!jestFunctionsToReport.length) {
return;
}
const jestFunctionsToImport = jestFunctionsToReport.map(
jestFunction => jestFunction.name,
);
const reportingNode = jestFunctionsToReport[0].head.node;

let firstNodeValue;
const jestFunctionsToImportFormatted = jestFunctionsToImport.join(', ');

if (firstNode.type === 'ExpressionStatement') {
const firstExpression = firstNode.expression as Literal;
const { value } = firstExpression;
const isModule = context.parserOptions.sourceType === 'module';

firstNodeValue = value;
}
context.report({
node: reportingNode,
messageId: 'preferImportingJestGlobal',
data: { jestFunctions: jestFunctionsToImportFormatted },
fix(fixer) {
const sourceCode = context.getSourceCode();
const [firstNode] = sourceCode.ast.body;

const useStrictDirectiveExists =
firstNode.type === 'ExpressionStatement' &&
firstNodeValue === 'use strict';
let firstNodeValue;

if (useStrictDirectiveExists) {
return fixer.insertTextAfter(
firstNode,
`\n${createFixerImports(usesImport, jestFunctionsToImport)}`,
);
}
if (firstNode.type === 'ExpressionStatement') {
const firstExpression = firstNode.expression as Literal;
const { value } = firstExpression;

const importNode = sourceCode.ast.body.find(
node =>
node.type === 'ImportDeclaration' &&
node.source.value === '@jest/globals',
);
firstNodeValue = value;
}

if (importNode && importNode.type === 'ImportDeclaration') {
const existingImports = importNode.specifiers.map(specifier => {
/* istanbul ignore else */
if (specifier.type === 'ImportSpecifier') {
return specifier.imported?.name;
}

// istanbul ignore next
return null;
});
const allImports = [
...new Set([
...existingImports.filter(
(imp): imp is string => imp !== null,
),
...jestFunctionsToImport,
]),
];

return fixer.replaceText(
importNode,
createFixerImports(usesImport, allImports),
);
}

const requireNode = sourceCode.ast.body.find(
node =>
node.type === 'VariableDeclaration' &&
node.declarations.some(
declaration =>
declaration.init &&
(declaration.init as any).callee &&
(declaration.init as any).callee.name === 'require' &&
(declaration.init as any).arguments?.[0]?.type ===
'Literal' &&
(declaration.init as any).arguments?.[0]?.value ===
'@jest/globals',
),
const useStrictDirectiveExists =
firstNode.type === 'ExpressionStatement' &&
firstNodeValue === 'use strict';

if (useStrictDirectiveExists) {
return fixer.insertTextAfter(
firstNode,
`\n${createFixerImports(isModule, jestFunctionsToImport)}`,
);
}

const importNode = sourceCode.ast.body.find(
node =>
node.type === 'ImportDeclaration' &&
node.source.value === '@jest/globals',
);

if (importNode && importNode.type === 'ImportDeclaration') {
const existingImports = importNode.specifiers.map(specifier => {
/* istanbul ignore else */
if (specifier.type === 'ImportSpecifier') {
return specifier.imported?.name;
}

// istanbul ignore next
return null;
});
const allImports = [
...new Set([
...existingImports.filter(
(imp): imp is string => imp !== null,
),
...jestFunctionsToImport,
]),
];

if (requireNode && requireNode.type === 'VariableDeclaration') {
const existingImports =
requireNode.declarations[0]?.id.type === 'ObjectPattern'
? requireNode.declarations[0]?.id.properties?.map(
property => {
return fixer.replaceText(
importNode,
createFixerImports(isModule, allImports),
);
}

const requireNode = sourceCode.ast.body.find(
node =>
node.type === 'VariableDeclaration' &&
node.declarations.some(
declaration =>
declaration.init &&
(declaration.init as any).callee &&
(declaration.init as any).callee.name === 'require' &&
(declaration.init as any).arguments?.[0]?.type ===
'Literal' &&
(declaration.init as any).arguments?.[0]?.value ===
'@jest/globals',
),
);

if (requireNode && requireNode.type === 'VariableDeclaration') {
const existingImports =
requireNode.declarations[0]?.id.type === 'ObjectPattern'
? requireNode.declarations[0]?.id.properties?.map(
property => {
/* istanbul ignore else */
if (property.type === 'Property') {
/* istanbul ignore else */
if (property.type === 'Property') {
/* istanbul ignore else */
if (property.key.type === 'Identifier') {
return property.key.name;
}
if (property.key.type === 'Identifier') {
return property.key.name;
}
}

// istanbul ignore next
return null;
},
) ||
// istanbul ignore next
[]
: // istanbul ignore next
[];
const allImports = [
...new Set([
...existingImports.filter(
(imp): imp is string => imp !== null,
),
...jestFunctionsToImport,
]),
];

// istanbul ignore next
return null;
},
) ||
// istanbul ignore next
[]
: // istanbul ignore next
[];
const allImports = [
...new Set([
...existingImports.filter(
(imp): imp is string => imp !== null,
),
...jestFunctionsToImport,
]),
];

return fixer.replaceText(
requireNode,
`${createFixerImports(usesImport, allImports)}`,
);
}

return fixer.insertTextBefore(
node,
`${createFixerImports(usesImport, jestFunctionsToImport)}\n`,
return fixer.replaceText(
requireNode,
`${createFixerImports(isModule, allImports)}`,
);
},
});
}
}

return fixer.insertTextBefore(
reportingNode,
`${createFixerImports(isModule, jestFunctionsToImport)}\n`,
);
},
});
},
};
},
Expand Down

0 comments on commit 65c50fe

Please sign in to comment.