diff --git a/packages/babel-core/test/fixtures/plugins/multiple-definition-evaluation/exec.js b/packages/babel-core/test/fixtures/plugins/multiple-definition-evaluation/exec.js index 522d97637c..2d8f08b6c6 100644 --- a/packages/babel-core/test/fixtures/plugins/multiple-definition-evaluation/exec.js +++ b/packages/babel-core/test/fixtures/plugins/multiple-definition-evaluation/exec.js @@ -1,8 +1,8 @@ -var code = multiline([ - "function foo() {", - " var a = a ? a : a;", - "}", -]); +var code = ` + function foo() { + var a = a ? a : a; + } +`; transform(code, { plugins: [ diff --git a/packages/babel-core/test/fixtures/plugins/regression-2772/exec.js b/packages/babel-core/test/fixtures/plugins/regression-2772/exec.js index b38b6197d9..b88b1876f2 100644 --- a/packages/babel-core/test/fixtures/plugins/regression-2772/exec.js +++ b/packages/babel-core/test/fixtures/plugins/regression-2772/exec.js @@ -1,12 +1,12 @@ -var code = multiline([ - "(function() {", - " var bar = 'lol';", - " function foo(b){", - " b === bar;", - " foo(b);", - " }", - "})();", -]); +var code = ` + (function() { + var bar = 'lol'; + function foo(b){ + b === bar + foo(b); + } + })(); +`; transform(code, { plugins: [ diff --git a/packages/babel-helper-transform-fixture-test-runner/package.json b/packages/babel-helper-transform-fixture-test-runner/package.json index 5387c003e0..54440589ca 100644 --- a/packages/babel-helper-transform-fixture-test-runner/package.json +++ b/packages/babel-helper-transform-fixture-test-runner/package.json @@ -23,6 +23,7 @@ "jest": "^24.8.0", "jest-diff": "^24.8.0", "lodash": "^4.17.19", + "quick-lru": "5.1.0", "resolve": "^1.3.2", "source-map": "^0.5.0" } diff --git a/packages/babel-helper-transform-fixture-test-runner/src/index.js b/packages/babel-helper-transform-fixture-test-runner/src/index.js index ac00464f16..2917364da3 100644 --- a/packages/babel-helper-transform-fixture-test-runner/src/index.js +++ b/packages/babel-helper-transform-fixture-test-runner/src/index.js @@ -14,34 +14,100 @@ import fs from "fs"; import path from "path"; import vm from "vm"; import checkDuplicatedNodes from "babel-check-duplicated-nodes"; +import QuickLRU from "quick-lru"; import diff from "jest-diff"; -const moduleCache = {}; -const testContext = vm.createContext({ - ...helpers, - process: process, - transform: babel.transform, - setTimeout: setTimeout, - setImmediate: setImmediate, - expect, -}); -testContext.global = testContext; +const cachedScripts = new QuickLRU({ maxSize: 10 }); +const contextModuleCache = new WeakMap(); +const sharedTestContext = createContext(); -// Initialize the test context with the polyfill, and then freeze the global to prevent implicit -// global creation in tests, which could cause things to bleed between tests. -runModuleInTestContext("@babel/polyfill", __filename); +function createContext() { + const context = vm.createContext({ + ...helpers, + process: process, + transform: babel.transform, + setTimeout: setTimeout, + setImmediate: setImmediate, + expect, + }); + context.global = context; -// Populate the "babelHelpers" global with Babel's helper utilities. -runCodeInTestContext(buildExternalHelpers(), { - filename: path.join(__dirname, "babel-helpers-in-memory.js"), -}); + const moduleCache = Object.create(null); + contextModuleCache.set(context, moduleCache); + + // Initialize the test context with the polyfill, and then freeze the global to prevent implicit + // global creation in tests, which could cause things to bleed between tests. + runModuleInTestContext( + "@babel/polyfill/dist/polyfill.min", + __filename, + context, + moduleCache, + ); + + // Populate the "babelHelpers" global with Babel's helper utilities. + runCacheableScriptInTestContext( + path.join(__dirname, "babel-helpers-in-memory.js"), + buildExternalHelpers, + context, + moduleCache, + ); + + return context; +} + +function runCacheableScriptInTestContext( + filename: string, + srcFn: () => string, + context: Context, + moduleCache: Object, +) { + let cached = cachedScripts.get(filename); + if (!cached) { + const code = `(function (exports, require, module, __filename, __dirname) {\n${srcFn()}\n});`; + cached = { + code, + cachedData: undefined, + }; + cachedScripts.set(filename, cached); + } + + const script = new vm.Script(cached.code, { + filename, + displayErrors: true, + lineOffset: -1, + cachedData: cached.cachedData, + produceCachedData: true, + }); + + if (script.cachedDataProduced) { + cached.cachedData = script.cachedData; + } + + const module = { + id: filename, + exports: {}, + }; + const req = id => runModuleInTestContext(id, filename, context, moduleCache); + const dirname = path.dirname(filename); + + script + .runInContext(context) + .call(module.exports, module.exports, req, module, filename, dirname); + + return module; +} /** * A basic implementation of CommonJS so we can execute `@babel/polyfill` inside our test context. * This allows us to run our unittests */ -function runModuleInTestContext(id: string, relativeFilename: string) { +function runModuleInTestContext( + id: string, + relativeFilename: string, + context: Context, + moduleCache: Object, +) { const filename = resolve.sync(id, { basedir: path.dirname(relativeFilename), }); @@ -50,23 +116,17 @@ function runModuleInTestContext(id: string, relativeFilename: string) { // the context's global scope. if (filename === id) return require(id); + // Modules can only evaluate once per context, so the moduleCache is a + // stronger cache guarantee than the LRU's Script cache. if (moduleCache[filename]) return moduleCache[filename].exports; - const module = (moduleCache[filename] = { - id: filename, - exports: {}, - }); - const dirname = path.dirname(filename); - const req = id => runModuleInTestContext(id, filename); - - const src = fs.readFileSync(filename, "utf8"); - const code = `(function (exports, require, module, __filename, __dirname) {\n${src}\n});`; - - vm.runInContext(code, testContext, { + const module = runCacheableScriptInTestContext( filename, - displayErrors: true, - lineOffset: -1, - }).call(module.exports, module.exports, req, module, filename, dirname); + () => fs.readFileSync(filename, "utf8"), + context, + moduleCache, + ); + moduleCache[filename] = module; return module.exports; } @@ -76,10 +136,15 @@ function runModuleInTestContext(id: string, relativeFilename: string) { * * Exposed for unit tests, not for use as an API. */ -export function runCodeInTestContext(code: string, opts: { filename: string }) { +export function runCodeInTestContext( + code: string, + opts: { filename: string }, + context = sharedTestContext, +) { const filename = opts.filename; const dirname = path.dirname(filename); - const req = id => runModuleInTestContext(id, filename); + const moduleCache = contextModuleCache.get(context); + const req = id => runModuleInTestContext(id, filename, context, moduleCache); const module = { id: filename, @@ -94,7 +159,7 @@ export function runCodeInTestContext(code: string, opts: { filename: string }) { // Note: This isn't doing .call(module.exports, ...) because some of our tests currently // rely on 'this === global'. const src = `(function(exports, require, module, __filename, __dirname, opts) {\n${code}\n});`; - return vm.runInContext(src, testContext, { + return vm.runInContext(src, context, { filename, displayErrors: true, lineOffset: -1, @@ -183,13 +248,14 @@ function run(task) { let resultExec; if (execCode) { + const context = createContext(); const execOpts = getOpts(exec); result = babel.transform(execCode, execOpts); checkDuplicatedNodes(babel, result.ast); execCode = result.code; try { - resultExec = runCodeInTestContext(execCode, execOpts); + resultExec = runCodeInTestContext(execCode, execOpts, context); } catch (err) { // Pass empty location to include the whole file in the output. err.message = diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread-loose-builtins/no-object-assign-exec/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread-loose-builtins/no-object-assign-exec/exec.js index b8efc6d372..40fdaabb0e 100644 --- a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread-loose-builtins/no-object-assign-exec/exec.js +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread-loose-builtins/no-object-assign-exec/exec.js @@ -1,27 +1,25 @@ "use strict"; -const NOSET = `NOSET${__filename}`; -const NOWRITE = `NOWRITE${__filename}`; -Object.defineProperty(Object.prototype, NOSET, { +Object.defineProperty(Object.prototype, 'NOSET', { get(value) { // noop }, }); -Object.defineProperty(Object.prototype, NOWRITE, { +Object.defineProperty(Object.prototype, 'NOWRITE', { writable: false, value: 'abc', }); -const obj = { [NOSET]: 123 }; +const obj = { 'NOSET': 123 }; // this won't work as expected if transformed as Object.assign (or equivalent) // because those trigger object setters (spread don't) expect(() => { const objSpread = { ...obj }; }).toThrow(); -const obj2 = { [NOWRITE]: 456 }; -// this throws `TypeError: Cannot assign to read only property 'NOWRITE'` +const obj2 = { 'NOWRITE': 456 }; +// this throws `TypeError: Cannot assign to read only property 'NOWRITE'` // if transformed as Object.assign (or equivalent) because those use *assignment* for creating properties // (spread defines them) expect(() => { diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread-loose/no-object-assign-exec/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread-loose/no-object-assign-exec/exec.js index b8efc6d372..4d937710e3 100644 --- a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread-loose/no-object-assign-exec/exec.js +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread-loose/no-object-assign-exec/exec.js @@ -1,27 +1,24 @@ "use strict"; -const NOSET = `NOSET${__filename}`; -const NOWRITE = `NOWRITE${__filename}`; - -Object.defineProperty(Object.prototype, NOSET, { +Object.defineProperty(Object.prototype, 'NOSET', { get(value) { // noop }, }); -Object.defineProperty(Object.prototype, NOWRITE, { +Object.defineProperty(Object.prototype, 'NOWRITE', { writable: false, value: 'abc', }); -const obj = { [NOSET]: 123 }; +const obj = { 'NOSET': 123 }; // this won't work as expected if transformed as Object.assign (or equivalent) // because those trigger object setters (spread don't) expect(() => { const objSpread = { ...obj }; }).toThrow(); -const obj2 = { [NOWRITE]: 456 }; -// this throws `TypeError: Cannot assign to read only property 'NOWRITE'` +const obj2 = { 'NOWRITE': 456 }; +// this throws `TypeError: Cannot assign to read only property 'NOWRITE'` // if transformed as Object.assign (or equivalent) because those use *assignment* for creating properties // (spread defines them) expect(() => { diff --git a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread/no-object-assign-exec/exec.js b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread/no-object-assign-exec/exec.js index e358e69cfe..2fbdb3cb1d 100644 --- a/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread/no-object-assign-exec/exec.js +++ b/packages/babel-plugin-proposal-object-rest-spread/test/fixtures/object-spread/no-object-assign-exec/exec.js @@ -1,31 +1,27 @@ "use strict"; -const NOSET = `NOSET${__filename}`; -const NOWRITE = `NOWRITE${__filename}`; - -Object.defineProperty(Object.prototype, NOSET, { - set(value) { +Object.defineProperty(Object.prototype, 'NOSET', { + get(value) { // noop }, }); -Object.defineProperty(Object.prototype, NOWRITE, { +Object.defineProperty(Object.prototype, 'NOWRITE', { writable: false, value: 'abc', }); -const obj = { [NOSET]: 123 }; +const obj = { NOSET: 123 }; // this wouldn't work as expected if transformed as Object.assign (or equivalent) // because those trigger object setters (spread don't) const objSpread = { ...obj }; +expect(objSpread).toHaveProperty('NOSET', 123); -const obj2 = { NOSET: 123, [NOWRITE]: 456 }; +const obj2 = { NOWRITE: 456 }; // this line would throw `TypeError: Cannot assign to read only property 'NOWRITE'` // if transformed as Object.assign (or equivalent) because those use *assignment* for creating properties // (spread defines them) const obj2Spread = { ...obj2 }; - -expect(objSpread).toEqual(obj); -expect(obj2Spread).toEqual(obj2); +expect(obj2Spread).toHaveProperty('NOWRITE', 456); const KEY = Symbol('key'); const obj3Spread = { ...{ get foo () { return 'bar' } }, [KEY]: 'symbol' }; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-10339/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-10339/exec.js index 9fea95129b..9073078104 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-10339/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-10339/exec.js @@ -1,12 +1,12 @@ -const code = multiline([ - "for (const {foo, ...bar} of { bar: [] }) {", - "() => foo;", - "const [qux] = bar;", - "try {} catch (e) {", - "let quux = qux;", - "}", - "}" -]); +const code = ` + for (const {foo, ...bar} of { bar: [] }) { + () => foo; + const [qux] = bar; + try {} catch (e) { + let quux = qux; + } + } +`; let programPath; let forOfPath; diff --git a/packages/babel-plugin-transform-classes/test/fixtures/regression/8499/exec.js b/packages/babel-plugin-transform-classes/test/fixtures/regression/8499/exec.js index 1a5d146d5e..143ef440d8 100644 --- a/packages/babel-plugin-transform-classes/test/fixtures/regression/8499/exec.js +++ b/packages/babel-plugin-transform-classes/test/fixtures/regression/8499/exec.js @@ -1,26 +1,15 @@ -const oldReflect = this.Reflect; -const oldHTMLElement = this.HTMLElement; +// Pretend that `Reflect.construct` isn't supported. +global.Reflect = undefined; -try { - // Pretend that `Reflect.construct` isn't supported. - this.Reflect = undefined; +global.HTMLElement = function() { + // Here, `this.HTMLElement` is this function, not the original HTMLElement + // constructor. `this.constructor` should be this function too, but isn't. + constructor = this.constructor; +}; - this.HTMLElement = function() { - // Here, `this.HTMLElement` is this function, not the original HTMLElement - // constructor. `this.constructor` should be this function too, but isn't. - constructor = this.constructor; - }; - - var constructor; - - class CustomElement extends HTMLElement {}; - new CustomElement(); - - expect(constructor).toBe(CustomElement); -} finally { - // Restore original env - this.Reflect = oldReflect; - this.HTMLElement = oldHTMLElement; -} +var constructor; +class CustomElement extends HTMLElement {} +new CustomElement(); +expect(constructor).toBe(CustomElement); diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/destructuring/array-symbol-unsupported/exec.js b/packages/babel-plugin-transform-destructuring/test/fixtures/destructuring/array-symbol-unsupported/exec.js index ea73919662..4ee3b0499b 100644 --- a/packages/babel-plugin-transform-destructuring/test/fixtures/destructuring/array-symbol-unsupported/exec.js +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/destructuring/array-symbol-unsupported/exec.js @@ -1,13 +1,9 @@ var a = (() => [1, 2, 3])(); // Simulate old environment -let _Symbol = Symbol; -Symbol = void 0; -try { - var [first, ...rest] = a; +global.Symbol = void 0; - expect(first).toBe(1); - expect(rest).toEqual([2, 3]); -} finally { - Symbol = _Symbol; -} +var [first, ...rest] = a; + +expect(first).toBe(1); +expect(rest).toEqual([2, 3]); diff --git a/packages/babel-plugin-transform-destructuring/test/fixtures/destructuring/non-iterable/exec.js b/packages/babel-plugin-transform-destructuring/test/fixtures/destructuring/non-iterable/exec.js index e0bac050d6..d110d30779 100644 --- a/packages/babel-plugin-transform-destructuring/test/fixtures/destructuring/non-iterable/exec.js +++ b/packages/babel-plugin-transform-destructuring/test/fixtures/destructuring/non-iterable/exec.js @@ -8,15 +8,8 @@ expect( () => [foo, bar] = {} ).toThrow(/destructure non-iterable/); -// Simulate old browser -let _Symbol = Symbol; -Symbol = void 0; -try { +global.Symbol = void 0; - expect( - () => [foo, bar] = {} - ).toThrow(/destructure non-iterable/); - -} finally { - Symbol = _Symbol; -} +expect( + () => [foo, bar] = {} +).toThrow(/destructure non-iterable/); diff --git a/packages/babel-plugin-transform-react-jsx-source/test/fixtures/react-source/basic-sample/exec.js b/packages/babel-plugin-transform-react-jsx-source/test/fixtures/react-source/basic-sample/exec.js index c3bb778238..21bf1293f1 100644 --- a/packages/babel-plugin-transform-react-jsx-source/test/fixtures/react-source/basic-sample/exec.js +++ b/packages/babel-plugin-transform-react-jsx-source/test/fixtures/react-source/basic-sample/exec.js @@ -3,13 +3,13 @@ var actual = transform( Object.assign({}, opts, { filename: '/fake/path/mock.js' }) ).code; -var expected = multiline([ - 'var _jsxFileName = "/fake/path/mock.js";', - 'var x = ;', -]); +var expected = ` +var _jsxFileName = "/fake/path/mock.js"; +var x = ; +`.trim(); expect(actual).toBe(expected); diff --git a/packages/babel-plugin-transform-spread/test/fixtures/spread/array-symbol-unsupported/exec.js b/packages/babel-plugin-transform-spread/test/fixtures/spread/array-symbol-unsupported/exec.js index 93ad0b7a1e..0d320afa2e 100644 --- a/packages/babel-plugin-transform-spread/test/fixtures/spread/array-symbol-unsupported/exec.js +++ b/packages/babel-plugin-transform-spread/test/fixtures/spread/array-symbol-unsupported/exec.js @@ -1,10 +1,6 @@ var a = (() => [2, 3])(); // Simulate old environment -let _Symbol = Symbol; -Symbol = void 0; -try { - expect([1, ...a]).toEqual([1, 2, 3]); -} finally { - Symbol = _Symbol; -} +global.Symbol = void 0; + +expect([1, ...a]).toEqual([1, 2, 3]); diff --git a/packages/babel-plugin-transform-spread/test/fixtures/spread/non-iterable/exec.js b/packages/babel-plugin-transform-spread/test/fixtures/spread/non-iterable/exec.js index 0ff7a48766..f00f0ae0cf 100644 --- a/packages/babel-plugin-transform-spread/test/fixtures/spread/non-iterable/exec.js +++ b/packages/babel-plugin-transform-spread/test/fixtures/spread/non-iterable/exec.js @@ -5,6 +5,6 @@ expect(() => [...undefined]).toThrow(/spread non-iterable/); expect(() => [...o]).toThrow(/spread non-iterable/); // Simulate old browser -Symbol = void 0; +global.Symbol = void 0; expect(() => [...o]).toThrow(/spread non-iterable/);