Skip to content

Commit

Permalink
Merge branch 'jest-community:main' into prefer-jest-globals
Browse files Browse the repository at this point in the history
  • Loading branch information
MadeinFrance committed Jan 26, 2024
2 parents 15c1f71 + ec205cd commit c06ca95
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 55 deletions.
119 changes: 115 additions & 4 deletions src/rules/__tests__/prefer-importing-jest-globals.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
valid: [
{
code: dedent`
// with import
import { test, expect } from '@jest/globals';
test('should pass', () => {
expect(true).toBeDefined();
});
`,
parserOptions: { sourceType: 'module' },
},
{
code: dedent`
// with require
const { test, expect } = require('@jest/globals');
test('should pass', () => {
expect(true).toBeDefined();
});
Expand All @@ -26,15 +36,13 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
{
code: dedent`
import { it as itChecks } from '@jest/globals';
itChecks("foo");
`,
parserOptions: { sourceType: 'module' },
},
{
code: dedent`
const { test } = require('@jest/globals');
test("foo");
`,
parserOptions: { sourceType: 'module' },
Expand All @@ -43,13 +51,97 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
invalid: [
{
code: dedent`
#!/usr/bin/env node
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
output: dedent`
#!/usr/bin/env node
const { describe, test, expect } = require('@jest/globals');
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
code: dedent`
// with comment above
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
output: dedent`
// with comment above
const { describe, test, expect } = require('@jest/globals');
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
code: dedent`
'use strict';
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
output: dedent`
'use strict';
const { describe, test, expect } = require('@jest/globals');
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
code: dedent`
import { test } from '@jest/globals';
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
output: dedent`
import { test, describe, expect } from '@jest/globals';
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
code: dedent`
const { test } = require('@jest/globals');
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
})
`,
output: dedent`
import { describe, test, expect } from '@jest/globals';
const { test, describe, expect } = require('@jest/globals');
describe("suite", () => {
test("foo");
expect(true).toBeDefined();
Expand All @@ -60,5 +152,24 @@ ruleTester.run('prefer-importing-jest-globals', rule, {
{ endColumn: 3, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
{
code: dedent`
const { pending } = require('actions');
describe('foo', () => {
test.each(['hello', 'world'])("%s", (a) => {});
});
`,
output: dedent`
const { describe, test } = require('@jest/globals');
const { pending } = require('actions');
describe('foo', () => {
test.each(['hello', 'world'])("%s", (a) => {});
});
`,
parserOptions: { sourceType: 'module' },
errors: [
{ endColumn: 4, column: 1, messageId: 'preferImportingJestGlobal' },
],
},
],
});
122 changes: 121 additions & 1 deletion src/rules/prefer-importing-jest-globals.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
import type { Literal } from 'estree';
import globalsJson from '../globals.json';
import { createRule, parseJestFnCall } from './utils';

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

return usesImport
? `import { ${allImportsFormatted} } from '@jest/globals';`
: `const { ${allImportsFormatted} } = require('@jest/globals');`;
};

export default createRule({
name: __filename,
meta: {
Expand Down Expand Up @@ -53,9 +65,117 @@ export default createRule({
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 (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',
),
);

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.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,
]),
];

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

return fixer.insertTextBefore(
node,
`import { ${jestFunctionsToImportFormatted} } from '@jest/globals';\n`,
`${createFixerImports(usesImport, jestFunctionsToImport)}\n`,
);
},
});
Expand Down
4 changes: 2 additions & 2 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@
"resolveJsonModule": true,
"isolatedModules": true,
"skipLibCheck": false,
"forceConsistentCasingInFileNames": true
"forceConsistentCasingInFileNames": true,
},
"files": ["eslint-remote-tester.config.ts"],
"include": ["src/**/*", "tools/**/*"],
"exclude": ["src/rules/__tests__/fixtures/**/*"]
"exclude": ["src/rules/__tests__/fixtures/**/*"],
}

0 comments on commit c06ca95

Please sign in to comment.