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 Jan 30, 2024
2 parents 1453eb8 + 541760c commit 3c9c4e5
Show file tree
Hide file tree
Showing 3 changed files with 275 additions and 287 deletions.
22 changes: 11 additions & 11 deletions src/rules/__tests__/prefer-importing-jest-globals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
Expand All @@ -88,7 +88,7 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
Expand All @@ -109,7 +109,7 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
{ endColumn: 9, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
Expand All @@ -129,27 +129,27 @@ 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 +160,15 @@ 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
250 changes: 127 additions & 123 deletions src/rules/prefer-importing-jest-globals.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Literal } from 'estree';
import { createRule, parseJestFnCall } from './utils';
import { type ParsedJestFnCall, createRule, parseJestFnCall } from './utils';

const createFixerImports = (
usesImport: boolean,
Expand Down Expand Up @@ -30,7 +30,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 +44,142 @@ 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;

let firstNodeValue;

if (firstNode.type === 'ExpressionStatement') {
const firstExpression = firstNode.expression as Literal;
const { value } = firstExpression;

firstNodeValue = value;
}

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

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

const importNode = sourceCode.ast.body.find(
node =>
node.type === 'ImportDeclaration' &&
node.source.value === '@jest/globals',
if (!jestFunctionsToReport.length) {
return;
}
const jestFunctionsToImport = jestFunctionsToReport.map(
jestFunction => jestFunction.name,
);
const reportingNode = jestFunctionsToReport[0].head.node;

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

context.report({
node: reportingNode,
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;

let firstNodeValue;

if (firstNode.type === 'ExpressionStatement') {
const firstExpression = firstNode.expression as Literal;
const { value } = firstExpression;

firstNodeValue = value;
}

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

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

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 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(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',
),
);

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(usesImport, allImports)}`,
);
},
});
}
}

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

0 comments on commit 3c9c4e5

Please sign in to comment.