From 39f107ffeb8cf9a0ddb7975cf1e18d55f04e196e Mon Sep 17 00:00:00 2001 From: Geoffrey Booth Date: Mon, 18 Sep 2023 19:48:24 -0700 Subject: [PATCH 1/6] module, esm: jsdoc for modules files PR-URL: https://github.com/nodejs/node/pull/49523 Reviewed-By: James M Snell Reviewed-By: Yagiz Nizipli --- lib/internal/dns/promises.js | 24 ++ lib/internal/modules/cjs/loader.js | 288 +++++++++++++++--- .../modules/esm/create_dynamic_module.js | 17 ++ lib/internal/modules/esm/fetch_module.js | 39 ++- .../modules/esm/handle_process_exit.js | 8 +- lib/internal/modules/esm/loader.js | 12 + lib/internal/modules/esm/package_config.js | 19 +- lib/internal/modules/esm/resolve.js | 238 ++++++++++----- lib/internal/modules/esm/translators.js | 53 ++++ lib/internal/modules/esm/utils.js | 46 ++- lib/internal/modules/esm/worker.js | 36 +++ lib/internal/modules/helpers.js | 63 +++- lib/internal/modules/run_main.js | 25 +- 13 files changed, 700 insertions(+), 168 deletions(-) diff --git a/lib/internal/dns/promises.js b/lib/internal/dns/promises.js index 79be8591bbcad2..1169b2735d4efe 100644 --- a/lib/internal/dns/promises.js +++ b/lib/internal/dns/promises.js @@ -113,6 +113,19 @@ function onlookupall(err, addresses) { } } +/** + * Creates a promise that resolves with the IP address of the given hostname. + * @param {0 | 4 | 6} family - The IP address family (4 or 6, or 0 for both). + * @param {string} hostname - The hostname to resolve. + * @param {boolean} all - Whether to resolve with all IP addresses for the hostname. + * @param {number} hints - One or more supported getaddrinfo flags (supply multiple via + * bitwise OR). + * @param {boolean} verbatim - Whether to use the hostname verbatim. + * @returns {Promise} The IP address(es) of the hostname. + * @typedef {object} DNSLookupResult + * @property {string} address - The IP address. + * @property {0 | 4 | 6} family - The IP address type. 4 for IPv4 or 6 for IPv6, or 0 (for both). + */ function createLookupPromise(family, hostname, all, hints, verbatim) { return new Promise((resolve, reject) => { if (!hostname) { @@ -154,6 +167,17 @@ function createLookupPromise(family, hostname, all, hints, verbatim) { } const validFamilies = [0, 4, 6]; +/** + * Get the IP address for a given hostname. + * @param {string} hostname - The hostname to resolve (ex. 'nodejs.org'). + * @param {object} [options] - Optional settings. + * @param {boolean} [options.all=false] - Whether to return all or just the first resolved address. + * @param {0 | 4 | 6} [options.family=0] - The record family. Must be 4, 6, or 0 (for both). + * @param {number} [options.hints] - One or more supported getaddrinfo flags (supply multiple via + * bitwise OR). + * @param {boolean} [options.verbatim=false] - Return results in same order DNS resolved them; + * otherwise IPv4 then IPv6. New code should supply `true`. + */ function lookup(hostname, options) { let hints = 0; let family = 0; diff --git a/lib/internal/modules/cjs/loader.js b/lib/internal/modules/cjs/loader.js index 19a7d7e671f5ab..5fe9e874c7ad9f 100644 --- a/lib/internal/modules/cjs/loader.js +++ b/lib/internal/modules/cjs/loader.js @@ -155,6 +155,11 @@ let requireDepth = 0; let isPreloading = false; let statCache = null; +/** + * Our internal implementation of `require`. + * @param {Module} module Parent module of what is being required + * @param {string} id Specifier of the child module being imported + */ function internalRequire(module, id) { validateString(id, 'id'); if (id === '') { @@ -169,6 +174,10 @@ function internalRequire(module, id) { } } +/** + * Get a path's properties, using an in-memory cache to minimize lookups. + * @param {string} filename Absolute path to the file + */ function stat(filename) { filename = path.toNamespacedPath(filename); if (statCache !== null) { @@ -195,25 +204,46 @@ ObjectDefineProperty(Module, '_stat', { configurable: true, }); +/** + * Update the parent's children array with the child module. + * @param {Module} parent Module requiring the children + * @param {Module} child Module being required + * @param {boolean} scan Add the child to the parent's children if not already present + */ function updateChildren(parent, child, scan) { const children = parent?.children; if (children && !(scan && ArrayPrototypeIncludes(children, child))) ArrayPrototypePush(children, child); } +/** + * Tell the watch mode that a module was required. + * @param {string} filename Absolute path of the module + */ function reportModuleToWatchMode(filename) { if (shouldReportRequiredModules() && process.send) { process.send({ 'watch:require': [filename] }); } } +/** + * Tell the watch mode that a module was not found. + * @param {string} basePath The absolute path that errored + * @param {string[]} extensions The extensions that were tried + */ function reportModuleNotFoundToWatchMode(basePath, extensions) { if (shouldReportRequiredModules() && process.send) { process.send({ 'watch:require': ArrayPrototypeMap(extensions, (ext) => path.resolve(`${basePath}${ext}`)) }); } } +/** @type {Map} */ const moduleParentCache = new SafeWeakMap(); +/** + * Create a new module instance. + * @param {string} id + * @param {Module} parent + */ function Module(id = '', parent) { this.id = id; this.path = path.dirname(id); @@ -236,16 +266,24 @@ function Module(id = '', parent) { this[require_private_symbol] = internalRequire; } +/** @type {Record} */ Module._cache = { __proto__: null }; +/** @type {Record} */ Module._pathCache = { __proto__: null }; +/** @type {Record void>} */ Module._extensions = { __proto__: null }; +/** @type {string[]} */ let modulePaths = []; +/** @type {string[]} */ Module.globalPaths = []; let patched = false; -// eslint-disable-next-line func-style -let wrap = function(script) { +/** + * Add the CommonJS wrapper around a module's source code. + * @param {string} script Module source code + */ +let wrap = function(script) { // eslint-disable-line func-style return Module.wrapper[0] + script + Module.wrapper[1]; }; @@ -296,10 +334,17 @@ const isPreloadingDesc = { get() { return isPreloading; } }; ObjectDefineProperty(Module.prototype, 'isPreloading', isPreloadingDesc); ObjectDefineProperty(BuiltinModule.prototype, 'isPreloading', isPreloadingDesc); +/** + * Get the parent of the current module from our cache. + */ function getModuleParent() { return moduleParentCache.get(this); } +/** + * Set the parent of the current module in our cache. + * @param {Module} value + */ function setModuleParent(value) { moduleParentCache.set(this, value); } @@ -326,7 +371,10 @@ ObjectDefineProperty(Module.prototype, 'parent', { Module._debug = pendingDeprecate(debug, 'Module._debug is deprecated.', 'DEP0077'); Module.isBuiltin = BuiltinModule.isBuiltin; -// This function is called during pre-execution, before any user code is run. +/** + * Prepare to run CommonJS code. + * This function is called during pre-execution, before any user code is run. + */ function initializeCJS() { // This need to be done at runtime in case --expose-internals is set. const builtinModules = BuiltinModule.getCanBeRequiredByUsersWithoutSchemeList(); @@ -374,6 +422,11 @@ ObjectDefineProperty(Module, '_readPackage', { configurable: true, }); +/** + * Get the nearest parent package.json file from a given path. + * Return the package.json data and the path to the package.json file, or false. + * @param {string} checkPath The path to start searching from. + */ function readPackageScope(checkPath) { const rootSeparatorIndex = StringPrototypeIndexOf(checkPath, sep); let separatorIndex; @@ -397,6 +450,13 @@ function readPackageScope(checkPath) { return false; } +/** + * Try to load a specifier as a package. + * @param {string} requestPath The path to what we are trying to load + * @param {string[]} exts File extensions to try appending in order to resolve the file + * @param {boolean} isMain Whether the file is the main entry point of the app + * @param {string} originalPath The specifier passed to `require` + */ function tryPackage(requestPath, exts, isMain, originalPath) { const pkg = _readPackage(requestPath).main; @@ -434,15 +494,20 @@ function tryPackage(requestPath, exts, isMain, originalPath) { return actual; } -// In order to minimize unnecessary lstat() calls, -// this cache is a list of known-real paths. -// Set to an empty Map to reset. +/** + * Cache for storing resolved real paths of modules. + * In order to minimize unnecessary lstat() calls, this cache is a list of known-real paths. + * Set to an empty Map to reset. + * @type {Map} + */ const realpathCache = new SafeMap(); -// Check if the file exists and is not a directory -// if using --preserve-symlinks and isMain is false, -// keep symlinks intact, otherwise resolve to the -// absolute realpath. +/** + * Check if the file exists and is not a directory if using `--preserve-symlinks` and `isMain` is false, keep symlinks + * intact, otherwise resolve to the absolute realpath. + * @param {string} requestPath The path to the file to load. + * @param {boolean} isMain Whether the file is the main module. + */ function tryFile(requestPath, isMain) { const rc = _stat(requestPath); if (rc !== 0) return; @@ -452,16 +517,26 @@ function tryFile(requestPath, isMain) { return toRealPath(requestPath); } + +/** + * Resolves the path of a given `require` specifier, following symlinks. + * @param {string} requestPath The `require` specifier + */ function toRealPath(requestPath) { return fs.realpathSync(requestPath, { [internalFS.realpathCacheKey]: realpathCache, }); } -// Given a path, check if the file exists with any of the set extensions -function tryExtensions(p, exts, isMain) { +/** + * Given a path, check if the file exists with any of the set extensions. + * @param {string} basePath The path and filename without extension + * @param {string[]} exts The extensions to try + * @param {boolean} isMain Whether the module is the main module + */ +function tryExtensions(basePath, exts, isMain) { for (let i = 0; i < exts.length; i++) { - const filename = tryFile(p + exts[i], isMain); + const filename = tryFile(basePath + exts[i], isMain); if (filename) { return filename; @@ -470,8 +545,10 @@ function tryExtensions(p, exts, isMain) { return false; } -// Find the longest (possibly multi-dot) extension registered in -// Module._extensions +/** + * Find the longest (possibly multi-dot) extension registered in `Module._extensions`. + * @param {string} filename The filename to find the longest registered extension for. + */ function findLongestRegisteredExtension(filename) { const name = path.basename(filename); let currentExtension; @@ -486,6 +563,10 @@ function findLongestRegisteredExtension(filename) { return '.js'; } +/** + * Tries to get the absolute file path of the parent module. + * @param {Module} parent The parent module object. + */ function trySelfParentPath(parent) { if (!parent) return false; @@ -500,6 +581,11 @@ function trySelfParentPath(parent) { } } +/** + * Attempt to resolve a module request using the parent module package metadata. + * @param {string} parentPath The path of the parent module + * @param {string} request The module request to resolve + */ function trySelf(parentPath, request) { if (!parentPath) return false; @@ -529,10 +615,18 @@ function trySelf(parentPath, request) { } } -// This only applies to requests of a specific form: -// 1. name/.* -// 2. @scope/name/.* +/** + * This only applies to requests of a specific form: + * 1. `name/.*` + * 2. `@scope/name/.*` + */ const EXPORTS_PATTERN = /^((?:@[^/\\%]+\/)?[^./\\%][^/\\%]*)(\/.*)?$/; + +/** + * Resolves the exports for a given module path and request. + * @param {string} nmPath The path to the module. + * @param {string} request The request for the module. + */ function resolveExports(nmPath, request) { // The implementation's behavior is meant to mirror resolution in ESM. const { 1: name, 2: expansion = '' } = @@ -556,9 +650,10 @@ function resolveExports(nmPath, request) { } /** - * @param {string} request a relative or absolute file path - * @param {Array} paths file system directories to search as file paths - * @param {boolean} isMain if the request is the main app entry point + * Get the absolute path to a module. + * @param {string} request Relative or absolute file path + * @param {Array} paths Folders to search as file paths + * @param {boolean} isMain Whether the request is the main app entry point * @returns {string | false} */ Module._findPath = function(request, paths, isMain) { @@ -680,11 +775,14 @@ Module._findPath = function(request, paths, isMain) { return false; }; -// 'node_modules' character codes reversed +/** `node_modules` character codes reversed */ const nmChars = [ 115, 101, 108, 117, 100, 111, 109, 95, 101, 100, 111, 110 ]; const nmLen = nmChars.length; if (isWindows) { - // 'from' is the __dirname of the module. + /** + * Get the paths to the `node_modules` folder for a given path. + * @param {string} from `__dirname` of the module + */ Module._nodeModulePaths = function(from) { // Guarantee that 'from' is absolute. from = path.resolve(from); @@ -700,6 +798,7 @@ if (isWindows) { StringPrototypeCharCodeAt(from, from.length - 2) === CHAR_COLON) return [from + 'node_modules']; + /** @type {string[]} */ const paths = []; for (let i = from.length - 1, p = 0, last = from.length; i >= 0; --i) { const code = StringPrototypeCharCodeAt(from, i); @@ -730,7 +829,10 @@ if (isWindows) { return paths; }; } else { // posix - // 'from' is the __dirname of the module. + /** + * Get the paths to the `node_modules` folder for a given path. + * @param {string} from `__dirname` of the module + */ Module._nodeModulePaths = function(from) { // Guarantee that 'from' is absolute. from = path.resolve(from); @@ -742,6 +844,7 @@ if (isWindows) { // note: this approach *only* works when the path is guaranteed // to be absolute. Doing a fully-edge-case-correct path.split // that works on both Windows and Posix is non-trivial. + /** @type {string[]} */ const paths = []; for (let i = from.length - 1, p = 0, last = from.length; i >= 0; --i) { const code = StringPrototypeCharCodeAt(from, i); @@ -769,6 +872,11 @@ if (isWindows) { }; } +/** + * Get the paths for module resolution. + * @param {string} request + * @param {Module} parent + */ Module._resolveLookupPaths = function(request, parent) { if (BuiltinModule.normalizeRequirableId(request)) { debug('looking for %j in []', request); @@ -782,6 +890,7 @@ Module._resolveLookupPaths = function(request, parent) { StringPrototypeCharAt(request, 1) !== '/' && (!isWindows || StringPrototypeCharAt(request, 1) !== '\\'))) { + /** @type {string[]} */ let paths; if (parent?.paths?.length) { paths = ArrayPrototypeSlice(modulePaths); @@ -811,6 +920,10 @@ Module._resolveLookupPaths = function(request, parent) { return parentDir; }; +/** + * Emits a warning when a non-existent property of module exports is accessed inside a circular dependency. + * @param {string} prop The name of the non-existent property. + */ function emitCircularRequireWarning(prop) { process.emitWarning( `Accessing non-existent property '${String(prop)}' of module exports ` + @@ -840,6 +953,12 @@ const CircularRequirePrototypeWarningProxy = new Proxy({}, { }, }); +/** + * Returns the exports object for a module that has a circular `require`. + * If the exports object is a plain object, it is wrapped in a proxy that warns + * about circular dependencies. + * @param {Module} module The module instance + */ function getExportsForCircularRequire(module) { if (module.exports && !isProxy(module.exports) && @@ -857,13 +976,17 @@ function getExportsForCircularRequire(module) { return module.exports; } -// Check the cache for the requested file. -// 1. If a module already exists in the cache: return its exports object. -// 2. If the module is native: call -// `BuiltinModule.prototype.compileForPublicLoader()` and return the exports. -// 3. Otherwise, create a new module for the file and save it to the cache. -// Then have it load the file contents before returning its exports -// object. +/** + * Load a module from cache if it exists, otherwise create a new module instance. + * 1. If a module already exists in the cache: return its exports object. + * 2. If the module is native: call + * `BuiltinModule.prototype.compileForPublicLoader()` and return the exports. + * 3. Otherwise, create a new module for the file and save it to the cache. + * Then have it load the file contents before returning its exports object. + * @param {string} request Specifier of module to load via `require` + * @param {string} parent Absolute path of the module importing the child + * @param {boolean} isMain Whether the module is the main entry point + */ Module._load = function(request, parent, isMain) { let relResolveCacheIdentifier; if (parent) { @@ -961,6 +1084,15 @@ Module._load = function(request, parent, isMain) { return module.exports; }; +/** + * Given a `require` string and its context, get its absolute file path. + * @param {string} request The specifier to resolve + * @param {Module} parent The module containing the `require` call + * @param {boolean} isMain Whether the module is the main entry point + * @param {ResolveFilenameOptions} options Options object + * @typedef {object} ResolveFilenameOptions + * @property {string[]} paths Paths to search for modules in + */ Module._resolveFilename = function(request, parent, isMain, options) { if (BuiltinModule.normalizeRequirableId(request)) { return request; @@ -1051,6 +1183,14 @@ Module._resolveFilename = function(request, parent, isMain, options) { throw err; }; +/** + * Finishes resolving an ES module specifier into an absolute file path. + * @param {string} resolved The resolved module specifier + * @param {string} parentPath The path of the parent module + * @param {string} pkgPath The path of the package.json file + * @throws {ERR_INVALID_MODULE_SPECIFIER} If the resolved module specifier contains encoded `/` or `\\` characters + * @throws {Error} If the module cannot be found + */ function finalizeEsmResolution(resolved, parentPath, pkgPath) { const { encodedSepRegEx } = require('internal/modules/esm/resolve'); if (RegExpPrototypeExec(encodedSepRegEx, resolved) !== null) @@ -1065,6 +1205,11 @@ function finalizeEsmResolution(resolved, parentPath, pkgPath) { throw err; } +/** + * Creates an error object for when a requested ES module cannot be found. + * @param {string} request The name of the requested module + * @param {string} [path] The path to the requested module + */ function createEsmNotFoundErr(request, path) { // eslint-disable-next-line no-restricted-syntax const err = new Error(`Cannot find module '${request}'`); @@ -1075,7 +1220,10 @@ function createEsmNotFoundErr(request, path) { return err; } -// Given a file name, pass it to the proper extension handler. +/** + * Given a file name, pass it to the proper extension handler. + * @param {string} filename The `require` specifier + */ Module.prototype.load = function(filename) { debug('load %j for module %j', filename, this.id); @@ -1101,9 +1249,12 @@ Module.prototype.load = function(filename) { cascadedLoader.cjsCache.set(this, exports); }; -// Loads a module at the given file path. Returns that module's -// `exports` property. -// Note: when using the experimental policy mechanism this function is overridden +/** + * Loads a module at the given file path. Returns that module's `exports` property. + * Note: when using the experimental policy mechanism this function is overridden. + * @param {string} id + * @throws {ERR_INVALID_ARG_TYPE} When `id` is not a string + */ Module.prototype.require = function(id) { validateString(id, 'id'); if (id === '') { @@ -1118,11 +1269,23 @@ Module.prototype.require = function(id) { } }; -// Resolved path to process.argv[1] will be lazily placed here -// (needed for setting breakpoint when called with --inspect-brk) +/** + * Resolved path to `process.argv[1]` will be lazily placed here + * (needed for setting breakpoint when called with `--inspect-brk`). + * @type {string | undefined} + */ let resolvedArgv; let hasPausedEntry = false; +/** @type {import('vm').Script} */ let Script; + +/** + * Wraps the given content in a script and runs it in a new context. + * @param {string} filename The name of the file being loaded + * @param {string} content The content of the file being loaded + * @param {Module} cjsModuleInstance The CommonJS loader instance + * @param {object} codeCache The SEA code cache + */ function wrapSafe(filename, content, cjsModuleInstance, codeCache) { if (patched) { const wrapper = Module.wrap(content); @@ -1188,10 +1351,12 @@ function wrapSafe(filename, content, cjsModuleInstance, codeCache) { } } -// Run the file contents in the correct scope or sandbox. Expose -// the correct helper variables (require, module, exports) to -// the file. -// Returns exception, if any. +/** + * Run the file contents in the correct scope or sandbox. Expose the correct helper variables (`require`, `module`, + * `exports`) to the file. Returns exception, if any. + * @param {string} content The source code of the module + * @param {string} filename The file path of the module + */ Module.prototype._compile = function(content, filename) { let moduleURL; let redirects; @@ -1246,7 +1411,11 @@ Module.prototype._compile = function(content, filename) { return result; }; -// Native extension for .js +/** + * Native handler for `.js` files. + * @param {Module} module The module to compile + * @param {string} filename The file path of the module + */ Module._extensions['.js'] = function(module, filename) { // If already analyzed the source, then it will be cached. const cached = cjsParseCache.get(module); @@ -1295,8 +1464,11 @@ Module._extensions['.js'] = function(module, filename) { module._compile(content, filename); }; - -// Native extension for .json +/** + * Native handler for `.json` files. + * @param {Module} module The module to compile + * @param {string} filename The file path of the module + */ Module._extensions['.json'] = function(module, filename) { const content = fs.readFileSync(filename, 'utf8'); @@ -1314,8 +1486,11 @@ Module._extensions['.json'] = function(module, filename) { } }; - -// Native extension for .node +/** + * Native handler for `.node` files. + * @param {Module} module The module to compile + * @param {string} filename The file path of the module + */ Module._extensions['.node'] = function(module, filename) { const manifest = policy()?.manifest; if (manifest) { @@ -1327,6 +1502,10 @@ Module._extensions['.node'] = function(module, filename) { return process.dlopen(module, path.toNamespacedPath(filename)); }; +/** + * Creates a `require` function that can be used to load modules from the specified path. + * @param {string} filename The path to the module + */ function createRequireFromPath(filename) { // Allow a directory to be passed as the filename const trailingSlash = @@ -1347,6 +1526,12 @@ function createRequireFromPath(filename) { const createRequireError = 'must be a file URL object, file URL string, or ' + 'absolute path string'; +/** + * Creates a new `require` function that can be used to load modules. + * @param {string | URL} filename The path or URL to the module context for this `require` + * @throws {ERR_INVALID_ARG_VALUE} If `filename` is not a string or URL, or if it is a relative path that cannot be + * resolved to an absolute path. + */ function createRequire(filename) { let filepath; @@ -1368,6 +1553,9 @@ function createRequire(filename) { Module.createRequire = createRequire; +/** + * Define the paths to use for resolving a module. + */ Module._initPaths = function() { const homeDir = isWindows ? process.env.USERPROFILE : safeGetenv('HOME'); const nodePath = isWindows ? process.env.NODE_PATH : safeGetenv('NODE_PATH'); @@ -1398,6 +1586,10 @@ Module._initPaths = function() { Module.globalPaths = ArrayPrototypeSlice(modulePaths); }; +/** + * Handle modules loaded via `--require`. + * @param {string[]} requests The values of `--require` + */ Module._preloadModules = function(requests) { if (!ArrayIsArray(requests)) return; @@ -1421,6 +1613,10 @@ Module._preloadModules = function(requests) { isPreloading = false; }; +/** + * If the user has overridden an export from a builtin module, this function can ensure that the override is used in + * both CommonJS and ES module contexts. + */ Module.syncBuiltinESMExports = function syncBuiltinESMExports() { for (const mod of BuiltinModule.map.values()) { if (BuiltinModule.canBeRequiredWithoutScheme(mod.id)) { diff --git a/lib/internal/modules/esm/create_dynamic_module.js b/lib/internal/modules/esm/create_dynamic_module.js index 5d7f3fed60ed8e..09f46272e8cb9e 100644 --- a/lib/internal/modules/esm/create_dynamic_module.js +++ b/lib/internal/modules/esm/create_dynamic_module.js @@ -11,6 +11,11 @@ let debug = require('internal/util/debuglog').debuglog('esm', (fn) => { debug = fn; }); +/** + * Creates an import statement for a given module path and index. + * @param {string} impt - The module path to import. + * @param {number} index - The index of the import statement. + */ function createImport(impt, index) { const imptPath = JSONStringify(impt); return `import * as $import_${index} from ${imptPath}; @@ -27,6 +32,17 @@ import.meta.exports[${nameStringLit}] = { };`; } +/** + * Creates a dynamic module with the given imports, exports, URL, and evaluate function. + * @param {string[]} imports - An array of imports. + * @param {string[]} exports - An array of exports. + * @param {string} [url=''] - The URL of the module. + * @param {(reflect: DynamicModuleReflect) => void} evaluate - The function to evaluate the module. + * @typedef {object} DynamicModuleReflect + * @property {string[]} imports - The imports of the module. + * @property {string[]} exports - The exports of the module. + * @property {(cb: (reflect: DynamicModuleReflect) => void) => void} onReady - Callback to evaluate the module. + */ const createDynamicModule = (imports, exports, url = '', evaluate) => { debug('creating ESM facade for %s with exports: %j', url, exports); const source = ` @@ -38,6 +54,7 @@ import.meta.done(); const m = new ModuleWrap(`${url}`, undefined, source, 0, 0); const readyfns = new SafeSet(); + /** @type {DynamicModuleReflect} */ const reflect = { exports: { __proto__: null }, onReady: (cb) => { readyfns.add(cb); }, diff --git a/lib/internal/modules/esm/fetch_module.js b/lib/internal/modules/esm/fetch_module.js index ca5c9c83c316de..21b7456899604f 100644 --- a/lib/internal/modules/esm/fetch_module.js +++ b/lib/internal/modules/esm/fetch_module.js @@ -44,37 +44,56 @@ const cacheForGET = new SafeMap(); // [2] Creating a new agent instead of using the gloabl agent improves // performance and precludes the agent becoming tainted. +/** @type {import('https').Agent} The Cached HTTP Agent for **secure** HTTP requests. */ let HTTPSAgent; -function HTTPSGet(url, opts) { +/** + * Make a HTTPs GET request (handling agent setup if needed, caching the agent to avoid + * redudant instantiations). + * @param {Parameters[0]} input - The URI to fetch. + * @param {Parameters[1]} options - See https.get() options. + */ +function HTTPSGet(input, options) { const https = require('https'); // [1] HTTPSAgent ??= new https.Agent({ // [2] keepAlive: true, }); - return https.get(url, { + return https.get(input, { agent: HTTPSAgent, - ...opts, + ...options, }); } +/** @type {import('https').Agent} The Cached HTTP Agent for **insecure** HTTP requests. */ let HTTPAgent; -function HTTPGet(url, opts) { +/** + * Make a HTTP GET request (handling agent setup if needed, caching the agent to avoid + * redudant instantiations). + * @param {Parameters[0]} input - The URI to fetch. + * @param {Parameters[1]} options - See http.get() options. + */ +function HTTPGet(input, options) { const http = require('http'); // [1] HTTPAgent ??= new http.Agent({ // [2] keepAlive: true, }); - return http.get(url, { + return http.get(input, { agent: HTTPAgent, - ...opts, + ...options, }); } -function dnsLookup(name, opts) { +/** @type {import('../../dns/promises.js').lookup} */ +function dnsLookup(hostname, options) { // eslint-disable-next-line no-func-assign dnsLookup = require('dns/promises').lookup; - return dnsLookup(name, opts); + return dnsLookup(hostname, options); } let zlib; +/** + * Create a decompressor for the Brotli format. + * @returns {import('zlib').BrotliDecompress} + */ function createBrotliDecompress() { zlib ??= require('zlib'); // [1] // eslint-disable-next-line no-func-assign @@ -82,6 +101,10 @@ function createBrotliDecompress() { return createBrotliDecompress(); } +/** + * Create an unzip handler. + * @returns {import('zlib').Unzip} + */ function createUnzip() { zlib ??= require('zlib'); // [1] // eslint-disable-next-line no-func-assign diff --git a/lib/internal/modules/esm/handle_process_exit.js b/lib/internal/modules/esm/handle_process_exit.js index 9d6b609ef1cfc3..4689ef6bb204c0 100644 --- a/lib/internal/modules/esm/handle_process_exit.js +++ b/lib/internal/modules/esm/handle_process_exit.js @@ -2,9 +2,11 @@ const { exitCodes: { kUnfinishedTopLevelAwait } } = internalBinding('errors'); -// Handle a Promise from running code that potentially does Top-Level Await. -// In that case, it makes sense to set the exit code to a specific non-zero -// value if the main code never finishes running. +/** + * Handle a Promise from running code that potentially does Top-Level Await. + * In that case, it makes sense to set the exit code to a specific non-zero value + * if the main code never finishes running. + */ function handleProcessExit() { process.exitCode ??= kUnfinishedTopLevelAwait; } diff --git a/lib/internal/modules/esm/loader.js b/lib/internal/modules/esm/loader.js index 109ec9be8aee69..0b08db7ffef7ec 100644 --- a/lib/internal/modules/esm/loader.js +++ b/lib/internal/modules/esm/loader.js @@ -28,16 +28,28 @@ const { } = require('internal/modules/esm/utils'); let defaultResolve, defaultLoad, defaultLoadSync, importMetaInitializer; +/** + * Lazy loads the module_map module and returns a new instance of ResolveCache. + * @returns {import('./module_map.js').ResolveCache')} + */ function newResolveCache() { const { ResolveCache } = require('internal/modules/esm/module_map'); return new ResolveCache(); } +/** + * Generate a load cache (to store the final result of a load-chain for a particular module). + * @returns {import('./module_map.js').LoadCache')} + */ function newLoadCache() { const { LoadCache } = require('internal/modules/esm/module_map'); return new LoadCache(); } +/** + * Lazy-load translators to avoid potentially unnecessary work at startup (ex if ESM is not used). + * @returns {import('./translators.js').Translators} + */ function getTranslators() { const { translators } = require('internal/modules/esm/translators'); return translators; diff --git a/lib/internal/modules/esm/package_config.js b/lib/internal/modules/esm/package_config.js index 4ca701d4810f74..5da47764c9de2c 100644 --- a/lib/internal/modules/esm/package_config.js +++ b/lib/internal/modules/esm/package_config.js @@ -7,8 +7,23 @@ const { URL, fileURLToPath } = require('internal/url'); const packageJsonReader = require('internal/modules/package_json_reader'); /** - * @param {URL | string} resolved - * @returns {PackageConfig} + * @typedef {object} PackageConfig + * @property {string} pjsonPath - The path to the package.json file. + * @property {boolean} exists - Whether the package.json file exists. + * @property {'none' | 'commonjs' | 'module'} type - The type of the package. + * @property {string} [name] - The name of the package. + * @property {string} [main] - The main entry point of the package. + * @property {PackageTarget} [exports] - The exports configuration of the package. + * @property {Record>} [imports] - The imports configuration of the package. + */ +/** + * @typedef {string | string[] | Record>} PackageTarget + */ + +/** + * Returns the package configuration for the given resolved URL. + * @param {URL | string} resolved - The resolved URL. + * @returns {PackageConfig} - The package configuration. */ function getPackageScopeConfig(resolved) { let packageJSONUrl = new URL('./package.json', resolved); diff --git a/lib/internal/modules/esm/resolve.js b/lib/internal/modules/esm/resolve.js index acb5ddca8af3d2..38ccf0a8496aa3 100644 --- a/lib/internal/modules/esm/resolve.js +++ b/lib/internal/modules/esm/resolve.js @@ -67,6 +67,13 @@ const { internalModuleStat } = internalBinding('fs'); const emittedPackageWarnings = new SafeSet(); +/** + * Emits a deprecation warning for the use of a deprecated trailing slash pattern mapping in the "exports" field + * module resolution of a package. + * @param {string} match - The deprecated trailing slash pattern mapping. + * @param {string} pjsonUrl - The URL of the package.json file. + * @param {string} base - The URL of the module that imported the package. + */ function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) { const pjsonPath = fileURLToPath(pjsonUrl); if (emittedPackageWarnings.has(pjsonPath + '|' + match)) @@ -84,6 +91,16 @@ function emitTrailingSlashPatternDeprecation(match, pjsonUrl, base) { const doubleSlashRegEx = /[/\\][/\\]/; +/** + * Emits a deprecation warning for invalid segment in module resolution. + * @param {string} target - The target module. + * @param {string} request - The requested module. + * @param {string} match - The matched module. + * @param {string} pjsonUrl - The package.json URL. + * @param {boolean} internal - Whether the module is in the "imports" or "exports" field. + * @param {string} base - The base URL. + * @param {boolean} isTarget - Whether the target is a module. + */ function emitInvalidSegmentDeprecation(target, request, match, pjsonUrl, internal, base, isTarget) { const pjsonPath = fileURLToPath(pjsonUrl); const double = RegExpPrototypeExec(doubleSlashRegEx, isTarget ? target : request) !== null; @@ -99,11 +116,12 @@ function emitInvalidSegmentDeprecation(target, request, match, pjsonUrl, interna } /** - * @param {URL} url - * @param {URL} packageJSONUrl - * @param {string | URL | undefined} base - * @param {string} [main] - * @returns {void} + * Emits a deprecation warning if the given URL is a module and + * the package.json file does not define a "main" or "exports" field. + * @param {URL} url - The URL of the module being resolved. + * @param {URL} packageJSONUrl - The URL of the package.json file for the module. + * @param {string | URL} [base] - The base URL for the module being resolved. + * @param {string} [main] - The "main" field from the package.json file. */ function emitLegacyIndexDeprecation(url, packageJSONUrl, base, main) { const format = defaultGetFormatWithoutErrors(url); @@ -198,10 +216,15 @@ function legacyMainResolve(packageJSONUrl, packageConfig, base) { const encodedSepRegEx = /%2F|%5C/i; /** - * @param {URL} resolved - * @param {string | URL | undefined} base - * @param {boolean} preserveSymlinks - * @returns {URL | undefined} + * Finalizes the resolution of a module specifier by checking if the resolved pathname contains encoded "/" or "\\" + * characters, checking if the resolved pathname is a directory or file, and resolving any symlinks if necessary. + * @param {URL} resolved - The resolved URL object. + * @param {string | URL | undefined} base - The base URL object. + * @param {boolean} preserveSymlinks - Whether to preserve symlinks or not. + * @returns {URL} - The finalized URL object. + * @throws {ERR_INVALID_MODULE_SPECIFIER} - If the resolved pathname contains encoded "/" or "\\" characters. + * @throws {ERR_UNSUPPORTED_DIR_IMPORT} - If the resolved pathname is a directory. + * @throws {ERR_MODULE_NOT_FOUND} - If the resolved pathname is not a file. */ function finalizeResolution(resolved, base, preserveSymlinks) { if (RegExpPrototypeExec(encodedSepRegEx, resolved.pathname) !== null) @@ -249,9 +272,11 @@ function finalizeResolution(resolved, base, preserveSymlinks) { } /** - * @param {string} specifier - * @param {URL} packageJSONUrl - * @param {string | URL | undefined} base + * Returns an error object indicating that the specified import is not defined. + * @param {string} specifier - The import specifier that is not defined. + * @param {URL} packageJSONUrl - The URL of the package.json file, or null if not available. + * @param {string | URL | undefined} base - The base URL to use for resolving relative URLs. + * @returns {ERR_PACKAGE_IMPORT_NOT_DEFINED} - The error object. */ function importNotDefined(specifier, packageJSONUrl, base) { return new ERR_PACKAGE_IMPORT_NOT_DEFINED( @@ -260,9 +285,11 @@ function importNotDefined(specifier, packageJSONUrl, base) { } /** - * @param {string} subpath - * @param {URL} packageJSONUrl - * @param {string | URL | undefined} base + * Returns an error object indicating that the specified subpath was not exported by the package. + * @param {string} subpath - The subpath that was not exported. + * @param {URL} packageJSONUrl - The URL of the package.json file. + * @param {string | URL | undefined} [base] - The base URL to use for resolving the subpath. + * @returns {ERR_PACKAGE_PATH_NOT_EXPORTED} - The error object. */ function exportsNotFound(subpath, packageJSONUrl, base) { return new ERR_PACKAGE_PATH_NOT_EXPORTED( @@ -271,12 +298,13 @@ function exportsNotFound(subpath, packageJSONUrl, base) { } /** - * - * @param {string} request - * @param {string} match - * @param {URL} packageJSONUrl - * @param {boolean} internal - * @param {string | URL | undefined} base + * Throws an error indicating that the given request is not a valid subpath match for the specified pattern. + * @param {string} request - The request that failed to match the pattern. + * @param {string} match - The pattern that the request was compared against. + * @param {URL} packageJSONUrl - The URL of the package.json file being resolved. + * @param {boolean} internal - Whether the resolution is for an "imports" or "exports" field in package.json. + * @param {string | URL | undefined} base - The base URL for the resolution. + * @throws {ERR_INVALID_MODULE_SPECIFIER} When the request is not a valid match for the pattern. */ function throwInvalidSubpath(request, match, packageJSONUrl, internal, base) { const reason = `request is not a valid match in pattern "${match}" for the "${ @@ -286,6 +314,15 @@ function throwInvalidSubpath(request, match, packageJSONUrl, internal, base) { base && fileURLToPath(base)); } +/** + * Creates an error object for an invalid package target. + * @param {string} subpath - The subpath. + * @param {import('internal/modules/esm/package_config.js').PackageTarget} target - The target. + * @param {URL} packageJSONUrl - The URL of the package.json file. + * @param {boolean} internal - Whether the package is internal. + * @param {string | URL | undefined} base - The base URL. + * @returns {ERR_INVALID_PACKAGE_TARGET} - The error object. + */ function invalidPackageTarget( subpath, target, packageJSONUrl, internal, base) { if (typeof target === 'object' && target !== null) { @@ -304,17 +341,19 @@ const invalidPackageNameRegEx = /^\.|%|\\/; const patternRegEx = /\*/g; /** - * - * @param {string} target - * @param {*} subpath - * @param {*} match - * @param {*} packageJSONUrl - * @param {*} base - * @param {*} pattern - * @param {*} internal - * @param {*} isPathMap - * @param {*} conditions - * @returns {URL} + * Resolves the package target string to a URL object. + * @param {string} target - The target string to resolve. + * @param {string} subpath - The subpath to append to the resolved URL. + * @param {RegExpMatchArray} match - The matched string array from the import statement. + * @param {string} packageJSONUrl - The URL of the package.json file. + * @param {string} base - The base URL to resolve the target against. + * @param {RegExp} pattern - The pattern to replace in the target string. + * @param {boolean} internal - Whether the target is internal to the package. + * @param {boolean} isPathMap - Whether the target is a path map. + * @param {string[]} conditions - The import conditions. + * @returns {URL} - The resolved URL object. + * @throws {ERR_INVALID_PACKAGE_TARGET} - If the target is invalid. + * @throws {ERR_INVALID_SUBPATH} - If the subpath is invalid. */ function resolvePackageTargetString( target, @@ -395,8 +434,9 @@ function resolvePackageTargetString( } /** - * @param {string} key - * @returns {boolean} + * Checks if the given key is a valid array index. + * @param {string} key - The key to check. + * @returns {boolean} - Returns `true` if the key is a valid array index, else `false`. */ function isArrayIndex(key) { const keyNum = +key; @@ -405,17 +445,17 @@ function isArrayIndex(key) { } /** - * - * @param {*} packageJSONUrl - * @param {string|[string]} target - * @param {*} subpath - * @param {*} packageSubpath - * @param {*} base - * @param {*} pattern - * @param {*} internal - * @param {*} isPathMap - * @param {*} conditions - * @returns {URL|null} + * Resolves the target of a package based on the provided parameters. + * @param {string} packageJSONUrl - The URL of the package.json file. + * @param {import('internal/modules/esm/package_config.js').PackageTarget} target - The target to resolve. + * @param {string} subpath - The subpath to resolve. + * @param {string} packageSubpath - The subpath of the package to resolve. + * @param {string} base - The base path to resolve. + * @param {RegExp} pattern - The pattern to match. + * @param {boolean} internal - Whether the package is internal. + * @param {boolean} isPathMap - Whether the package is a path map. + * @param {Set} conditions - The conditions to match. + * @returns {URL | null | undefined} - The resolved target, or null if not found, or undefined if not resolvable. */ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath, base, pattern, internal, isPathMap, conditions) { @@ -486,11 +526,10 @@ function resolvePackageTarget(packageJSONUrl, target, subpath, packageSubpath, } /** - * - * @param {import('internal/modules/esm/package_config.js').Exports} exports - * @param {URL} packageJSONUrl - * @param {string | URL | undefined} base - * @returns {boolean} + * Is the given exports object using the shorthand syntax? + * @param {import('internal/modules/esm/package_config.js').PackageConfig['exports']} exports + * @param {URL} packageJSONUrl The URL of the package.json file. + * @param {string | URL | undefined} base The base URL. */ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) { if (typeof exports === 'string' || ArrayIsArray(exports)) return true; @@ -516,12 +555,13 @@ function isConditionalExportsMainSugar(exports, packageJSONUrl, base) { } /** - * @param {URL} packageJSONUrl - * @param {string} packageSubpath - * @param {PackageConfig} packageConfig - * @param {string | URL | undefined} base - * @param {Set} conditions - * @returns {URL} + * Resolves the exports of a package. + * @param {URL} packageJSONUrl - The URL of the package.json file. + * @param {string} packageSubpath - The subpath of the package to resolve. + * @param {import('internal/modules/esm/package_config.js').PackageConfig} packageConfig - The package metadata. + * @param {string | URL | undefined} base - The base path to resolve from. + * @param {Set} conditions - An array of conditions to match. + * @returns {URL} - The resolved package target. */ function packageExportsResolve( packageJSONUrl, packageSubpath, packageConfig, base, conditions) { @@ -598,6 +638,13 @@ function packageExportsResolve( throw exportsNotFound(packageSubpath, packageJSONUrl, base); } +/** + * Compares two strings that may contain a wildcard character ('*') and returns a value indicating their order. + * @param {string} a - The first string to compare. + * @param {string} b - The second string to compare. + * @returns {number} - A negative number if `a` should come before `b`, a positive number if `a` should come after `b`, + * or 0 if they are equal. + */ function patternKeyCompare(a, b) { const aPatternIndex = StringPrototypeIndexOf(a, '*'); const bPatternIndex = StringPrototypeIndexOf(b, '*'); @@ -613,10 +660,13 @@ function patternKeyCompare(a, b) { } /** - * @param {string} name - * @param {string | URL | undefined} base - * @param {Set} conditions - * @returns {URL} + * Resolves the given import name for a package. + * @param {string} name - The name of the import to resolve. + * @param {string | URL | undefined} base - The base URL to resolve the import from. + * @param {Set} conditions - An object containing the import conditions. + * @throws {ERR_INVALID_MODULE_SPECIFIER} If the import name is not valid. + * @throws {ERR_PACKAGE_IMPORT_NOT_DEFINED} If the import name cannot be resolved. + * @returns {URL} The resolved import URL. */ function packageImportsResolve(name, base, conditions) { if (name === '#' || StringPrototypeStartsWith(name, '#/') || @@ -679,8 +729,8 @@ function packageImportsResolve(name, base, conditions) { } /** - * @param {URL} url - * @returns {import('internal/modules/esm/package_config.js').PackageType} + * Returns the package type for a given URL. + * @param {URL} url - The URL to get the package type for. */ function getPackageType(url) { const packageConfig = getPackageScopeConfig(url); @@ -688,9 +738,9 @@ function getPackageType(url) { } /** - * @param {string} specifier - * @param {string | URL | undefined} base - * @returns {{ packageName: string, packageSubpath: string, isScoped: boolean }} + * Parse a package name from a specifier. + * @param {string} specifier - The import specifier. + * @param {string | URL | undefined} base - The parent URL. */ function parsePackageName(specifier, base) { let separatorIndex = StringPrototypeIndexOf(specifier, '/'); @@ -726,10 +776,11 @@ function parsePackageName(specifier, base) { } /** - * @param {string} specifier - * @param {string | URL | undefined} base - * @param {Set} conditions - * @returns {resolved: URL, format? : string} + * Resolves a package specifier to a URL. + * @param {string} specifier - The package specifier to resolve. + * @param {string | URL | undefined} base - The base URL to use for resolution. + * @param {Set} conditions - An object containing the conditions for resolution. + * @returns {URL} - The resolved URL. */ function packageResolve(specifier, base, conditions) { if (BuiltinModule.canBeRequiredWithoutScheme(specifier)) { @@ -790,13 +841,17 @@ function packageResolve(specifier, base, conditions) { } /** - * @param {string} specifier - * @returns {boolean} + * Checks if a specifier is a bare specifier. + * @param {string} specifier - The specifier to check. */ function isBareSpecifier(specifier) { return specifier[0] && specifier[0] !== '/' && specifier[0] !== '.'; } +/** + * Determines whether a specifier is a relative path. + * @param {string} specifier - The specifier to check. + */ function isRelativeSpecifier(specifier) { if (specifier[0] === '.') { if (specifier.length === 1 || specifier[1] === '/') return true; @@ -807,6 +862,10 @@ function isRelativeSpecifier(specifier) { return false; } +/** + * Determines whether a specifier should be treated as a relative or absolute path. + * @param {string} specifier - The specifier to check. + */ function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) { if (specifier === '') return false; if (specifier[0] === '/') return true; @@ -814,11 +873,11 @@ function shouldBeTreatedAsRelativeOrAbsolutePath(specifier) { } /** - * @param {string} specifier - * @param {string | URL | undefined} base - * @param {Set} conditions - * @param {boolean} preserveSymlinks - * @returns {url: URL, format?: string} + * Resolves a module specifier to a URL. + * @param {string} specifier - The module specifier to resolve. + * @param {string | URL | undefined} base - The base URL to resolve against. + * @param {Set} conditions - An object containing environment conditions. + * @param {boolean} preserveSymlinks - Whether to preserve symlinks in the resolved URL. */ function moduleResolve(specifier, base, conditions, preserveSymlinks) { const isRemote = base.protocol === 'http:' || @@ -846,10 +905,9 @@ function moduleResolve(specifier, base, conditions, preserveSymlinks) { } /** - * Try to resolve an import as a CommonJS module - * @param {string} specifier - * @param {string} parentURL - * @returns {boolean|string} + * Try to resolve an import as a CommonJS module. + * @param {string} specifier - The specifier to resolve. + * @param {string} parentURL - The base URL. */ function resolveAsCommonJS(specifier, parentURL) { try { @@ -891,7 +949,14 @@ function resolveAsCommonJS(specifier, parentURL) { } } -// TODO(@JakobJingleheimer): de-dupe `specifier` & `parsed` +/** + * Throw an error if an import is not allowed. + * TODO(@JakobJingleheimer): de-dupe `specifier` & `parsed` + * @param {string} specifier - The import specifier. + * @param {URL} parsed - The parsed URL of the import specifier. + * @param {URL} parsedParentURL - The parsed URL of the parent module. + * @throws {ERR_NETWORK_IMPORT_DISALLOWED} - If the import is disallowed. + */ function checkIfDisallowedImport(specifier, parsed, parsedParentURL) { if (parsedParentURL) { // Avoid accessing the `protocol` property due to the lazy getters. @@ -937,6 +1002,7 @@ function checkIfDisallowedImport(specifier, parsed, parsedParentURL) { /** * Validate user-input in `context` supplied by a custom loader. + * @param {string | URL | undefined} parentURL - The parent URL. */ function throwIfInvalidParentURL(parentURL) { if (parentURL === undefined) { @@ -947,7 +1013,15 @@ function throwIfInvalidParentURL(parentURL) { } } - +/** + * Resolves the given specifier using the provided context, which includes the parent URL and conditions. + * Throws an error if the parent URL is invalid or if the resolution is disallowed by the policy manifest. + * Otherwise, attempts to resolve the specifier and returns the resulting URL and format. + * @param {string} specifier - The specifier to resolve. + * @param {object} [context={}] - The context object containing the parent URL and conditions. + * @param {string} [context.parentURL] - The URL of the parent module. + * @param {string[]} [context.conditions] - The conditions for resolving the specifier. + */ function defaultResolve(specifier, context = {}) { let { parentURL, conditions } = context; throwIfInvalidParentURL(parentURL); diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index b143cd0ad34d0e..cf13aadcb4645f 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -18,7 +18,11 @@ const { globalThis: { WebAssembly }, } = primordials; +/** @type {import('internal/util/types')} */ let _TYPES = null; +/** + * Lazily loads and returns the internal/util/types module. + */ function lazyTypes() { if (_TYPES !== null) return _TYPES; return _TYPES = require('internal/util/types'); @@ -52,7 +56,13 @@ const asyncESM = require('internal/process/esm_loader'); const { emitWarningSync } = require('internal/process/warning'); const { internalCompileFunction } = require('internal/vm'); +/** @type {import('deps/cjs-module-lexer/lexer.js').parse} */ let cjsParse; +/** + * Initializes the CommonJS module lexer parser. + * If WebAssembly is available, it uses the optimized version from the dist folder. + * Otherwise, it falls back to the JavaScript version from the lexer folder. + */ async function initCJSParse() { if (typeof WebAssembly === 'undefined') { cjsParse = require('internal/deps/cjs-module-lexer/lexer').parse; @@ -73,6 +83,14 @@ exports.translators = translators; exports.enrichCJSError = enrichCJSError; let DECODER = null; +/** + * Asserts that the given body is a buffer source (either a string, array buffer, or typed array). + * Throws an error if the body is not a buffer source. + * @param {string | ArrayBufferView | ArrayBuffer} body - The body to check. + * @param {boolean} allowString - Whether or not to allow a string as a valid buffer source. + * @param {string} hookName - The name of the hook being called. + * @throws {ERR_INVALID_RETURN_PROPERTY_VALUE} If the body is not a buffer source. + */ function assertBufferSource(body, allowString, hookName) { if (allowString && typeof body === 'string') { return; @@ -89,6 +107,11 @@ function assertBufferSource(body, allowString, hookName) { ); } +/** + * Converts a buffer or buffer-like object to a string. + * @param {string | ArrayBuffer | ArrayBufferView} body - The buffer or buffer-like object to convert to a string. + * @returns {string} The resulting string. + */ function stringify(body) { if (typeof body === 'string') return body; assertBufferSource(body, false, 'transformSource'); @@ -97,6 +120,10 @@ function stringify(body) { return DECODER.decode(body); } +/** + * Converts a URL to a file path if the URL protocol is 'file:'. + * @param {string} url - The URL to convert. + */ function errPath(url) { const parsed = new URL(url); if (parsed.protocol === 'file:') { @@ -105,6 +132,14 @@ function errPath(url) { return url; } +/** + * Dynamically imports a module using the ESM loader. + * @param {string} specifier - The module specifier to import. + * @param {object} options - An object containing options for the import. + * @param {string} options.url - The URL of the module requesting the import. + * @param {Record} [assertions] - An object containing assertions for the import. + * @returns {Promise} The imported module. + */ async function importModuleDynamically(specifier, { url }, assertions) { return asyncESM.esmLoader.import(specifier, url, assertions); } @@ -126,6 +161,7 @@ translators.set('module', async function moduleStrategy(url, source, isMain) { }); /** + * Provide a more informative error for CommonJS imports. * @param {Error | any} err * @param {string} [content] Content of the file, if known. * @param {string} [filename] Useful only if `content` is unknown. @@ -148,6 +184,10 @@ function enrichCJSError(err, content, filename) { * This translator creates its own version of the `require` function passed into CommonJS modules. * Any monkey patches applied to the CommonJS Loader will not affect this module. * Any `require` calls in this module will load all children in the same way. + * @param {import('internal/modules/cjs/loader').Module} module - The module to load. + * @param {string} source - The source code of the module. + * @param {string} url - The URL of the module. + * @param {string} filename - The filename of the module. */ function loadCJSModule(module, source, url, filename) { let compiledWrapper; @@ -212,6 +252,14 @@ function loadCJSModule(module, source, url, filename) { // TODO: can we use a weak map instead? const cjsCache = new SafeMap(); +/** + * Creates a ModuleWrap object for a CommonJS module. + * @param {string} url - The URL of the module. + * @param {string} source - The source code of the module. + * @param {boolean} isMain - Whether the module is the main module. + * @param {typeof loadCJSModule} [loadCJS=loadCJSModule] - The function to load the CommonJS module. + * @returns {ModuleWrap} The ModuleWrap object for the CommonJS module. + */ function createCJSModuleWrap(url, source, isMain, loadCJS = loadCJSModule) { debug(`Translating CJSModule ${url}`); @@ -298,6 +346,11 @@ translators.set('commonjs', async function commonjsStrategy(url, source, }); +/** + * Pre-parses a CommonJS module's exports and re-exports. + * @param {string} filename - The filename of the module. + * @param {string} [source] - The source code of the module. + */ function cjsPreparseModuleExports(filename, source) { // TODO: Do we want to keep hitting the user mutable CJS loader here? let module = CJSModule._cache[filename]; diff --git a/lib/internal/modules/esm/utils.js b/lib/internal/modules/esm/utils.js index 98578438302445..2c4b2a4a3c2aec 100644 --- a/lib/internal/modules/esm/utils.js +++ b/lib/internal/modules/esm/utils.js @@ -1,11 +1,6 @@ 'use strict'; -const { - ArrayIsArray, - SafeSet, - SafeWeakMap, - ObjectFreeze, -} = primordials; +const { ArrayIsArray, SafeSet, SafeWeakMap, ObjectFreeze } = primordials; const { privateSymbols: { @@ -29,18 +24,28 @@ const { const assert = require('internal/assert'); let defaultConditions; +/** + * Returns the default conditions for ES module loading. + */ function getDefaultConditions() { assert(defaultConditions !== undefined); return defaultConditions; } +/** @type {Set} */ let defaultConditionsSet; +/** + * Returns the default conditions for ES module loading, as a Set. + */ function getDefaultConditionsSet() { assert(defaultConditionsSet !== undefined); return defaultConditionsSet; } -// This function is called during pre-execution, before any user code is run. +/** + * Initializes the default conditions for ESM module loading. + * This function is called during pre-execution, before any user code is run. + */ function initializeDefaultConditions() { const userConditions = getOptionValue('--conditions'); const noAddons = getOptionValue('--no-addons'); @@ -62,8 +67,11 @@ function initializeDefaultConditions() { function getConditionsSet(conditions) { if (conditions !== undefined && conditions !== getDefaultConditions()) { if (!ArrayIsArray(conditions)) { - throw new ERR_INVALID_ARG_VALUE('conditions', conditions, - 'expected an array'); + throw new ERR_INVALID_ARG_VALUE( + 'conditions', + conditions, + 'expected an array', + ); } return new SafeSet(conditions); } @@ -144,9 +152,13 @@ async function importModuleDynamicallyCallback(symbol, specifier, assertions) { throw new ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING(); } -// This is configured during pre-execution. Specifically it's set to true for -// the loader worker in internal/main/worker_thread.js. let _isLoaderWorker = false; +/** + * Initializes handling of ES modules. + * This is configured during pre-execution. Specifically it's set to true for + * the loader worker in internal/main/worker_thread.js. + * @param {boolean} [isLoaderWorker=false] - A boolean indicating whether the loader is a worker or not. + */ function initializeESM(isLoaderWorker = false) { _isLoaderWorker = isLoaderWorker; initializeDefaultConditions(); @@ -156,10 +168,17 @@ function initializeESM(isLoaderWorker = false) { setImportModuleDynamicallyCallback(importModuleDynamicallyCallback); } +/** + * Determine whether the current process is a loader worker. + * @returns {boolean} Whether the current process is a loader worker. + */ function isLoaderWorker() { return _isLoaderWorker; } +/** + * Register module customization hooks. + */ async function initializeHooks() { const customLoaderURLs = getOptionValue('--experimental-loader'); @@ -181,10 +200,7 @@ async function initializeHooks() { const parentURL = getCWDURL().href; for (let i = 0; i < customLoaderURLs.length; i++) { - await hooks.register( - customLoaderURLs[i], - parentURL, - ); + await hooks.register(customLoaderURLs[i], parentURL); } const preloadScripts = hooks.initializeGlobalPreload(); diff --git a/lib/internal/modules/esm/worker.js b/lib/internal/modules/esm/worker.js index 380906accf6fe9..2d45b3b9f46234 100644 --- a/lib/internal/modules/esm/worker.js +++ b/lib/internal/modules/esm/worker.js @@ -32,6 +32,11 @@ const { const { initializeHooks } = require('internal/modules/esm/utils'); +/** + * Transfers an ArrayBuffer, TypedArray, or DataView to a worker thread. + * @param {boolean} hasError - Whether an error occurred during transfer. + * @param {ArrayBuffer | TypedArray | DataView} source - The data to transfer. + */ function transferArrayBuffer(hasError, source) { if (hasError || source == null) return; if (isArrayBuffer(source)) return [source]; @@ -39,6 +44,11 @@ function transferArrayBuffer(hasError, source) { if (isDataView(source)) return [DataViewPrototypeGetBuffer(source)]; } +/** + * Wraps a message with a status and body, and serializes the body if necessary. + * @param {string} status - The status of the message. + * @param {unknown} body - The body of the message. + */ function wrapMessage(status, body) { if (status === 'success' || body === null || (typeof body !== 'object' && @@ -65,6 +75,14 @@ function wrapMessage(status, body) { }; } +/** + * Initializes a worker thread for a customized module loader. + * @param {SharedArrayBuffer} lock - The lock used to synchronize communication between the worker and the main thread. + * @param {MessagePort} syncCommPort - The message port used for synchronous communication between the worker and the + * main thread. + * @param {(err: Error, origin?: string) => void} errorHandler - The function to use for uncaught exceptions. + * @returns {Promise} A promise that resolves when the worker thread has been initialized. + */ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { let hooks, preloadScripts, initializationError; let hasInitializationError = false; @@ -107,6 +125,9 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); let immediate; + /** + * Checks for messages on the syncCommPort and handles them asynchronously. + */ function checkForMessages() { immediate = setImmediate(checkForMessages).unref(); // We need to let the event loop tick a few times to give the main thread a chance to send @@ -140,6 +161,13 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { setImmediate(() => {}); }); + /** + * Handles incoming messages from the main thread or other workers. + * @param {object} options - The options object. + * @param {string} options.method - The name of the hook. + * @param {Array} options.args - The arguments to pass to the method. + * @param {MessagePort} options.port - The message port to use for communication. + */ async function handleMessage({ method, args, port }) { // Each potential exception needs to be caught individually so that the correct error is sent to // the main thread. @@ -198,11 +226,19 @@ async function customizedModuleWorker(lock, syncCommPort, errorHandler) { } /** + * Initializes a worker thread for a module with customized hooks. * ! Run everything possible within this function so errors get reported. + * @param {{lock: SharedArrayBuffer}} workerData - The lock used to synchronize with the main thread. + * @param {MessagePort} syncCommPort - The communication port used to communicate with the main thread. */ module.exports = function setupModuleWorker(workerData, syncCommPort) { const lock = new Int32Array(workerData.lock); + /** + * Handles errors that occur in the worker thread. + * @param {Error} err - The error that occurred. + * @param {string} [origin='unhandledRejection'] - The origin of the error. + */ function errorHandler(err, origin = 'unhandledRejection') { AtomicsAdd(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION, 1); AtomicsNotify(lock, WORKER_TO_MAIN_THREAD_NOTIFICATION); diff --git a/lib/internal/modules/helpers.js b/lib/internal/modules/helpers.js index 307a34cb09b512..cc32e95c4eb413 100644 --- a/lib/internal/modules/helpers.js +++ b/lib/internal/modules/helpers.js @@ -37,7 +37,13 @@ let debug = require('internal/util/debuglog').debuglog('module', (fn) => { debug = fn; }); +/** @typedef {import('internal/modules/cjs/loader.js').Module} Module */ + +/** @type {Set} */ let cjsConditions; +/** + * Define the conditions that apply to the CommonJS loader. + */ function initializeCjsConditions() { const userConditions = getOptionValue('--conditions'); const noAddons = getOptionValue('--no-addons'); @@ -51,6 +57,9 @@ function initializeCjsConditions() { ]); } +/** + * Get the conditions that apply to the CommonJS loader. + */ function getCjsConditions() { if (cjsConditions === undefined) { initializeCjsConditions(); @@ -58,27 +67,45 @@ function getCjsConditions() { return cjsConditions; } -function loadBuiltinModule(filename, request) { - if (!BuiltinModule.canBeRequiredByUsers(filename)) { +/** + * Provide one of Node.js' public modules to user code. + * @param {string} id - The identifier/specifier of the builtin module to load + * @param {string} request - The module requiring or importing the builtin module + */ +function loadBuiltinModule(id, request) { + if (!BuiltinModule.canBeRequiredByUsers(id)) { return; } - const mod = BuiltinModule.map.get(filename); + /** @type {import('internal/bootstrap/realm.js').BuiltinModule} */ + const mod = BuiltinModule.map.get(id); debug('load built-in module %s', request); // compileForPublicLoader() throws if canBeRequiredByUsers is false: mod.compileForPublicLoader(); return mod; } +/** @type {Module} */ let $Module = null; +/** + * Import the Module class on first use. + */ function lazyModule() { $Module = $Module || require('internal/modules/cjs/loader').Module; return $Module; } -// Invoke with makeRequireFunction(module) where |module| is the Module object -// to use as the context for the require() function. -// Use redirects to set up a mapping from a policy and restrict dependencies +/** + * Invoke with `makeRequireFunction(module)` where `module` is the `Module` object to use as the context for the + * `require()` function. + * Use redirects to set up a mapping from a policy and restrict dependencies. + */ const urlToFileCache = new SafeMap(); +/** + * Create the module-scoped `require` function to pass into CommonJS modules. + * @param {Module} mod - The module to create the `require` function for. + * @param {ReturnType} redirects + * @typedef {(specifier: string) => unknown} RequireFunction + */ function makeRequireFunction(mod, redirects) { // lazy due to cycle const Module = lazyModule(); @@ -86,6 +113,7 @@ function makeRequireFunction(mod, redirects) { throw new ERR_INVALID_ARG_TYPE('mod', 'Module', mod); } + /** @type {RequireFunction} */ let require; if (redirects) { const id = mod.filename || mod.id; @@ -131,6 +159,11 @@ function makeRequireFunction(mod, redirects) { }; } + /** + * The `resolve` method that gets attached to module-scope `require`. + * @param {string} request + * @param {Parameters[3]} options + */ function resolve(request, options) { validateString(request, 'request'); return Module._resolveFilename(request, mod, false, options); @@ -138,6 +171,10 @@ function makeRequireFunction(mod, redirects) { require.resolve = resolve; + /** + * The `paths` method that gets attached to module-scope `require`. + * @param {string} request + */ function paths(request) { validateString(request, 'request'); return Module._resolveLookupPaths(request, mod); @@ -159,6 +196,7 @@ function makeRequireFunction(mod, redirects) { * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) * because the buffer-to-string conversion in `fs.readFileSync()` * translates it to FEFF, the UTF-16 BOM. + * @param {string} content */ function stripBOM(content) { if (StringPrototypeCharCodeAt(content) === 0xFEFF) { @@ -167,6 +205,11 @@ function stripBOM(content) { return content; } +/** + * Add built-in modules to a global or REPL scope object. + * @param {Record} object - The object such as `globalThis` to add the built-in modules to. + * @param {string} dummyModuleName - The label representing the set of built-in modules to add. + */ function addBuiltinLibsToObject(object, dummyModuleName) { // Make built-in modules available directly (loaded lazily). const Module = require('internal/modules/cjs/loader').Module; @@ -227,9 +270,8 @@ function addBuiltinLibsToObject(object, dummyModuleName) { } /** - * + * If a referrer is an URL instance or absolute path, convert it into an URL string. * @param {string | URL} referrer - * @returns {string} */ function normalizeReferrerURL(referrer) { if (typeof referrer === 'string' && path.isAbsolute(referrer)) { @@ -238,7 +280,10 @@ function normalizeReferrerURL(referrer) { return new URL(referrer).href; } -// For error messages only - used to check if ESM syntax is in use. +/** + * For error messages only, check if ESM syntax is in use. + * @param {string} code + */ function hasEsmSyntax(code) { debug('Checking for ESM syntax'); const parser = require('internal/deps/acorn/acorn/dist/acorn').Parser; diff --git a/lib/internal/modules/run_main.js b/lib/internal/modules/run_main.js index 0bfe7b11241416..7ffb87e11506f7 100644 --- a/lib/internal/modules/run_main.js +++ b/lib/internal/modules/run_main.js @@ -7,6 +7,10 @@ const { const { getOptionValue } = require('internal/options'); const path = require('path'); +/** + * Get the absolute path to the main entry point. + * @param {string} main Entry point path + */ function resolveMainPath(main) { // Note extension resolution for the main entry point can be deprecated in a // future major. @@ -23,6 +27,10 @@ function resolveMainPath(main) { return mainPath; } +/** + * Determine whether the main entry point should be loaded through the ESM Loader. + * @param {string} mainPath Absolute path to the main entry point + */ function shouldUseESMLoader(mainPath) { /** * @type {string[]} userLoaders A list of custom loaders registered by the user @@ -46,6 +54,10 @@ function shouldUseESMLoader(mainPath) { return pkg && pkg.data.type === 'module'; } +/** + * Run the main entry point through the ESM Loader. + * @param {string} mainPath Absolute path to the main entry point + */ function runMainESM(mainPath) { const { loadESM } = require('internal/process/esm_loader'); const { pathToFileURL } = require('internal/url'); @@ -57,6 +69,10 @@ function runMainESM(mainPath) { })); } +/** + * Handle process exit events around the main entry point promise. + * @param {Promise} promise Main entry point promise + */ async function handleMainPromise(promise) { const { handleProcessExit, @@ -69,9 +85,12 @@ async function handleMainPromise(promise) { } } -// For backwards compatibility, we have to run a bunch of -// monkey-patchable code that belongs to the CJS loader (exposed by -// `require('module')`) even when the entry point is ESM. +/** + * Parse the CLI main entry point string and run it. + * For backwards compatibility, we have to run a bunch of monkey-patchable code that belongs to the CJS loader (exposed + * by `require('module')`) even when the entry point is ESM. + * @param {string} main CLI main entry point string + */ function executeUserEntryPoint(main = process.argv[1]) { const resolvedMain = resolveMainPath(main); const useESMLoader = shouldUseESMLoader(resolvedMain); From 9577a00f98833e755d706e859cfb8a782807b61c Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Fri, 29 Sep 2023 09:46:32 +0100 Subject: [PATCH 2/6] esm: fix cache collision on JSON files using file: URL PR-URL: https://github.com/nodejs/node/pull/49887 Fixes: https://github.com/nodejs/node/issues/49724 Reviewed-By: Geoffrey Booth Reviewed-By: LiviaMedeiros Reviewed-By: Jacob Smith Reviewed-By: Chemi Atlow --- lib/internal/modules/esm/translators.js | 10 ++- test/es-module/test-esm-json.mjs | 62 ++++++++++++++++++- test/es-module/test-esm-virtual-json.mjs | 30 +++++++++ test/fixtures/errors/force_colors.snapshot | 10 +-- ...urce_map_sourcemapping_url_string.snapshot | 2 +- 5 files changed, 104 insertions(+), 10 deletions(-) create mode 100644 test/es-module/test-esm-virtual-json.mjs diff --git a/lib/internal/modules/esm/translators.js b/lib/internal/modules/esm/translators.js index cf13aadcb4645f..c9f7845a664cb7 100644 --- a/lib/internal/modules/esm/translators.js +++ b/lib/internal/modules/esm/translators.js @@ -11,6 +11,7 @@ const { SafeArrayIterator, SafeMap, SafeSet, + StringPrototypeIncludes, StringPrototypeReplaceAll, StringPrototypeSlice, StringPrototypeStartsWith, @@ -440,9 +441,12 @@ translators.set('json', function jsonStrategy(url, source) { debug(`Loading JSONModule ${url}`); const pathname = StringPrototypeStartsWith(url, 'file:') ? fileURLToPath(url) : null; + const shouldCheckAndPopulateCJSModuleCache = + // We want to involve the CJS loader cache only for `file:` URL with no search query and no hash. + pathname && !StringPrototypeIncludes(url, '?') && !StringPrototypeIncludes(url, '#'); let modulePath; let module; - if (pathname) { + if (shouldCheckAndPopulateCJSModuleCache) { modulePath = isWindows ? StringPrototypeReplaceAll(pathname, '/', '\\') : pathname; module = CJSModule._cache[modulePath]; @@ -454,7 +458,7 @@ translators.set('json', function jsonStrategy(url, source) { } } source = stringify(source); - if (pathname) { + if (shouldCheckAndPopulateCJSModuleCache) { // A require call could have been called on the same file during loading and // that resolves synchronously. To make sure we always return the identical // export, we have to check again if the module already exists or not. @@ -481,7 +485,7 @@ translators.set('json', function jsonStrategy(url, source) { err.message = errPath(url) + ': ' + err.message; throw err; } - if (pathname) { + if (shouldCheckAndPopulateCJSModuleCache) { CJSModule._cache[modulePath] = module; } cjsCache.set(url, module); diff --git a/test/es-module/test-esm-json.mjs b/test/es-module/test-esm-json.mjs index 2740c0097f77da..82232838b79150 100644 --- a/test/es-module/test-esm-json.mjs +++ b/test/es-module/test-esm-json.mjs @@ -2,7 +2,10 @@ import { spawnPromisified } from '../common/index.mjs'; import * as fixtures from '../common/fixtures.mjs'; import assert from 'node:assert'; import { execPath } from 'node:process'; -import { describe, it } from 'node:test'; +import { describe, it, test } from 'node:test'; + +import { mkdir, rm, writeFile } from 'node:fs/promises'; +import * as tmpdir from '../common/tmpdir.js'; import secret from '../fixtures/experimental.json' assert { type: 'json' }; @@ -21,4 +24,61 @@ describe('ESM: importing JSON', () => { assert.strictEqual(code, 0); assert.strictEqual(signal, null); }); + + test('should load different modules when the URL is different', async (t) => { + const root = tmpdir.fileURL(`./test-esm-json-${Math.random()}/`); + try { + await mkdir(root, { recursive: true }); + + await t.test('json', async () => { + let i = 0; + const url = new URL('./foo.json', root); + await writeFile(url, JSON.stringify({ id: i++ })); + const absoluteURL = await import(`${url}`, { + assert: { type: 'json' }, + }); + await writeFile(url, JSON.stringify({ id: i++ })); + const queryString = await import(`${url}?a=2`, { + assert: { type: 'json' }, + }); + await writeFile(url, JSON.stringify({ id: i++ })); + const hash = await import(`${url}#a=2`, { + assert: { type: 'json' }, + }); + await writeFile(url, JSON.stringify({ id: i++ })); + const queryStringAndHash = await import(`${url}?a=2#a=2`, { + assert: { type: 'json' }, + }); + + assert.notDeepStrictEqual(absoluteURL, queryString); + assert.notDeepStrictEqual(absoluteURL, hash); + assert.notDeepStrictEqual(queryString, hash); + assert.notDeepStrictEqual(absoluteURL, queryStringAndHash); + assert.notDeepStrictEqual(queryString, queryStringAndHash); + assert.notDeepStrictEqual(hash, queryStringAndHash); + }); + + await t.test('js', async () => { + let i = 0; + const url = new URL('./foo.mjs', root); + await writeFile(url, `export default ${JSON.stringify({ id: i++ })}\n`); + const absoluteURL = await import(`${url}`); + await writeFile(url, `export default ${JSON.stringify({ id: i++ })}\n`); + const queryString = await import(`${url}?a=1`); + await writeFile(url, `export default ${JSON.stringify({ id: i++ })}\n`); + const hash = await import(`${url}#a=1`); + await writeFile(url, `export default ${JSON.stringify({ id: i++ })}\n`); + const queryStringAndHash = await import(`${url}?a=1#a=1`); + + assert.notDeepStrictEqual(absoluteURL, queryString); + assert.notDeepStrictEqual(absoluteURL, hash); + assert.notDeepStrictEqual(queryString, hash); + assert.notDeepStrictEqual(absoluteURL, queryStringAndHash); + assert.notDeepStrictEqual(queryString, queryStringAndHash); + assert.notDeepStrictEqual(hash, queryStringAndHash); + }); + } finally { + await rm(root, { force: true, recursive: true }); + } + }); }); diff --git a/test/es-module/test-esm-virtual-json.mjs b/test/es-module/test-esm-virtual-json.mjs new file mode 100644 index 00000000000000..8ff185a428ef01 --- /dev/null +++ b/test/es-module/test-esm-virtual-json.mjs @@ -0,0 +1,30 @@ +import '../common/index.mjs'; +import * as fixtures from '../common/fixtures.mjs'; +import { register } from 'node:module'; +import assert from 'node:assert'; + +async function resolve(referrer, context, next) { + const result = await next(referrer, context); + const url = new URL(result.url); + url.searchParams.set('randomSeed', Math.random()); + result.url = url.href; + return result; +} + +function load(url, context, next) { + if (context.importAssertions.type === 'json') { + return { + shortCircuit: true, + format: 'json', + source: JSON.stringify({ data: Math.random() }), + }; + } + return next(url, context); +} + +register(`data:text/javascript,export ${encodeURIComponent(resolve)};export ${encodeURIComponent(load)}`); + +assert.notDeepStrictEqual( + await import(fixtures.fileURL('empty.json'), { assert: { type: 'json' } }), + await import(fixtures.fileURL('empty.json'), { assert: { type: 'json' } }), +); diff --git a/test/fixtures/errors/force_colors.snapshot b/test/fixtures/errors/force_colors.snapshot index be1d45d0d8e8ba..8416731841187a 100644 --- a/test/fixtures/errors/force_colors.snapshot +++ b/test/fixtures/errors/force_colors.snapshot @@ -4,11 +4,11 @@ throw new Error('Should include grayed stack trace') Error: Should include grayed stack trace at Object. (/test*force_colors.js:1:7) - at Module._compile (node:internal*modules*cjs*loader:1241:14) - at Module._extensions..js (node:internal*modules*cjs*loader:1295:10) - at Module.load (node:internal*modules*cjs*loader:1091:32) - at Module._load (node:internal*modules*cjs*loader:938:12) - at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:83:12) + at Module._compile (node:internal*modules*cjs*loader:1406:14) + at Module._extensions..js (node:internal*modules*cjs*loader:1464:10) + at Module.load (node:internal*modules*cjs*loader:1239:32) + at Module._load (node:internal*modules*cjs*loader:1061:12) + at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:102:12)  at node:internal*main*run_main_module:23:47 Node.js * diff --git a/test/fixtures/source-map/output/source_map_sourcemapping_url_string.snapshot b/test/fixtures/source-map/output/source_map_sourcemapping_url_string.snapshot index 2c1e11eeb9eab1..97a0fc702652f3 100644 --- a/test/fixtures/source-map/output/source_map_sourcemapping_url_string.snapshot +++ b/test/fixtures/source-map/output/source_map_sourcemapping_url_string.snapshot @@ -1,3 +1,3 @@ Error: an exception. at Object. (*typescript-sourcemapping_url_string.ts:3:7) - at Module._compile (node:internal*modules*cjs*loader:1241:14) + at Module._compile (node:internal*modules*cjs*loader:1406:14) From 0100c7b2ec513f65f0dd7d5c745e0df86688c0cd Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sun, 22 Oct 2023 13:18:21 +0200 Subject: [PATCH 3/6] deps: V8: cherry-pick a0fd3209dda8 Original commit message: [import-attributes] Implement import attributes, with `assert` fallback In the past six months, the old import assertions proposal has been renamed to "import attributes" with the follwing major changes: 1. the keyword is now `with` instead of `assert` 2. unknown assertions cause an error rather than being ignored To preserve backward compatibility with existing applications that use `assert`, implementations _can_ keep it around as a fallback for both the static and dynamic forms. Additionally, the proposal has some minor changes that came up during the stage 3 reviews: 3. dynamic import first reads all the attributes, and then verifies that they are all strings 4. there is no need for a `[no LineTerminator here]` restriction before the `with` keyword 5. static import syntax allows any `LiteralPropertyName` as attribute keys, to align with every other syntax using key-value pairs The new syntax is enabled by a new `--harmony-import-attributes` flag, disabled by default. However, the new behavioral changes also apply to the old syntax that is under the `--harmony-import-assertions` flag. This patch does implements (1), (3), (4) and (5). Handling of unknown import assertions was not implemented directly in V8, but delegated to embedders. As such, it will be implemented separately in d8 and Chromium. To simplify the review, this patch doesn't migrate usage of the term "assertions" to "attributes". There are many variables and internal functions that could be easily renamed as soon as this patch landes. There is one usage in the public API (`ModuleRequest::GetImportAssertions`) that will probably need to be aliased and then removed following the same process as for other API breaking changes. Bug: v8:13856 Change-Id: I78b167348d898887332c5ca7468bc5d58cd9b1ca Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4632799 Commit-Queue: Shu-yu Guo Reviewed-by: Adam Klein Cr-Commit-Position: refs/heads/main@{#89110} Refs: https://github.com/v8/v8/commit/159c82c5e6392e78b9bba7161b1bed6e23758984 Refs: https://github.com/v8/v8/commit/a0fd3209dda8527d7da810abe231df27fffe789e --- common.gypi | 2 +- deps/v8/include/v8-script.h | 7 +- deps/v8/src/execution/isolate.cc | 48 +++- deps/v8/src/flags/flag-definitions.h | 1 + deps/v8/src/init/bootstrapper.cc | 1 + deps/v8/src/init/heap-symbols.h | 1 + deps/v8/src/parsing/parser-base.h | 4 +- deps/v8/src/parsing/parser.cc | 39 +-- deps/v8/src/roots/static-roots.h | 146 +++++------ .../harmony/modules-import-attributes-1.mjs | 9 + .../harmony/modules-import-attributes-2.mjs | 9 + .../harmony/modules-import-attributes-3.mjs | 9 + .../harmony/modules-import-attributes-4.mjs | 9 + .../modules-import-attributes-dynamic-1.mjs | 12 + .../modules-import-attributes-dynamic-10.mjs | 19 ++ .../modules-import-attributes-dynamic-11.mjs | 19 ++ .../modules-import-attributes-dynamic-12.mjs | 26 ++ .../modules-import-attributes-dynamic-13.mjs | 26 ++ .../modules-import-attributes-dynamic-2.mjs | 13 + .../modules-import-attributes-dynamic-3.mjs | 13 + .../modules-import-attributes-dynamic-4.mjs | 14 ++ .../modules-import-attributes-dynamic-5.mjs | 12 + .../modules-import-attributes-dynamic-6.mjs | 18 ++ .../modules-import-attributes-dynamic-7.mjs | 63 +++++ .../modules-import-attributes-dynamic-8.mjs | 13 + .../modules-import-attributes-dynamic-9.mjs | 13 + ...tributes-dynamic-assertions-fallback-1.mjs | 15 ++ ...tributes-dynamic-assertions-fallback-2.mjs | 15 ++ .../test/unittests/parser/parsing-unittest.cc | 229 ++++++++++++++++++ 29 files changed, 698 insertions(+), 107 deletions(-) create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-1.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-2.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-3.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-4.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-1.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-10.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-11.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-12.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-13.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-2.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-3.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-4.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-5.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-6.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-7.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-8.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-9.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-assertions-fallback-1.mjs create mode 100644 deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-assertions-fallback-2.mjs diff --git a/common.gypi b/common.gypi index d783c7f970237a..519ddcd28e6524 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.16', + 'v8_embedder_string': '-node.17', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/include/v8-script.h b/deps/v8/include/v8-script.h index 4a8ccab7e28d1d..f15d148b28d85c 100644 --- a/deps/v8/include/v8-script.h +++ b/deps/v8/include/v8-script.h @@ -142,10 +142,9 @@ class V8_EXPORT ModuleRequest : public Data { * * All assertions present in the module request will be supplied in this * list, regardless of whether they are supported by the host. Per - * https://tc39.es/proposal-import-assertions/#sec-hostgetsupportedimportassertions, - * hosts are expected to ignore assertions that they do not support (as - * opposed to, for example, triggering an error if an unsupported assertion is - * present). + * https://tc39.es/proposal-import-attributes/#sec-hostgetsupportedimportattributes, + * hosts are expected to throw for assertions that they do not support (as + * opposed to, for example, ignoring them). */ Local GetImportAssertions() const; diff --git a/deps/v8/src/execution/isolate.cc b/deps/v8/src/execution/isolate.cc index 06c707da96ca34..1c63f0c56bf719 100644 --- a/deps/v8/src/execution/isolate.cc +++ b/deps/v8/src/execution/isolate.cc @@ -5156,7 +5156,8 @@ MaybeHandle Isolate::GetImportAssertionsFromArgument( // The parser shouldn't have allowed the second argument to import() if // the flag wasn't enabled. - DCHECK(v8_flags.harmony_import_assertions); + DCHECK(v8_flags.harmony_import_assertions || + v8_flags.harmony_import_attributes); if (!import_assertions_argument->IsJSReceiver()) { this->Throw( @@ -5166,18 +5167,35 @@ MaybeHandle Isolate::GetImportAssertionsFromArgument( Handle import_assertions_argument_receiver = Handle::cast(import_assertions_argument); - Handle key = factory()->assert_string(); Handle import_assertions_object; - if (!JSReceiver::GetProperty(this, import_assertions_argument_receiver, key) - .ToHandle(&import_assertions_object)) { - // This can happen if the property has a getter function that throws - // an error. - return MaybeHandle(); + + if (v8_flags.harmony_import_attributes) { + Handle with_key = factory()->with_string(); + if (!JSReceiver::GetProperty(this, import_assertions_argument_receiver, + with_key) + .ToHandle(&import_assertions_object)) { + // This can happen if the property has a getter function that throws + // an error. + return MaybeHandle(); + } } - // If there is no 'assert' option in the options bag, it's not an error. Just - // do the import() as if no assertions were provided. + if (v8_flags.harmony_import_assertions && + (!v8_flags.harmony_import_attributes || + import_assertions_object->IsUndefined())) { + Handle assert_key = factory()->assert_string(); + if (!JSReceiver::GetProperty(this, import_assertions_argument_receiver, + assert_key) + .ToHandle(&import_assertions_object)) { + // This can happen if the property has a getter function that throws + // an error. + return MaybeHandle(); + } + } + + // If there is no 'with' or 'assert' option in the options bag, it's not an + // error. Just do the import() as if no assertions were provided. if (import_assertions_object->IsUndefined()) return import_assertions_array; if (!import_assertions_object->IsJSReceiver()) { @@ -5199,6 +5217,8 @@ MaybeHandle Isolate::GetImportAssertionsFromArgument( return MaybeHandle(); } + bool has_non_string_attribute = false; + // The assertions will be passed to the host in the form: [key1, // value1, key2, value2, ...]. constexpr size_t kAssertionEntrySizeForDynamicImport = 2; @@ -5216,9 +5236,7 @@ MaybeHandle Isolate::GetImportAssertionsFromArgument( } if (!assertion_value->IsString()) { - this->Throw(*factory()->NewTypeError( - MessageTemplate::kNonStringImportAssertionValue)); - return MaybeHandle(); + has_non_string_attribute = true; } import_assertions_array->set((i * kAssertionEntrySizeForDynamicImport), @@ -5227,6 +5245,12 @@ MaybeHandle Isolate::GetImportAssertionsFromArgument( *assertion_value); } + if (has_non_string_attribute) { + this->Throw(*factory()->NewTypeError( + MessageTemplate::kNonStringImportAssertionValue)); + return MaybeHandle(); + } + return import_assertions_array; } diff --git a/deps/v8/src/flags/flag-definitions.h b/deps/v8/src/flags/flag-definitions.h index 8a3ab897c67e98..1bb489833b81f8 100644 --- a/deps/v8/src/flags/flag-definitions.h +++ b/deps/v8/src/flags/flag-definitions.h @@ -235,6 +235,7 @@ DEFINE_BOOL(harmony_shipping, true, "enable all shipped harmony features") // Features that are still work in progress (behind individual flags). #define HARMONY_INPROGRESS_BASE(V) \ + V(harmony_import_attributes, "harmony import attributes") \ V(harmony_weak_refs_with_cleanup_some, \ "harmony weak references with FinalizationRegistry.prototype.cleanupSome") \ V(harmony_temporal, "Temporal") \ diff --git a/deps/v8/src/init/bootstrapper.cc b/deps/v8/src/init/bootstrapper.cc index 69d01ff33902aa..c468c502e703df 100644 --- a/deps/v8/src/init/bootstrapper.cc +++ b/deps/v8/src/init/bootstrapper.cc @@ -4523,6 +4523,7 @@ void Genesis::InitializeConsole(Handle extras_binding) { void Genesis::InitializeGlobal_##id() {} EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_import_assertions) +EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_import_attributes) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_symbol_as_weakmap_key) EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_rab_gsab_transfer) diff --git a/deps/v8/src/init/heap-symbols.h b/deps/v8/src/init/heap-symbols.h index 5388b88d4e7599..1ca46fdc81faa4 100644 --- a/deps/v8/src/init/heap-symbols.h +++ b/deps/v8/src/init/heap-symbols.h @@ -462,6 +462,7 @@ V(_, week_string, "week") \ V(_, weeks_string, "weeks") \ V(_, weekOfYear_string, "weekOfYear") \ + V(_, with_string, "with") \ V(_, word_string, "word") \ V(_, yearMonthFromFields_string, "yearMonthFromFields") \ V(_, year_string, "year") \ diff --git a/deps/v8/src/parsing/parser-base.h b/deps/v8/src/parsing/parser-base.h index ff5af7dfec9204..cfba92d7e884b8 100644 --- a/deps/v8/src/parsing/parser-base.h +++ b/deps/v8/src/parsing/parser-base.h @@ -3774,7 +3774,9 @@ ParserBase::ParseImportExpressions() { AcceptINScope scope(this, true); ExpressionT specifier = ParseAssignmentExpressionCoverGrammar(); - if (v8_flags.harmony_import_assertions && Check(Token::COMMA)) { + if ((v8_flags.harmony_import_assertions || + v8_flags.harmony_import_attributes) && + Check(Token::COMMA)) { if (Check(Token::RPAREN)) { // A trailing comma allowed after the specifier. return factory()->NewImportCallExpression(specifier, pos); diff --git a/deps/v8/src/parsing/parser.cc b/deps/v8/src/parsing/parser.cc index 6886e037ac2f97..336d88b2ea195b 100644 --- a/deps/v8/src/parsing/parser.cc +++ b/deps/v8/src/parsing/parser.cc @@ -1344,27 +1344,28 @@ ZonePtrList* Parser::ParseNamedImports(int pos) { } ImportAssertions* Parser::ParseImportAssertClause() { - // AssertClause : - // assert '{' '}' - // assert '{' AssertEntries '}' + // WithClause : + // with '{' '}' + // with '{' WithEntries ','? '}' - // AssertEntries : - // IdentifierName: AssertionKey - // IdentifierName: AssertionKey , AssertEntries + // WithEntries : + // LiteralPropertyName + // LiteralPropertyName ':' StringLiteral , WithEntries - // AssertionKey : - // IdentifierName - // StringLiteral + // (DEPRECATED) + // AssertClause : + // assert '{' '}' + // assert '{' WithEntries ','? '}' auto import_assertions = zone()->New(zone()); - if (!v8_flags.harmony_import_assertions) { - return import_assertions; - } - - // Assert clause is optional, and cannot be preceded by a LineTerminator. - if (scanner()->HasLineTerminatorBeforeNext() || - !CheckContextualKeyword(ast_value_factory()->assert_string())) { + if (v8_flags.harmony_import_attributes && Check(Token::WITH)) { + // 'with' keyword consumed + } else if (v8_flags.harmony_import_assertions && + !scanner()->HasLineTerminatorBeforeNext() && + CheckContextualKeyword(ast_value_factory()->assert_string())) { + // 'assert' keyword consumed + } else { return import_assertions; } @@ -1372,8 +1373,12 @@ ImportAssertions* Parser::ParseImportAssertClause() { while (peek() != Token::RBRACE) { const AstRawString* attribute_key = nullptr; - if (Check(Token::STRING)) { + if (Check(Token::STRING) || Check(Token::SMI)) { attribute_key = GetSymbol(); + } else if (Check(Token::NUMBER)) { + attribute_key = GetNumberAsSymbol(); + } else if (Check(Token::BIGINT)) { + attribute_key = GetBigIntAsSymbol(); } else { attribute_key = ParsePropertyName(); } diff --git a/deps/v8/src/roots/static-roots.h b/deps/v8/src/roots/static-roots.h index df53f43736888f..16729572de88fc 100644 --- a/deps/v8/src/roots/static-roots.h +++ b/deps/v8/src/roots/static-roots.h @@ -693,82 +693,83 @@ struct StaticReadOnlyRoot { static constexpr Tagged_t kweek_string = 0x5a55; static constexpr Tagged_t kweeks_string = 0x5a65; static constexpr Tagged_t kweekOfYear_string = 0x5a79; - static constexpr Tagged_t kword_string = 0x5a91; - static constexpr Tagged_t kyearMonthFromFields_string = 0x5aa1; - static constexpr Tagged_t kyear_string = 0x5ac1; - static constexpr Tagged_t kyears_string = 0x5ad1; - static constexpr Tagged_t kUninitializedValue = 0x5af5; - static constexpr Tagged_t kArgumentsMarker = 0x5b2d; - static constexpr Tagged_t kTerminationException = 0x5b65; - static constexpr Tagged_t kException = 0x5ba5; - static constexpr Tagged_t kOptimizedOut = 0x5bc1; - static constexpr Tagged_t kStaleRegister = 0x5bf9; - static constexpr Tagged_t kSelfReferenceMarker = 0x5c31; - static constexpr Tagged_t kBasicBlockCountersMarker = 0x5c71; - static constexpr Tagged_t karray_buffer_wasm_memory_symbol = 0x5cb5; - static constexpr Tagged_t kcall_site_info_symbol = 0x5cc5; - static constexpr Tagged_t kconsole_context_id_symbol = 0x5cd5; - static constexpr Tagged_t kconsole_context_name_symbol = 0x5ce5; - static constexpr Tagged_t kclass_fields_symbol = 0x5cf5; - static constexpr Tagged_t kclass_positions_symbol = 0x5d05; - static constexpr Tagged_t kerror_end_pos_symbol = 0x5d15; - static constexpr Tagged_t kerror_script_symbol = 0x5d25; - static constexpr Tagged_t kerror_stack_symbol = 0x5d35; - static constexpr Tagged_t kerror_start_pos_symbol = 0x5d45; - static constexpr Tagged_t kfrozen_symbol = 0x5d55; - static constexpr Tagged_t kinterpreter_trampoline_symbol = 0x5d65; - static constexpr Tagged_t knative_context_index_symbol = 0x5d75; - static constexpr Tagged_t knonextensible_symbol = 0x5d85; - static constexpr Tagged_t kpromise_debug_marker_symbol = 0x5d95; - static constexpr Tagged_t kpromise_debug_message_symbol = 0x5da5; - static constexpr Tagged_t kpromise_forwarding_handler_symbol = 0x5db5; - static constexpr Tagged_t kpromise_handled_by_symbol = 0x5dc5; - static constexpr Tagged_t kpromise_awaited_by_symbol = 0x5dd5; - static constexpr Tagged_t kregexp_result_names_symbol = 0x5de5; - static constexpr Tagged_t kregexp_result_regexp_input_symbol = 0x5df5; - static constexpr Tagged_t kregexp_result_regexp_last_index_symbol = 0x5e05; - static constexpr Tagged_t ksealed_symbol = 0x5e15; - static constexpr Tagged_t kstrict_function_transition_symbol = 0x5e25; + static constexpr Tagged_t kwith_string = 0x5a91; + static constexpr Tagged_t kword_string = 0x5aa1; + static constexpr Tagged_t kyearMonthFromFields_string = 0x5ab1; + static constexpr Tagged_t kyear_string = 0x5ad1; + static constexpr Tagged_t kyears_string = 0x5ae1; + static constexpr Tagged_t kUninitializedValue = 0x5b05; + static constexpr Tagged_t kArgumentsMarker = 0x5b3d; + static constexpr Tagged_t kTerminationException = 0x5b75; + static constexpr Tagged_t kException = 0x5bb5; + static constexpr Tagged_t kOptimizedOut = 0x5bd1; + static constexpr Tagged_t kStaleRegister = 0x5c09; + static constexpr Tagged_t kSelfReferenceMarker = 0x5c41; + static constexpr Tagged_t kBasicBlockCountersMarker = 0x5c81; + static constexpr Tagged_t karray_buffer_wasm_memory_symbol = 0x5cc5; + static constexpr Tagged_t kcall_site_info_symbol = 0x5cd5; + static constexpr Tagged_t kconsole_context_id_symbol = 0x5ce5; + static constexpr Tagged_t kconsole_context_name_symbol = 0x5cf5; + static constexpr Tagged_t kclass_fields_symbol = 0x5d05; + static constexpr Tagged_t kclass_positions_symbol = 0x5d15; + static constexpr Tagged_t kerror_end_pos_symbol = 0x5d25; + static constexpr Tagged_t kerror_script_symbol = 0x5d35; + static constexpr Tagged_t kerror_stack_symbol = 0x5d45; + static constexpr Tagged_t kerror_start_pos_symbol = 0x5d55; + static constexpr Tagged_t kfrozen_symbol = 0x5d65; + static constexpr Tagged_t kinterpreter_trampoline_symbol = 0x5d75; + static constexpr Tagged_t knative_context_index_symbol = 0x5d85; + static constexpr Tagged_t knonextensible_symbol = 0x5d95; + static constexpr Tagged_t kpromise_debug_marker_symbol = 0x5da5; + static constexpr Tagged_t kpromise_debug_message_symbol = 0x5db5; + static constexpr Tagged_t kpromise_forwarding_handler_symbol = 0x5dc5; + static constexpr Tagged_t kpromise_handled_by_symbol = 0x5dd5; + static constexpr Tagged_t kpromise_awaited_by_symbol = 0x5de5; + static constexpr Tagged_t kregexp_result_names_symbol = 0x5df5; + static constexpr Tagged_t kregexp_result_regexp_input_symbol = 0x5e05; + static constexpr Tagged_t kregexp_result_regexp_last_index_symbol = 0x5e15; + static constexpr Tagged_t ksealed_symbol = 0x5e25; + static constexpr Tagged_t kstrict_function_transition_symbol = 0x5e35; static constexpr Tagged_t ktemplate_literal_function_literal_id_symbol = - 0x5e35; - static constexpr Tagged_t ktemplate_literal_slot_id_symbol = 0x5e45; - static constexpr Tagged_t kwasm_exception_tag_symbol = 0x5e55; - static constexpr Tagged_t kwasm_exception_values_symbol = 0x5e65; - static constexpr Tagged_t kwasm_uncatchable_symbol = 0x5e75; - static constexpr Tagged_t kwasm_wrapped_object_symbol = 0x5e85; - static constexpr Tagged_t kwasm_debug_proxy_cache_symbol = 0x5e95; - static constexpr Tagged_t kwasm_debug_proxy_names_symbol = 0x5ea5; - static constexpr Tagged_t kasync_iterator_symbol = 0x5eb5; - static constexpr Tagged_t kintl_fallback_symbol = 0x5ee5; - static constexpr Tagged_t kmatch_all_symbol = 0x5f1d; - static constexpr Tagged_t kmatch_symbol = 0x5f49; - static constexpr Tagged_t ksearch_symbol = 0x5f71; - static constexpr Tagged_t ksplit_symbol = 0x5f9d; - static constexpr Tagged_t kto_primitive_symbol = 0x5fc5; - static constexpr Tagged_t kunscopables_symbol = 0x5ff5; - static constexpr Tagged_t khas_instance_symbol = 0x6025; - static constexpr Tagged_t kto_string_tag_symbol = 0x6055; - static constexpr Tagged_t kconstructor_string = 0x60ad; - static constexpr Tagged_t knext_string = 0x60c5; - static constexpr Tagged_t kresolve_string = 0x60d5; - static constexpr Tagged_t kthen_string = 0x60e9; - static constexpr Tagged_t kiterator_symbol = 0x60f9; - static constexpr Tagged_t kreplace_symbol = 0x6109; - static constexpr Tagged_t kspecies_symbol = 0x6119; - static constexpr Tagged_t kis_concat_spreadable_symbol = 0x6129; - static constexpr Tagged_t kEmptySlowElementDictionary = 0x6139; - static constexpr Tagged_t kEmptySymbolTable = 0x615d; - static constexpr Tagged_t kEmptyOrderedHashMap = 0x6179; - static constexpr Tagged_t kEmptyOrderedHashSet = 0x618d; - static constexpr Tagged_t kEmptyFeedbackMetadata = 0x61a1; - static constexpr Tagged_t kGlobalThisBindingScopeInfo = 0x61ad; - static constexpr Tagged_t kEmptyFunctionScopeInfo = 0x61cd; - static constexpr Tagged_t kNativeScopeInfo = 0x61f1; - static constexpr Tagged_t kShadowRealmScopeInfo = 0x6209; + 0x5e45; + static constexpr Tagged_t ktemplate_literal_slot_id_symbol = 0x5e55; + static constexpr Tagged_t kwasm_exception_tag_symbol = 0x5e65; + static constexpr Tagged_t kwasm_exception_values_symbol = 0x5e75; + static constexpr Tagged_t kwasm_uncatchable_symbol = 0x5e85; + static constexpr Tagged_t kwasm_wrapped_object_symbol = 0x5e95; + static constexpr Tagged_t kwasm_debug_proxy_cache_symbol = 0x5ea5; + static constexpr Tagged_t kwasm_debug_proxy_names_symbol = 0x5eb5; + static constexpr Tagged_t kasync_iterator_symbol = 0x5ec5; + static constexpr Tagged_t kintl_fallback_symbol = 0x5ef5; + static constexpr Tagged_t kmatch_all_symbol = 0x5f2d; + static constexpr Tagged_t kmatch_symbol = 0x5f59; + static constexpr Tagged_t ksearch_symbol = 0x5f81; + static constexpr Tagged_t ksplit_symbol = 0x5fad; + static constexpr Tagged_t kto_primitive_symbol = 0x5fd5; + static constexpr Tagged_t kunscopables_symbol = 0x6005; + static constexpr Tagged_t khas_instance_symbol = 0x6035; + static constexpr Tagged_t kto_string_tag_symbol = 0x6065; + static constexpr Tagged_t kconstructor_string = 0x60bd; + static constexpr Tagged_t knext_string = 0x60d5; + static constexpr Tagged_t kresolve_string = 0x60e5; + static constexpr Tagged_t kthen_string = 0x60f9; + static constexpr Tagged_t kiterator_symbol = 0x6109; + static constexpr Tagged_t kreplace_symbol = 0x6119; + static constexpr Tagged_t kspecies_symbol = 0x6129; + static constexpr Tagged_t kis_concat_spreadable_symbol = 0x6139; + static constexpr Tagged_t kEmptySlowElementDictionary = 0x6149; + static constexpr Tagged_t kEmptySymbolTable = 0x616d; + static constexpr Tagged_t kEmptyOrderedHashMap = 0x6189; + static constexpr Tagged_t kEmptyOrderedHashSet = 0x619d; + static constexpr Tagged_t kEmptyFeedbackMetadata = 0x61b1; + static constexpr Tagged_t kGlobalThisBindingScopeInfo = 0x61bd; + static constexpr Tagged_t kEmptyFunctionScopeInfo = 0x61dd; + static constexpr Tagged_t kNativeScopeInfo = 0x6201; + static constexpr Tagged_t kShadowRealmScopeInfo = 0x6219; static constexpr Tagged_t kWasmNull = 0xfffd; }; -static constexpr std::array StaticReadOnlyRootsPointerTable = { +static constexpr std::array StaticReadOnlyRootsPointerTable = { StaticReadOnlyRoot::kFreeSpaceMap, StaticReadOnlyRoot::kOnePointerFillerMap, StaticReadOnlyRoot::kTwoPointerFillerMap, @@ -1365,6 +1366,7 @@ static constexpr std::array StaticReadOnlyRootsPointerTable = { StaticReadOnlyRoot::kweek_string, StaticReadOnlyRoot::kweeks_string, StaticReadOnlyRoot::kweekOfYear_string, + StaticReadOnlyRoot::kwith_string, StaticReadOnlyRoot::kword_string, StaticReadOnlyRoot::kyearMonthFromFields_string, StaticReadOnlyRoot::kyear_string, diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-1.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-1.mjs new file mode 100644 index 00000000000000..3cf6bac870b80f --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-1.mjs @@ -0,0 +1,9 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --harmony-import-attributes + +import { life } from 'modules-skip-1.mjs' with { }; + +assertEquals(42, life()); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-2.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-2.mjs new file mode 100644 index 00000000000000..63f5859ca0f1d0 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-2.mjs @@ -0,0 +1,9 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --harmony-import-attributes + +import json from 'modules-skip-1.json' with { type: 'json' }; + +assertEquals(42, json.life); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-3.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-3.mjs new file mode 100644 index 00000000000000..34db1852367c19 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-3.mjs @@ -0,0 +1,9 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --harmony-import-attributes + +import {life} from 'modules-skip-imports-json-1.mjs'; + +assertEquals(42, life()); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-4.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-4.mjs new file mode 100644 index 00000000000000..66bdc11c359ed4 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-4.mjs @@ -0,0 +1,9 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --harmony-import-attributes + +import json from 'modules-skip-1.json' with { type: 'json', notARealAssertion: 'value'}; + +assertEquals(42, json.life); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-1.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-1.mjs new file mode 100644 index 00000000000000..644fc96a6e2111 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-1.mjs @@ -0,0 +1,12 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var life; +import('modules-skip-1.mjs', { }).then(namespace => life = namespace.life()); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-10.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-10.mjs new file mode 100644 index 00000000000000..972fbed24bfff2 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-10.mjs @@ -0,0 +1,19 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var result1; +var result2; +import('modules-skip-1.json', { get with() { throw 'bad \'with\' getter!'; } }).then( + () => assertUnreachable('Should have failed due to throwing getter'), + error => result1 = error); +import('modules-skip-1.json', { with: { get assertionKey() { throw 'bad \'assertionKey\' getter!'; } } }).then( + () => assertUnreachable('Should have failed due to throwing getter'), + error => result2 = error); + +%PerformMicrotaskCheckpoint(); + +assertEquals('bad \'with\' getter!', result1); +assertEquals('bad \'assertionKey\' getter!', result2); \ No newline at end of file diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-11.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-11.mjs new file mode 100644 index 00000000000000..c39a5f9d4e2e06 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-11.mjs @@ -0,0 +1,19 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes --harmony-top-level-await + +var life1; +var life2; +import('modules-skip-1.json', { with: { type: 'json' } }).then( + namespace => life1 = namespace.default.life); + +// Try loading the same module a second time. +import('modules-skip-1.json', { with: { type: 'json' } }).then( + namespace => life2 = namespace.default.life); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life1); +assertEquals(42, life2); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-12.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-12.mjs new file mode 100644 index 00000000000000..1d8fb21a502237 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-12.mjs @@ -0,0 +1,26 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +let result1; +let result2; + +let badAssertProxy1 = new Proxy({}, { ownKeys() { throw "bad ownKeys!"; } }); +import('./modules-skip-1.mjs', { with: badAssertProxy1 }).then( + () => assertUnreachable('Should have failed due to throwing ownKeys'), + error => result1 = error); + +let badAssertProxy2 = new Proxy( + {foo: "bar"}, + { getOwnPropertyDescriptor() { throw "bad getOwnPropertyDescriptor!"; } }); +import('./modules-skip-1.mjs', { with: badAssertProxy2 }).then( + () => assertUnreachable( + 'Should have failed due to throwing getOwnPropertyDescriptor'), + error => result2 = error); + +%PerformMicrotaskCheckpoint(); + +assertEquals('bad ownKeys!', result1); +assertEquals('bad getOwnPropertyDescriptor!', result2); \ No newline at end of file diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-13.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-13.mjs new file mode 100644 index 00000000000000..6e23c07c281db1 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-13.mjs @@ -0,0 +1,26 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +let getters = 0; +let result; + +import('./modules-skip-1.mjs', { with: { + get attr1() { + getters++; + return {}; + }, + get attr2() { + getters++; + return {}; + }, +} }).then( + () => assertUnreachable('Should have failed due to invalid attributes values'), + error => result = error); + +%PerformMicrotaskCheckpoint(); + +assertEquals(2, getters); +assertInstanceof(result, TypeError); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-2.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-2.mjs new file mode 100644 index 00000000000000..b8e59fb0bfe9c3 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-2.mjs @@ -0,0 +1,13 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var life; +import('modules-skip-1.mjs', { with: { } }).then( + namespace => life = namespace.life()); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-3.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-3.mjs new file mode 100644 index 00000000000000..da570eb2836edd --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-3.mjs @@ -0,0 +1,13 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var life; +import('modules-skip-1.json', { with: { type: 'json' } }).then( + namespace => life = namespace.default.life); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-4.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-4.mjs new file mode 100644 index 00000000000000..fff6fe2bbef84c --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-4.mjs @@ -0,0 +1,14 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var result; +import('modules-skip-1.json', { with: { type: 'notARealType' } }).then( + () => assertUnreachable('Should have failed due to bad module type'), + error => result = error.message); + +%PerformMicrotaskCheckpoint(); + +assertEquals('Invalid module type was asserted', result); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-5.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-5.mjs new file mode 100644 index 00000000000000..cdb1567c330764 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-5.mjs @@ -0,0 +1,12 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var life; +import('modules-skip-imports-json-1.mjs',).then(namespace => life = namespace.life()); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-6.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-6.mjs new file mode 100644 index 00000000000000..ad84e3edd5ffe1 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-6.mjs @@ -0,0 +1,18 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var life; +import('modules-skip-1.json', { with: { type: 'json', notARealAssertion: 'value' } }).then( + namespace => life = namespace.default.life); + +var life2; +import('modules-skip-1.json', { with: { 0: 'value', type: 'json' } }).then( + namespace => life2 = namespace.default.life); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life); +assertEquals(42, life2); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-7.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-7.mjs new file mode 100644 index 00000000000000..530d833cce5a4d --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-7.mjs @@ -0,0 +1,63 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var result1; +var result2; +var result3; +var result4; +var result5; +var result6; +var result7; +var result8; +var result9; +var result10; +import('modules-skip-1.json', null).then( + () => assertUnreachable('Should have failed due to non-object parameter'), + error => result1 = error.message); +import('modules-skip-1.json', 7).then( + () => assertUnreachable('Should have failed due to non-object parameter'), + error => result2 = error.message); +import('modules-skip-1.json', 'string').then( + () => assertUnreachable('Should have failed due to non-object parameter'), + error => result3 = error.message); +import('modules-skip-1.json', { with: null}).then( + () => assertUnreachable('Should have failed due to bad with object'), + error => result4 = error.message); +import('modules-skip-1.json', { with: 7}).then( + () => assertUnreachable('Should have failed due to bad with object'), + error => result5 = error.message); +import('modules-skip-1.json', { with: 'string'}).then( + () => assertUnreachable('Should have failed due to bad with object'), + error => result6 = error.message); +import('modules-skip-1.json', { with: { a: null }}).then( + () => assertUnreachable('Should have failed due to bad with object'), + error => result7 = error.message); +import('modules-skip-1.json', { with: { a: undefined }}).then( + () => assertUnreachable('Should have failed due to bad assertion value'), + error => result8 = error.message); +import('modules-skip-1.json', { with: { a: 7 }}).then( + () => assertUnreachable('Should have failed due to bad assertion value'), + error => result9 = error.message); + import('modules-skip-1.json', { with: { a: { } }}).then( + () => assertUnreachable('Should have failed due to bad assertion value'), + error => result10 = error.message); + +%PerformMicrotaskCheckpoint(); + +const argumentNotObjectError = 'The second argument to import() must be an object'; +const assertOptionNotObjectError = 'The \'assert\' option must be an object'; +const assertionValueNotStringError = 'Import assertion value must be a string'; + +assertEquals(argumentNotObjectError, result1); +assertEquals(argumentNotObjectError, result2); +assertEquals(argumentNotObjectError, result3); +assertEquals(assertOptionNotObjectError, result4); +assertEquals(assertOptionNotObjectError, result5); +assertEquals(assertOptionNotObjectError, result6); +assertEquals(assertionValueNotStringError, result7); +assertEquals(assertionValueNotStringError, result8); +assertEquals(assertionValueNotStringError, result9); +assertEquals(assertionValueNotStringError, result10); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-8.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-8.mjs new file mode 100644 index 00000000000000..74a4504e253d6a --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-8.mjs @@ -0,0 +1,13 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var life; +import('modules-skip-1.mjs', undefined).then( + namespace => life = namespace.life()); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-9.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-9.mjs new file mode 100644 index 00000000000000..1b56c70df61682 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-9.mjs @@ -0,0 +1,13 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes + +var life; +import('modules-skip-1.mjs', { with: undefined }).then( + namespace => life = namespace.life()); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-assertions-fallback-1.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-assertions-fallback-1.mjs new file mode 100644 index 00000000000000..3faaf0dccea1c9 --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-assertions-fallback-1.mjs @@ -0,0 +1,15 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes --harmony-import-assertions + +var life; +import('modules-skip-1.mjs', { + with: {}, + get assert() { throw 'Should not read assert'; } +}).then(namespace => life = namespace.life()); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life); diff --git a/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-assertions-fallback-2.mjs b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-assertions-fallback-2.mjs new file mode 100644 index 00000000000000..4c83d2fbd716cc --- /dev/null +++ b/deps/v8/test/mjsunit/harmony/modules-import-attributes-dynamic-assertions-fallback-2.mjs @@ -0,0 +1,15 @@ +// Copyright 2021 the V8 project authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Flags: --allow-natives-syntax --harmony-import-attributes --harmony-import-assertions + +var life; +import('modules-skip-1.json', { + with: undefined, + assert: { type: 'json' } +}).then(namespace => life = namespace.default.life); + +%PerformMicrotaskCheckpoint(); + +assertEquals(42, life); diff --git a/deps/v8/test/unittests/parser/parsing-unittest.cc b/deps/v8/test/unittests/parser/parsing-unittest.cc index 9048d7f1faf674..24e178e2ca5eb6 100644 --- a/deps/v8/test/unittests/parser/parsing-unittest.cc +++ b/deps/v8/test/unittests/parser/parsing-unittest.cc @@ -4876,6 +4876,11 @@ TEST_F(ParsingTest, BasicImportAssertionParsing) { "import { a as b } from 'm.js' assert { \nc: 'd'};", "import { a as b } from 'm.js' assert { c:\n 'd'};", "import { a as b } from 'm.js' assert { c:'d'\n};", + + "import { a as b } from 'm.js' assert { 0: 'b', };", + "import { a as b } from 'm.js' assert { 0n: 'b', };", + "import { a as b } from 'm.js' assert { '0': 'b', };", + "import { a as b } from 'm.js' assert { 0.0: 'b', };", }; // clang-format on @@ -4943,10 +4948,234 @@ TEST_F(ParsingTest, ImportAssertionParsingErrors) { "import { a } from 'm.js' assert { , b: c };", "import { a } from 'm.js' assert { a: 'b', a: 'c' };", "import { a } from 'm.js' assert { a: 'b', 'a': 'c' };", + + "import { a } from 'm.js' assert { 0: 'b', '0': 'c' };", + "import { a } from 'm.js' assert { 0n: 'b', '0': 'c' };", + "import { a } from 'm.js' assert { 0: 'b', 0n: 'c' };", + "import { a } from 'm.js' assert { 0: 'b', 0.0: 'c' };", + "import { a } from 'm.js' assert { '0': 'b', 0n: 'c' };", + + "import 'm.js' with { a: 'b' };" + }; + // clang-format on + + i::v8_flags.harmony_import_assertions = true; + i::Isolate* isolate = i_isolate(); + i::Factory* factory = isolate->factory(); + + isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - + 128 * 1024); + + for (unsigned i = 0; i < arraysize(kErrorSources); ++i) { + i::Handle source = + factory->NewStringFromAsciiChecked(kErrorSources[i]); + + i::Handle script = factory->NewScript(source); + i::UnoptimizedCompileState compile_state; + i::ReusableUnoptimizedCompileState reusable_state(isolate); + i::UnoptimizedCompileFlags flags = + i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); + flags.set_is_module(true); + i::ParseInfo info(isolate, flags, &compile_state, &reusable_state); + CHECK(!i::parsing::ParseProgram(&info, script, isolate, + parsing::ReportStatisticsMode::kYes)); + CHECK(info.pending_error_handler()->has_pending_error()); + } +} + +TEST_F(ParsingTest, BasicImportAttributesParsing) { + // clang-format off + const char* kSources[] = { + "import { a as b } from 'm.js' with { };", + "import n from 'n.js' with { };", + "export { a as b } from 'm.js' with { };", + "export * from 'm.js' with { };", + "import 'm.js' with { };", + "import * as foo from 'bar.js' with { };", + + "import { a as b } from 'm.js' with { a: 'b' };", + "import { a as b } from 'm.js' with { c: 'd' };", + "import { a as b } from 'm.js' with { 'c': 'd' };", + "import { a as b } from 'm.js' with { a: 'b', 'c': 'd', e: 'f' };", + "import { a as b } from 'm.js' with { 'c': 'd', };", + "import n from 'n.js' with { 'c': 'd' };", + "export { a as b } from 'm.js' with { 'c': 'd' };", + "export * from 'm.js' with { 'c': 'd' };", + "import 'm.js' with { 'c': 'd' };", + "import * as foo from 'bar.js' with { 'c': 'd' };", + + "import { a as b } from 'm.js' with { \nc: 'd'};", + "import { a as b } from 'm.js' with { c:\n 'd'};", + "import { a as b } from 'm.js' with { c:'d'\n};", + + "import { a as b } from 'm.js' with { 0: 'b', };", + "import { a as b } from 'm.js' with { 0n: 'b', };", + "import { a as b } from 'm.js' with { '0': 'b', };", + "import { a as b } from 'm.js' with { 0.0: 'b', };", + + "import 'm.js'\n with { };", + "import 'm.js' \nwith { };", + "import { a } from 'm.js'\n with { };", + "export * from 'm.js'\n with { };" + }; + // clang-format on + + i::v8_flags.harmony_import_attributes = true; + i::Isolate* isolate = i_isolate(); + i::Factory* factory = isolate->factory(); + + isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - + 128 * 1024); + + for (unsigned i = 0; i < arraysize(kSources); ++i) { + i::Handle source = + factory->NewStringFromAsciiChecked(kSources[i]); + + // Show that parsing as a module works + { + i::Handle script = factory->NewScript(source); + i::UnoptimizedCompileState compile_state; + i::ReusableUnoptimizedCompileState reusable_state(isolate); + i::UnoptimizedCompileFlags flags = + i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); + flags.set_is_module(true); + i::ParseInfo info(isolate, flags, &compile_state, &reusable_state); + CHECK_PARSE_PROGRAM(&info, script, isolate); + } + + // And that parsing a script does not. + { + i::UnoptimizedCompileState compile_state; + i::ReusableUnoptimizedCompileState reusable_state(isolate); + i::Handle script = factory->NewScript(source); + i::UnoptimizedCompileFlags flags = + i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); + i::ParseInfo info(isolate, flags, &compile_state, &reusable_state); + CHECK(!i::parsing::ParseProgram(&info, script, isolate, + parsing::ReportStatisticsMode::kYes)); + CHECK(info.pending_error_handler()->has_pending_error()); + } + } +} + +TEST_F(ParsingTest, ImportAttributesParsingErrors) { + // clang-format off + const char* kErrorSources[] = { + "import { a } from 'm.js' with {;", + "import { a } from 'm.js' with };", + "import { a } from 'm.js' , with { };", + "import { a } from 'm.js' with , { };", + "import { a } from 'm.js' with { , };", + "import { a } from 'm.js' with { b };", + "import { a } from 'm.js' with { 'b' };", + "import { a } from 'm.js' with { for };", + "import { a } from 'm.js' with { with };", + "export { a } with { };", + "export * with { };", + + "import { a } from 'm.js' with { 1: 2 };", + "import { a } from 'm.js' with { b: c };", + "import { a } from 'm.js' with { 'b': c };", + "import { a } from 'm.js' with { , b: c };", + "import { a } from 'm.js' with { a: 'b', a: 'c' };", + "import { a } from 'm.js' with { a: 'b', 'a': 'c' };", + + "import { a } from 'm.js' with { 0: 'b', '0': 'c' };", + "import { a } from 'm.js' with { 0n: 'b', '0': 'c' };", + "import { a } from 'm.js' with { 0: 'b', 0n: 'c' };", + "import { a } from 'm.js' with { 0: 'b', 0.0: 'c' };", + "import { a } from 'm.js' with { '0': 'b', 0n: 'c' };", + + "import 'm.js' assert { a: 'b' };" + }; + // clang-format on + + i::v8_flags.harmony_import_assertions = false; + i::v8_flags.harmony_import_attributes = true; + i::Isolate* isolate = i_isolate(); + i::Factory* factory = isolate->factory(); + + isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - + 128 * 1024); + + for (unsigned i = 0; i < arraysize(kErrorSources); ++i) { + i::Handle source = + factory->NewStringFromAsciiChecked(kErrorSources[i]); + + i::Handle script = factory->NewScript(source); + i::UnoptimizedCompileState compile_state; + i::ReusableUnoptimizedCompileState reusable_state(isolate); + i::UnoptimizedCompileFlags flags = + i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); + flags.set_is_module(true); + i::ParseInfo info(isolate, flags, &compile_state, &reusable_state); + CHECK(!i::parsing::ParseProgram(&info, script, isolate, + parsing::ReportStatisticsMode::kYes)); + CHECK(info.pending_error_handler()->has_pending_error()); + } +} + +TEST_F(ParsingTest, BasicImportAttributesAndAssertionsParsing) { + // clang-format off + const char* kSources[] = { + "import { a } from 'm.js' assert { };", + "import { a } from 'm.js' with { };", + "import { a } from 'm.js'\n with { };", + }; + // clang-format on + + i::v8_flags.harmony_import_assertions = true; + i::v8_flags.harmony_import_attributes = true; + i::Isolate* isolate = i_isolate(); + i::Factory* factory = isolate->factory(); + + isolate->stack_guard()->SetStackLimit(i::GetCurrentStackPosition() - + 128 * 1024); + + for (unsigned i = 0; i < arraysize(kSources); ++i) { + i::Handle source = + factory->NewStringFromAsciiChecked(kSources[i]); + + // Show that parsing as a module works + { + i::Handle script = factory->NewScript(source); + i::UnoptimizedCompileState compile_state; + i::ReusableUnoptimizedCompileState reusable_state(isolate); + i::UnoptimizedCompileFlags flags = + i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); + flags.set_is_module(true); + i::ParseInfo info(isolate, flags, &compile_state, &reusable_state); + CHECK_PARSE_PROGRAM(&info, script, isolate); + } + + // And that parsing a script does not. + { + i::UnoptimizedCompileState compile_state; + i::ReusableUnoptimizedCompileState reusable_state(isolate); + i::Handle script = factory->NewScript(source); + i::UnoptimizedCompileFlags flags = + i::UnoptimizedCompileFlags::ForScriptCompile(isolate, *script); + i::ParseInfo info(isolate, flags, &compile_state, &reusable_state); + CHECK(!i::parsing::ParseProgram(&info, script, isolate, + parsing::ReportStatisticsMode::kYes)); + CHECK(info.pending_error_handler()->has_pending_error()); + } + } +} + +TEST_F(ParsingTest, ImportAttributesAndAssertionsParsingErrors) { + // clang-format off + const char* kErrorSources[] = { + "import { a } from 'm.js'\n assert { };", + "import { a } from 'm.js' with { } assert { };", + "import { a } from 'm.js' with assert { };", + "import { a } from 'm.js' assert { } with { };", + "import { a } from 'm.js' assert with { };", }; // clang-format on i::v8_flags.harmony_import_assertions = true; + i::v8_flags.harmony_import_attributes = true; i::Isolate* isolate = i_isolate(); i::Factory* factory = isolate->factory(); From ecb8bec88d66cb050b8be8e68ee3d1a4bc25458b Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Tue, 24 Oct 2023 12:07:14 +0200 Subject: [PATCH 4/6] deps: V8: cherry-pick ea996ad04a68 Original commit message: [import-attributes] Remove support for numeric keys During the 2023-09 TC39 meeting the proposal has been updated to remove support for bigint and float literals as import attribute keys, due to implementation difficulties in other engines and minimal added value for JS developers. GH issue: https://github.com/tc39/proposal-import-attributes/issues/145 Bug: v8:13856 Change-Id: I0ede2bb10d6ca338a4b0870a1261ccbcd088c16f Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4899760 Reviewed-by: Shu-yu Guo Commit-Queue: Joyee Cheung Cr-Commit-Position: refs/heads/main@{#90318} Refs: https://github.com/v8/v8/commit/ea996ad04a684e32cd93018f0aad2f46e6b86975 --- common.gypi | 2 +- deps/v8/src/parsing/parser.cc | 12 ++-------- .../test/unittests/parser/parsing-unittest.cc | 22 ++----------------- 3 files changed, 5 insertions(+), 31 deletions(-) diff --git a/common.gypi b/common.gypi index 519ddcd28e6524..1aa75a409316c1 100644 --- a/common.gypi +++ b/common.gypi @@ -36,7 +36,7 @@ # Reset this number to 0 on major V8 upgrades. # Increment by one for each non-official patch applied to deps/v8. - 'v8_embedder_string': '-node.17', + 'v8_embedder_string': '-node.18', ##### V8 defaults for Node.js ##### diff --git a/deps/v8/src/parsing/parser.cc b/deps/v8/src/parsing/parser.cc index 336d88b2ea195b..4a3fceed6af7c6 100644 --- a/deps/v8/src/parsing/parser.cc +++ b/deps/v8/src/parsing/parser.cc @@ -1372,16 +1372,8 @@ ImportAssertions* Parser::ParseImportAssertClause() { Expect(Token::LBRACE); while (peek() != Token::RBRACE) { - const AstRawString* attribute_key = nullptr; - if (Check(Token::STRING) || Check(Token::SMI)) { - attribute_key = GetSymbol(); - } else if (Check(Token::NUMBER)) { - attribute_key = GetNumberAsSymbol(); - } else if (Check(Token::BIGINT)) { - attribute_key = GetBigIntAsSymbol(); - } else { - attribute_key = ParsePropertyName(); - } + const AstRawString* attribute_key = + Check(Token::STRING) ? GetSymbol() : ParsePropertyName(); Scanner::Location location = scanner()->location(); diff --git a/deps/v8/test/unittests/parser/parsing-unittest.cc b/deps/v8/test/unittests/parser/parsing-unittest.cc index 24e178e2ca5eb6..add2d791c6177f 100644 --- a/deps/v8/test/unittests/parser/parsing-unittest.cc +++ b/deps/v8/test/unittests/parser/parsing-unittest.cc @@ -4877,10 +4877,7 @@ TEST_F(ParsingTest, BasicImportAssertionParsing) { "import { a as b } from 'm.js' assert { c:\n 'd'};", "import { a as b } from 'm.js' assert { c:'d'\n};", - "import { a as b } from 'm.js' assert { 0: 'b', };", - "import { a as b } from 'm.js' assert { 0n: 'b', };", "import { a as b } from 'm.js' assert { '0': 'b', };", - "import { a as b } from 'm.js' assert { 0.0: 'b', };", }; // clang-format on @@ -4942,19 +4939,13 @@ TEST_F(ParsingTest, ImportAssertionParsingErrors) { "import { a } from 'm.js'\n assert { };", "export * from 'm.js'\n assert { };", - "import { a } from 'm.js' assert { 1: 2 };", + "import { a } from 'm.js' assert { x: 2 };", "import { a } from 'm.js' assert { b: c };", "import { a } from 'm.js' assert { 'b': c };", "import { a } from 'm.js' assert { , b: c };", "import { a } from 'm.js' assert { a: 'b', a: 'c' };", "import { a } from 'm.js' assert { a: 'b', 'a': 'c' };", - "import { a } from 'm.js' assert { 0: 'b', '0': 'c' };", - "import { a } from 'm.js' assert { 0n: 'b', '0': 'c' };", - "import { a } from 'm.js' assert { 0: 'b', 0n: 'c' };", - "import { a } from 'm.js' assert { 0: 'b', 0.0: 'c' };", - "import { a } from 'm.js' assert { '0': 'b', 0n: 'c' };", - "import 'm.js' with { a: 'b' };" }; // clang-format on @@ -5008,10 +4999,7 @@ TEST_F(ParsingTest, BasicImportAttributesParsing) { "import { a as b } from 'm.js' with { c:\n 'd'};", "import { a as b } from 'm.js' with { c:'d'\n};", - "import { a as b } from 'm.js' with { 0: 'b', };", - "import { a as b } from 'm.js' with { 0n: 'b', };", "import { a as b } from 'm.js' with { '0': 'b', };", - "import { a as b } from 'm.js' with { 0.0: 'b', };", "import 'm.js'\n with { };", "import 'm.js' \nwith { };", @@ -5073,19 +5061,13 @@ TEST_F(ParsingTest, ImportAttributesParsingErrors) { "export { a } with { };", "export * with { };", - "import { a } from 'm.js' with { 1: 2 };", + "import { a } from 'm.js' with { x: 2 };", "import { a } from 'm.js' with { b: c };", "import { a } from 'm.js' with { 'b': c };", "import { a } from 'm.js' with { , b: c };", "import { a } from 'm.js' with { a: 'b', a: 'c' };", "import { a } from 'm.js' with { a: 'b', 'a': 'c' };", - "import { a } from 'm.js' with { 0: 'b', '0': 'c' };", - "import { a } from 'm.js' with { 0n: 'b', '0': 'c' };", - "import { a } from 'm.js' with { 0: 'b', 0n: 'c' };", - "import { a } from 'm.js' with { 0: 'b', 0.0: 'c' };", - "import { a } from 'm.js' with { '0': 'b', 0n: 'c' };", - "import 'm.js' assert { a: 'b' };" }; // clang-format on From d22b640a3281c98b6d826f9e9f5c30b329b2ce37 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Sat, 14 Oct 2023 05:52:38 +0200 Subject: [PATCH 5/6] esm: use import attributes instead of import assertions The old import assertions proposal has been renamed to "import attributes" with the follwing major changes: 1. The keyword is now `with` instead of `assert`. 2. Unknown assertions cause an error rather than being ignored, This commit updates the documentation to encourage folks to use the new syntax, and add aliases for module customization hooks. PR-URL: https://github.com/nodejs/node/pull/50140 Fixes: https://github.com/nodejs/node/issues/50134 Refs: https://github.com/v8/v8/commit/159c82c5e6392e78b9bba7161b1bed6e23758984 Reviewed-By: Geoffrey Booth Reviewed-By: Jacob Smith Reviewed-By: Benjamin Gruenbaum --- .eslintignore | 2 + .eslintrc.js | 4 +- doc/api/errors.md | 17 +++- doc/api/esm.md | 39 ++++++---- doc/api/module.md | 19 +++-- lib/internal/errors.js | 9 ++- lib/internal/modules/cjs/loader.js | 8 +- lib/internal/modules/esm/assert.js | 53 ++++++------- lib/internal/modules/esm/hooks.js | 54 ++++++++++--- lib/internal/modules/esm/load.js | 22 ++++-- lib/internal/modules/esm/loader.js | 77 ++++++++++--------- lib/internal/modules/esm/module_job.js | 10 +-- lib/internal/modules/esm/module_map.js | 16 ++-- lib/internal/modules/esm/translators.js | 16 ++-- lib/internal/modules/esm/utils.js | 15 +++- lib/internal/process/execution.js | 4 +- lib/repl.js | 8 +- src/module_wrap.cc | 44 +++++------ src/module_wrap.h | 2 +- src/node.cc | 7 ++ .../test-esm-assertionless-json-import.js | 10 +-- test/es-module/test-esm-data-urls.js | 10 +-- ...s => test-esm-dynamic-import-attribute.js} | 8 +- ... => test-esm-dynamic-import-attribute.mjs} | 8 +- .../es-module/test-esm-import-assertion-2.mjs | 6 -- .../test-esm-import-assertion-validation.js | 45 ----------- .../test-esm-import-assertion-warning.mjs | 43 +++++++++-- ...1.mjs => test-esm-import-attributes-1.mjs} | 2 +- ...4.mjs => test-esm-import-attributes-2.mjs} | 4 +- ...3.mjs => test-esm-import-attributes-3.mjs} | 4 +- ...s => test-esm-import-attributes-errors.js} | 26 +++---- ... => test-esm-import-attributes-errors.mjs} | 24 ++---- .../test-esm-import-attributes-validation.js | 45 +++++++++++ test/es-module/test-esm-json-cache.mjs | 2 +- test/es-module/test-esm-json.mjs | 11 ++- test/es-module/test-esm-virtual-json.mjs | 6 +- .../assertionless-json-import.mjs | 14 ++-- .../builtin-named-exports-loader.mjs | 2 +- .../es-module-loaders/hooks-custom.mjs | 4 +- .../es-module-loaders/hooks-input.mjs | 12 +-- .../loader-invalid-format.mjs | 2 +- .../es-module-loaders/loader-invalid-url.mjs | 4 +- .../es-module-loaders/loader-with-dep.mjs | 4 +- .../not-found-assert-loader.mjs | 4 +- .../es-module-loaders/string-sources.mjs | 2 +- .../es-modules/import-json-named-export.mjs | 2 +- test/fixtures/es-modules/json-modules.mjs | 2 +- .../parallel/test-vm-module-dynamic-import.js | 6 +- tools/dep_updaters/update-eslint.sh | 4 +- .../lib/index.js | 19 ----- .../LICENSE | 0 .../lib/index.js | 31 ++++++++ .../package.json | 10 ++- .../data/features/css-display-contents.js | 2 +- .../node_modules/caniuse-lite/package.json | 2 +- .../full-chromium-versions.js | 50 +++++++++++- .../full-chromium-versions.json | 2 +- .../electron-to-chromium/full-versions.js | 32 +++++++- .../electron-to-chromium/full-versions.json | 2 +- .../electron-to-chromium/package.json | 2 +- 60 files changed, 541 insertions(+), 353 deletions(-) rename test/es-module/{test-esm-dynamic-import-assertion.js => test-esm-dynamic-import-attribute.js} (78%) rename test/es-module/{test-esm-dynamic-import-assertion.mjs => test-esm-dynamic-import-attribute.mjs} (75%) delete mode 100644 test/es-module/test-esm-import-assertion-2.mjs delete mode 100644 test/es-module/test-esm-import-assertion-validation.js rename test/es-module/{test-esm-import-assertion-1.mjs => test-esm-import-attributes-1.mjs} (57%) rename test/es-module/{test-esm-import-assertion-4.mjs => test-esm-import-attributes-2.mjs} (70%) rename test/es-module/{test-esm-import-assertion-3.mjs => test-esm-import-attributes-3.mjs} (69%) rename test/es-module/{test-esm-import-assertion-errors.js => test-esm-import-attributes-errors.js} (59%) rename test/es-module/{test-esm-import-assertion-errors.mjs => test-esm-import-attributes-errors.mjs} (56%) create mode 100644 test/es-module/test-esm-import-attributes-validation.js delete mode 100644 tools/node_modules/eslint/node_modules/@babel/plugin-syntax-import-assertions/lib/index.js rename tools/node_modules/eslint/node_modules/@babel/{plugin-syntax-import-assertions => plugin-syntax-import-attributes}/LICENSE (100%) create mode 100644 tools/node_modules/eslint/node_modules/@babel/plugin-syntax-import-attributes/lib/index.js rename tools/node_modules/eslint/node_modules/@babel/{plugin-syntax-import-assertions => plugin-syntax-import-attributes}/package.json (66%) diff --git a/.eslintignore b/.eslintignore index 268232d911bbb5..64f34660d87a8f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -9,5 +9,7 @@ tools/github_reporter benchmark/tmp benchmark/fixtures doc/**/*.js +doc/changelogs/CHANGELOG_v1*.md +!doc/changelogs/CHANGELOG_v18.md !doc/api_assets/*.js !.eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js index 47b218c55e9171..82481b3d2be1c6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -18,7 +18,7 @@ const hacks = [ 'eslint-plugin-jsdoc', 'eslint-plugin-markdown', '@babel/eslint-parser', - '@babel/plugin-syntax-import-assertions', + '@babel/plugin-syntax-import-attributes', ]; Module._findPath = (request, paths, isMain) => { const r = ModuleFindPath(request, paths, isMain); @@ -44,7 +44,7 @@ module.exports = { parserOptions: { babelOptions: { plugins: [ - Module._findPath('@babel/plugin-syntax-import-assertions'), + Module._findPath('@babel/plugin-syntax-import-attributes'), ], }, requireConfigFile: false, diff --git a/doc/api/errors.md b/doc/api/errors.md index 95ad3c9c671954..89048c6485df0f 100644 --- a/doc/api/errors.md +++ b/doc/api/errors.md @@ -1759,7 +1759,8 @@ added: - v16.14.0 --> -An import assertion has failed, preventing the specified module to be imported. +An import `type` attribute was provided, but the specified module is of a +different type. @@ -1771,7 +1772,7 @@ added: - v16.14.0 --> -An import assertion is missing, preventing the specified module to be imported. +An import attribute is missing, preventing the specified module to be imported. @@ -1783,7 +1784,17 @@ added: - v16.14.0 --> -An import assertion is not supported by this version of Node.js. +An import attribute is not supported by this version of Node.js. + + + +### `ERR_IMPORT_ATTRIBUTE_UNSUPPORTED` + + + +An import attribute is not supported by this version of Node.js. diff --git a/doc/api/esm.md b/doc/api/esm.md index b40b4ccb6f0865..a96badcf91b55d 100644 --- a/doc/api/esm.md +++ b/doc/api/esm.md @@ -7,6 +7,9 @@ -> Stability: 1 - Experimental +> Stability: 1.1 - Active development + +> This feature was previously named "Import assertions", and using the `assert` +> keyword instead of `with`. Any uses in code of the prior `assert` keyword +> should be updated to use `with` instead. -The [Import Assertions proposal][] adds an inline syntax for module import +The [Import Attributes proposal][] adds an inline syntax for module import statements to pass on more information alongside the module specifier. ```js -import fooData from './foo.json' assert { type: 'json' }; +import fooData from './foo.json' with { type: 'json' }; const { default: barData } = - await import('./bar.json', { assert: { type: 'json' } }); + await import('./bar.json', { with: { type: 'json' } }); ``` -Node.js supports the following `type` values, for which the assertion is +Node.js supports the following `type` values, for which the attribute is mandatory: -| Assertion `type` | Needed for | +| Attribute `type` | Needed for | | ---------------- | ---------------- | | `'json'` | [JSON modules][] | @@ -544,10 +557,10 @@ separate cache. JSON files can be referenced by `import`: ```js -import packageConfig from './package.json' assert { type: 'json' }; +import packageConfig from './package.json' with { type: 'json' }; ``` -The `assert { type: 'json' }` syntax is mandatory; see [Import Assertions][]. +The `with { type: 'json' }` syntax is mandatory; see [Import Attributes][]. The imported JSON only exposes a `default` export. There is no support for named exports. A cache entry is created in the CommonJS cache to avoid duplication. @@ -1050,8 +1063,8 @@ resolution for ESM specifiers is [commonjs-extension-resolution-loader][]. [Determining module system]: packages.md#determining-module-system [Dynamic `import()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import [ES Module Integration Proposal for WebAssembly]: https://github.com/webassembly/esm-integration -[Import Assertions]: #import-assertions -[Import Assertions proposal]: https://github.com/tc39/proposal-import-assertions +[Import Attributes]: #import-attributes +[Import Attributes proposal]: https://github.com/tc39/proposal-import-attributes [JSON modules]: #json-modules [Module customization hooks]: module.md#customization-hooks [Node.js Module Resolution And Loading Algorithm]: #resolution-algorithm-specification diff --git a/doc/api/module.md b/doc/api/module.md index 8bf5c09041a41a..857c02142b199f 100644 --- a/doc/api/module.md +++ b/doc/api/module.md @@ -458,6 +458,11 @@ register('./path-to-my-hooks.js', { + * `linker` {Function} * `specifier` {string} The specifier of the requested module: ```mjs @@ -625,15 +633,14 @@ The identifier of the current module, as set in the constructor. * `referencingModule` {vm.Module} The `Module` object `link()` is called on. * `extra` {Object} - * `assert` {Object} The data from the assertion: - - ```js - import foo from 'foo' assert { name: 'value' }; - // ^^^^^^^^^^^^^^^^^ the assertion + * `attributes` {Object} The data from the attribute: + ```mjs + import foo from 'foo' with { name: 'value' }; + // ^^^^^^^^^^^^^^^^^ the attribute ``` - Per ECMA-262, hosts are expected to ignore assertions that they do not - support, as opposed to, for example, triggering an error if an - unsupported assertion is present. + Per ECMA-262, hosts are expected to trigger an error if an + unsupported attribute is present. + * `assert` {Object} Alias for `extra.attributes`. * Returns: {vm.Module|Promise} * Returns: {Promise} @@ -732,7 +739,7 @@ changes: - v17.0.0 - v16.12.0 pr-url: https://github.com/nodejs/node/pull/40249 - description: Added support for import assertions to the + description: Added support for import attributes to the `importModuleDynamically` parameter. --> @@ -762,7 +769,7 @@ changes: `import()` will reject with [`ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING`][]. * `specifier` {string} specifier passed to `import()` * `module` {vm.Module} - * `importAssertions` {Object} The `"assert"` value passed to the + * `importAttributes` {Object} The `"with"` value passed to the [`optionsExpression`][] optional parameter, or an empty object if no value was provided. * Returns: {Module Namespace Object|vm.Module} Returning a `vm.Module` is @@ -977,7 +984,7 @@ changes: - v17.0.0 - v16.12.0 pr-url: https://github.com/nodejs/node/pull/40249 - description: Added support for import assertions to the + description: Added support for import attributes to the `importModuleDynamically` parameter. - version: v15.9.0 pr-url: https://github.com/nodejs/node/pull/35431 @@ -1021,7 +1028,7 @@ changes: considered stable. * `specifier` {string} specifier passed to `import()` * `function` {Function} - * `importAssertions` {Object} The `"assert"` value passed to the + * `importAttributes` {Object} The `"with"` value passed to the [`optionsExpression`][] optional parameter, or an empty object if no value was provided. * Returns: {Module Namespace Object|vm.Module} Returning a `vm.Module` is @@ -1207,7 +1214,7 @@ changes: - v17.0.0 - v16.12.0 pr-url: https://github.com/nodejs/node/pull/40249 - description: Added support for import assertions to the + description: Added support for import attributes to the `importModuleDynamically` parameter. - version: v6.3.0 pr-url: https://github.com/nodejs/node/pull/6635 @@ -1245,7 +1252,7 @@ changes: using it in a production environment. * `specifier` {string} specifier passed to `import()` * `script` {vm.Script} - * `importAssertions` {Object} The `"assert"` value passed to the + * `importAttributes` {Object} The `"with"` value passed to the [`optionsExpression`][] optional parameter, or an empty object if no value was provided. * Returns: {Module Namespace Object|vm.Module} Returning a `vm.Module` is @@ -1285,7 +1292,7 @@ changes: - v17.0.0 - v16.12.0 pr-url: https://github.com/nodejs/node/pull/40249 - description: Added support for import assertions to the + description: Added support for import attributes to the `importModuleDynamically` parameter. - version: v14.6.0 pr-url: https://github.com/nodejs/node/pull/34023 @@ -1344,7 +1351,7 @@ changes: using it in a production environment. * `specifier` {string} specifier passed to `import()` * `script` {vm.Script} - * `importAssertions` {Object} The `"assert"` value passed to the + * `importAttributes` {Object} The `"with"` value passed to the [`optionsExpression`][] optional parameter, or an empty object if no value was provided. * Returns: {Module Namespace Object|vm.Module} Returning a `vm.Module` is @@ -1388,7 +1395,7 @@ changes: - v17.0.0 - v16.12.0 pr-url: https://github.com/nodejs/node/pull/40249 - description: Added support for import assertions to the + description: Added support for import attributes to the `importModuleDynamically` parameter. - version: v6.3.0 pr-url: https://github.com/nodejs/node/pull/6635 @@ -1424,7 +1431,7 @@ changes: using it in a production environment. * `specifier` {string} specifier passed to `import()` * `script` {vm.Script} - * `importAssertions` {Object} The `"assert"` value passed to the + * `importAttributes` {Object} The `"with"` value passed to the [`optionsExpression`][] optional parameter, or an empty object if no value was provided. * Returns: {Module Namespace Object|vm.Module} Returning a `vm.Module` is diff --git a/lib/internal/vm/module.js b/lib/internal/vm/module.js index c77ee9b107e0ad..78fe45ff9c8ebb 100644 --- a/lib/internal/vm/module.js +++ b/lib/internal/vm/module.js @@ -304,8 +304,8 @@ class SourceTextModule extends Module { this[kLink] = async (linker) => { this.#statusOverride = 'linking'; - const promises = this[kWrap].link(async (identifier, assert) => { - const module = await linker(identifier, this, { assert }); + const promises = this[kWrap].link(async (identifier, attributes) => { + const module = await linker(identifier, this, { attributes, assert: attributes }); if (module[kWrap] === undefined) { throw new ERR_VM_MODULE_NOT_MODULE(); } diff --git a/test/parallel/test-vm-module-link.js b/test/parallel/test-vm-module-link.js index 16694d5d846075..6b19a4d4916868 100644 --- a/test/parallel/test-vm-module-link.js +++ b/test/parallel/test-vm-module-link.js @@ -1,6 +1,6 @@ 'use strict'; -// Flags: --experimental-vm-modules +// Flags: --experimental-vm-modules --harmony-import-attributes const common = require('../common'); @@ -126,12 +126,14 @@ async function circular2() { async function asserts() { const m = new SourceTextModule(` - import "foo" assert { n1: 'v1', n2: 'v2' }; + import "foo" with { n1: 'v1', n2: 'v2' }; `, { identifier: 'm' }); await m.link((s, r, p) => { assert.strictEqual(s, 'foo'); assert.strictEqual(r.identifier, 'm'); + assert.strictEqual(p.attributes.n1, 'v1'); assert.strictEqual(p.assert.n1, 'v1'); + assert.strictEqual(p.attributes.n2, 'v2'); assert.strictEqual(p.assert.n2, 'v2'); return new SourceTextModule(''); });