The enum rewrite turns a package's numeric enum into one object holding
its names and its reverse mapping, so the object's fields have different
types. A reverse read with a runtime key, such as
DiagLogLevel[DiagLogLevel.WARN] in @opentelemetry/api, then failed with
SC1090 ("the declared fields have no one common type").
A keyed read of such a record already answers a checked-dynamic value in
JavaScript files. It now does the same where the checker types the read
'any'. The new npm case tsc-enum-reverse-reads matches Node, as does the
opentelemetry-api case again.
hot-shots, a StatsD client compiled from its JavaScript, failed when a
program constructed a client. Each gap on its UDP path is closed
generically:
- Sloppy-mode functions with named parameters may read `arguments`
where the aliasing of their parameters cannot show: only
`arguments.length`, or element reads without parameter or element
writes.
- An `any` slot admits any boxable function value, including functions
with optional parameters (`C.prototype.m = function (...)` through an
untyped receiver).
- In a JavaScript file TypeScript does not check, a prototype method
takes its documented parameters as checked-dynamic values, as
constructors already did.
- JavaScript dgram sends run completion callbacks, buffer lists,
connected sends and the six-argument offset/length form.
- A dgram socket crosses into the checked-dynamic tree as a handle
(SCR_DYNH_DGRAM_SOCKET) whose methods are function values, with
untyped listeners that survive 'close'. The handle lives in its own
runtime unit (scr_dgram_dyn.c, the dgramDyn feature), linked only
when a program converts a socket to or from an untyped value.
- `String.prototype.concat` on strings, and `parseInt` over a possibly
missing or untyped value in JavaScript.
Corpus programs 6590-6596 and the hot-shots-udp npm
case (hot-shots 8.5.0 vendored) match Node in both lanes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In JavaScript, `ReleaseEmitter = EventEmitter` over `const EventEmitter =
require("events")` was refused with SC1090 ("a runtime-provided class value
as an untyped value"), and `class Channel extends require("events")` failed
on the require call as a value. Bundled npm code does both, for example a
small semaphore that aliases EventEmitter and constructs its release channel
through the alias.
- The events module binding and the EventEmitter or stream class itself
turn untyped as the runtime's untyped constructor that constructor
functions already use (scr_dyn_builtin_class.c). `new` through it builds
an emitter object with Node's listener API, and `===` against the class
compares identity.
- `x instanceof EventEmitter` on an untyped value also recognizes a boxed
compiled emitter (an instance of a program class extending EventEmitter),
which the prototype test alone answered false for. The same holds through
an untyped binding narrowed to the class.
- `class X extends require("events")` extends the runtime emitter, as
through a require binding.
- A typed array of the class built from such values (`[EventEmitter]`) is
refused with SC1090 rather than failing its class check at run time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- dns.lookup with options known only at run time (a checked-dynamic
value, or one cast to an overload's option type) may answer a string or,
with `all: true`, an address list, whatever the overload the checker
picked says: an inline callback now binds its parameters as
checked-dynamic values. Array.isArray tests a checker-narrowed dyn read's
dyn value instead of folding on the extracted type, so it never answers
false for an address list the declared type omits.
- net.connect({ lookup }) accepts a lookup of Node's own type
(net.LookupFunction: options plus a callback taking an address string
or list), called as Node's autoSelectFamily dial does ({ hints, all:
true }); the fallback declarations spell net.LookupFunction that way.
dns.lookup accepts Node's hints flags (ADDRCONFIG, V4MAPPED, ALL) and
passes them to getaddrinfo.
- A CommonJS export typed as the empty object literal (tsc's enum output
`exports.E || (exports.E = {})` after `exports.E = void 0`) rides
checked-dynamic storage, so the preamble's undefined no longer traps at
module load: @opentelemetry/api loads and its no-op metrics and trace
APIs run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The export-alias rewrite skipped a whole function when a let or const
anywhere inside it declared the alias's name, so the function's other
references to the alias kept the alias name after its declaration was
removed. A function now shadows the alias only through its parameters,
its own name, its vars and the declarations directly in its body; a
block, case block, catch clause or for loop shadows it through its own
declarations, and the walk skips just that scope.
- Promise.race admits a Promise<never> entry (the deadline idiom) under
any result type: it can only reject, so its fulfillment adapter traps.
A Promise<never> executor's one-parameter resolve validates against the
void inner.
- Array(n).fill(v) and new Array(n).fill(v), which the checker types as
any[], take v's type.
- Object.entries/values over an index-signature record cast to a narrower
value type check each value into the element type instead of refusing.
- dns.LookupAllOptions and the lookup-function option union that holds it
(LookupOneOptions | LookupAllOptions | number | undefined) ride
checked-dynamic storage, and dns.lookup passes a cast of a
checked-dynamic options value through unchanged.
- npm packages: a CommonJS module that names its export object through a
variable (`var ip = exports; ip.isPrivate = ...`, the ip package) is
respelled through `exports`, and tsc output that adds a default-export
object (`exports.default = { metrics: metrics_api_1.metrics }`, the
@opentelemetry/api index) keeps its named exports linked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bundle that embeds fs-extra (build-utils does) compiled the package's
own JavaScript, whose computed export table and graceful-fs internals the
static model cannot follow. A __commonJS chunk whose key names a shimmed
package's main file at a supported version now requires the shim
instead, through a forwarder declaration beside the bundle: the checker
follows it to the shim (a package's JavaScript cannot load TypeScript
sources directly), and module resolution answers the shim for its path.
The package's other chunks are no longer reached. Calls through such a
require of a TypeScript module lower when the export never reads `this`
(require() answers the module's namespace).
package-shim-guard.test.ts compiles every registered shim module under
both type worlds and fails on any lowering diagnostic or fence inside the
shims, reached or not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A bundled package's error class defined inside an __esm initializer
(`NowBuildError = class extends Error { … }`) now hoists to a declaration
like the classes over builtin module members, so a program class can
extend it and `new` and `instanceof` work against both. Reading a standard
global constructor the bundle never rebinds runs no code. References in
esbuild's `0 && (module.exports = { … })` annotation for Node's CommonJS
lexer never run and no longer block the move.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A class a bundled ES module's __esm initializer stores into a module
binding (`Multi = class extends import_stream.Readable { … }`) becomes a
class declaration at the same text position: the initializer's body splits
around it into run-once pieces, so a class over a runtime-provided base no
longer has to exist as a stored class value. Only definitions whose
evaluation runs no code (no static fields, static blocks or computed names;
heritage absent, a builtin module member, or a hoisted class), declared
once, assigned only there and read only inside functions after the
assignment move.
npm selection now also scans the program modules a literal import()
reaches, with their static graphs: a workspace package loaded on demand
had its npm dependencies refused as "not part of the compiled module
graph".
`new import_x.C()` respelled `new require("x").C()`, which constructs the
require call; the interop binding now becomes `(require("x"))` there.
util.inherits onto a runtime-provided class (stream.Readable, the from2
and multistream shape) fences where it is written instead of failing in
Node's argument validation at run time.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two fixes for the CommonJS cycle check and the bundled-chunk splitter:
- During a cycle's initialization, a read of a partner's exported
function is safe only where the value neither runs nor stays: an
expression statement, typeof, void, !, an equality operand, or the test
of if, while, do and ?:. A const alias (`const f = binding.fn`), an
object or array literal, a return, or an argument can be called before
the cycle settles, so these keep SC1016 (Node throws when the function
reads a binding in its temporal dead zone). A top-level const that only
publishes the function in an export statement, as the static rewrite
spells an export-table re-export, stays a read.
- A copied helper whose initializer is a named function expression
reaches itself through its own name. Each chunk gets its own copy, so
that name must also be used only as a callee; otherwise the chunk keeps
the package refused with the existing reason.
isarray, safe-buffer, process-nextick-args, inherits and util-deprecate
evaluate to a Node builtin on Node 24 at their checked major versions, so
a require of one compiles as that builtin expression at the require's
position: from an opted-in package's files (the installed version where
Node's resolution finds it), from the program's own CommonJS files, and
through an esbuild __commonJS chunk whose pnpm path key names the package
and its version. The package's files never join the program.
readable-stream 2.x and 4.x compile as node:stream, the package's own
READABLE_STREAM=disable behavior (a documented divergence). A use of the
entry's export picks the builtin module (member reads of the carried
classes) or stream.Readable (value uses); a mixture keeps the package.
3.x stays as is: its disable branch throws on Node 24.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A CommonJS module whose export object is one of its own top-level
functions (semver 5/6's `exports = module.exports = SemVer`) reaches
its members through that function after the statement: member reads
and writes through module.exports (and, after the rebinding, every
`exports` reference) become members of the function, the expando form
both resolvers model, and the rebinding itself drops. Constructor
functions exported whole with members attached afterwards no longer
lose those members at runtime.
Replacing module.exports or rebinding exports inside a branch (an
environment-dependent export object, readable-stream 2.x) now names the
construct instead of the generic assignment fence, and a required
builtin module object is truthy in the LLVM backend.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A call through a bundled ES module's namespace (`ns.fn()`, `(ns.fn)()`,
a tagged template) passes the namespace as `this`. Node's namespace is
the __toCommonJS object (`__esModule` first and non-enumerable,
non-configurable getters); the lowered object of getters differs. The
namespace lowers only when every such call targets a top-level function,
never reassigned, that reads no `this` (an arrow inside it included);
otherwise the package stays refused.
Three fixes for the bundled-chunk splitter and the CommonJS cycle check:
- A reference counts as local only when a scope between it and the chunk
factory (or the copied helper) declares its name. A nested function,
block, loop or catch binding with the name of a top-level helper no
longer hides the chunk's read of that helper. Before, the helper was not
copied and the chunk failed at runtime on the unresolved reference.
- A top-level function is copied into a chunk only when the bundle calls
it and uses it in no other way. Each chunk gets its own copy, so a
function that is exported, compared, constructed, or given properties
would lose its shared identity. Such a chunk now keeps the package
refused.
- During a cycle's initialization, a read of a partner's exported
function is safe only as a plain value read. A call through
esbuild's `(0, binding.fn)()` spelling, `new`, a tagged template, a
member of the function, or passing it to a call can run the function,
so these uses keep SC1016 (Node throws when the function reads a binding
in its temporal dead zone).
A loader `L = () => M ??= NS` over a lazily initialized module's
namespace (optionally through __toESM) is respelled at every `L().name`
as the module's initializer call followed by the export's live read;
loaders whose result escapes as a value stay refused. The canonical
export table keeps a getter it cannot chase as a live accessor, so a
lazy re-export still initializes its module at the first read. An
ES-module importer copies those accessors once when it links the
CommonJS module, in the lexer's order, exactly like Node's loader;
require() keeps reading them live.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An __esm initializer becomes a run-once closure over its body in the
bundle's own scope. Interop bindings it assigns once from require()
become that require at the assignment, with their member reads respelled
as reads of the required module; a namespace read only through members
becomes an object literal of getters over the module's live bindings.
Asynchronous initializers, initializer results, and namespaces kept as
values (a memoized lazy loader) stay refused with a message naming them.
The default selection now also compiles a package typed by a
DefinitelyTyped twin that describes its installed release (same major,
or major.minor below 1.0), and untyped dependencies of compiled
packages. A twin for another release is refused with both versions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Interop the rewrite cannot finish now refuses its package only when the
compiled program can load that file: the module order closed over every
literal require() and import() and, in package JavaScript, every import
and export declaration (a computed specifier keeps every file loadable).
A package reached through a leaf subpath and types from its main entry
stays static. Auto mode also no longer refuses a whitespace/syntax
minified bundle whose interop helpers keep their names: the recognizers
judge it instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Unit tests run each split bundle against its original under Node and
pin the refusal reasons; npm-static tests compile the new cases and
compare them with Node byte for byte.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Real esbuild output (0.25 plain and whitespace/syntax minified, 0.17)
of one bundle with lazily and eagerly required chunks, a chunk that is
itself an esbuild dist, an external through __toESM, a .cjs bundle in a
type-module package, and per-file esbuild CommonJS cycles entered from
either side. Split chunks are require-only modules: their whole export
table is their surface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>