From 44a4027785a5d6291b6ae98eb021531decd81974 Mon Sep 17 00:00:00 2001 From: Robert Kieffer Date: Tue, 14 Feb 2023 09:10:32 -0800 Subject: [PATCH 1/6] Revert "chore: add validation of import assertions (#13805)" This reverts commit b40429649d5e673eb5c572db8a5cfd1218320a40. --- CHANGELOG.md | 2 +- .../runtime_import_assertions.test.js | 78 ---------- packages/jest-runtime/src/index.ts | 137 ++---------------- 3 files changed, 15 insertions(+), 202 deletions(-) delete mode 100644 packages/jest-runtime/src/__tests__/runtime_import_assertions.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index ce86f3d161fa..b4b4e06a54f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,7 +69,7 @@ - `[jest-resolve]` Correct node core module detection when using `node:` specifiers ([#13806](https://github.com/facebook/jest/pull/13806)) - `[jest-runtime]` Support WASM files that import JS resources ([#13608](https://github.com/facebook/jest/pull/13608)) - `[jest-runtime]` Use the `scriptTransformer` cache in `jest-runner` ([#13735](https://github.com/facebook/jest/pull/13735)) -- `[jest-runtime]` Enforce import assertions when importing JSON in ESM ([#12755](https://github.com/facebook/jest/pull/12755) & [#13805](https://github.com/facebook/jest/pull/13805)) +- `[jest-runtime]` Enforce import assertions when importing JSON in ESM ([#12755](https://github.com/facebook/jest/pull/12755)) - `[jest-snapshot]` Make sure to import `babel` outside of the sandbox ([#13694](https://github.com/facebook/jest/pull/13694)) - `[jest-transform]` Ensure the correct configuration is passed to preprocessors specified multiple times in the `transform` option ([#13770](https://github.com/facebook/jest/pull/13770)) diff --git a/packages/jest-runtime/src/__tests__/runtime_import_assertions.test.js b/packages/jest-runtime/src/__tests__/runtime_import_assertions.test.js deleted file mode 100644 index 645f0cc31de8..000000000000 --- a/packages/jest-runtime/src/__tests__/runtime_import_assertions.test.js +++ /dev/null @@ -1,78 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import {pathToFileURL} from 'url'; -import {onNodeVersions} from '@jest/test-utils'; - -let runtime; - -// version where `vm` API gets `import assertions` -onNodeVersions('>=16.12.0', () => { - beforeAll(async () => { - const createRuntime = require('createRuntime'); - - runtime = await createRuntime(__filename); - }); - - describe('import assertions', () => { - const fileUrl = pathToFileURL(__filename).href; - const jsonFileName = `${__filename}on`; - const jsonFileUrl = pathToFileURL(jsonFileName).href; - - it('works if passed correct import assertion', () => { - expect(() => - runtime.validateImportAssertions(jsonFileName, '', {type: 'json'}), - ).not.toThrow(); - }); - - it('does nothing if no assertions passed for js file', () => { - expect(() => - runtime.validateImportAssertions(__filename, '', undefined), - ).not.toThrow(); - expect(() => - runtime.validateImportAssertions(__filename, '', {}), - ).not.toThrow(); - }); - - it('throws if invalid assertions are passed', () => { - expect(() => - runtime.validateImportAssertions(jsonFileName, '', {type: null}), - ).toThrow('Import assertion value must be a string'); - expect(() => - runtime.validateImportAssertions(jsonFileName, '', {type: 42}), - ).toThrow('Import assertion value must be a string'); - expect(() => - runtime.validateImportAssertions(jsonFileName, '', { - type: 'javascript', - }), - ).toThrow('Import assertion type "javascript" is unsupported'); - }); - - it('throws if missing json assertions', () => { - const errorMessage = `Module "${jsonFileUrl}" needs an import assertion of type "json"`; - - expect(() => - runtime.validateImportAssertions(jsonFileName, '', {}), - ).toThrow(errorMessage); - expect(() => - runtime.validateImportAssertions(jsonFileName, '', { - somethingElse: 'json', - }), - ).toThrow(errorMessage); - expect(() => runtime.validateImportAssertions(jsonFileName, '')).toThrow( - errorMessage, - ); - }); - - it('throws if json assertion passed on wrong file', () => { - expect(() => - runtime.validateImportAssertions(__filename, '', {type: 'json'}), - ).toThrow(`Module "${fileUrl}" is not of type "json"`); - }); - }); -}); diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index f33a894327a9..10bb64b9f02e 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -159,23 +159,6 @@ const supportsNodeColonModulePrefixInRequire = (() => { } })(); -const kImplicitAssertType = Symbol('kImplicitAssertType'); - -// copied from https://github.com/nodejs/node/blob/7dd458382580f68cf7d718d96c8f4d2d3fe8b9db/lib/internal/modules/esm/assert.js#L20-L32 -const formatTypeMap: {[type: string]: string | typeof kImplicitAssertType} = { - // @ts-expect-error - copied - __proto__: null, - builtin: kImplicitAssertType, - commonjs: kImplicitAssertType, - json: 'json', - module: kImplicitAssertType, - wasm: kImplicitAssertType, -}; - -const supportedAssertionTypes = new Set( - Object.values(formatTypeMap).filter(type => type !== kImplicitAssertType), -); - export default class Runtime { private readonly _cacheFS: Map; private readonly _cacheFSBuffer = new Map(); @@ -435,10 +418,21 @@ export default class Runtime { private async loadEsmModule( modulePath: string, query = '', - importAssertions?: ImportAssertions, + importAssertions: ImportAssertions = {}, ): Promise { - if (runtimeSupportsImportAssertions) { - this.validateImportAssertions(modulePath, query, importAssertions); + if ( + runtimeSupportsImportAssertions && + modulePath.endsWith('.json') && + importAssertions.type !== 'json' + ) { + const error: NodeJS.ErrnoException = new Error( + `Module "${ + modulePath + (query ? `?${query}` : '') + }" needs an import assertion of type "json"`, + ); + error.code = 'ERR_IMPORT_ASSERTION_TYPE_MISSING'; + + throw error; } const cacheKey = modulePath + query; @@ -578,83 +572,6 @@ export default class Runtime { return module; } - private validateImportAssertions( - modulePath: string, - query: string, - importAssertions: ImportAssertions = { - // @ts-expect-error - copy https://github.com/nodejs/node/blob/7dd458382580f68cf7d718d96c8f4d2d3fe8b9db/lib/internal/modules/esm/assert.js#LL55C50-L55C65 - __proto__: null, - }, - ) { - const format = this.getModuleFormat(modulePath); - const validType = formatTypeMap[format]; - const url = pathToFileURL(modulePath); - - if (query) { - url.search = query; - } - - const urlString = url.href; - - const assertionType = importAssertions.type; - - switch (validType) { - case undefined: - // Ignore assertions for module formats we don't recognize, to allow new - // formats in the future. - return; - - case kImplicitAssertType: - // This format doesn't allow an import assertion type, so the property - // must not be set on the import assertions object. - if (Object.prototype.hasOwnProperty.call(importAssertions, 'type')) { - handleInvalidAssertionType(urlString, assertionType); - } - return; - - case assertionType: - // The asserted type is the valid type for this format. - return; - - default: - // There is an expected type for this format, but the value of - // `importAssertions.type` might not have been it. - if (!Object.prototype.hasOwnProperty.call(importAssertions, 'type')) { - // `type` wasn't specified at all. - const error: NodeJS.ErrnoException = new Error( - `Module "${urlString}" needs an import assertion of type "json"`, - ); - error.code = 'ERR_IMPORT_ASSERTION_TYPE_MISSING'; - - throw error; - } - handleInvalidAssertionType(urlString, assertionType); - } - } - - private getModuleFormat(modulePath: string) { - if (this._resolver.isCoreModule(modulePath)) { - return 'builtin'; - } - - if (isWasm(modulePath)) { - return 'wasm'; - } - - const fileExtension = path.extname(modulePath); - - if (fileExtension === '.json') { - return 'json'; - } - - if (this.unstable_shouldLoadAsEsm(modulePath)) { - return 'module'; - } - - // any unknown format should be treated as JS - return 'commonjs'; - } - private async resolveModule( specifier: string, referencingIdentifier: string, @@ -2608,29 +2525,3 @@ async function evaluateSyntheticModule(module: SyntheticModule) { return module; } - -function handleInvalidAssertionType(url: string, type: unknown) { - if (typeof type !== 'string') { - throw new TypeError('Import assertion value must be a string'); - } - - // `type` might not have been one of the types we understand. - if (!supportedAssertionTypes.has(type)) { - const error: NodeJS.ErrnoException = new Error( - `Import assertion type "${type}" is unsupported`, - ); - - error.code = 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED'; - - throw error; - } - - // `type` was the wrong value for this format. - const error: NodeJS.ErrnoException = new Error( - `Module "${url}" is not of type "${type}"`, - ); - - error.code = 'ERR_IMPORT_ASSERTION_TYPE_FAILED'; - - throw error; -} From b3d78dd6343afa91f9462b88bdfaabd1f1fe7d50 Mon Sep 17 00:00:00 2001 From: Robert Kieffer Date: Tue, 14 Feb 2023 09:13:16 -0800 Subject: [PATCH 2/6] Revert "fix: enforce import assertions when importing JSON in ESM (#12755)" This reverts commit 6f8e91804b5515a64d1baf439bc1d84ee0ea61cd. --- .../__snapshots__/nativeEsm.test.ts.snap | 16 --- e2e/__tests__/nativeEsm.test.ts | 63 --------- .../native-esm-import-assertions.test.js | 19 --- ...tive-esm-missing-import-assertions.test.js | 17 --- packages/jest-runtime/package.json | 2 - packages/jest-runtime/src/index.ts | 132 +++++------------- yarn.lock | 2 - 7 files changed, 38 insertions(+), 213 deletions(-) delete mode 100644 e2e/native-esm/__tests__/native-esm-import-assertions.test.js delete mode 100644 e2e/native-esm/__tests__/native-esm-missing-import-assertions.test.js diff --git a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap index 1e8b6bd6ed39..f3724c4ef073 100644 --- a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap +++ b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap @@ -1,13 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`on node <16.12.0 does not enforce import assertions 1`] = ` -"Test Suites: 1 passed, 1 total -Tests: 2 passed, 2 total -Snapshots: 0 total -Time: <> -Ran all test suites matching /native-esm-missing-import-assertions.test/i." -`; - exports[`on node >=16.9.0 support re-exports from CJS of dual packages 1`] = ` "Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -16,14 +8,6 @@ Time: <> Ran all test suites matching /native-esm-deep-cjs-reexport.test.js/i." `; -exports[`on node >=16.12.0 supports import assertions 1`] = ` -"Test Suites: 1 passed, 1 total -Tests: 2 passed, 2 total -Snapshots: 0 total -Time: <> -Ran all test suites matching /native-esm-import-assertions.test/i." -`; - exports[`runs WebAssembly (Wasm) test with native ESM 1`] = ` "Test Suites: 1 passed, 1 total Tests: 6 passed, 6 total diff --git a/e2e/__tests__/nativeEsm.test.ts b/e2e/__tests__/nativeEsm.test.ts index d4a1e9631a12..f91b3e57fa3c 100644 --- a/e2e/__tests__/nativeEsm.test.ts +++ b/e2e/__tests__/nativeEsm.test.ts @@ -93,66 +93,3 @@ test('runs WebAssembly (Wasm) test with native ESM', () => { expect(stdout).toBe(''); expect(exitCode).toBe(0); }); - -// version where `vm` API gets `import assertions` -onNodeVersions('>=16.12.0', () => { - test('enforces import assertions', () => { - const {exitCode, stderr, stdout} = runJest( - DIR, - ['native-esm-missing-import-assertions.test'], - {nodeOptions: '--experimental-vm-modules --no-warnings'}, - ); - - const {rest} = extractSummary(stderr); - - expect(rest).toContain( - 'package.json" needs an import assertion of type "json"', - ); - expect(stdout).toBe(''); - expect(exitCode).toBe(1); - }); - - test('supports import assertions', () => { - const {exitCode, stderr, stdout} = runJest( - DIR, - ['native-esm-import-assertions.test'], - {nodeOptions: '--experimental-vm-modules --no-warnings'}, - ); - - const {summary} = extractSummary(stderr); - - expect(summary).toMatchSnapshot(); - expect(stdout).toBe(''); - expect(exitCode).toBe(0); - }); -}); - -onNodeVersions('<16.12.0', () => { - test('does not enforce import assertions', () => { - const {exitCode, stderr, stdout} = runJest( - DIR, - ['native-esm-missing-import-assertions.test'], - {nodeOptions: '--experimental-vm-modules --no-warnings'}, - ); - - const {summary} = extractSummary(stderr); - - expect(summary).toMatchSnapshot(); - expect(stdout).toBe(''); - expect(exitCode).toBe(0); - }); - - test('syntax error for import assertions', () => { - const {exitCode, stderr, stdout} = runJest( - DIR, - ['native-esm-import-assertions.test'], - {nodeOptions: '--experimental-vm-modules --no-warnings'}, - ); - - const {rest} = extractSummary(stderr); - - expect(rest).toContain('SyntaxError: Unexpected identifier'); - expect(stdout).toBe(''); - expect(exitCode).toBe(1); - }); -}); diff --git a/e2e/native-esm/__tests__/native-esm-import-assertions.test.js b/e2e/native-esm/__tests__/native-esm-import-assertions.test.js deleted file mode 100644 index 6a2d16b42df7..000000000000 --- a/e2e/native-esm/__tests__/native-esm-import-assertions.test.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import json from '../package.json' assert {type: 'json'}; - -test('supports static import', () => { - expect(json).toHaveProperty('jest.testEnvironment', 'node'); -}); - -test('supports dynamic import', async () => { - const {default: json} = await import('../package.json', { - assert: {type: 'json'}, - }); - expect(json).toHaveProperty('jest.testEnvironment', 'node'); -}); diff --git a/e2e/native-esm/__tests__/native-esm-missing-import-assertions.test.js b/e2e/native-esm/__tests__/native-esm-missing-import-assertions.test.js deleted file mode 100644 index aece5bd8615d..000000000000 --- a/e2e/native-esm/__tests__/native-esm-missing-import-assertions.test.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import json from '../package.json'; - -test('supports static import', () => { - expect(json).toHaveProperty('jest.testEnvironment', 'node'); -}); - -test('supports dynamic import', async () => { - const {default: json} = await import('../package.json'); - expect(json).toHaveProperty('jest.testEnvironment', 'node'); -}); diff --git a/packages/jest-runtime/package.json b/packages/jest-runtime/package.json index 60a7464715b2..c4638697fa6e 100644 --- a/packages/jest-runtime/package.json +++ b/packages/jest-runtime/package.json @@ -37,7 +37,6 @@ "jest-resolve": "workspace:^", "jest-snapshot": "workspace:^", "jest-util": "workspace:^", - "semver": "^7.3.5", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -45,7 +44,6 @@ "@jest/test-utils": "workspace:^", "@types/glob": "^7.1.1", "@types/graceful-fs": "^4.1.3", - "@types/semver": "^7.1.0", "jest-environment-node": "workspace:^" }, "engines": { diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 10bb64b9f02e..35948a9464b4 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -21,7 +21,6 @@ import { import {parse as parseCjs} from 'cjs-module-lexer'; import {CoverageInstrumenter, V8Coverage} from 'collect-v8-coverage'; import * as fs from 'graceful-fs'; -import {satisfies as semverSatisfies} from 'semver'; import slash = require('slash'); import stripBOM = require('strip-bom'); import type { @@ -63,11 +62,6 @@ import { const esmIsAvailable = typeof SourceTextModule === 'function'; -const runtimeSupportsImportAssertions = semverSatisfies( - process.versions.node, - '^16.12.0 || >=17.0.0', -); - const dataURIRegex = /^data:(?text\/javascript|application\/json|application\/wasm)(?:;(?charset=utf-8|base64))?,(?.*)$/; @@ -415,26 +409,11 @@ export default class Runtime { ); } + // not async _now_, but transform will be private async loadEsmModule( modulePath: string, query = '', - importAssertions: ImportAssertions = {}, ): Promise { - if ( - runtimeSupportsImportAssertions && - modulePath.endsWith('.json') && - importAssertions.type !== 'json' - ) { - const error: NodeJS.ErrnoException = new Error( - `Module "${ - modulePath + (query ? `?${query}` : '') - }" needs an import assertion of type "json"`, - ); - error.code = 'ERR_IMPORT_ASSERTION_TYPE_MISSING'; - - throw error; - } - const cacheKey = modulePath + query; if (this._fileTransformsMutex.has(cacheKey)) { @@ -472,7 +451,6 @@ export default class Runtime { this.readFileBuffer(modulePath), modulePath, context, - importAssertions, ); this._esmoduleRegistry.set(cacheKey, wasm); @@ -499,54 +477,39 @@ export default class Runtime { }); try { - let module; - if (modulePath.endsWith('.json')) { - module = new SyntheticModule( - ['default'], - function () { - const obj = JSON.parse(transformedCode); - // @ts-expect-error: TS doesn't know what `this` is - this.setExport('default', obj); - }, - {context, identifier: modulePath}, - ); - } else { - module = new SourceTextModule(transformedCode, { - context, - identifier: modulePath, - importModuleDynamically: async ( - specifier: string, - referencingModule: VMModule, - importAssertions?: ImportAssertions, - ) => { - invariant( - runtimeSupportsVmModules, - 'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules', - ); - const module = await this.resolveModule( - specifier, - referencingModule.identifier, - referencingModule.context, - importAssertions, - ); + const module = new SourceTextModule(transformedCode, { + context, + identifier: modulePath, + importModuleDynamically: async ( + specifier: string, + referencingModule: VMModule, + ) => { + invariant( + runtimeSupportsVmModules, + 'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules', + ); + const module = await this.resolveModule( + specifier, + referencingModule.identifier, + referencingModule.context, + ); - return this.linkAndEvaluateModule(module); - }, - initializeImportMeta: (meta: JestImportMeta) => { - meta.url = pathToFileURL(modulePath).href; + return this.linkAndEvaluateModule(module); + }, + initializeImportMeta: (meta: JestImportMeta) => { + meta.url = pathToFileURL(modulePath).href; - let jest = this.jestObjectCaches.get(modulePath); + let jest = this.jestObjectCaches.get(modulePath); - if (!jest) { - jest = this._createJestObjectFor(modulePath); + if (!jest) { + jest = this._createJestObjectFor(modulePath); - this.jestObjectCaches.set(modulePath, jest); - } + this.jestObjectCaches.set(modulePath, jest); + } - meta.jest = jest; - }, - }); - } + meta.jest = jest; + }, + }); invariant( !this._esmoduleRegistry.has(cacheKey), @@ -576,7 +539,6 @@ export default class Runtime { specifier: string, referencingIdentifier: string, context: VMContext, - importAssertions: ImportAssertions = {}, ): Promise { if (this.isTornDown) { this._logFormattedReferenceError( @@ -637,7 +599,6 @@ export default class Runtime { Buffer.from(match.groups.code, 'base64'), specifier, context, - importAssertions, ); } else { let code = match.groups.code; @@ -666,7 +627,6 @@ export default class Runtime { importModuleDynamically: async ( specifier: string, referencingModule: VMModule, - importAssertions?: ImportAssertions, ) => { invariant( runtimeSupportsVmModules, @@ -676,7 +636,6 @@ export default class Runtime { specifier, referencingModule.identifier, referencingModule.context, - importAssertions, ); return this.linkAndEvaluateModule(module); @@ -713,11 +672,9 @@ export default class Runtime { if ( this._resolver.isCoreModule(resolved) || - this.unstable_shouldLoadAsEsm(resolved) || - // json files are modules when imported in modules - resolved.endsWith('.json') + this.unstable_shouldLoadAsEsm(resolved) ) { - return this.loadEsmModule(resolved, query, importAssertions); + return this.loadEsmModule(resolved, query); } return this.loadCjsAsEsm(referencingIdentifier, resolved, context); @@ -739,18 +696,12 @@ export default class Runtime { // this method can await it this._esmModuleLinkingMap.set( module, - module.link( - ( - specifier: string, - referencingModule: VMModule, - importCallOptions?: ImportCallOptions, - ) => - this.resolveModule( - specifier, - referencingModule.identifier, - referencingModule.context, - importCallOptions?.assert, - ), + module.link((specifier: string, referencingModule: VMModule) => + this.resolveModule( + specifier, + referencingModule.identifier, + referencingModule.context, + ), ), ); } @@ -1684,11 +1635,7 @@ export default class Runtime { displayErrors: true, filename: scriptFilename, // @ts-expect-error: Experimental ESM API - importModuleDynamically: async ( - specifier: string, - _script: Script, - importAssertions?: ImportAssertions, - ) => { + importModuleDynamically: async (specifier: string) => { invariant( runtimeSupportsVmModules, 'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules', @@ -1702,7 +1649,6 @@ export default class Runtime { specifier, scriptFilename, context, - importAssertions, ); return this.linkAndEvaluateModule(module); @@ -1754,7 +1700,6 @@ export default class Runtime { source: Buffer, identifier: string, context: VMContext, - importAssertions: ImportAssertions | undefined, ) { const wasmModule = await WebAssembly.compile(source); @@ -1768,7 +1713,6 @@ export default class Runtime { module, identifier, context, - importAssertions, ); moduleLookup[module] = await this.linkAndEvaluateModule(resolvedModule); diff --git a/yarn.lock b/yarn.lock index c05d756d5eb6..1a2cbefc33f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13039,7 +13039,6 @@ __metadata: "@types/glob": ^7.1.1 "@types/graceful-fs": ^4.1.3 "@types/node": "*" - "@types/semver": ^7.1.0 chalk: ^4.0.0 cjs-module-lexer: ^1.0.0 collect-v8-coverage: ^1.0.0 @@ -13053,7 +13052,6 @@ __metadata: jest-resolve: "workspace:^" jest-snapshot: "workspace:^" jest-util: "workspace:^" - semver: ^7.3.5 slash: ^3.0.0 strip-bom: ^4.0.0 languageName: unknown From 35feb857c97773a1e1e1c03bfb204d871e9ce3e1 Mon Sep 17 00:00:00 2001 From: Robert Kieffer Date: Tue, 14 Feb 2023 09:40:26 -0800 Subject: [PATCH 3/6] restore CHANGELOG --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4b4e06a54f7..ce86f3d161fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,7 +69,7 @@ - `[jest-resolve]` Correct node core module detection when using `node:` specifiers ([#13806](https://github.com/facebook/jest/pull/13806)) - `[jest-runtime]` Support WASM files that import JS resources ([#13608](https://github.com/facebook/jest/pull/13608)) - `[jest-runtime]` Use the `scriptTransformer` cache in `jest-runner` ([#13735](https://github.com/facebook/jest/pull/13735)) -- `[jest-runtime]` Enforce import assertions when importing JSON in ESM ([#12755](https://github.com/facebook/jest/pull/12755)) +- `[jest-runtime]` Enforce import assertions when importing JSON in ESM ([#12755](https://github.com/facebook/jest/pull/12755) & [#13805](https://github.com/facebook/jest/pull/13805)) - `[jest-snapshot]` Make sure to import `babel` outside of the sandbox ([#13694](https://github.com/facebook/jest/pull/13694)) - `[jest-transform]` Ensure the correct configuration is passed to preprocessors specified multiple times in the `transform` option ([#13770](https://github.com/facebook/jest/pull/13770)) From 08fb53179d4d856ef59a41d1081ac7568e901048 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Wed, 15 Feb 2023 10:31:32 +0100 Subject: [PATCH 4/6] still make sure json is esm --- packages/jest-runtime/src/index.ts | 72 ++++++++++++++++++------------ 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 35948a9464b4..ce567bde1730 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -409,7 +409,6 @@ export default class Runtime { ); } - // not async _now_, but transform will be private async loadEsmModule( modulePath: string, query = '', @@ -477,39 +476,52 @@ export default class Runtime { }); try { - const module = new SourceTextModule(transformedCode, { - context, - identifier: modulePath, - importModuleDynamically: async ( - specifier: string, - referencingModule: VMModule, - ) => { - invariant( - runtimeSupportsVmModules, - 'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules', - ); - const module = await this.resolveModule( - specifier, - referencingModule.identifier, - referencingModule.context, - ); + let module; + if (modulePath.endsWith('.json')) { + module = new SyntheticModule( + ['default'], + function () { + const obj = JSON.parse(transformedCode); + // @ts-expect-error: TS doesn't know what `this` is + this.setExport('default', obj); + }, + {context, identifier: modulePath}, + ); + } else { + module = new SourceTextModule(transformedCode, { + context, + identifier: modulePath, + importModuleDynamically: async ( + specifier: string, + referencingModule: VMModule, + ) => { + invariant( + runtimeSupportsVmModules, + 'You need to run with a version of node that supports ES Modules in the VM API. See https://jestjs.io/docs/ecmascript-modules', + ); + const module = await this.resolveModule( + specifier, + referencingModule.identifier, + referencingModule.context, + ); - return this.linkAndEvaluateModule(module); - }, - initializeImportMeta: (meta: JestImportMeta) => { - meta.url = pathToFileURL(modulePath).href; + return this.linkAndEvaluateModule(module); + }, + initializeImportMeta: (meta: JestImportMeta) => { + meta.url = pathToFileURL(modulePath).href; - let jest = this.jestObjectCaches.get(modulePath); + let jest = this.jestObjectCaches.get(modulePath); - if (!jest) { - jest = this._createJestObjectFor(modulePath); + if (!jest) { + jest = this._createJestObjectFor(modulePath); - this.jestObjectCaches.set(modulePath, jest); - } + this.jestObjectCaches.set(modulePath, jest); + } - meta.jest = jest; - }, - }); + meta.jest = jest; + }, + }); + } invariant( !this._esmoduleRegistry.has(cacheKey), @@ -671,6 +683,8 @@ export default class Runtime { const resolved = await this._resolveModule(referencingIdentifier, path); if ( + // json files are modules when imported in modules + resolved.endsWith('.json') || this._resolver.isCoreModule(resolved) || this.unstable_shouldLoadAsEsm(resolved) ) { From c1647ffe3d7ccb11e50513400f8b72f2781cc37f Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Wed, 15 Feb 2023 10:34:14 +0100 Subject: [PATCH 5/6] changelog --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ce86f3d161fa..213fd4026d2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ### Features -- `[jest-message-util]` Add support for [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) +- `[jest-message-util]` Add support for [error causes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) ([#13868](https://github.com/facebook/jest/pull/13868)) +- `[jest-runtime]` Revert `import assertions` for JSON modules as it's been relegated to Stage 2 ([#13911](https://github.com/facebook/jest/pull/13911)) ### Fixes From 127c6a53df3ef58c7507a48863a93b71640d2621 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Wed, 15 Feb 2023 10:37:26 +0100 Subject: [PATCH 6/6] keep tests but change their assertions --- .../__snapshots__/nativeEsm.test.ts.snap | 16 +++++++ e2e/__tests__/nativeEsm.test.ts | 47 +++++++++++++++++++ .../native-esm-import-assertions.test.js | 19 ++++++++ ...tive-esm-missing-import-assertions.test.js | 17 +++++++ 4 files changed, 99 insertions(+) create mode 100644 e2e/native-esm/__tests__/native-esm-import-assertions.test.js create mode 100644 e2e/native-esm/__tests__/native-esm-missing-import-assertions.test.js diff --git a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap index f3724c4ef073..c1b2cee1867c 100644 --- a/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap +++ b/e2e/__tests__/__snapshots__/nativeEsm.test.ts.snap @@ -1,5 +1,13 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`does not enforce import assertions 1`] = ` +"Test Suites: 1 passed, 1 total +Tests: 2 passed, 2 total +Snapshots: 0 total +Time: <> +Ran all test suites matching /native-esm-missing-import-assertions.test/i." +`; + exports[`on node >=16.9.0 support re-exports from CJS of dual packages 1`] = ` "Test Suites: 1 passed, 1 total Tests: 1 passed, 1 total @@ -8,6 +16,14 @@ Time: <> Ran all test suites matching /native-esm-deep-cjs-reexport.test.js/i." `; +exports[`on node >=16.12.0 supports import assertions 1`] = ` +"Test Suites: 1 passed, 1 total +Tests: 2 passed, 2 total +Snapshots: 0 total +Time: <> +Ran all test suites matching /native-esm-import-assertions.test/i." +`; + exports[`runs WebAssembly (Wasm) test with native ESM 1`] = ` "Test Suites: 1 passed, 1 total Tests: 6 passed, 6 total diff --git a/e2e/__tests__/nativeEsm.test.ts b/e2e/__tests__/nativeEsm.test.ts index f91b3e57fa3c..d31b46ac7c1c 100644 --- a/e2e/__tests__/nativeEsm.test.ts +++ b/e2e/__tests__/nativeEsm.test.ts @@ -93,3 +93,50 @@ test('runs WebAssembly (Wasm) test with native ESM', () => { expect(stdout).toBe(''); expect(exitCode).toBe(0); }); + +test('does not enforce import assertions', () => { + const {exitCode, stderr, stdout} = runJest( + DIR, + ['native-esm-missing-import-assertions.test'], + {nodeOptions: '--experimental-vm-modules --no-warnings'}, + ); + + const {summary} = extractSummary(stderr); + + expect(summary).toMatchSnapshot(); + expect(stdout).toBe(''); + expect(exitCode).toBe(0); +}); + +// version where `vm` API gets `import assertions` +onNodeVersions('>=16.12.0', () => { + test('supports import assertions', () => { + const {exitCode, stderr, stdout} = runJest( + DIR, + ['native-esm-import-assertions.test'], + {nodeOptions: '--experimental-vm-modules --no-warnings'}, + ); + + const {summary} = extractSummary(stderr); + + expect(summary).toMatchSnapshot(); + expect(stdout).toBe(''); + expect(exitCode).toBe(0); + }); +}); + +onNodeVersions('<16.12.0', () => { + test('syntax error for import assertions', () => { + const {exitCode, stderr, stdout} = runJest( + DIR, + ['native-esm-import-assertions.test'], + {nodeOptions: '--experimental-vm-modules --no-warnings'}, + ); + + const {rest} = extractSummary(stderr); + + expect(rest).toContain('SyntaxError: Unexpected identifier'); + expect(stdout).toBe(''); + expect(exitCode).toBe(1); + }); +}); diff --git a/e2e/native-esm/__tests__/native-esm-import-assertions.test.js b/e2e/native-esm/__tests__/native-esm-import-assertions.test.js new file mode 100644 index 000000000000..6a2d16b42df7 --- /dev/null +++ b/e2e/native-esm/__tests__/native-esm-import-assertions.test.js @@ -0,0 +1,19 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import json from '../package.json' assert {type: 'json'}; + +test('supports static import', () => { + expect(json).toHaveProperty('jest.testEnvironment', 'node'); +}); + +test('supports dynamic import', async () => { + const {default: json} = await import('../package.json', { + assert: {type: 'json'}, + }); + expect(json).toHaveProperty('jest.testEnvironment', 'node'); +}); diff --git a/e2e/native-esm/__tests__/native-esm-missing-import-assertions.test.js b/e2e/native-esm/__tests__/native-esm-missing-import-assertions.test.js new file mode 100644 index 000000000000..aece5bd8615d --- /dev/null +++ b/e2e/native-esm/__tests__/native-esm-missing-import-assertions.test.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import json from '../package.json'; + +test('supports static import', () => { + expect(json).toHaveProperty('jest.testEnvironment', 'node'); +}); + +test('supports dynamic import', async () => { + const {default: json} = await import('../package.json'); + expect(json).toHaveProperty('jest.testEnvironment', 'node'); +});