From 6db5366d7f52f353981b8bd150e60fb680626525 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Sun, 20 Sep 2026 14:02:44 -0500 Subject: [PATCH] Add static node:module parity --- .../node-v24-compatibility-meta.json | 2 +- .../src/generated/node-v24-compatibility.json | 32 ++-- .../generated/node-v24-backlog.json | 62 +++++--- .../generated/node-v24-internal.json | 51 +++++-- internal/compatibility/static-support.json | 3 + .../ambient/scriptc-node-fallback.d.ts | 16 +- .../src/frontend/lowering/lower-builtins.ts | 143 +++++++++++++++++- .../src/frontend/lowering/lower-calls.ts | 6 +- .../src/frontend/lowering/lower-modules.ts | 7 +- .../src/frontend/lowering/lower-stmts.ts | 39 ++++- .../compiler/src/frontend/lowering/lowerer.ts | 142 ++++++++++++----- .../src/frontend/lowering/surfaces.ts | 12 +- packages/compiler/src/frontend/program.ts | 90 +++++++++-- packages/compiler/surface-manifest.json | 6 +- .../test/ts7/baselines/order-parity.json | 9 ++ .../2932-create-require-program/factory.cjs | 5 + .../2932-create-require-program/local.cjs | 7 + .../2932-create-require-program/main.ts | 37 +++++ .../2932-create-require-program/package.json | 6 + .../2932-create-require-program/side.cjs | 1 + tests/diagnostics/create-require.ts | 12 +- .../fixtures/npm-static/create-require-cli.ts | 7 + .../__snapshots__/create-require.ts.txt | 8 +- tests/harness/npm-static.test.ts | 21 +++ 24 files changed, 592 insertions(+), 132 deletions(-) create mode 100644 tests/corpus/2932-create-require-program/factory.cjs create mode 100644 tests/corpus/2932-create-require-program/local.cjs create mode 100644 tests/corpus/2932-create-require-program/main.ts create mode 100644 tests/corpus/2932-create-require-program/package.json create mode 100644 tests/corpus/2932-create-require-program/side.cjs create mode 100644 tests/fixtures/npm-static/create-require-cli.ts diff --git a/docs/src/generated/node-v24-compatibility-meta.json b/docs/src/generated/node-v24-compatibility-meta.json index a6f6fb000..c187e713a 100644 --- a/docs/src/generated/node-v24-compatibility-meta.json +++ b/docs/src/generated/node-v24-compatibility-meta.json @@ -2,6 +2,6 @@ "schemaVersion": 3, "nodeVersion": "24.15.0", "nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119", - "artifactVersion": "4bd472c2324101f87572", + "artifactVersion": "743056178b8be4cb1d5c", "rowCount": 3662 } diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index ec93b426d..311d8f76e 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -650,9 +650,9 @@ "apiEntries": 30, "static": { "supported": 0, - "partial": 0, - "refused": 3, - "not-implemented": 27, + "partial": 5, + "refused": 0, + "not-implemented": 25, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -57660,7 +57660,7 @@ "anchor": "", "nodeStability": null, "static": { - "status": "not-implemented", + "status": "partial", "detail": "Derived from the exact API rows in this chapter.", "verification": "derived" }, @@ -57706,9 +57706,9 @@ "anchor": "modulecreaterequirefilename", "nodeStability": null, "static": { - "status": "refused", - "detail": "Explicitly rejected by a named compiler diagnostic.", - "verification": "explicit-refusal" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" }, "dynamic": { "status": "partial", @@ -57757,9 +57757,9 @@ "anchor": "moduleisbuiltinmodulename", "nodeStability": null, "static": { - "status": "refused", - "detail": "Explicitly rejected by a named compiler diagnostic.", - "verification": "explicit-refusal" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" }, "dynamic": { "status": "partial", @@ -57864,9 +57864,9 @@ "anchor": "modulesyncbuiltinesmexports", "nodeStability": null, "static": { - "status": "refused", - "detail": "Explicitly rejected by a named compiler diagnostic.", - "verification": "explicit-refusal" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" }, "dynamic": { "status": "partial", @@ -57887,9 +57887,9 @@ "anchor": "the-module-object", "nodeStability": null, "static": { - "status": "not-implemented", - "detail": "No static compiler lowering is registered for this API yet.", - "verification": "registry-gap" + "status": "partial", + "detail": "Implemented for the documented scriptc module-loader subset.", + "verification": "test-backed" }, "dynamic": { "status": "partial", diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index 427c7101e..30503487d 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -7,9 +7,9 @@ "tierItemCount": 6663, "tiers": { "static": { - "replace-refusal": 51, - "verify-gap": 2715, - "audit-partial": 446, + "replace-refusal": 48, + "verify-gap": 2714, + "audit-partial": 450, "classify": 2, "implement": 81 }, @@ -61175,12 +61175,18 @@ "priority": "normal", "tiers": { "static": { - "status": "refused", - "action": "replace-refusal", - "verification": "explicit-refusal", + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", "confidence": "high", - "source": "surface-manifest:unsupported:node-builtin.module.createRequire", - "tests": [] + "source": "compiler-feature:module.module.createRequire", + "tests": [ + "tests/corpus/2631-create-require/main.ts", + "tests/corpus/2637-create-require-bare/main.ts", + "tests/corpus/2675-create-require-nested-esm.js", + "tests/corpus/2932-create-require-program/main.ts", + "tests/harness/npm-static.test.ts" + ] }, "dynamic": { "status": "partial", @@ -61239,12 +61245,14 @@ "priority": "normal", "tiers": { "static": { - "status": "refused", - "action": "replace-refusal", - "verification": "explicit-refusal", + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", "confidence": "high", - "source": "surface-manifest:unsupported:node-builtin.module.isBuiltin", - "tests": [] + "source": "compiler-feature:module.module.isBuiltin", + "tests": [ + "tests/corpus/2932-create-require-program/main.ts" + ] }, "dynamic": { "status": "partial", @@ -61369,12 +61377,14 @@ "priority": "normal", "tiers": { "static": { - "status": "refused", - "action": "replace-refusal", - "verification": "explicit-refusal", + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", "confidence": "high", - "source": "surface-manifest:unsupported:node-builtin.module.syncBuiltinESMExports", - "tests": [] + "source": "compiler-feature:module.module.syncBuiltinESMExports", + "tests": [ + "tests/corpus/2932-create-require-program/main.ts" + ] }, "dynamic": { "status": "partial", @@ -61400,12 +61410,16 @@ "priority": "normal", "tiers": { "static": { - "status": "not-implemented", - "action": "verify-gap", - "verification": "registry-gap", - "confidence": "low", - "source": "compiler-unmatched:module", - "tests": [] + "status": "partial", + "action": "audit-partial", + "verification": "test-backed", + "confidence": "high", + "source": "compiler-feature:module.builtinModules", + "tests": [ + "tests/corpus/2631-create-require/main.ts", + "tests/corpus/2637-create-require-bare/main.ts", + "tests/corpus/2932-create-require-program/main.ts" + ] }, "dynamic": { "status": "partial", diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 4e94da274..876694581 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -723,9 +723,9 @@ "apiEntries": 30, "static": { "supported": 0, - "partial": 0, - "refused": 3, - "not-implemented": 27, + "partial": 5, + "refused": 0, + "not-implemented": 25, "by-design": 0, "unreviewed": 0, "not-applicable": 0 @@ -76542,8 +76542,15 @@ "anchor": "", "nodeStability": null, "static": { - "status": "not-implemented", - "evidence": "derived:descendants" + "status": "partial", + "evidence": "derived:descendants", + "tests": [ + "tests/corpus/2631-create-require/main.ts", + "tests/corpus/2637-create-require-bare/main.ts", + "tests/corpus/2675-create-require-nested-esm.js", + "tests/corpus/2932-create-require-program/main.ts", + "tests/harness/npm-static.test.ts" + ] }, "dynamic": { "status": "partial", @@ -76590,8 +76597,15 @@ "anchor": "modulecreaterequirefilename", "nodeStability": null, "static": { - "status": "refused", - "evidence": "surface-manifest:unsupported:node-builtin.module.createRequire" + "status": "partial", + "evidence": "compiler-feature:module.module.createRequire", + "tests": [ + "tests/corpus/2631-create-require/main.ts", + "tests/corpus/2637-create-require-bare/main.ts", + "tests/corpus/2675-create-require-nested-esm.js", + "tests/corpus/2932-create-require-program/main.ts", + "tests/harness/npm-static.test.ts" + ] }, "dynamic": { "status": "partial", @@ -76643,8 +76657,11 @@ "anchor": "moduleisbuiltinmodulename", "nodeStability": null, "static": { - "status": "refused", - "evidence": "surface-manifest:unsupported:node-builtin.module.isBuiltin" + "status": "partial", + "evidence": "compiler-feature:module.module.isBuiltin", + "tests": [ + "tests/corpus/2932-create-require-program/main.ts" + ] }, "dynamic": { "status": "partial", @@ -76750,8 +76767,11 @@ "anchor": "modulesyncbuiltinesmexports", "nodeStability": null, "static": { - "status": "refused", - "evidence": "surface-manifest:unsupported:node-builtin.module.syncBuiltinESMExports" + "status": "partial", + "evidence": "compiler-feature:module.module.syncBuiltinESMExports", + "tests": [ + "tests/corpus/2932-create-require-program/main.ts" + ] }, "dynamic": { "status": "partial", @@ -76776,8 +76796,13 @@ "anchor": "the-module-object", "nodeStability": null, "static": { - "status": "not-implemented", - "evidence": "compiler-unmatched:module" + "status": "partial", + "evidence": "compiler-feature:module.builtinModules", + "tests": [ + "tests/corpus/2631-create-require/main.ts", + "tests/corpus/2637-create-require-bare/main.ts", + "tests/corpus/2932-create-require-program/main.ts" + ] }, "dynamic": { "status": "partial", diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index 295b67aca..8479390f7 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -81,6 +81,9 @@ { "chapter": "modules", "symbols": ["require.resolve", "require.resolve.paths"], "status": "partial", "evidence": ["tests/corpus/2858-import-meta-resolve/main.mjs", "tests/corpus/2860-require-resolve/main.cjs"] }, { "chapter": "modules", "symbols": ["cache", "children", "id", "isPreloading", "loaded", "parent", "path", "paths", "Module"], "status": "partial", "evidence": ["tests/corpus/2880-cjs-module-graph/main.cjs"] }, { "chapter": "modules", "symbols": ["extensions"], "status": "not-implemented" }, + { "chapter": "module", "symbols": ["module.createRequire"], "status": "partial", "evidence": ["tests/corpus/2631-create-require/main.ts", "tests/corpus/2637-create-require-bare/main.ts", "tests/corpus/2675-create-require-nested-esm.js", "tests/corpus/2932-create-require-program/main.ts", "tests/harness/npm-static.test.ts"] }, + { "chapter": "module", "symbols": ["module.isBuiltin", "module.syncBuiltinESMExports"], "status": "partial", "evidence": ["tests/corpus/2932-create-require-program/main.ts"] }, + { "chapter": "module", "symbols": ["builtinModules"], "status": "partial", "evidence": ["tests/corpus/2631-create-require/main.ts", "tests/corpus/2637-create-require-bare/main.ts", "tests/corpus/2932-create-require-program/main.ts"] }, { "chapter": "modules", "symbols": ["node:", "node_modules"], "status": "partial", "evidence": ["tests/corpus/1598-cjs-builtin-require/main.js", "tests/corpus/1596-cjs-modules/main.js"] }, { "chapter": "esm", "symbols": ["import", "file:", "node:", "await"], "status": "partial", "evidence": ["tests/corpus/950-modules-basic/main.ts", "tests/corpus/2646-top-level-await.ts"] }, { "chapter": "esm", "symbols": ["url", "dirname", "filename", "main"], "status": "partial", "evidence": ["tests/corpus/2701-import-meta/main.mjs"] }, diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index de8a5577f..20149ee6e 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -947,15 +947,19 @@ declare var performance: import("node:perf_hooks").Performance; * createRequire(import.meta.url) (or __filename) whose require calls * take STATIC string literals — the indirection erases at compile time. * A builtin spec makes the binding a namespace import in const clothing; - * a relative .json document bakes and parses (JSON.parse's `unknown` - * stance — validate with a checked cast); an installed npm package loads - * through the island's require-condition entry under --dynamic; a bare - * name nothing installed resolves compiles to Node's catchable - * MODULE_NOT_FOUND throw (the optional-dependency try/require pattern). + * relative and package-import program modules use the compiled module + * graph; a relative .json document bakes and parses (JSON.parse's + * `unknown` stance — validate with a checked cast); an installed npm + * package loads through the island's require-condition entry under + * --dynamic, or through the compiled graph under --npm-static; a bare name + * nothing installed resolves compiles to Node's catchable MODULE_NOT_FOUND + * throw (the optional-dependency try/require pattern). * Dynamic specifiers fence: a compiled binary's module graph is fixed at * build time. builtinModules is the baked Node v24 list (a fresh * mutable array per read where Node ships one frozen singleton); - * isBuiltin and syncBuiltinESMExports fence per site. Both spellings + * isBuiltin checks runtime strings against that pinned list, and + * syncBuiltinESMExports is a no-op because the static builtin surface is + * immutable. Both spellings * name the builtin, like in Node (the builtin wins over the npm package * named "module" for the bare specifier there too). */ declare module "node:module" { diff --git a/packages/compiler/src/frontend/lowering/lower-builtins.ts b/packages/compiler/src/frontend/lowering/lower-builtins.ts index 955f60b47..227363286 100644 --- a/packages/compiler/src/frontend/lowering/lower-builtins.ts +++ b/packages/compiler/src/frontend/lowering/lower-builtins.ts @@ -8,7 +8,7 @@ import { dirname, resolve } from "node:path"; import * as ts from "../ts7/adapter.js"; import type { Lowerer } from "./lowerer.js"; import { PoisonError, dynUndefinedExpr, ladderFenceExpr, nodeThrowExpr, own } from "./lowerer.js"; -import { canonicalBuiltinModule, isJsSourceFile, isNodeEsmFile, locOf, requireSpecOf } from "../program.js"; +import { canonicalBuiltinModule, isJsSourceFile, isNodeEsmFile, locOf, npmStaticDepSf7, requireSpecOf, resolveImport } from "../program.js"; import { isRelativeSpecifier } from "../workspace-registry.js"; import { probeNodeRequireRefusal } from "../npm.js"; import { isNpmStaticPackage } from "../npm-static.js"; @@ -27,6 +27,7 @@ import { builtinConstLit, fenceOrDropOptionKey, isChildSurfaceMember, + NODE_BUILTIN_MODULES_V24, } from "./surfaces.js"; import { lowerAbsenceProbe } from "./lower-exprs.js"; import { conditionalSpreadOf, lowerDynObjectLiteral } from "./expressions/object-literals.js"; @@ -469,6 +470,136 @@ function lowerBuiltinOptionalDefault( return cr !== null && cr.spec !== null && canonicalBuiltinModule(cr.spec) !== null; } +/** A statically compiled program module reached through a canonical + * createRequire binding. This is the ESM twin of an ordinary CommonJS + * require edge: the same project resolver, require-condition npm-static + * entry, module initializer, and export registrations own the target. */ + export function createRequireProgramModuleOf( + lowerer: Lowerer, + expr: ts.Expression | undefined, + ): { spec: string; baseFile: ts.SourceFile; dep: ts.SourceFile } | null { + if (expr === undefined) return null; + const call = stripTypeCasts(expr); + if (!ts.isCallExpression(call)) return null; + const cr = createRequireSpecOf(lowerer, call); + if (cr === null || cr.spec === null || canonicalBuiltinModule(cr.spec) !== null) return null; + const dep = resolveImport(lowerer.program, cr.baseFile, cr.spec) ?? + npmStaticDepSf7(lowerer.program, cr.baseFile, cr.spec); + if (dep === null || dep.fileName.endsWith(".json")) return null; + return { spec: cr.spec, baseFile: cr.baseFile, dep }; + } + +/** True for a const identifier whose initializer is a createRequire call + * reaching a compiled program module. The binding is namespace/value + * alias plumbing; its declaration emits only the dependency's run-once + * initializer at the source position. */ + export function createRequireProgramModuleDecl( + lowerer: Lowerer, + nameNode: ts.Node, + init: ts.Expression | undefined, + ): boolean { + if (createRequireProgramModuleOf(lowerer, init) === null) return false; + if (ts.isIdentifier(nameNode)) return true; + if (!ts.isObjectBindingPattern(nameNode)) return false; + return nameNode.elements.every((element) => + element.name !== undefined && element.dotDotDotToken === undefined && element.initializer === undefined && + ts.isIdentifier(element.name) && + (element.propertyName === undefined || ts.isIdentifier(element.propertyName) || ts.isStringLiteralLike(element.propertyName)), + ); + } + +/** node:module's two compiler-only calls. isBuiltin compares one evaluated + * string against the pinned Node 24 list (bare builtins also accept their + * node: spelling; prefix-only entries do not gain a bare alias). + * syncBuiltinESMExports is observably a no-op inside the static surface: + * builtin exports cannot be mutated, so its only supported behavior is + * evaluating no arguments and returning undefined. */ + export function lowerNodeModuleCall( + lowerer: Lowerer, + expr: ts.CallExpression, + bi: { module: string; member: string }, + loc: SrcLoc, + ): IrExpr | null { + if (bi.module !== "module") return null; + if (bi.member === "syncBuiltinESMExports") { + if (expr.arguments.length !== 0 || expr.arguments.some(ts.isSpreadElement)) { + lowerer.noLowering( + `module.syncBuiltinESMExports with ${expr.arguments.length} arguments`, + expr, + "syncBuiltinESMExports() takes no arguments", + ); + } + if (!ts.isExpressionStatement(expr.parent)) { + lowerer.noLowering( + "module.syncBuiltinESMExports used as a value", + expr, + "call syncBuiltinESMExports() as its own statement; the supported static effect is a no-op", + ); + } + return { kind: "unitLit", unit: "undefined", type: UNDEFINED_T, loc }; + } + if (bi.member !== "isBuiltin") return null; + if (expr.arguments.length !== 1 || expr.arguments.some(ts.isSpreadElement)) { + lowerer.noLowering( + `module.isBuiltin with ${expr.arguments.length} arguments`, + expr, + "isBuiltin(moduleName) takes one string", + ); + } + const argumentNode = expr.arguments[0]!; + const argument = lowerer.lowerExpr(argumentNode); + if (argument.type.kind !== "string") { + if (argument.type.kind === "dyn" || argument.type.kind === "jsval" || argument.type.kind === "union") { + lowerer.noLowering( + "module.isBuiltin with a runtime-polymorphic argument", + argumentNode, + "narrow the module name to a string first; statically non-string values return false", + ); + } + return { + kind: "seqExpr", + stmts: [{ kind: "exprStmt", expr: argument, loc: locOf(argumentNode) }], + result: boolLit(false, loc), + type: BOOL, + loc, + }; + } + const slot = lowerer.declareHiddenLocal("%builtinName", STRING); + const ref = (): IrExpr => varRef(slot.id, STRING, loc); + const accepted = NODE_BUILTIN_MODULES_V24.flatMap((name) => + name.startsWith("node:") ? [name] : [name, `node:${name}`], + ); + let staticArgument: ts.Expression = argumentNode; + while ( + ts.isParenthesizedExpression(staticArgument) || ts.isAsExpression(staticArgument) || + ts.isTypeAssertion(staticArgument) || ts.isNonNullExpression(staticArgument) + ) { + staticArgument = staticArgument.expression; + } + if (ts.isStringLiteralLike(staticArgument)) { + return boolLit(accepted.includes(staticArgument.text), loc); + } + let result: IrExpr = boolLit(false, loc); + for (let i = accepted.length - 1; i >= 0; i--) { + const equal: IrExpr = { + kind: "strEq", + negated: false, + left: ref(), + right: strLit(accepted[i]!, loc), + type: BOOL, + loc, + }; + result = { kind: "logical", op: "||", left: equal, right: result, type: BOOL, loc }; + } + return { + kind: "seqExpr", + stmts: [{ kind: "varDecl", localId: slot.id, init: argument, loc }], + result, + type: BOOL, + loc, + }; + } + /** `require("spec")` through a createRequire binding — the erasure per * target. Builtins are reached here only OUTSIDE the const-namespace- * binding shape (that declaration erases; member uses resolve through @@ -502,6 +633,14 @@ function lowerBuiltinOptionalDefault( `module namespace objects as values (bind it first: const m = require("${spec}"), then access members through the binding)`, ); } + const programModule = createRequireProgramModuleOf(lowerer, call); + if (programModule !== null) { + lowerer.noLowering( + `createRequire's module namespace value for '${spec}'`, + call, + `bind it once (const m = require(${JSON.stringify(spec)})) and access statically-known members through that binding`, + ); + } if (spec.startsWith("#")) { lowerer.noLowering( `createRequire's require of the '${spec}' project import`, @@ -514,7 +653,7 @@ function lowerBuiltinOptionalDefault( lowerer.noLowering( `createRequire's require of '${spec}'`, call, - "relative requires lower for .json documents only — a program module is a static import", + "relative program modules lower when they resolve into the compiled graph; relative .json documents bake at build time", ); } const abs = spec.startsWith("/") diff --git a/packages/compiler/src/frontend/lowering/lower-calls.ts b/packages/compiler/src/frontend/lowering/lower-calls.ts index 42a70a9e1..d2b0b6bd9 100644 --- a/packages/compiler/src/frontend/lowering/lower-calls.ts +++ b/packages/compiler/src/frontend/lowering/lower-calls.ts @@ -19,7 +19,7 @@ import { mixinFnShapeOf } from "./lower-mixins.js"; import { dynStringReceiver, lowerArrayFromCall, lowerDynArrayFilterCall, lowerDynArrayFlatMapCall, lowerGroupByStaticCall, lowerIteratorHelperCall, lowerObjectAssignIndexShape, lowerObjectFromEntriesCall, lowerObjectIterOverIndexShape, lowerTupleReadMethodCall } from "./lower-containers.js"; import { bufEncoding } from "./containers/bytes.js"; import { lowerRegexMethodCall, lowerStringMethodCall } from "./containers/string-and-regexp.js"; -import { lowerChildStreamMethodCall, lowerChildWriterMethodCall, lowerCreateRequireCall, lowerCryptoHashMethodCall, lowerDirentMethodCall, lowerFileHandleMethodCall, lowerImportMetaResolveCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerRequireResolveCall, lowerWatcherMethodCall } from "./lower-builtins.js"; +import { lowerChildStreamMethodCall, lowerChildWriterMethodCall, lowerCreateRequireCall, lowerCryptoHashMethodCall, lowerDirentMethodCall, lowerFileHandleMethodCall, lowerImportMetaResolveCall, lowerNodeModuleCall, lowerPerfHooksCall, lowerProcStreamMethodCall, lowerReflectApplyCall, lowerRequireResolveCall, lowerWatcherMethodCall } from "./lower-builtins.js"; import { lowerAbsenceProbe, lowerPromiseAllTupleCall, lowerPromiseRejectCall, templateRawTextOf } from "./lower-exprs.js"; import { isSafeToDiscard } from "./expressions/evaluation-safety.js"; import { tryLowerExpression } from "./expressions/try-lower-expression.js"; @@ -3304,6 +3304,8 @@ function lowerProjectedBuiltinCall( if (cryptoServed) return cryptoServed; const timersInterval = lowerer.lowerTimersPromisesSetInterval(expr, bi, loc); if (timersInterval) return timersInterval; + const nodeModuleServed = lowerNodeModuleCall(lowerer, expr, bi, loc); + if (nodeModuleServed) return nodeModuleServed; const builtinFn = builtinModuleFnOf(lowerer, bi.module, bi.member); if (!builtinFn) { lowerer.noLowering( @@ -4058,6 +4060,8 @@ export function lowerCall(lowerer: Lowerer, expr: ts.CallExpression): IrExpr { if (cryptoServed) return cryptoServed; const timersInterval = lowerer.lowerTimersPromisesSetInterval(expr, bi, loc); if (timersInterval) return timersInterval; + const nodeModuleServed = lowerNodeModuleCall(lowerer, expr, bi, loc); + if (nodeModuleServed) return nodeModuleServed; const builtinFn = builtinModuleFnOf(lowerer, bi.module, bi.member); if (!builtinFn) { // Typed by @types/node (the fallback declarations only declare diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index ca3b6e272..ea9d503b7 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -14,7 +14,7 @@ import type { CycleEdge } from "../program.js"; import { invalidJsonModuleDiag, npmEmbedFailedDiag, requiresDynamicImportDiag } from "../../diagnostics/diagnostic.js"; import { BOOL, DYN, IrClassDef, IrExpr, IrFunction, IrGlobal, IrRecordShape, IrStmt, IrType, IrUnionDef, JSVAL, RUNTIME_ERROR_CLASSES, STRING, SrcLoc, VOID, arrayOf, canConvertToDyn, isUnitType } from "../../ir/ir.js"; import { ENTRY_NAME, PoisonError, boundIdentifiersOf, dynFallbackType, dynUndefinedExpr, importCallHandleType, newFnCtx, uncheckedOverloadHandleCall } from "./lowerer.js"; -import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf, createRequireBindingDecl, createRequireNamespaceDecl, createRequireSpecOf, isPromisifyCall, registerBuiltinCallableAlias, textCodecBindingDecl } from "./lower-builtins.js"; +import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf, createRequireBindingDecl, createRequireNamespaceDecl, createRequireProgramModuleDecl, createRequireSpecOf, isPromisifyCall, registerBuiltinCallableAlias, textCodecBindingDecl } from "./lower-builtins.js"; import { bindingContextualGenericFnNodeOf, bindingGenericFnAliasInfoOf, bindingGenericFnInfoOf, bindingGenericFnNodeOf, bindingNeverReassigned, deadUnmappableBinding, implicitLocalFnInfoOf, implicitLocalFnNodeOf, nullishGenericBindingUnitOf, registerOverloadedCallableAlias } from "./lower-calls.js"; import { isVarDeclared, numericIteratorSourceOf, provenanceElidedConstDecl } from "./lower-stmts.js"; import { streamClassAliasDecl } from "./lower-stream.js"; @@ -1274,6 +1274,11 @@ export function collectGlobals(lowerer: Lowerer, sf: ts.SourceFile, topStmts: ts // `const fs = require("node:fs")` through that binding at file // scope — a namespace import in const clothing, same story. if (isConst && createRequireNamespaceDecl(lowerer, decl.name, decl.initializer)) continue; + // `const local = require("./local.cjs")` through createRequire: + // the binding is static module alias plumbing. Its statement emits + // the dependency init at this exact source position; no value slot + // represents the namespace object itself. + if (isConst && createRequireProgramModuleDecl(lowerer, decl.name, decl.initializer)) continue; // `const { createSign } = crypto` over a builtin NAMESPACE binding // at file scope: alias plumbing like the destructured-require form // — no storage (the statement lowering skips by the same test). diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 85c39c20e..6a31113e8 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -23,7 +23,7 @@ import type { ClassInfo, ClassIteratorInfo } from "./lower-classes.js"; import { genericIfaceBindingKeepsClass } from "./lower-classes.js"; import { lowerStreamUnderscoreAssign, streamClassAliasDecl } from "./lower-stream.js"; import { lowerHttpResPropertyAssignment, lowerHttpServerTimeoutAssignment, lowerServerCloseOverrideAssignment } from "./lower-server.js"; -import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf, createRequireBindingDecl, createRequireCalleeFileOf, createRequireNamespaceDecl, registerBuiltinCallableAlias, textCodecBindingDecl } from "./lower-builtins.js"; +import { builtinMemberRequireDecl, builtinNamespaceDestructureModuleOf, createRequireBindingDecl, createRequireCalleeFileOf, createRequireNamespaceDecl, createRequireProgramModuleDecl, createRequireProgramModuleOf, lowerNodeModuleCall, registerBuiltinCallableAlias, textCodecBindingDecl } from "./lower-builtins.js"; import { lowerEnumDeclaration } from "./lower-enums.js"; import { abstractPropertyDeclOf, aliasTypeofNarrows, isMatchSliceType, lowerAbsenceProbe, lowerGroupsProjection, lowerOptionalNumber, matchResultNamedGroupsOf, runtimeOptionalTrueIds, symbolFieldInfo, withRuntimeOptionalNarrowed } from "./lower-exprs.js"; import { isSafeToRepeat } from "./expressions/evaluation-safety.js"; @@ -1110,6 +1110,22 @@ export function lowerStmt(lowerer: Lowerer, stmt: ts.Statement): IrStmt | IrStmt const isConst = (list.flags & ts.NodeFlags.Const) !== 0; const isLet = (list.flags & ts.NodeFlags.Let) !== 0 || (list.flags & ts.NodeFlags.BlockScoped) === 0; return list.declarations.flatMap((decl) => { + // The canonical ESM bridge to a compiled program module. Like a + // top-level CommonJS require declaration, the binding is alias + // plumbing while the dependency evaluates at this statement's + // exact position through its run-once initializer. + if (isConst && createRequireProgramModuleDecl(lowerer, decl.name, decl.initializer)) { + if (!ts.isSourceFile(stmt.parent)) { + lowerer.unsupported( + "SC1090", + decl, + "createRequire program-module bindings outside the module's top level (move the binding to the top of the file; side-effect-only requires may remain nested)", + ); + } + const target = createRequireProgramModuleOf(lowerer, decl.initializer)!; + const init = lowerer.requireInitStmt(target.spec, decl); + return init ? [init] : []; + } // CommonJS require declarations (JS files): at the module's top // level the BINDINGS are alias plumbing (no storage; // resolveValueSymbol routes the reads), but the require itself is @@ -4869,6 +4885,27 @@ function isEsModuleStamp(expr: ts.Expression): boolean { ts.isExpressionStatement(stmtNode) && ts.isSourceFile(stmtNode.parent) && isJsSourceFile(stmtNode.parent); + // createRequire's side-effect program-module form uses the same inline + // run-once initializer as a CommonJS require statement. The expression + // has no represented namespace value in statement position. + if (ts.isCallExpression(expr)) { + const created = createRequireProgramModuleOf(lowerer, expr); + if (created !== null) { + return lowerer.requireInitStmt(created.spec, expr) ?? { kind: "block", body: [], loc: locOf(expr) }; + } + // syncBuiltinESMExports() is the other compiler-only statement: the + // call validates its zero-argument shape, then emits no code because + // the static builtin surface is immutable. + const bi = ts.isIdentifier(expr.expression) + ? lowerer.builtinImportOf(expr.expression) + : ts.isPropertyAccessExpression(expr.expression) + ? lowerer.builtinMemberOf(expr.expression) + : null; + if (bi?.module === "module" && bi.member === "syncBuiltinESMExports") { + lowerNodeModuleCall(lowerer, expr, bi, locOf(expr)); + return { kind: "block", body: [], loc: locOf(expr) }; + } + } if ( requireSpecOf(expr) !== null && ts.isCallExpression(expr) && diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 4f061571e..6f402c0d3 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -101,7 +101,7 @@ import { lowerBufferStaticCall, lowerBytesMethodCall, lowerBytesNew } from "./co import { lowerRegexMethodCall, lowerStringMethodCall } from "./containers/string-and-regexp.js"; import { lowerStreamModuleCall } from "./lower-stream.js"; import { lowerEmitOverrideSpec, type EmitSpecCtx, type EmitSpecRequest } from "./lower-event-emitter.js"; -import { builtinImportOf, createRequireBindingDecl, createRequireNamespaceDecl, createRequireSpecOf, stripTypeCasts, lowerBuiltinModuleCall, lowerTimersPromisesSetInterval, lowerFsToUnixTimestampCall, lowerFsLadderCall, lowerChildArgsArg, lowerSpawnSyncCall, lowerSpawnCall, lowerExecFileCall, lowerExecSyncCall, recordToEnvPairs, lowerJsonMethodCall, fencedBuiltinImportOf, lowerCryptoComposedCall, lowerUrlMethodCall, lowerSearchParamsMethodCall, lowerStatsMethodCall, lowerChildMethodCall, lowerAtomicsCall, lowerBuiltinExtraProperty, registerPromisifiedBuiltinDecl, lowerExecFileAsyncCall, execFileAsyncHelper, lowerStringDecoderMethodCall, strdecHelper, lowerReadlineMethodCall, lowerDcChannelMethodCall, lowerDcChannelProperty, lowerAlsMethodCall, lowerDcTracingChannelMethodCall, lowerDcTracingChannelProperty, lowerJsonProperty, lowerErrorCodeProperty, lowerProcessProperty, isProcessEnv, envValueType, lowerProcessEnvGet, lowerProcessMethodCall, lowerProcessOptionalMethodCall, lowerTimeoutMethodCall, envSnapshotHelper, isConsoleLog, consoleCallMember, lowerNumberStaticCall, lowerNumberStaticProperty, lowerDateCall, lowerTextCodecCall, lowerCryptoModuleCall, lowerFsConstantsProperty, lowerBuiltinConstantsProperty, builtinConstantBindingOf, builtinConstantsDestructureDecl, lowerProcessStreamProperty, lowerStringStaticCall, lowerStringLastIndexOfCall, lowerPromiseStaticCall, textCodecBindingClassOf } from "./lower-builtins.js"; +import { builtinImportOf, createRequireBindingDecl, createRequireNamespaceDecl, createRequireProgramModuleOf, createRequireSpecOf, stripTypeCasts, lowerBuiltinModuleCall, lowerNodeModuleCall, lowerTimersPromisesSetInterval, lowerFsToUnixTimestampCall, lowerFsLadderCall, lowerChildArgsArg, lowerSpawnSyncCall, lowerSpawnCall, lowerExecFileCall, lowerExecSyncCall, recordToEnvPairs, lowerJsonMethodCall, fencedBuiltinImportOf, lowerCryptoComposedCall, lowerUrlMethodCall, lowerSearchParamsMethodCall, lowerStatsMethodCall, lowerChildMethodCall, lowerAtomicsCall, lowerBuiltinExtraProperty, registerPromisifiedBuiltinDecl, lowerExecFileAsyncCall, execFileAsyncHelper, lowerStringDecoderMethodCall, strdecHelper, lowerReadlineMethodCall, lowerDcChannelMethodCall, lowerDcChannelProperty, lowerAlsMethodCall, lowerDcTracingChannelMethodCall, lowerDcTracingChannelProperty, lowerJsonProperty, lowerErrorCodeProperty, lowerProcessProperty, isProcessEnv, envValueType, lowerProcessEnvGet, lowerProcessMethodCall, lowerProcessOptionalMethodCall, lowerTimeoutMethodCall, envSnapshotHelper, isConsoleLog, consoleCallMember, lowerNumberStaticCall, lowerNumberStaticProperty, lowerDateCall, lowerTextCodecCall, lowerCryptoModuleCall, lowerFsConstantsProperty, lowerBuiltinConstantsProperty, builtinConstantBindingOf, builtinConstantsDestructureDecl, lowerProcessStreamProperty, lowerStringStaticCall, lowerStringLastIndexOfCall, lowerPromiseStaticCall, textCodecBindingClassOf } from "./lower-builtins.js"; import { fenceFetchObjectAssignment, fenceFetchObjectBinding, fenceStaticAbortControllerMemberRead, fenceStaticHeadersIteration, fenceStaticHeadersMember, fenceStaticReadableStreamMember, fenceStaticResponseMember, fenceUnsupportedFetchConstructorMember, isIslandExpr, islandFuncValueFence, islandRegexpOf, jsvalIn, requireDynamicApi, islandGlobalFnOf, lowerAbortControllerNew, lowerDynamicHeadersIteratorCall, lowerDynamicHeadersSpread, lowerDynamicImportCall, lowerFetchCall, lowerFetchElementMethodCall, lowerResponseNew, lowerStaticFetchCompanionCall, lowerStaticAbortControllerCall, lowerStaticAbortSignalListenerCall, lowerStaticReadableStreamCancelCall, lowerStaticReadableStreamControllerCall, lowerStaticReadableStreamNew, lowerStaticReadableStreamReaderCall, lowerStaticResponseCall, lowerIslandMethodCall, lowerMathProperty, npmPackageOf, npmMemberFence, npmPackageOfSymbol } from "./lower-island.js"; import { lowerHttpHeadersElement, lowerNetModuleCall, lowerServerMethodCall, lowerServerProperty, lowerTlsRootCertificates } from "./lower-server.js"; import { lowerDgramDnsModuleCall, lowerDgramMethodCall } from "./lower-dgram.js"; @@ -1964,31 +1964,32 @@ export class Lowerer { if (ident.parent && ts.isShorthandPropertyAssignment(ident.parent) && ident.parent.name === ident) { symbol = this.checker.getShorthandAssignmentValueSymbol(ident.parent) ?? symbol; } + // createRequire's return type is intentionally `any`, so tsgo does not + // make the binding an import alias. When the target replaces + // module.exports with one represented scalar/function/class value, + // reconnect the binding to that export symbol explicitly; ordinary + // identifier/call/new/global paths then apply unchanged. + const createdWhole = this.createRequireWholeExportSymbolOf(ident); + if (createdWhole) symbol = createdWhole; + const createdMember = this.createRequireDestructuredExportSymbolOf(ident); + if (createdMember) symbol = createdMember; // tsgo synthesizes no expando symbol at a CJS MEMBER-EXPORT use site // (`common.GREETING` where the exporter attached GREETING with // `module.exports.GREETING = ...` — 5.9.3 answered the expando // property symbol here), but the exporter's MODULE symbol still // carries the member in its exports table; resolve through it so both // ends of the export key one symbol identity, like 5.9.3's. - if (!symbol && ident.parent && ts.isPropertyAccessExpression(ident.parent) && ident.parent.name === ident) { + if (ident.parent && ts.isPropertyAccessExpression(ident.parent) && ident.parent.name === ident) { const recv = ident.parent.expression; if (ts.isIdentifier(recv) && this.cjsLocalModuleBindingOf(recv)) { - const recvSym = this.checker.getSymbolAtLocation(recv); - const recvDecls = recvSym ? this.checker.declarationsOf(recvSym) : []; - const recvDecl = recvDecls.find(ts.isImportClause) ?? recvDecls[0]; - if (recvDecl && ts.isVariableDeclaration(recvDecl) && recvDecl.initializer) { - const spec = requireSpecOf(recvDecl.initializer); - const dep = spec === null - ? null - : resolveImport(this.program, recvDecl.getSourceFile(), spec) ?? - npmStaticDepSf7(this.program, recvDecl.getSourceFile(), spec); - if (dep) symbol = this.cjsModuleExportSymbol(dep, ident.text); - } else if (recvDecl && ts.isImportClause(recvDecl)) { - // The DEFAULT-import spelling of the same binding: the dep is - // the import declaration's resolved CJS module. - const dep = this.cjsDefaultImportDepOf(recvDecl); - if (dep) symbol = this.cjsModuleExportSymbol(dep, ident.text); - } + const dep = this.localModuleBindingDepOf(recv); + const exported = dep ? this.cjsModuleExportSymbol(dep, ident.text) : undefined; + // A createRequire call is typed as `any`, so tsgo can manufacture a + // transient property symbol with no relation to the target module. + // Prefer the real export-table symbol whenever the receiver is a + // proven static module binding; ordinary direct require aliases + // resolve to the same identity. + if (exported) symbol = exported; } } if (!symbol) return null; @@ -2276,7 +2277,16 @@ export class Lowerer { * property symbol exists at the attachment/use sites). */ cjsModuleExportSymbol(sf: ts.SourceFile, name: string): ts.Symbol | undefined { const moduleSym = this.checker.getSymbolAtLocation(sf); - return moduleSym?.getExports().get(name as ts.__String); + const exports = moduleSym?.getExports(); + const direct = exports?.get(name as ts.__String); + if (direct) return direct; + // tsgo represents `module.exports = { ... }` as one `export=` symbol + // and does not duplicate the object literal's properties into the + // module export map. Resolve through that root type so createRequire's + // any-typed namespace can recover the same property symbols an + // ordinary direct require receives from checker inference. + const root = exports?.get("export=" as ts.__String); + return root ? this.checker.getPropertyOfType(this.checker.getTypeOfSymbol(root), name) : undefined; } /** The local VALUE symbol behind a CJS export-table property symbol — @@ -2353,6 +2363,78 @@ export class Lowerer { return dep; } + /** The compiled module behind a require-like namespace binding: an + * ordinary CommonJS require declaration, a canonical createRequire + * declaration, or an ESM default import of CommonJS. One resolver keeps + * member-symbol fallback and namespace classification in lockstep. */ + private localModuleBindingDepOf(expr: ts.Identifier): ts.SourceFile | null { + const sym = this.checker.getSymbolAtLocation(expr); + const decls = sym ? this.checker.declarationsOf(sym) : []; + const decl = decls.find(ts.isImportClause) ?? decls[0]; + if (!decl) return null; + if (ts.isImportClause(decl)) return this.cjsDefaultImportDepOf(decl); + if (!ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name) || !decl.initializer) return null; + const directSpec = requireSpecOf(decl.initializer); + if (directSpec !== null) { + return resolveImport(this.program, decl.getSourceFile(), directSpec) ?? + npmStaticDepSf7(this.program, decl.getSourceFile(), directSpec); + } + return createRequireProgramModuleOf(this, decl.initializer)?.dep ?? null; + } + + /** The represented single value behind a createRequire binding, or null + * when the target is a namespace/table module. Collection has already + * registered executable exports before entry-module uses lower, so the + * registries are the authority for whether the export= value exists in + * static storage or callable/class metadata. */ + private createRequireWholeExportSymbolOf(ident: ts.Identifier): ts.Symbol | null { + const binding = this.checker.getSymbolAtLocation(ident); + const decl = binding ? this.checker.declarationsOf(binding).find(ts.isVariableDeclaration) : undefined; + if (!decl || !ts.isIdentifier(decl.name) || !decl.initializer) return null; + const target = createRequireProgramModuleOf(this, decl.initializer); + if (target === null) return null; + const moduleSym = this.checker.getSymbolAtLocation(target.dep); + let symbol = moduleSym?.getExports().get("export=" as ts.__String); + if (!symbol) return null; + const wholeAssignment = this.checker.declarationsOf(symbol).find((d): d is ts.BinaryExpression => { + if (!ts.isBinaryExpression(d) || !ts.isExpressionStatement(d.parent) || !ts.isSourceFile(d.parent.parent)) return false; + const exported = cjsExportAssignmentOf(d.parent); + return exported?.kind === "table" && exported.obj === null && cjsExportDiscardReason(d.parent) === null; + }); + if (wholeAssignment === undefined) return null; + let rhs: ts.Expression = wholeAssignment.right; + while (ts.isParenthesizedExpression(rhs)) rhs = rhs.expression; + if (ts.isIdentifier(rhs)) { + const value = this.checker.getSymbolAtLocation(rhs); + if (value) { + const resolved = value.flags & ts.SymbolFlags.Alias ? this.checker.getAliasedSymbol(value) : value; + return resolved; + } + } + if (symbol.flags & ts.SymbolFlags.Alias) symbol = this.checker.getAliasedSymbol(symbol); + symbol = this.cjsExportValueSymbol(symbol) ?? symbol; + return symbol; + } + + /** The target export behind `const { member: local } = require("...")` + * through createRequire. The binding is import-style alias plumbing; + * plain identifiers, renames, and string-literal property names share the + * target module's real export symbol. */ + private createRequireDestructuredExportSymbolOf(ident: ts.Identifier): ts.Symbol | null { + const binding = this.checker.getSymbolAtLocation(ident); + const element = binding ? this.checker.declarationsOf(binding).find(ts.isBindingElement) : undefined; + if (!element || element.name === undefined || !ts.isIdentifier(element.name) || !ts.isObjectBindingPattern(element.parent)) return null; + const declaration = element.parent.parent; + if (!ts.isVariableDeclaration(declaration) || !declaration.initializer) return null; + const target = createRequireProgramModuleOf(this, declaration.initializer); + if (target === null) return null; + const property = element.propertyName; + const name = property && (ts.isIdentifier(property) || ts.isStringLiteralLike(property)) + ? property.text + : ident.text; + return this.cjsModuleExportSymbol(target.dep, name) ?? null; + } + /** True when `expr` is an identifier bound by a top-level * `const x = require("./local")` of a project module — relative, * tsconfig-aliased, or package.json-mediated — or of a bare specifier @@ -2366,24 +2448,8 @@ export class Lowerer { cjsLocalModuleBindingOf(expr: ts.Expression): boolean { if (!ts.isIdentifier(expr)) return false; const sym = this.checker.getSymbolAtLocation(expr); - const decls = sym ? this.checker.declarationsOf(sym) : []; - const decl = decls.find(ts.isImportClause) ?? decls[0]; - if (!decl) return false; - if (ts.isImportClause(decl)) { - if (decl.name === undefined || this.cjsDefaultImportDepOf(decl) === null) return false; - } else { - if (!ts.isVariableDeclaration(decl) || !ts.isIdentifier(decl.name) || !decl.initializer) { - return false; - } - const spec = requireSpecOf(decl.initializer); - if (spec === null) return false; - if ( - resolveImport(this.program, decl.getSourceFile(), spec) === null && - npmStaticDepSf7(this.program, decl.getSourceFile(), spec) === null - ) { - return false; - } - } + if (this.localModuleBindingDepOf(expr) === null) return false; + if (this.createRequireWholeExportSymbolOf(expr) !== null) return false; // SINGLE-VALUE exporters (`module.exports = Countdown` / `= double` / // `= 42`): the requirer's binding IS the exported value, not a // namespace over an export table — the alias resolves straight to the @@ -9913,6 +9979,8 @@ export class Lowerer { // at the call site — no runtime entry exists to table. const cryptoServed = this.lowerCryptoModuleCall(call, bi, locOf(access)); if (cryptoServed) return cryptoServed; + const nodeModuleServed = lowerNodeModuleCall(this, call, bi, locOf(access)); + if (nodeModuleServed) return nodeModuleServed; const builtinFn = builtinModuleFnOf(this, bi.module, bi.member); if (!builtinFn) { this.noLowering( diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 3082ca67a..a85602cad 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -1264,7 +1264,7 @@ export function builtinConstLit(value: string | number | boolean, loc: { file: s * Node ships one frozen singleton; each read here mints a fresh string * array — a divergence only mutation could observe, and mutating Node's * frozen array throws anyway. */ -const NODE_BUILTIN_MODULES_V24: readonly string[] = [ +export const NODE_BUILTIN_MODULES_V24: readonly string[] = [ "_http_agent", "_http_client", "_http_common", "_http_incoming", "_http_outgoing", "_http_server", "_stream_duplex", "_stream_passthrough", "_stream_readable", "_stream_transform", "_stream_wrap", "_stream_writable", @@ -1353,15 +1353,13 @@ export const BUILTIN_MODULE_FENCE_HINTS: Record