From 1ab16e2426819af2c534e8a99b98f244626de512 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sun, 5 Mar 2023 18:42:52 -0400 Subject: [PATCH] refactor(core): InternalModuleLoader checks if all files were used (#18005) This commit changes "InternalModuleLoader" from "deno_core" to store a list of used modules during snapshotting. If a module was not used during snapshotting "InternalModuleLoader" will panic in its "Drop" handler signaling to the embedder that they made a mistake somewhere. --- core/modules.rs | 37 + ext/node/lib.rs | 8 - ext/node/polyfills/global.ts | 91 -- ext/node/polyfills/internal/blob.mjs | 7 - ext/node/polyfills/internal/crypto/types.ts | 46 - ext/node/polyfills/internal/freelist.ts | 30 - .../polyfills/internal/streams/legacy.mjs | 113 --- ext/node/polyfills/module.js | 20 - ext/node/polyfills/module_esm.ts | 842 ------------------ ext/node/polyfills/upstream_modules.ts | 39 - 10 files changed, 37 insertions(+), 1196 deletions(-) delete mode 100644 ext/node/polyfills/global.ts delete mode 100644 ext/node/polyfills/internal/blob.mjs delete mode 100644 ext/node/polyfills/internal/crypto/types.ts delete mode 100644 ext/node/polyfills/internal/freelist.ts delete mode 100644 ext/node/polyfills/internal/streams/legacy.mjs delete mode 100644 ext/node/polyfills/module.js delete mode 100644 ext/node/polyfills/module_esm.ts delete mode 100644 ext/node/polyfills/upstream_modules.ts diff --git a/core/modules.rs b/core/modules.rs index b6220bb3bc..3d335f8ebe 100644 --- a/core/modules.rs +++ b/core/modules.rs @@ -328,6 +328,7 @@ pub type InternalModuleLoaderCb = pub struct InternalModuleLoader { module_loader: Rc, esm_sources: Vec, + used_esm_sources: RefCell>, maybe_load_callback: Option, } @@ -336,6 +337,7 @@ impl Default for InternalModuleLoader { Self { module_loader: Rc::new(NoopModuleLoader), esm_sources: vec![], + used_esm_sources: RefCell::new(HashMap::default()), maybe_load_callback: None, } } @@ -347,14 +349,43 @@ impl InternalModuleLoader { esm_sources: Vec, maybe_load_callback: Option, ) -> Self { + let used_esm_sources: HashMap = esm_sources + .iter() + .map(|file_source| (file_source.specifier.to_string(), false)) + .collect(); + InternalModuleLoader { module_loader: module_loader.unwrap_or_else(|| Rc::new(NoopModuleLoader)), esm_sources, + used_esm_sources: RefCell::new(used_esm_sources), maybe_load_callback, } } } +impl Drop for InternalModuleLoader { + fn drop(&mut self) { + let used_esm_sources = self.used_esm_sources.get_mut(); + let unused_modules: Vec<_> = used_esm_sources + .iter() + .filter(|(_s, v)| !*v) + .map(|(s, _)| s) + .collect(); + + if !unused_modules.is_empty() { + let mut msg = + "Following modules were passed to InternalModuleLoader but never used:\n" + .to_string(); + for m in unused_modules { + msg.push_str(" - "); + msg.push_str(m); + msg.push('\n'); + } + panic!("{}", msg); + } + } +} + impl ModuleLoader for InternalModuleLoader { fn resolve( &self, @@ -400,6 +431,12 @@ impl ModuleLoader for InternalModuleLoader { .find(|file_source| file_source.specifier == module_specifier.as_str()); if let Some(file_source) = maybe_file_source { + { + let mut used_esm_sources = self.used_esm_sources.borrow_mut(); + let used = used_esm_sources.get_mut(&file_source.specifier).unwrap(); + *used = true; + } + let result = if let Some(load_callback) = &self.maybe_load_callback { load_callback(file_source) } else { diff --git a/ext/node/lib.rs b/ext/node/lib.rs index d84b263398..6df408ffd0 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -179,7 +179,6 @@ pub fn init_polyfill() -> Extension { "events.ts", "fs.ts", "fs/promises.ts", - "global.ts", "http.ts", "http2.ts", "https.ts", @@ -211,7 +210,6 @@ pub fn init_polyfill() -> Extension { "internal_binding/uv.ts", "internal/assert.mjs", "internal/async_hooks.ts", - "internal/blob.mjs", "internal/buffer.mjs", "internal/child_process.ts", "internal/cli_table.ts", @@ -234,7 +232,6 @@ pub fn init_polyfill() -> Extension { "internal/crypto/random.ts", "internal/crypto/scrypt.ts", "internal/crypto/sig.ts", - "internal/crypto/types.ts", "internal/crypto/util.ts", "internal/crypto/x509.ts", "internal/dgram.ts", @@ -245,7 +242,6 @@ pub fn init_polyfill() -> Extension { "internal/errors.ts", "internal/event_target.mjs", "internal/fixed_queue.ts", - "internal/freelist.ts", "internal/fs/streams.mjs", "internal/fs/utils.mjs", "internal/hide_stack_frames.ts", @@ -270,7 +266,6 @@ pub fn init_polyfill() -> Extension { "internal/streams/duplex.mjs", "internal/streams/end-of-stream.mjs", "internal/streams/lazy_transform.mjs", - "internal/streams/legacy.mjs", "internal/streams/passthrough.mjs", "internal/streams/readable.mjs", "internal/streams/state.mjs", @@ -287,8 +282,6 @@ pub fn init_polyfill() -> Extension { "internal/util/types.ts", "internal/validators.mjs", "module_all.ts", - "module_esm.ts", - "module.js", "net.ts", "os.ts", "path.ts", @@ -318,7 +311,6 @@ pub fn init_polyfill() -> Extension { "timers/promises.ts", "tls.ts", "tty.ts", - "upstream_modules.ts", "url.ts", "util.ts", "util/types.ts", diff --git a/ext/node/polyfills/global.ts b/ext/node/polyfills/global.ts deleted file mode 100644 index 2ab33921ca..0000000000 --- a/ext/node/polyfills/global.ts +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// deno-lint-ignore-file no-var -import processModule from "internal:deno_node/process.ts"; -import { Buffer as bufferModule } from "internal:deno_node/buffer.ts"; -import { - clearInterval, - clearTimeout, - setInterval, - setTimeout, -} from "internal:deno_node/timers.ts"; -import timers from "internal:deno_node/timers.ts"; - -type GlobalType = { - process: typeof processModule; - Buffer: typeof bufferModule; - setImmediate: typeof timers.setImmediate; - clearImmediate: typeof timers.clearImmediate; - setTimeout: typeof timers.setTimeout; - clearTimeout: typeof timers.clearTimeout; - setInterval: typeof timers.setInterval; - clearInterval: typeof timers.clearInterval; -}; - -declare global { - interface Window { - global: GlobalType; - } - - interface globalThis { - global: GlobalType; - } - - var global: GlobalType; - var process: typeof processModule; - var Buffer: typeof bufferModule; - type Buffer = bufferModule; - var setImmediate: typeof timers.setImmediate; - var clearImmediate: typeof timers.clearImmediate; -} - -Object.defineProperty(globalThis, "global", { - value: new Proxy(globalThis, { - get(target, prop, receiver) { - switch (prop) { - case "setInterval": - return setInterval; - case "setTimeout": - return setTimeout; - case "clearInterval": - return clearInterval; - case "clearTimeout": - return clearTimeout; - default: - return Reflect.get(target, prop, receiver); - } - }, - }), - writable: false, - enumerable: false, - configurable: true, -}); - -Object.defineProperty(globalThis, "process", { - value: processModule, - enumerable: false, - writable: true, - configurable: true, -}); - -Object.defineProperty(globalThis, "Buffer", { - value: bufferModule, - enumerable: false, - writable: true, - configurable: true, -}); - -Object.defineProperty(globalThis, "setImmediate", { - value: timers.setImmediate, - enumerable: true, - writable: true, - configurable: true, -}); - -Object.defineProperty(globalThis, "clearImmediate", { - value: timers.clearImmediate, - enumerable: true, - writable: true, - configurable: true, -}); - -export {}; diff --git a/ext/node/polyfills/internal/blob.mjs b/ext/node/polyfills/internal/blob.mjs deleted file mode 100644 index 1b685fad40..0000000000 --- a/ext/node/polyfills/internal/blob.mjs +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// Node's implementation checks for a symbol they put in the blob prototype -// Since the implementation of Blob is Deno's, the only option is to check the -// objects constructor -export function isBlob(object) { - return object instanceof Blob; -} diff --git a/ext/node/polyfills/internal/crypto/types.ts b/ext/node/polyfills/internal/crypto/types.ts deleted file mode 100644 index 3231d378ef..0000000000 --- a/ext/node/polyfills/internal/crypto/types.ts +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// Copyright Joyent, Inc. and Node.js contributors. All rights reserved. MIT license. - -import { Buffer } from "internal:deno_node/buffer.ts"; - -export type HASH_DATA = string | ArrayBufferView | Buffer; - -export type BinaryToTextEncoding = "base64" | "base64url" | "hex" | "binary"; - -export type CharacterEncoding = "utf8" | "utf-8" | "utf16le" | "latin1"; - -export type LegacyCharacterEncoding = "ascii" | "binary" | "ucs2" | "ucs-2"; - -export type Encoding = - | BinaryToTextEncoding - | CharacterEncoding - | LegacyCharacterEncoding; - -export type ECDHKeyFormat = "compressed" | "uncompressed" | "hybrid"; - -export type BinaryLike = string | ArrayBufferView; - -export type KeyFormat = "pem" | "der"; - -export type KeyType = - | "rsa" - | "rsa-pss" - | "dsa" - | "ec" - | "ed25519" - | "ed448" - | "x25519" - | "x448"; - -export interface PrivateKeyInput { - key: string | Buffer; - format?: KeyFormat | undefined; - type?: "pkcs1" | "pkcs8" | "sec1" | undefined; - passphrase?: string | Buffer | undefined; -} - -export interface PublicKeyInput { - key: string | Buffer; - format?: KeyFormat | undefined; - type?: "pkcs1" | "spki" | undefined; -} diff --git a/ext/node/polyfills/internal/freelist.ts b/ext/node/polyfills/internal/freelist.ts deleted file mode 100644 index 8faba8e680..0000000000 --- a/ext/node/polyfills/internal/freelist.ts +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// Copyright Joyent and Node contributors. All rights reserved. MIT license. - -type Fn = (...args: unknown[]) => T; -export class FreeList { - name: string; - ctor: Fn; - max: number; - list: Array; - constructor(name: string, max: number, ctor: Fn) { - this.name = name; - this.ctor = ctor; - this.max = max; - this.list = []; - } - - alloc(): T { - return this.list.length > 0 - ? this.list.pop() - : Reflect.apply(this.ctor, this, arguments); - } - - free(obj: T) { - if (this.list.length < this.max) { - this.list.push(obj); - return true; - } - return false; - } -} diff --git a/ext/node/polyfills/internal/streams/legacy.mjs b/ext/node/polyfills/internal/streams/legacy.mjs deleted file mode 100644 index bb2d30bc44..0000000000 --- a/ext/node/polyfills/internal/streams/legacy.mjs +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// Copyright Joyent and Node contributors. All rights reserved. MIT license. -// deno-lint-ignore-file - -import EE from "internal:deno_node/events.ts"; - -function Stream(opts) { - EE.call(this, opts); -} -Object.setPrototypeOf(Stream.prototype, EE.prototype); -Object.setPrototypeOf(Stream, EE); - -Stream.prototype.pipe = function (dest, options) { - // deno-lint-ignore no-this-alias - const source = this; - - function ondata(chunk) { - if (dest.writable && dest.write(chunk) === false && source.pause) { - source.pause(); - } - } - - source.on("data", ondata); - - function ondrain() { - if (source.readable && source.resume) { - source.resume(); - } - } - - dest.on("drain", ondrain); - - // If the 'end' option is not supplied, dest.end() will be called when - // source gets the 'end' or 'close' events. Only dest.end() once. - if (!dest._isStdio && (!options || options.end !== false)) { - source.on("end", onend); - source.on("close", onclose); - } - - let didOnEnd = false; - function onend() { - if (didOnEnd) return; - didOnEnd = true; - - dest.end(); - } - - function onclose() { - if (didOnEnd) return; - didOnEnd = true; - - if (typeof dest.destroy === "function") dest.destroy(); - } - - // Don't leave dangling pipes when there are errors. - function onerror(er) { - cleanup(); - if (EE.listenerCount(this, "error") === 0) { - this.emit("error", er); - } - } - - prependListener(source, "error", onerror); - prependListener(dest, "error", onerror); - - // Remove all the event listeners that were added. - function cleanup() { - source.removeListener("data", ondata); - dest.removeListener("drain", ondrain); - - source.removeListener("end", onend); - source.removeListener("close", onclose); - - source.removeListener("error", onerror); - dest.removeListener("error", onerror); - - source.removeListener("end", cleanup); - source.removeListener("close", cleanup); - - dest.removeListener("close", cleanup); - } - - source.on("end", cleanup); - source.on("close", cleanup); - - dest.on("close", cleanup); - dest.emit("pipe", source); - - // Allow for unix-like usage: A.pipe(B).pipe(C) - return dest; -}; - -function prependListener(emitter, event, fn) { - // Sadly this is not cacheable as some libraries bundle their own - // event emitter implementation with them. - if (typeof emitter.prependListener === "function") { - return emitter.prependListener(event, fn); - } - - // This is a hack to make sure that our error handler is attached before any - // userland ones. NEVER DO THIS. This is here only because this code needs - // to continue to work with older versions of Node.js that do not include - // the prependListener() method. The goal is to eventually remove this hack. - if (!emitter._events || !emitter._events[event]) { - emitter.on(event, fn); - } else if (Array.isArray(emitter._events[event])) { - emitter._events[event].unshift(fn); - } else { - emitter._events[event] = [fn, emitter._events[event]]; - } -} - -export { prependListener, Stream }; diff --git a/ext/node/polyfills/module.js b/ext/node/polyfills/module.js deleted file mode 100644 index 2b7c20e26d..0000000000 --- a/ext/node/polyfills/module.js +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. - -const internals = globalThis.__bootstrap.internals; -const m = internals.require.Module; -export const _cache = m._cache; -export const _extensions = m._extensions; -export const _findPath = m._findPath; -export const _initPaths = m._initPaths; -export const _load = m._load; -export const _nodeModulePaths = m._nodeModulePaths; -export const _pathCache = m._pathCache; -export const _preloadModules = m._preloadModules; -export const _resolveFilename = m._resolveFilename; -export const _resolveLookupPaths = m._resolveLookupPaths; -export const builtinModules = m.builtinModules; -export const createRequire = m.createRequire; -export const globalPaths = m.globalPaths; -export const Module = m.Module; -export const wrap = m.wrap; -export default m; diff --git a/ext/node/polyfills/module_esm.ts b/ext/node/polyfills/module_esm.ts deleted file mode 100644 index 5b1cef0bba..0000000000 --- a/ext/node/polyfills/module_esm.ts +++ /dev/null @@ -1,842 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -/** - * NOTE(bartlomieju): - * Functionality of this file is ported in Rust in `cli/compat/esm_resolver.ts`. - * Unfortunately we have no way to call ESM resolution in Rust from TypeScript code. - */ - -import { fileURLToPath, pathToFileURL } from "internal:deno_node/url.ts"; -import { - ERR_INVALID_MODULE_SPECIFIER, - ERR_INVALID_PACKAGE_CONFIG, - ERR_INVALID_PACKAGE_TARGET, - ERR_MODULE_NOT_FOUND, - ERR_PACKAGE_IMPORT_NOT_DEFINED, - ERR_PACKAGE_PATH_NOT_EXPORTED, - NodeError, -} from "internal:deno_node/internal/errors.ts"; - -const { hasOwn } = Object; - -export const encodedSepRegEx = /%2F|%2C/i; - -function throwInvalidSubpath( - subpath: string, - packageJSONUrl: string, - internal: boolean, - base: string, -) { - const reason = `request is not a valid subpath for the "${ - internal ? "imports" : "exports" - }" resolution of ${fileURLToPath(packageJSONUrl)}`; - throw new ERR_INVALID_MODULE_SPECIFIER( - subpath, - reason, - base && fileURLToPath(base), - ); -} - -function throwInvalidPackageTarget( - subpath: string, - // deno-lint-ignore no-explicit-any - target: any, - packageJSONUrl: string, - internal: boolean, - base: string, -) { - if (typeof target === "object" && target !== null) { - target = JSON.stringify(target, null, ""); - } else { - target = `${target}`; - } - throw new ERR_INVALID_PACKAGE_TARGET( - fileURLToPath(new URL(".", packageJSONUrl)), - subpath, - target, - internal, - base && fileURLToPath(base), - ); -} - -function throwImportNotDefined( - specifier: string, - packageJSONUrl: URL | undefined, - base: string | URL, -): TypeError & { code: string } { - throw new ERR_PACKAGE_IMPORT_NOT_DEFINED( - specifier, - packageJSONUrl && fileURLToPath(new URL(".", packageJSONUrl)), - fileURLToPath(base), - ); -} - -function throwExportsNotFound( - subpath: string, - packageJSONUrl: string, - base?: string, -): Error & { code: string } { - throw new ERR_PACKAGE_PATH_NOT_EXPORTED( - subpath, - fileURLToPath(new URL(".", packageJSONUrl)), - base && fileURLToPath(base), - ); -} - -function patternKeyCompare(a: string, b: string): number { - const aPatternIndex = a.indexOf("*"); - const bPatternIndex = b.indexOf("*"); - const baseLenA = aPatternIndex === -1 ? a.length : aPatternIndex + 1; - const baseLenB = bPatternIndex === -1 ? b.length : bPatternIndex + 1; - if (baseLenA > baseLenB) return -1; - if (baseLenB > baseLenA) return 1; - if (aPatternIndex === -1) return 1; - if (bPatternIndex === -1) return -1; - if (a.length > b.length) return -1; - if (b.length > a.length) return 1; - return 0; -} - -function fileExists(url: string | URL): boolean { - try { - const info = Deno.statSync(url); - return info.isFile; - } catch { - return false; - } -} - -function tryStatSync(path: string): { isDirectory: boolean } { - try { - const info = Deno.statSync(path); - return { isDirectory: info.isDirectory }; - } catch { - return { isDirectory: false }; - } -} - -/** - * Legacy CommonJS main resolution: - * 1. let M = pkg_url + (json main field) - * 2. TRY(M, M.js, M.json, M.node) - * 3. TRY(M/index.js, M/index.json, M/index.node) - * 4. TRY(pkg_url/index.js, pkg_url/index.json, pkg_url/index.node) - * 5. NOT_FOUND - */ -function legacyMainResolve( - packageJSONUrl: URL, - packageConfig: PackageConfig, - base: string | URL, -): URL { - let guess; - if (packageConfig.main !== undefined) { - // Note: fs check redundances will be handled by Descriptor cache here. - if ( - fileExists(guess = new URL(`./${packageConfig.main}`, packageJSONUrl)) - ) { - return guess; - } else if ( - fileExists(guess = new URL(`./${packageConfig.main}.js`, packageJSONUrl)) - ) { - // pass - } else if ( - fileExists( - guess = new URL(`./${packageConfig.main}.json`, packageJSONUrl), - ) - ) { - // pass - } else if ( - fileExists( - guess = new URL(`./${packageConfig.main}.node`, packageJSONUrl), - ) - ) { - // pass - } else if ( - fileExists( - guess = new URL(`./${packageConfig.main}/index.js`, packageJSONUrl), - ) - ) { - // pass - } else if ( - fileExists( - guess = new URL(`./${packageConfig.main}/index.json`, packageJSONUrl), - ) - ) { - // pass - } else if ( - fileExists( - guess = new URL(`./${packageConfig.main}/index.node`, packageJSONUrl), - ) - ) { - // pass - } else guess = undefined; - if (guess) { - // TODO(bartlomieju): - // emitLegacyIndexDeprecation(guess, packageJSONUrl, base, - // packageConfig.main); - return guess; - } - // Fallthrough. - } - if (fileExists(guess = new URL("./index.js", packageJSONUrl))) { - // pass - } // So fs. - else if (fileExists(guess = new URL("./index.json", packageJSONUrl))) { - // pass - } else if (fileExists(guess = new URL("./index.node", packageJSONUrl))) { - // pass - } else guess = undefined; - if (guess) { - // TODO(bartlomieju): - // emitLegacyIndexDeprecation(guess, packageJSONUrl, base, packageConfig.main); - return guess; - } - // Not found. - throw new ERR_MODULE_NOT_FOUND( - fileURLToPath(new URL(".", packageJSONUrl)), - fileURLToPath(base), - ); -} - -function parsePackageName( - specifier: string, - base: string | URL, -): { packageName: string; packageSubpath: string; isScoped: boolean } { - let separatorIndex = specifier.indexOf("/"); - let validPackageName = true; - let isScoped = false; - if (specifier[0] === "@") { - isScoped = true; - if (separatorIndex === -1 || specifier.length === 0) { - validPackageName = false; - } else { - separatorIndex = specifier.indexOf("/", separatorIndex + 1); - } - } - - const packageName = separatorIndex === -1 - ? specifier - : specifier.slice(0, separatorIndex); - - // Package name cannot have leading . and cannot have percent-encoding or - // separators. - for (let i = 0; i < packageName.length; i++) { - if (packageName[i] === "%" || packageName[i] === "\\") { - validPackageName = false; - break; - } - } - - if (!validPackageName) { - throw new ERR_INVALID_MODULE_SPECIFIER( - specifier, - "is not a valid package name", - fileURLToPath(base), - ); - } - - const packageSubpath = "." + - (separatorIndex === -1 ? "" : specifier.slice(separatorIndex)); - - return { packageName, packageSubpath, isScoped }; -} - -function packageResolve( - specifier: string, - base: string, - conditions: Set, -): URL | undefined { - const { packageName, packageSubpath, isScoped } = parsePackageName( - specifier, - base, - ); - - // ResolveSelf - const packageConfig = getPackageScopeConfig(base); - if (packageConfig.exists) { - const packageJSONUrl = pathToFileURL(packageConfig.pjsonPath); - if ( - packageConfig.name === packageName && - packageConfig.exports !== undefined && packageConfig.exports !== null - ) { - return packageExportsResolve( - packageJSONUrl.toString(), - packageSubpath, - packageConfig, - base, - conditions, - ); - } - } - - let packageJSONUrl = new URL( - "./node_modules/" + packageName + "/package.json", - base, - ); - let packageJSONPath = fileURLToPath(packageJSONUrl); - let lastPath; - do { - const stat = tryStatSync( - packageJSONPath.slice(0, packageJSONPath.length - 13), - ); - if (!stat.isDirectory) { - lastPath = packageJSONPath; - packageJSONUrl = new URL( - (isScoped ? "../../../../node_modules/" : "../../../node_modules/") + - packageName + "/package.json", - packageJSONUrl, - ); - packageJSONPath = fileURLToPath(packageJSONUrl); - continue; - } - - // Package match. - const packageConfig = getPackageConfig(packageJSONPath, specifier, base); - if (packageConfig.exports !== undefined && packageConfig.exports !== null) { - return packageExportsResolve( - packageJSONUrl.toString(), - packageSubpath, - packageConfig, - base, - conditions, - ); - } - if (packageSubpath === ".") { - return legacyMainResolve(packageJSONUrl, packageConfig, base); - } - return new URL(packageSubpath, packageJSONUrl); - // Cross-platform root check. - } while (packageJSONPath.length !== lastPath.length); - - // TODO(bartlomieju): this is false positive - // deno-lint-ignore no-unreachable - throw new ERR_MODULE_NOT_FOUND(packageName, fileURLToPath(base)); -} - -const invalidSegmentRegEx = /(^|\\|\/)(\.\.?|node_modules)(\\|\/|$)/; -const patternRegEx = /\*/g; - -function resolvePackageTargetString( - target: string, - subpath: string, - match: string, - packageJSONUrl: string, - base: string, - pattern: boolean, - internal: boolean, - conditions: Set, -): URL | undefined { - if (subpath !== "" && !pattern && target[target.length - 1] !== "/") { - throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); - } - - if (!target.startsWith("./")) { - if ( - internal && !target.startsWith("../") && - !target.startsWith("/") - ) { - let isURL = false; - try { - new URL(target); - isURL = true; - } catch { - // pass - } - if (!isURL) { - const exportTarget = pattern - ? target.replace(patternRegEx, () => subpath) - : target + subpath; - return packageResolve(exportTarget, packageJSONUrl, conditions); - } - } - throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); - } - - if (invalidSegmentRegEx.test(target.slice(2))) { - throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); - } - - const resolved = new URL(target, packageJSONUrl); - const resolvedPath = resolved.pathname; - const packagePath = new URL(".", packageJSONUrl).pathname; - - if (!resolvedPath.startsWith(packagePath)) { - throwInvalidPackageTarget(match, target, packageJSONUrl, internal, base); - } - - if (subpath === "") return resolved; - - if (invalidSegmentRegEx.test(subpath)) { - const request = pattern - ? match.replace("*", () => subpath) - : match + subpath; - throwInvalidSubpath(request, packageJSONUrl, internal, base); - } - - if (pattern) { - return new URL(resolved.href.replace(patternRegEx, () => subpath)); - } - return new URL(subpath, resolved); -} - -function isArrayIndex(key: string): boolean { - const keyNum = +key; - if (`${keyNum}` !== key) return false; - return keyNum >= 0 && keyNum < 0xFFFF_FFFF; -} - -function resolvePackageTarget( - packageJSONUrl: string, - // deno-lint-ignore no-explicit-any - target: any, - subpath: string, - packageSubpath: string, - base: string, - pattern: boolean, - internal: boolean, - conditions: Set, -): URL | undefined { - if (typeof target === "string") { - return resolvePackageTargetString( - target, - subpath, - packageSubpath, - packageJSONUrl, - base, - pattern, - internal, - conditions, - ); - } else if (Array.isArray(target)) { - if (target.length === 0) { - return undefined; - } - - let lastException; - for (let i = 0; i < target.length; i++) { - const targetItem = target[i]; - let resolved; - try { - resolved = resolvePackageTarget( - packageJSONUrl, - targetItem, - subpath, - packageSubpath, - base, - pattern, - internal, - conditions, - ); - } catch (e: unknown) { - lastException = e; - if (e instanceof NodeError && e.code === "ERR_INVALID_PACKAGE_TARGET") { - continue; - } - throw e; - } - if (resolved === undefined) { - continue; - } - if (resolved === null) { - lastException = null; - continue; - } - return resolved; - } - if (lastException === undefined || lastException === null) { - return undefined; - } - throw lastException; - } else if (typeof target === "object" && target !== null) { - const keys = Object.getOwnPropertyNames(target); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (isArrayIndex(key)) { - throw new ERR_INVALID_PACKAGE_CONFIG( - fileURLToPath(packageJSONUrl), - base, - '"exports" cannot contain numeric property keys.', - ); - } - } - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - if (key === "default" || conditions.has(key)) { - const conditionalTarget = target[key]; - const resolved = resolvePackageTarget( - packageJSONUrl, - conditionalTarget, - subpath, - packageSubpath, - base, - pattern, - internal, - conditions, - ); - if (resolved === undefined) { - continue; - } - return resolved; - } - } - return undefined; - } else if (target === null) { - return undefined; - } - throwInvalidPackageTarget( - packageSubpath, - target, - packageJSONUrl, - internal, - base, - ); -} - -export function packageExportsResolve( - packageJSONUrl: string, - packageSubpath: string, - packageConfig: PackageConfig, - base: string, - conditions: Set, - // @ts-ignore `URL` needs to be forced due to control flow -): URL { - let exports = packageConfig.exports; - if (isConditionalExportsMainSugar(exports, packageJSONUrl, base)) { - exports = { ".": exports }; - } - - if ( - hasOwn(exports, packageSubpath) && - !packageSubpath.includes("*") && - !packageSubpath.endsWith("/") - ) { - const target = exports[packageSubpath]; - const resolved = resolvePackageTarget( - packageJSONUrl, - target, - "", - packageSubpath, - base, - false, - false, - conditions, - ); - if (resolved === null || resolved === undefined) { - throwExportsNotFound(packageSubpath, packageJSONUrl, base); - } - return resolved!; - } - - let bestMatch = ""; - let bestMatchSubpath = ""; - const keys = Object.getOwnPropertyNames(exports); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const patternIndex = key.indexOf("*"); - if ( - patternIndex !== -1 && - packageSubpath.startsWith(key.slice(0, patternIndex)) - ) { - // When this reaches EOL, this can throw at the top of the whole function: - // - // if (StringPrototypeEndsWith(packageSubpath, '/')) - // throwInvalidSubpath(packageSubpath) - // - // To match "imports" and the spec. - if (packageSubpath.endsWith("/")) { - // TODO(@bartlomieju): - // emitTrailingSlashPatternDeprecation( - // packageSubpath, - // packageJSONUrl, - // base, - // ); - } - const patternTrailer = key.slice(patternIndex + 1); - if ( - packageSubpath.length >= key.length && - packageSubpath.endsWith(patternTrailer) && - patternKeyCompare(bestMatch, key) === 1 && - key.lastIndexOf("*") === patternIndex - ) { - bestMatch = key; - bestMatchSubpath = packageSubpath.slice( - patternIndex, - packageSubpath.length - patternTrailer.length, - ); - } - } - } - - if (bestMatch) { - const target = exports[bestMatch]; - const resolved = resolvePackageTarget( - packageJSONUrl, - target, - bestMatchSubpath, - bestMatch, - base, - true, - false, - conditions, - ); - if (resolved === null || resolved === undefined) { - throwExportsNotFound(packageSubpath, packageJSONUrl, base); - } - return resolved!; - } - - throwExportsNotFound(packageSubpath, packageJSONUrl, base); -} - -export interface PackageConfig { - pjsonPath: string; - exists: boolean; - name?: string; - main?: string; - // deno-lint-ignore no-explicit-any - exports?: any; - // deno-lint-ignore no-explicit-any - imports?: any; - type?: string; -} - -const packageJSONCache = new Map(); /* string -> PackageConfig */ - -function getPackageConfig( - path: string, - specifier: string | URL, - base?: string | URL, -): PackageConfig { - const existing = packageJSONCache.get(path); - if (existing !== undefined) { - return existing; - } - - let source: string | undefined; - try { - source = new TextDecoder().decode( - Deno.readFileSync(path), - ); - } catch { - // pass - } - - if (source === undefined) { - const packageConfig = { - pjsonPath: path, - exists: false, - main: undefined, - name: undefined, - type: "none", - exports: undefined, - imports: undefined, - }; - packageJSONCache.set(path, packageConfig); - return packageConfig; - } - - let packageJSON; - try { - packageJSON = JSON.parse(source); - } catch (error) { - throw new ERR_INVALID_PACKAGE_CONFIG( - path, - (base ? `"${specifier}" from ` : "") + fileURLToPath(base || specifier), - // @ts-ignore there's no assertion for type and `error` is thus `unknown` - error.message, - ); - } - - let { imports, main, name, type } = packageJSON; - const { exports } = packageJSON; - if (typeof imports !== "object" || imports === null) imports = undefined; - if (typeof main !== "string") main = undefined; - if (typeof name !== "string") name = undefined; - // Ignore unknown types for forwards compatibility - if (type !== "module" && type !== "commonjs") type = "none"; - - const packageConfig = { - pjsonPath: path, - exists: true, - main, - name, - type, - exports, - imports, - }; - packageJSONCache.set(path, packageConfig); - return packageConfig; -} - -function getPackageScopeConfig(resolved: URL | string): PackageConfig { - let packageJSONUrl = new URL("./package.json", resolved); - while (true) { - const packageJSONPath = packageJSONUrl.pathname; - if (packageJSONPath.endsWith("node_modules/package.json")) { - break; - } - const packageConfig = getPackageConfig( - fileURLToPath(packageJSONUrl), - resolved, - ); - if (packageConfig.exists) return packageConfig; - - const lastPackageJSONUrl = packageJSONUrl; - packageJSONUrl = new URL("../package.json", packageJSONUrl); - - // Terminates at root where ../package.json equals ../../package.json - // (can't just check "/package.json" for Windows support). - if (packageJSONUrl.pathname === lastPackageJSONUrl.pathname) break; - } - const packageJSONPath = fileURLToPath(packageJSONUrl); - const packageConfig = { - pjsonPath: packageJSONPath, - exists: false, - main: undefined, - name: undefined, - type: "none", - exports: undefined, - imports: undefined, - }; - packageJSONCache.set(packageJSONPath, packageConfig); - return packageConfig; -} - -export function packageImportsResolve( - name: string, - base: string, - conditions: Set, - // @ts-ignore `URL` needs to be forced due to control flow -): URL { - if ( - name === "#" || name.startsWith("#/") || - name.startsWith("/") - ) { - const reason = "is not a valid internal imports specifier name"; - throw new ERR_INVALID_MODULE_SPECIFIER(name, reason, fileURLToPath(base)); - } - let packageJSONUrl; - const packageConfig = getPackageScopeConfig(base); - if (packageConfig.exists) { - packageJSONUrl = pathToFileURL(packageConfig.pjsonPath); - const imports = packageConfig.imports; - if (imports) { - if ( - hasOwn(imports, name) && - !name.includes("*") - ) { - const resolved = resolvePackageTarget( - packageJSONUrl.toString(), - imports[name], - "", - name, - base, - false, - true, - conditions, - ); - if (resolved !== null && resolved !== undefined) { - return resolved; - } - } else { - let bestMatch = ""; - let bestMatchSubpath = ""; - const keys = Object.getOwnPropertyNames(imports); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const patternIndex = key.indexOf("*"); - if ( - patternIndex !== -1 && - name.startsWith( - key.slice(0, patternIndex), - ) - ) { - const patternTrailer = key.slice(patternIndex + 1); - if ( - name.length >= key.length && - name.endsWith(patternTrailer) && - patternKeyCompare(bestMatch, key) === 1 && - key.lastIndexOf("*") === patternIndex - ) { - bestMatch = key; - bestMatchSubpath = name.slice( - patternIndex, - name.length - patternTrailer.length, - ); - } - } - } - - if (bestMatch) { - const target = imports[bestMatch]; - const resolved = resolvePackageTarget( - packageJSONUrl.toString(), - target, - bestMatchSubpath, - bestMatch, - base, - true, - true, - conditions, - ); - if (resolved !== null && resolved !== undefined) { - return resolved; - } - } - } - } - } - throwImportNotDefined(name, packageJSONUrl, base); -} - -function isConditionalExportsMainSugar( - // deno-lint-ignore no-explicit-any - exports: any, - packageJSONUrl: string, - base: string, -): boolean { - if (typeof exports === "string" || Array.isArray(exports)) return true; - if (typeof exports !== "object" || exports === null) return false; - - const keys = Object.getOwnPropertyNames(exports); - let isConditionalSugar = false; - let i = 0; - for (let j = 0; j < keys.length; j++) { - const key = keys[j]; - const curIsConditionalSugar = key === "" || key[0] !== "."; - if (i++ === 0) { - isConditionalSugar = curIsConditionalSugar; - } else if (isConditionalSugar !== curIsConditionalSugar) { - const message = - "\"exports\" cannot contain some keys starting with '.' and some not." + - " The exports object must either be an object of package subpath keys" + - " or an object of main entry condition name keys only."; - throw new ERR_INVALID_PACKAGE_CONFIG( - fileURLToPath(packageJSONUrl), - base, - message, - ); - } - } - return isConditionalSugar; -} diff --git a/ext/node/polyfills/upstream_modules.ts b/ext/node/polyfills/upstream_modules.ts deleted file mode 100644 index ed8d6faa00..0000000000 --- a/ext/node/polyfills/upstream_modules.ts +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. -// Upstream modules -const callerPath = `const callerCallsite = require("caller-callsite"); -const re = /^file:/; - -module.exports = () => { - const fileUrl = callerCallsite().getFileName(); - return fileUrl.replace(re, ""); -}; -`; - -// From: https://github.com/stefanpenner/get-caller-file/blob/2383bf9e98ed3c568ff69d7586cf59c0f1dcb9d3/index.ts -const getCallerFile = ` -const re = /^file:\\/\\//; - -module.exports = function getCallerFile(position = 2) { - if (position >= Error.stackTraceLimit) { - throw new TypeError('getCallerFile(position) requires position be less then Error.stackTraceLimit but position was: "' + position + '" and Error.stackTraceLimit was: "' + Error.stackTraceLimit + '"'); - } - - const oldPrepareStackTrace = Error.prepareStackTrace; - Error.prepareStackTrace = (_, stack) => stack; - const stack = new Error().stack; - Error.prepareStackTrace = oldPrepareStackTrace; - - - if (stack !== null && typeof stack === 'object') { - // stack[0] holds this file - // stack[1] holds where this function was called - // stack[2] holds the file we're interested in - return stack[position] ? stack[position].getFileName().replace(re, "") : undefined; - } -}; -`; - -export default { - "caller-path": callerPath, - "get-caller-file": getCallerFile, -} as Record;