From 8e1319bbe8f9e7a5f42a1a46e83e8aee3934dd88 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 13 Apr 2020 13:38:56 +0200 Subject: [PATCH 01/20] fix: hoist imports of `@jest/globals` correctly --- e2e/__tests__/babelPluginJestHoist.test.ts | 2 +- .../__tests__/importJest.test.js | 18 ++++++++++++++++++ packages/babel-plugin-jest-hoist/src/index.ts | 6 ++++++ packages/jest-runtime/src/index.ts | 17 +++++++++-------- 4 files changed, 34 insertions(+), 9 deletions(-) create mode 100644 e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js diff --git a/e2e/__tests__/babelPluginJestHoist.test.ts b/e2e/__tests__/babelPluginJestHoist.test.ts index 2f7cbd6aafdc..5cb0ecf291af 100644 --- a/e2e/__tests__/babelPluginJestHoist.test.ts +++ b/e2e/__tests__/babelPluginJestHoist.test.ts @@ -15,7 +15,7 @@ beforeEach(() => { run('yarn', DIR); }); -it('sucessfully runs the tests inside `babel-plugin-jest-hoist/`', () => { +it('successfully runs the tests inside `babel-plugin-jest-hoist/`', () => { const {json} = runWithJson(DIR, ['--no-cache', '--coverage']); expect(json.success).toBe(true); expect(json.numTotalTestSuites).toBe(3); diff --git a/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js new file mode 100644 index 000000000000..1a6da15da042 --- /dev/null +++ b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js @@ -0,0 +1,18 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + */ + +import {jest} from '@jest/globals'; + +// The virtual mock call below will be hoisted above this `require` call. +const virtualModule = require('virtual-module'); + +jest.mock('virtual-module', () => 'kiwi', {virtual: true}); + +test('works with virtual modules', () => { + expect(virtualModule).toBe('kiwi'); +}); diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 43f19e39c52f..42b134eac38a 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -183,6 +183,12 @@ export default (): {visitor: Visitor} => { path.node._blockHoist = Infinity; } }, + ImportDeclaration(path) { + if (path.node.source.value === '@jest/globals') { + // @ts-ignore: private, magical property + path.node._blockHoist = Infinity; + } + }, }; return {visitor}; diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index d80cfd780b8e..16ca19aa6636 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -331,11 +331,6 @@ class Runtime { modulePath = manualMock; } - if (moduleName === '@jest/globals') { - // @ts-ignore: we don't care that it's not assignable to T - return this.getGlobalsForFile(from); - } - if (moduleName && this._resolver.isCoreModule(moduleName)) { return this._requireCoreModule(moduleName); } @@ -526,12 +521,18 @@ class Runtime { }; } - requireModuleOrMock(from: Config.Path, moduleName: string): unknown { + requireModuleOrMock(from: Config.Path, moduleName: string): T { + // this module is unmockable + if (moduleName === '@jest/globals') { + // @ts-ignore: we don't care that it's not assignable to T + return this.getGlobalsForFile(from); + } + try { if (this._shouldMock(from, moduleName)) { - return this.requireMock(from, moduleName); + return this.requireMock(from, moduleName); } else { - return this.requireModule(from, moduleName); + return this.requireModule(from, moduleName); } } catch (e) { const moduleNotFound = Resolver.tryCastModuleNotFoundError(e); From 9b91b6227537884678e9dabb1b5c54b7679a44cc Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 13 Apr 2020 14:17:05 +0200 Subject: [PATCH 02/20] add VariableDeclaration visitor --- packages/babel-plugin-jest-hoist/src/index.ts | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 42b134eac38a..d06dea95d8e0 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -189,6 +189,36 @@ export default (): {visitor: Visitor} => { path.node._blockHoist = Infinity; } }, + VariableDeclaration(path) { + const declarations = path.get('declarations'); + + if (declarations.length === 1) { + const declarationInit = declarations[0].get('init'); + + if (declarationInit.isCallExpression()) { + const callee = declarationInit.get('callee') as NodePath; + const callArguments = declarationInit.get('arguments') as Array< + NodePath + >; + + if ( + callee.isIdentifier() && + callee.node.name === 'require' && + callArguments.length === 1 + ) { + const [argument] = callArguments; + + if ( + argument.isStringLiteral() && + argument.node.value === '@jest/globals' + ) { + // @ts-ignore: private, magical property + path.node._blockHoist = Infinity; + } + } + } + } + }, }; return {visitor}; From 9ebd34fd92971e84d8239ebe327a31d33f04f766 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Tue, 14 Apr 2020 01:10:24 +0200 Subject: [PATCH 03/20] rewrite babel-plugin-jest-hoist --- .../__tests__/importJest.test.js | 21 +- .../__tests__/integration.test.js | 19 +- packages/babel-plugin-jest-hoist/package.json | 7 +- packages/babel-plugin-jest-hoist/src/index.ts | 203 +++++++++++------- yarn.lock | 14 +- 5 files changed, 180 insertions(+), 84 deletions(-) diff --git a/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js index 1a6da15da042..4bd842dca995 100644 --- a/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js +++ b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js @@ -7,12 +7,31 @@ */ import {jest} from '@jest/globals'; +import * as JestGlobals from '@jest/globals'; // The virtual mock call below will be hoisted above this `require` call. const virtualModule = require('virtual-module'); +const virtualModule2 = require('virtual-module-2'); jest.mock('virtual-module', () => 'kiwi', {virtual: true}); +JestGlobals.jest.mock('virtual-module-2', () => 'banana', {virtual: true}); -test('works with virtual modules', () => { +// The mock call below will not be hoisted +const a = require('../__test_modules__/a'); + +{ + const jest = {mock: () => {}}; + jest.mock('../__test_modules__/a', () => 'too late'); +} + +test('named import', () => { expect(virtualModule).toBe('kiwi'); }); + +test('namespace import', () => { + expect(virtualModule2).toBe('banana'); +}); + +test('fake jest, shadowed import', () => { + expect(a()).toBe('unmocked'); +}); diff --git a/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js b/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js index 615f0e31b4e9..498fd79a4d34 100644 --- a/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js +++ b/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js @@ -16,6 +16,7 @@ import b from '../__test_modules__/b'; import c from '../__test_modules__/c'; import d from '../__test_modules__/d'; import e from '../__test_modules__/e'; +import f from '../__test_modules__/f'; import jestBackticks from '../__test_modules__/jestBackticks'; // The virtual mock call below will be hoisted above this `require` call. @@ -25,7 +26,10 @@ const virtualModule = require('virtual-module'); jest.unmock('react'); jest.deepUnmock('../__test_modules__/Unmocked'); jest.unmock('../__test_modules__/c').unmock('../__test_modules__/d'); -jest.mock('../__test_modules__/e', () => { +(function () { + jest.mock('../__test_modules__/e'); +}); +jest.mock('../__test_modules__/f', () => { if (!global.CALLS) { global.CALLS = 0; } @@ -52,8 +56,12 @@ jest.mock('has-flow-types', () => (props: {children: mixed}) => 3, { // These will not be hoisted jest.unmock('../__test_modules__/a').dontMock('../__test_modules__/b'); // eslint-disable-next-line no-useless-concat -jest.unmock('../__test_modules__/' + 'c'); +jest.unmock('../__test_modules__/' + 'a'); jest.dontMock('../__test_modules__/Mocked'); +{ + const jest = {unmock: () => {}}; + jest.unmock('../__test_modules__/a'); +} // This must not throw an error const myObject = {mock: () => {}}; @@ -84,14 +92,17 @@ describe('babel-plugin-jest-hoist', () => { expect(d._isMockFunction).toBe(undefined); expect(d()).toEqual('unmocked'); + + expect(e._isMockFunction).toBe(undefined); + expect(e()).toEqual('unmocked'); }); it('hoists mock call with 2 arguments', () => { const path = require('path'); - expect(e._isMock).toBe(true); + expect(f._isMock).toBe(true); - const mockFn = e.fn(); + const mockFn = f.fn(); expect(mockFn()).toEqual([path.sep, undefined, undefined]); }); diff --git a/packages/babel-plugin-jest-hoist/package.json b/packages/babel-plugin-jest-hoist/package.json index e46bfbd5f680..fd30c2ad723b 100644 --- a/packages/babel-plugin-jest-hoist/package.json +++ b/packages/babel-plugin-jest-hoist/package.json @@ -20,10 +20,13 @@ } }, "dependencies": { - "@types/babel__traverse": "^7.0.6" + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__traverse": "^7.0.6", + "lodash.sortby": "^4.7.0" }, "devDependencies": { - "@babel/types": "^7.3.3", + "@types/lodash.sortby": "^4.7.6", "@types/node": "*" }, "publishConfig": { diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index d06dea95d8e0..95c3eed91c3c 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -7,7 +7,19 @@ */ import type {NodePath, Visitor} from '@babel/traverse'; -import type {Identifier} from '@babel/types'; +import { + Expression, + Identifier, + Node, + Program, + callExpression, + identifier, +} from '@babel/types'; +import {statement} from '@babel/template'; + +const JEST_GLOBAL_NAME = 'jest'; +const JEST_GLOBALS_MODULE_NAME = '@jest/globals'; +const JEST_GLOBALS_MODULE_JEST_EXPORT_NAME = 'jest'; // We allow `jest`, `expect`, `require`, all default Node.js globals and all // ES2015 built-ins to be used inside of a `jest.mock` factory. @@ -70,19 +82,19 @@ const WHITELISTED_IDENTIFIERS = new Set( ].sort(), ); -const JEST_GLOBAL = {name: 'jest'}; -// TODO: Should be Visitor<{ids: Set>}>, but `ReferencedIdentifier` doesn't exist const IDVisitor = { - ReferencedIdentifier(path: NodePath) { - // @ts-ignore: passed as Visitor State - this.ids.add(path); + ReferencedIdentifier( + path: NodePath, + {ids}: {ids: Set>}, + ) { + ids.add(path); }, blacklist: ['TypeAnnotation', 'TSTypeAnnotation', 'TSTypeReference'], }; const FUNCTIONS: Record< string, - (args: Array) => boolean + (args: Array>) => boolean > = Object.create(null); FUNCTIONS.mock = args => { @@ -100,7 +112,7 @@ FUNCTIONS.mock = args => { const ids: Set> = new Set(); const parentScope = moduleFactory.parentPath.scope; - // @ts-ignore: Same as above: ReferencedIdentifier doesn't exist + // @ts-ignore: ReferencedIdentifier is not known on visitors moduleFactory.traverse(IDVisitor, {ids}); for (const id of ids) { const {name} = id.node; @@ -152,74 +164,113 @@ FUNCTIONS.deepUnmock = args => args.length === 1 && args[0].isStringLiteral(); FUNCTIONS.disableAutomock = FUNCTIONS.enableAutomock = args => args.length === 0; -export default (): {visitor: Visitor} => { - const shouldHoistExpression = (expr: NodePath): boolean => { - if (!expr.isCallExpression()) { - return false; - } +const createJestObjectGetter = statement` +function GETTER_NAME() { + const { JEST_GLOBALS_MODULE_JEST_EXPORT_NAME } = require("JEST_GLOBALS_MODULE_NAME"); + GETTER_NAME = () => JEST_GLOBALS_MODULE_JEST_EXPORT_NAME; + return JEST_GLOBALS_MODULE_JEST_EXPORT_NAME; +} +`; - // TODO: avoid type casts - the types can be arrays (is it possible to ignore that without casting?) - const callee = expr.get('callee') as NodePath; - const expressionArguments = expr.get('arguments'); - const object = callee.get('object') as NodePath; - const property = callee.get('property') as NodePath; - return ( - property.isIdentifier() && - FUNCTIONS[property.node.name] && - (object.isIdentifier(JEST_GLOBAL) || - (callee.isMemberExpression() && shouldHoistExpression(object))) && - FUNCTIONS[property.node.name]( - Array.isArray(expressionArguments) - ? expressionArguments - : [expressionArguments], - ) - ); - }; - - const visitor: Visitor = { - ExpressionStatement(path) { - if (shouldHoistExpression(path.get('expression') as NodePath)) { - // @ts-ignore: private, magical property - path.node._blockHoist = Infinity; - } - }, - ImportDeclaration(path) { - if (path.node.source.value === '@jest/globals') { - // @ts-ignore: private, magical property - path.node._blockHoist = Infinity; - } - }, - VariableDeclaration(path) { - const declarations = path.get('declarations'); - - if (declarations.length === 1) { - const declarationInit = declarations[0].get('init'); - - if (declarationInit.isCallExpression()) { - const callee = declarationInit.get('callee') as NodePath; - const callArguments = declarationInit.get('arguments') as Array< - NodePath - >; - - if ( - callee.isIdentifier() && - callee.node.name === 'require' && - callArguments.length === 1 - ) { - const [argument] = callArguments; - - if ( - argument.isStringLiteral() && - argument.node.value === '@jest/globals' - ) { - // @ts-ignore: private, magical property - path.node._blockHoist = Infinity; - } - } - } - } - }, - }; +const isIdentifierJestObject = (identifier: NodePath): boolean => { + // global + if ( + identifier.node.name === JEST_GLOBAL_NAME && + !identifier.scope.hasBinding(JEST_GLOBAL_NAME) + ) { + return true; + } + // import from '@jest/globals' + if ( + identifier.referencesImport( + JEST_GLOBALS_MODULE_NAME, + JEST_GLOBALS_MODULE_JEST_EXPORT_NAME, + ) + ) { + return true; + } + // TODO require('@jest/globals') - return {visitor}; + return false; }; + +const getJestIdentifierIfHoistable = ( + expr: NodePath, +): NodePath | null => { + if (!expr.isCallExpression()) { + return null; + } + + const callee = expr.get<'callee'>('callee'); + const args = expr.get<'arguments'>('arguments'); + + if (!callee.isMemberExpression()) { + return null; + } + + const object = callee.get<'object'>('object'); + const property = callee.get<'property'>('property') as + | NodePath + | NodePath; + + if (!property.isIdentifier()) { + return null; + } + const propertyName = property.node.name; + + const jestObject = + object.isIdentifier() && isIdentifierJestObject(object) + ? object + : // The Jest object could be returned from another call since the functions are all chainable. + getJestIdentifierIfHoistable(object); + if (!jestObject) { + return null; + } + + // Important: Call the function check last + // It might throw an error to display to the user, + // which should only happen if we're already sure it's a call on the Jest object. + const functionLooksHoistable = + FUNCTIONS[propertyName] && FUNCTIONS[propertyName](args); + + return functionLooksHoistable ? jestObject : null; +}; + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +export default (): { + visitor: Visitor; +} => ({ + visitor: { + Program(path) { + const jestObjGetterName = path.scope.generateUid('getJestObj'); + path.unshiftContainer('body', [ + createJestObjectGetter({ + GETTER_NAME: jestObjGetterName, + JEST_GLOBALS_MODULE_JEST_EXPORT_NAME, + JEST_GLOBALS_MODULE_NAME, + }), + ]); + + path.traverse({ + ExpressionStatement(path) { + const jestIdentifier = getJestIdentifierIfHoistable( + path.get<'expression'>('expression'), + ); + if (jestIdentifier) { + jestIdentifier.replaceWith( + callExpression(identifier(jestObjGetterName), []), + ); + const {node: mockStmt} = path; + + path.remove(); + const program = path.scope.getProgramParent().path as NodePath< + Program + >; + program.unshiftContainer('body', [mockStmt]); + } + }, + }); + }, + }, +}); +/* eslint-enable @typescript-eslint/explicit-module-boundary-types */ diff --git a/yarn.lock b/yarn.lock index 7a6349aa8a03..a1455c34ae08 100644 --- a/yarn.lock +++ b/yarn.lock @@ -994,7 +994,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/template@^7.0.0", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": +"@babel/template@^7.0.0", "@babel/template@^7.3.3", "@babel/template@^7.7.4", "@babel/template@^7.8.3", "@babel/template@^7.8.6": version "7.8.6" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b" integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg== @@ -2382,6 +2382,18 @@ resolved "https://registry.yarnpkg.com/@types/lockfile/-/lockfile-1.0.1.tgz#434a3455e89843312f01976e010c60f1bcbd56f7" integrity sha512-65WZedEm4AnOsBDdsapJJG42MhROu3n4aSSiu87JXF/pSdlubxZxp3S1yz3kTfkJ2KBPud4CpjoHVAptOm9Zmw== +"@types/lodash.sortby@^4.7.6": + version "4.7.6" + resolved "https://registry.yarnpkg.com/@types/lodash.sortby/-/lodash.sortby-4.7.6.tgz#eed689835f274b553db4ae16a4a23f58b79618a1" + integrity sha512-EnvAOmKvEg7gdYpYrS6+fVFPw5dL9rBnJi3vcKI7wqWQcLJVF/KRXK9dH29HjGNVvFUj0s9prRP3J8jEGnGKDw== + dependencies: + "@types/lodash" "*" + +"@types/lodash@*": + version "4.14.149" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" + integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== + "@types/lolex@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@types/lolex/-/lolex-5.1.0.tgz#11b4c4756c007306d0feeaf2f08f88350c635d2b" From c7a16bfc2a76f05e68f5e427da7667fa36f03275 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Mon, 20 Apr 2020 22:47:48 +0200 Subject: [PATCH 04/20] remove unused import --- packages/babel-plugin-jest-hoist/package.json | 4 +--- yarn.lock | 12 ------------ 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/babel-plugin-jest-hoist/package.json b/packages/babel-plugin-jest-hoist/package.json index fd30c2ad723b..7dab0c395e85 100644 --- a/packages/babel-plugin-jest-hoist/package.json +++ b/packages/babel-plugin-jest-hoist/package.json @@ -22,11 +22,9 @@ "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", - "@types/babel__traverse": "^7.0.6", - "lodash.sortby": "^4.7.0" + "@types/babel__traverse": "^7.0.6" }, "devDependencies": { - "@types/lodash.sortby": "^4.7.6", "@types/node": "*" }, "publishConfig": { diff --git a/yarn.lock b/yarn.lock index a1455c34ae08..1402b349d37d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2382,18 +2382,6 @@ resolved "https://registry.yarnpkg.com/@types/lockfile/-/lockfile-1.0.1.tgz#434a3455e89843312f01976e010c60f1bcbd56f7" integrity sha512-65WZedEm4AnOsBDdsapJJG42MhROu3n4aSSiu87JXF/pSdlubxZxp3S1yz3kTfkJ2KBPud4CpjoHVAptOm9Zmw== -"@types/lodash.sortby@^4.7.6": - version "4.7.6" - resolved "https://registry.yarnpkg.com/@types/lodash.sortby/-/lodash.sortby-4.7.6.tgz#eed689835f274b553db4ae16a4a23f58b79618a1" - integrity sha512-EnvAOmKvEg7gdYpYrS6+fVFPw5dL9rBnJi3vcKI7wqWQcLJVF/KRXK9dH29HjGNVvFUj0s9prRP3J8jEGnGKDw== - dependencies: - "@types/lodash" "*" - -"@types/lodash@*": - version "4.14.149" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440" - integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ== - "@types/lolex@^5.1.0": version "5.1.0" resolved "https://registry.yarnpkg.com/@types/lolex/-/lolex-5.1.0.tgz#11b4c4756c007306d0feeaf2f08f88350c635d2b" From fcca78b8ebd6e662667b2db8fa23d4851c4a0d5b Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Mon, 20 Apr 2020 22:58:04 +0200 Subject: [PATCH 05/20] cleanup --- packages/babel-plugin-jest-hoist/src/index.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 95c3eed91c3c..58fcd311018a 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -241,9 +241,9 @@ export default (): { visitor: Visitor; } => ({ visitor: { - Program(path) { - const jestObjGetterName = path.scope.generateUid('getJestObj'); - path.unshiftContainer('body', [ + Program(program) { + const jestObjGetterName = program.scope.generateUid('getJestObj'); + program.unshiftContainer('body', [ createJestObjectGetter({ GETTER_NAME: jestObjGetterName, JEST_GLOBALS_MODULE_JEST_EXPORT_NAME, @@ -251,21 +251,18 @@ export default (): { }), ]); - path.traverse({ - ExpressionStatement(path) { + program.traverse({ + ExpressionStatement(stmt) { const jestIdentifier = getJestIdentifierIfHoistable( - path.get<'expression'>('expression'), + stmt.get<'expression'>('expression'), ); if (jestIdentifier) { jestIdentifier.replaceWith( callExpression(identifier(jestObjGetterName), []), ); - const {node: mockStmt} = path; + const {node: mockStmt} = stmt; - path.remove(); - const program = path.scope.getProgramParent().path as NodePath< - Program - >; + stmt.remove(); program.unshiftContainer('body', [mockStmt]); } }, From b4dc59020321d9fe8e06b239ecb6c4ba808f302d Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Mon, 20 Apr 2020 23:05:25 +0200 Subject: [PATCH 06/20] lint cleanup --- packages/babel-plugin-jest-hoist/src/index.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 58fcd311018a..78ee47c9a4ed 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -11,7 +11,6 @@ import { Expression, Identifier, Node, - Program, callExpression, identifier, } from '@babel/types'; @@ -236,11 +235,11 @@ const getJestIdentifierIfHoistable = ( return functionLooksHoistable ? jestObject : null; }; -/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ export default (): { visitor: Visitor; } => ({ visitor: { + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types Program(program) { const jestObjGetterName = program.scope.generateUid('getJestObj'); program.unshiftContainer('body', [ @@ -270,4 +269,3 @@ export default (): { }, }, }); -/* eslint-enable @typescript-eslint/explicit-module-boundary-types */ From 78a44b6ee514bd2daddc63589b8d8011bf45e7e2 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Mon, 20 Apr 2020 23:32:35 +0200 Subject: [PATCH 07/20] correct test --- e2e/babel-plugin-jest-hoist/__tests__/integration.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js b/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js index 498fd79a4d34..89663520d417 100644 --- a/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js +++ b/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js @@ -27,7 +27,7 @@ jest.unmock('react'); jest.deepUnmock('../__test_modules__/Unmocked'); jest.unmock('../__test_modules__/c').unmock('../__test_modules__/d'); (function () { - jest.mock('../__test_modules__/e'); + jest.unmock('../__test_modules__/e'); }); jest.mock('../__test_modules__/f', () => { if (!global.CALLS) { @@ -93,7 +93,7 @@ describe('babel-plugin-jest-hoist', () => { expect(d._isMockFunction).toBe(undefined); expect(d()).toEqual('unmocked'); - expect(e._isMockFunction).toBe(undefined); + expect(e._isMock).toBe(undefined); expect(e()).toEqual('unmocked'); }); @@ -111,10 +111,10 @@ describe('babel-plugin-jest-hoist', () => { global.CALLS = 0; - require('../__test_modules__/e'); + require('../__test_modules__/f'); expect(global.CALLS).toEqual(1); - require('../__test_modules__/e'); + require('../__test_modules__/f'); expect(global.CALLS).toEqual(1); delete global.CALLS; From b18ac407360ffc0ac1147d41ac0e8b2dc6ebc3f0 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Mon, 20 Apr 2020 23:33:03 +0200 Subject: [PATCH 08/20] fix by using post hook --- packages/babel-plugin-jest-hoist/src/index.ts | 60 +++++++++---------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 78ee47c9a4ed..d60ed1a1204e 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -6,15 +6,17 @@ * */ -import type {NodePath, Visitor} from '@babel/traverse'; +import type {NodePath} from '@babel/traverse'; import { Expression, Identifier, Node, + Program, callExpression, identifier, } from '@babel/types'; import {statement} from '@babel/template'; +import type {PluginObj} from '@babel/core'; const JEST_GLOBAL_NAME = 'jest'; const JEST_GLOBALS_MODULE_NAME = '@jest/globals'; @@ -235,37 +237,33 @@ const getJestIdentifierIfHoistable = ( return functionLooksHoistable ? jestObject : null; }; -export default (): { - visitor: Visitor; -} => ({ - visitor: { - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - Program(program) { - const jestObjGetterName = program.scope.generateUid('getJestObj'); - program.unshiftContainer('body', [ - createJestObjectGetter({ - GETTER_NAME: jestObjGetterName, - JEST_GLOBALS_MODULE_JEST_EXPORT_NAME, - JEST_GLOBALS_MODULE_NAME, - }), - ]); - - program.traverse({ - ExpressionStatement(stmt) { - const jestIdentifier = getJestIdentifierIfHoistable( - stmt.get<'expression'>('expression'), +export default (): PluginObj => ({ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + post({path: program}: {path: NodePath}) { + const jestObjGetterName = program.scope.generateUid('getJestObj'); + program.unshiftContainer('body', [ + createJestObjectGetter({ + GETTER_NAME: jestObjGetterName, + JEST_GLOBALS_MODULE_JEST_EXPORT_NAME, + JEST_GLOBALS_MODULE_NAME, + }), + ]); + program.traverse({ + ExpressionStatement(stmt) { + const jestIdentifier = getJestIdentifierIfHoistable( + stmt.get<'expression'>('expression'), + ); + if (jestIdentifier) { + jestIdentifier.replaceWith( + callExpression(identifier(jestObjGetterName), []), ); - if (jestIdentifier) { - jestIdentifier.replaceWith( - callExpression(identifier(jestObjGetterName), []), - ); - const {node: mockStmt} = stmt; + const {node: mockStmt} = stmt; - stmt.remove(); - program.unshiftContainer('body', [mockStmt]); - } - }, - }); - }, + stmt.remove(); + program.unshiftContainer('body', [mockStmt]); + } + }, + }); }, + visitor: {}, }); From 066deab330d226b04d553dd1e8bbeb07899ffb72 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Tue, 21 Apr 2020 23:26:37 +0200 Subject: [PATCH 09/20] almost fix namespace import --- packages/babel-plugin-jest-hoist/src/index.ts | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index d60ed1a1204e..b519c7bae9ed 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -173,24 +173,35 @@ function GETTER_NAME() { } `; -const isIdentifierJestObject = (identifier: NodePath): boolean => { +const isJestObject = (expression: NodePath): boolean => { // global if ( - identifier.node.name === JEST_GLOBAL_NAME && - !identifier.scope.hasBinding(JEST_GLOBAL_NAME) + expression.isIdentifier() && + expression.node.name === JEST_GLOBAL_NAME && + !expression.scope.hasBinding(JEST_GLOBAL_NAME) ) { return true; } - // import from '@jest/globals' + // import { jest } from '@jest/globals' if ( - identifier.referencesImport( + expression.referencesImport( JEST_GLOBALS_MODULE_NAME, JEST_GLOBALS_MODULE_JEST_EXPORT_NAME, ) ) { return true; } - // TODO require('@jest/globals') + // import * as JestGlobals from '@jest/globals' + if ( + expression.isMemberExpression() && + expression.node.computed && + expression + .get<'object'>('object') + .referencesImport(JEST_GLOBALS_MODULE_NAME, '*') && + expression.node.property === JEST_GLOBALS_MODULE_JEST_EXPORT_NAME + ) { + return true; + } return false; }; @@ -219,11 +230,10 @@ const getJestIdentifierIfHoistable = ( } const propertyName = property.node.name; - const jestObject = - object.isIdentifier() && isIdentifierJestObject(object) - ? object - : // The Jest object could be returned from another call since the functions are all chainable. - getJestIdentifierIfHoistable(object); + const jestObject = isJestObject(object) + ? object + : // The Jest object could be returned from another call since the functions are all chainable. + getJestIdentifierIfHoistable(object); if (!jestObject) { return null; } From 20c8f5818613b400b94384e14b81d8685df34b90 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Wed, 22 Apr 2020 15:50:06 +0200 Subject: [PATCH 10/20] =?UTF-8?q?finally=20fixed=20I=20think=20?= =?UTF-8?q?=F0=9F=92=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../__tests__/importJest.test.js | 30 +++++---- packages/babel-plugin-jest-hoist/src/index.ts | 67 ++++++++++++------- 2 files changed, 60 insertions(+), 37 deletions(-) diff --git a/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js index 4bd842dca995..b279184a013a 100644 --- a/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js +++ b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js @@ -9,29 +9,35 @@ import {jest} from '@jest/globals'; import * as JestGlobals from '@jest/globals'; -// The virtual mock call below will be hoisted above this `require` call. -const virtualModule = require('virtual-module'); -const virtualModule2 = require('virtual-module-2'); +import a from '../__test_modules__/a'; +import b from '../__test_modules__/b'; +import c from '../__test_modules__/c'; -jest.mock('virtual-module', () => 'kiwi', {virtual: true}); -JestGlobals.jest.mock('virtual-module-2', () => 'banana', {virtual: true}); +// These will be hoisted above imports -// The mock call below will not be hoisted -const a = require('../__test_modules__/a'); +jest.unmock('../__test_modules__/a'); +JestGlobals.jest.unmock('../__test_modules__/b'); + +// These will not be hoisted above imports { - const jest = {mock: () => {}}; - jest.mock('../__test_modules__/a', () => 'too late'); + const jest = {unmock: () => {}}; + jest.unmock('../__test_modules__/c'); } +// tests + test('named import', () => { - expect(virtualModule).toBe('kiwi'); + expect(a._isMockFunction).toBe(undefined); + expect(a()).toBe('unmocked'); }); test('namespace import', () => { - expect(virtualModule2).toBe('banana'); + expect(b._isMockFunction).toBe(undefined); + expect(b()).toBe('unmocked'); }); test('fake jest, shadowed import', () => { - expect(a()).toBe('unmocked'); + expect(c._isMockFunction).toBe(true); + expect(c()).toBe(undefined); }); diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index b519c7bae9ed..d3b72cb8ef13 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -14,6 +14,7 @@ import { Program, callExpression, identifier, + isIdentifier, } from '@babel/types'; import {statement} from '@babel/template'; import type {PluginObj} from '@babel/core'; @@ -194,11 +195,11 @@ const isJestObject = (expression: NodePath): boolean => { // import * as JestGlobals from '@jest/globals' if ( expression.isMemberExpression() && - expression.node.computed && + !expression.node.computed && expression .get<'object'>('object') .referencesImport(JEST_GLOBALS_MODULE_NAME, '*') && - expression.node.property === JEST_GLOBALS_MODULE_JEST_EXPORT_NAME + expression.node.property.name === JEST_GLOBALS_MODULE_JEST_EXPORT_NAME ) { return true; } @@ -206,7 +207,7 @@ const isJestObject = (expression: NodePath): boolean => { return false; }; -const getJestIdentifierIfHoistable = ( +const extractJestObjExprIfHoistable = ( expr: NodePath, ): NodePath | null => { if (!expr.isCallExpression()) { @@ -230,11 +231,11 @@ const getJestIdentifierIfHoistable = ( } const propertyName = property.node.name; - const jestObject = isJestObject(object) + const jestObjExpr = isJestObject(object) ? object : // The Jest object could be returned from another call since the functions are all chainable. - getJestIdentifierIfHoistable(object); - if (!jestObject) { + extractJestObjExprIfHoistable(object); + if (!jestObjExpr) { return null; } @@ -244,36 +245,52 @@ const getJestIdentifierIfHoistable = ( const functionLooksHoistable = FUNCTIONS[propertyName] && FUNCTIONS[propertyName](args); - return functionLooksHoistable ? jestObject : null; + return functionLooksHoistable ? jestObjExpr : null; }; -export default (): PluginObj => ({ - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types - post({path: program}: {path: NodePath}) { - const jestObjGetterName = program.scope.generateUid('getJestObj'); +/* eslint-disable sort-keys,@typescript-eslint/explicit-module-boundary-types */ +export default (): PluginObj<{jestObjGetterIdentifier: Identifier}> => ({ + pre({path: program}: {path: NodePath}) { + this.jestObjGetterIdentifier = program.scope.generateUidIdentifier( + 'getJestObj', + ); program.unshiftContainer('body', [ createJestObjectGetter({ - GETTER_NAME: jestObjGetterName, + GETTER_NAME: this.jestObjGetterIdentifier.name, JEST_GLOBALS_MODULE_JEST_EXPORT_NAME, JEST_GLOBALS_MODULE_NAME, }), ]); - program.traverse({ - ExpressionStatement(stmt) { - const jestIdentifier = getJestIdentifierIfHoistable( - stmt.get<'expression'>('expression'), + }, + visitor: { + ExpressionStatement(exprStmt) { + const jestObjExpr = extractJestObjExprIfHoistable( + exprStmt.get<'expression'>('expression'), + ); + if (jestObjExpr) { + jestObjExpr.replaceWith( + callExpression(this.jestObjGetterIdentifier, []), ); - if (jestIdentifier) { - jestIdentifier.replaceWith( - callExpression(identifier(jestObjGetterName), []), - ); - const {node: mockStmt} = stmt; - - stmt.remove(); - program.unshiftContainer('body', [mockStmt]); + } + }, + }, + post({path: program}: {path: NodePath}) { + program.traverse({ + CallExpression: callExpr => { + const { + node: {callee}, + } = callExpr; + if ( + isIdentifier(callee) && + callee.name === this.jestObjGetterIdentifier.name + ) { + const mockStmt = callExpr.getStatementParent(); + const mockStmtNode = mockStmt.node; + mockStmt.remove(); + program.unshiftContainer('body', [mockStmtNode]); } }, }); }, - visitor: {}, }); +/* eslint-enable sort-keys,@typescript-eslint/explicit-module-boundary-types */ From db4785a0c0fd043999c2e3c4516177f54f0bb824 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Thu, 23 Apr 2020 23:09:44 +0200 Subject: [PATCH 11/20] remove unused import --- packages/babel-plugin-jest-hoist/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index d3b72cb8ef13..8be5f1258c4f 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -13,7 +13,6 @@ import { Node, Program, callExpression, - identifier, isIdentifier, } from '@babel/types'; import {statement} from '@babel/template'; From cd059ecf7b77b582ba05890bdc025621e09aaec0 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Fri, 24 Apr 2020 00:32:05 +0200 Subject: [PATCH 12/20] test aliased import --- .../__tests__/importJest.test.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js index b279184a013a..21b5c50fcc82 100644 --- a/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js +++ b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js @@ -7,22 +7,25 @@ */ import {jest} from '@jest/globals'; +import {jest as aliasedJest} from '@jest/globals'; import * as JestGlobals from '@jest/globals'; import a from '../__test_modules__/a'; import b from '../__test_modules__/b'; import c from '../__test_modules__/c'; +import d from '../__test_modules__/d'; // These will be hoisted above imports jest.unmock('../__test_modules__/a'); -JestGlobals.jest.unmock('../__test_modules__/b'); +aliasedJest.unmock('../__test_modules__/b'); +JestGlobals.jest.unmock('../__test_modules__/c'); // These will not be hoisted above imports { const jest = {unmock: () => {}}; - jest.unmock('../__test_modules__/c'); + jest.unmock('../__test_modules__/d'); } // tests @@ -37,7 +40,12 @@ test('namespace import', () => { expect(b()).toBe('unmocked'); }); +test('namespace import', () => { + expect(c._isMockFunction).toBe(undefined); + expect(c()).toBe('unmocked'); +}); + test('fake jest, shadowed import', () => { - expect(c._isMockFunction).toBe(true); - expect(c()).toBe(undefined); + expect(d._isMockFunction).toBe(true); + expect(d()).toBe(undefined); }); From b29d9728b3f7be6697658d481b4e24c54d71a291 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Fri, 24 Apr 2020 00:37:46 +0200 Subject: [PATCH 13/20] computed check, thanks nicolo --- packages/babel-plugin-jest-hoist/src/index.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 8be5f1258c4f..ff5add917266 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -216,18 +216,12 @@ const extractJestObjExprIfHoistable = ( const callee = expr.get<'callee'>('callee'); const args = expr.get<'arguments'>('arguments'); - if (!callee.isMemberExpression()) { + if (!callee.isMemberExpression() || callee.node.computed) { return null; } const object = callee.get<'object'>('object'); - const property = callee.get<'property'>('property') as - | NodePath - | NodePath; - - if (!property.isIdentifier()) { - return null; - } + const property = callee.get<'property'>('property') as NodePath; const propertyName = property.node.name; const jestObjExpr = isJestObject(object) From 36ad2f4def9153030c800abec9dd3e70963908a4 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Fri, 24 Apr 2020 00:39:16 +0200 Subject: [PATCH 14/20] optional chaining HYPE --- packages/babel-plugin-jest-hoist/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index ff5add917266..282c37259d67 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -235,8 +235,7 @@ const extractJestObjExprIfHoistable = ( // Important: Call the function check last // It might throw an error to display to the user, // which should only happen if we're already sure it's a call on the Jest object. - const functionLooksHoistable = - FUNCTIONS[propertyName] && FUNCTIONS[propertyName](args); + const functionLooksHoistable = FUNCTIONS[propertyName]?.(args); return functionLooksHoistable ? jestObjExpr : null; }; From f547d251091c2cf00db0b10f49fcf577afbe80ba Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Fri, 24 Apr 2020 00:40:42 +0200 Subject: [PATCH 15/20] test file fixes --- e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js index 21b5c50fcc82..2933ad991e6e 100644 --- a/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js +++ b/e2e/babel-plugin-jest-hoist/__tests__/importJest.test.js @@ -6,9 +6,11 @@ * */ +/* eslint-disable import/no-duplicates */ import {jest} from '@jest/globals'; import {jest as aliasedJest} from '@jest/globals'; import * as JestGlobals from '@jest/globals'; +/* eslint-enable import/no-duplicates */ import a from '../__test_modules__/a'; import b from '../__test_modules__/b'; @@ -35,7 +37,7 @@ test('named import', () => { expect(a()).toBe('unmocked'); }); -test('namespace import', () => { +test('aliased named import', () => { expect(b._isMockFunction).toBe(undefined); expect(b()).toBe('unmocked'); }); From 80dabd03b1bd668ab7259d57f635966b6ac7cf87 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 26 Apr 2020 20:33:12 +0200 Subject: [PATCH 16/20] hoist within scope --- .../__tests__/integration.test.js | 10 ++++++++-- packages/babel-plugin-jest-hoist/src/index.ts | 8 ++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js b/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js index 89663520d417..3e3dd10de3e2 100644 --- a/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js +++ b/e2e/babel-plugin-jest-hoist/__tests__/integration.test.js @@ -15,7 +15,6 @@ import a from '../__test_modules__/a'; import b from '../__test_modules__/b'; import c from '../__test_modules__/c'; import d from '../__test_modules__/d'; -import e from '../__test_modules__/e'; import f from '../__test_modules__/f'; import jestBackticks from '../__test_modules__/jestBackticks'; @@ -26,9 +25,15 @@ const virtualModule = require('virtual-module'); jest.unmock('react'); jest.deepUnmock('../__test_modules__/Unmocked'); jest.unmock('../__test_modules__/c').unmock('../__test_modules__/d'); + +let e; (function () { + const _getJestObj = 42; + e = require('../__test_modules__/e').default; + // hoisted to the top of the function scope jest.unmock('../__test_modules__/e'); -}); +})(); + jest.mock('../__test_modules__/f', () => { if (!global.CALLS) { global.CALLS = 0; @@ -60,6 +65,7 @@ jest.unmock('../__test_modules__/' + 'a'); jest.dontMock('../__test_modules__/Mocked'); { const jest = {unmock: () => {}}; + // Would error (used before initialization) if hoisted to the top of the scope jest.unmock('../__test_modules__/a'); } diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 282c37259d67..9af2f8413d55 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -266,6 +266,7 @@ export default (): PluginObj<{jestObjGetterIdentifier: Identifier}> => ({ } }, }, + // in `post` to make sure we come after an import transform and can unshift above the `require`s post({path: program}: {path: NodePath}) { program.traverse({ CallExpression: callExpr => { @@ -278,8 +279,11 @@ export default (): PluginObj<{jestObjGetterIdentifier: Identifier}> => ({ ) { const mockStmt = callExpr.getStatementParent(); const mockStmtNode = mockStmt.node; - mockStmt.remove(); - program.unshiftContainer('body', [mockStmtNode]); + const mockStmtParent = mockStmt.parentPath; + if (mockStmtParent.isBlock()) { + mockStmt.remove(); + mockStmtParent.unshiftContainer('body', [mockStmtNode]); + } } }, }); From 9a022c13e8fde32bb2ab8eddfa0f04744040b4e1 Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 26 Apr 2020 20:52:49 +0200 Subject: [PATCH 17/20] test count --- e2e/__tests__/babelPluginJestHoist.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/__tests__/babelPluginJestHoist.test.ts b/e2e/__tests__/babelPluginJestHoist.test.ts index 5cb0ecf291af..04a2f3e2c9e2 100644 --- a/e2e/__tests__/babelPluginJestHoist.test.ts +++ b/e2e/__tests__/babelPluginJestHoist.test.ts @@ -18,5 +18,5 @@ beforeEach(() => { it('successfully runs the tests inside `babel-plugin-jest-hoist/`', () => { const {json} = runWithJson(DIR, ['--no-cache', '--coverage']); expect(json.success).toBe(true); - expect(json.numTotalTestSuites).toBe(3); + expect(json.numTotalTestSuites).toBe(4); }); From d528e1bf3f3e1bde9d17bbeaa459963cb11c2eff Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 26 Apr 2020 21:36:38 +0200 Subject: [PATCH 18/20] don't declare getJestObj if unnecessary --- packages/babel-plugin-jest-hoist/src/index.ts | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/packages/babel-plugin-jest-hoist/src/index.ts b/packages/babel-plugin-jest-hoist/src/index.ts index 9af2f8413d55..714154256109 100644 --- a/packages/babel-plugin-jest-hoist/src/index.ts +++ b/packages/babel-plugin-jest-hoist/src/index.ts @@ -241,18 +241,30 @@ const extractJestObjExprIfHoistable = ( }; /* eslint-disable sort-keys,@typescript-eslint/explicit-module-boundary-types */ -export default (): PluginObj<{jestObjGetterIdentifier: Identifier}> => ({ +export default (): PluginObj<{ + declareJestObjGetterIdentifier: () => Identifier; + jestObjGetterIdentifier?: Identifier; +}> => ({ pre({path: program}: {path: NodePath}) { - this.jestObjGetterIdentifier = program.scope.generateUidIdentifier( - 'getJestObj', - ); - program.unshiftContainer('body', [ - createJestObjectGetter({ - GETTER_NAME: this.jestObjGetterIdentifier.name, - JEST_GLOBALS_MODULE_JEST_EXPORT_NAME, - JEST_GLOBALS_MODULE_NAME, - }), - ]); + this.declareJestObjGetterIdentifier = () => { + if (this.jestObjGetterIdentifier) { + return this.jestObjGetterIdentifier; + } + + this.jestObjGetterIdentifier = program.scope.generateUidIdentifier( + 'getJestObj', + ); + + program.unshiftContainer('body', [ + createJestObjectGetter({ + GETTER_NAME: this.jestObjGetterIdentifier.name, + JEST_GLOBALS_MODULE_JEST_EXPORT_NAME, + JEST_GLOBALS_MODULE_NAME, + }), + ]); + + return this.jestObjGetterIdentifier; + }; }, visitor: { ExpressionStatement(exprStmt) { @@ -261,7 +273,7 @@ export default (): PluginObj<{jestObjGetterIdentifier: Identifier}> => ({ ); if (jestObjExpr) { jestObjExpr.replaceWith( - callExpression(this.jestObjGetterIdentifier, []), + callExpression(this.declareJestObjGetterIdentifier(), []), ); } }, @@ -275,7 +287,7 @@ export default (): PluginObj<{jestObjGetterIdentifier: Identifier}> => ({ } = callExpr; if ( isIdentifier(callee) && - callee.name === this.jestObjGetterIdentifier.name + callee.name === this.jestObjGetterIdentifier?.name ) { const mockStmt = callExpr.getStatementParent(); const mockStmtNode = mockStmt.node; From e4778f882c9cff1c44d11dcd3cb2371cbc7ee48e Mon Sep 17 00:00:00 2001 From: Tim Seckinger Date: Sun, 26 Apr 2020 21:40:28 +0200 Subject: [PATCH 19/20] snapshot updates, idk... --- .../__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap | 2 +- e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap | 4 ++-- e2e/__tests__/resolveBrowserField.test.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap b/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap index 571dd419e639..f4728989cc42 100644 --- a/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap +++ b/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap @@ -20,6 +20,6 @@ exports[`prints console.logs when run with forceExit 3`] = ` console.log Hey - at Object. (__tests__/a-banana.js:1:1) + at Object.log (__tests__/a-banana.js:1:30) `; diff --git a/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap b/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap index a3fabf97c9d7..8e64da342946 100644 --- a/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap +++ b/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap @@ -9,8 +9,8 @@ exports[`on node >=10 prints coverage 1`] = ` ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- -All files | 100 | 100 | 100 | 100 | - Thing.js | 100 | 100 | 100 | 100 | +All files | 100 | 100 | 0 | 100 | + Thing.js | 100 | 100 | 0 | 100 | x.css | 100 | 100 | 100 | 100 | ----------|---------|----------|---------|---------|-------------------" `; diff --git a/e2e/__tests__/resolveBrowserField.test.ts b/e2e/__tests__/resolveBrowserField.test.ts index 2c37924e2bc5..95a20a5353c1 100644 --- a/e2e/__tests__/resolveBrowserField.test.ts +++ b/e2e/__tests__/resolveBrowserField.test.ts @@ -66,7 +66,7 @@ test('preserves module identity for symlinks when using browser field resolution console.log needs-preserved-id executed - at Object. (packages/needs-preserved-id/index.js:1:13) + at Object.log (packages/needs-preserved-id/index.js:1:9) `); expect(exitCode).toEqual(0); }); From 5d352b4ec96745e801efcc3e8d79c814eaf29ec9 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Sun, 26 Apr 2020 22:01:14 +0200 Subject: [PATCH 20/20] Revert "snapshot updates, idk..." This reverts commit e4778f882c9cff1c44d11dcd3cb2371cbc7ee48e. --- .../__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap | 2 +- e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap | 4 ++-- e2e/__tests__/resolveBrowserField.test.ts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap b/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap index f4728989cc42..571dd419e639 100644 --- a/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap +++ b/e2e/__tests__/__snapshots__/consoleLogOutputWhenRunInBand.test.ts.snap @@ -20,6 +20,6 @@ exports[`prints console.logs when run with forceExit 3`] = ` console.log Hey - at Object.log (__tests__/a-banana.js:1:30) + at Object. (__tests__/a-banana.js:1:1) `; diff --git a/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap b/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap index 8e64da342946..a3fabf97c9d7 100644 --- a/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap +++ b/e2e/__tests__/__snapshots__/v8Coverage.test.ts.snap @@ -9,8 +9,8 @@ exports[`on node >=10 prints coverage 1`] = ` ----------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------|---------|----------|---------|---------|------------------- -All files | 100 | 100 | 0 | 100 | - Thing.js | 100 | 100 | 0 | 100 | +All files | 100 | 100 | 100 | 100 | + Thing.js | 100 | 100 | 100 | 100 | x.css | 100 | 100 | 100 | 100 | ----------|---------|----------|---------|---------|-------------------" `; diff --git a/e2e/__tests__/resolveBrowserField.test.ts b/e2e/__tests__/resolveBrowserField.test.ts index 95a20a5353c1..2c37924e2bc5 100644 --- a/e2e/__tests__/resolveBrowserField.test.ts +++ b/e2e/__tests__/resolveBrowserField.test.ts @@ -66,7 +66,7 @@ test('preserves module identity for symlinks when using browser field resolution console.log needs-preserved-id executed - at Object.log (packages/needs-preserved-id/index.js:1:9) + at Object. (packages/needs-preserved-id/index.js:1:13) `); expect(exitCode).toEqual(0); });