Commit Graph
74 Commits
Author SHA1 Message Date
Chris Tate 24eb054395 Compile the hot-shots StatsD client over UDP (#1568) 2026-10-20 21:29:48 -05:00
Chris Tate d59d8ff6f2 Merge main and count declaration writes to parameters 2026-10-20 21:05:19 -05:00
Chris Tate eb8a148605 Merge main 2026-10-20 21:01:40 -05:00
Chris Tate b04166a8f0 Read runtime keys of package enum objects as checked-dynamic values
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.
2026-10-20 20:04:06 -05:00
Chris Tate f3da708774 Compile polyfill packages that are Node builtins as the builtins (#1458) 2026-10-20 18:28:42 -05:00
Malte UblandClaude Opus 5.5 2f50141c0c Compile the hot-shots StatsD client over UDP
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>
2026-10-20 15:56:59 -07:00
Malte UblandClaude Opus 5.5 fec779abbb Use the events module and EventEmitter as untyped constructor values
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>
2026-10-20 12:41:53 -07:00
Chris Tate 8c40f92019 Merge main 2026-10-20 11:00:03 -05:00
Malte UblandClaude Opus 5.5 f669c46f0d Carry runtime-decided dns.lookup answers dynamically, dial Node-typed net lookups, and keep tsc enum exports undefined until assigned
- 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>
2026-10-20 07:58:15 -07:00
Chris Tate cb99a06fe5 Shadow the export alias only in the scope that declares it again
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.
2026-10-20 09:35:14 -05:00
Chris Tate 90ff0af5b7 Merge main 2026-10-20 09:33:27 -05:00
Malte UblandClaude Opus 5.5 465abda816 Compile the remaining safe-fetch shapes: Promise<never> race entries, typed Array(n).fill, narrowed header entries, lookup option unions and CommonJS export aliases
- 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>
2026-10-20 06:36:48 -07:00
Malte UblandClaude Opus 5.5 bebc0946bc Point bundled copies of shimmed packages at the shim; guard every shim
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>
2026-10-20 01:21:06 -07:00
Malte UblandClaude Opus 5.5 5b53ce7144 Hoist lazily defined classes over global constructors
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>
2026-10-19 22:51:07 -07:00
Malte UblandClaude Opus 5.5 1a17bd3405 Merge branch 'opt/npm-polyfill-shims' into opt/npm-esm-class-hoist
Renumbers this branch's npm case 2572 to 2588, after the renumbered
polyfill cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:44:23 -07:00
Malte UblandClaude Opus 5.5 1b282f9c80 Merge branch 'opt/npm-cjs-exports' into opt/npm-polyfill-shims
Renumbers this branch's npm cases 2569-2571 to 2585-2587: #1411 now uses
2571.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:44:08 -07:00
Malte UblandClaude Opus 5.5 dde27c7e21 Merge branch 'opt/npm-lazy-loaders' into opt/npm-cjs-exports
Renumbers this branch's npm cases 2567-2568 to 2583-2584: #1422 now uses
2567.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:43:36 -07:00
Malte UblandClaude Opus 5.5 6fc85e848d Merge branch 'opt/npm-esm-wrappers' into opt/npm-lazy-loaders
Renumbers this branch's npm cases 2564-2566 to 2580-2582: #1411 now uses
2564-2566.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 21:42:59 -07:00
Malte UblandClaude Opus 5.5 3d8c7259a0 Hoist lazily initialized ES module classes and follow on-demand workspace imports
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>
2026-10-19 20:33:19 -07:00
Chris Tate 1af7a862ce Keep stored cycle functions and self-referencing helper copies refused
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.
2026-10-19 22:05:25 -05:00
Chris Tate d07188d250 Merge base 2026-10-19 22:00:59 -05:00
Malte UblandClaude Opus 5.5 f80e4c1a09 Compile polyfill packages that are Node builtins as the builtins
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>
2026-10-19 19:52:05 -07:00
Malte UblandClaude Opus 5.5 5670bc182e Respell members of a function export object; name conditional exports
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>
2026-10-19 18:21:52 -07:00
Chris Tate 4f7539103c Keep a lowered ES module namespace unobservable through calls that pass it as this
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.
2026-10-19 19:10:11 -05:00
Chris Tate c18a4a790a Resolve chunk references by scope, keep shared functions out of chunk copies, and treat indirect calls as calls in cycles
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).
2026-10-19 19:07:43 -05:00
Malte UblandClaude Opus 5.5 f7f239ad53 Lower memoized lazy loaders of bundled ES-module namespaces
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>
2026-10-19 15:55:37 -07:00
Malte UblandClaude Opus 5.5 f42fd146d9 Lower esbuild's lazy ES modules; select @types-typed packages by default
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>
2026-10-19 14:00:32 -07:00
Malte UblandClaude Opus 5.5 405f643742 Judge bundled packages by the files their imports can load
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>
2026-10-19 13:31:48 -07:00
Malte UblandClaude Opus 5.5 22edb6deb0 Test bundled chunks, cycles and refusals
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>
2026-10-19 12:20:28 -07:00
Malte UblandClaude Opus 5.5 17647d8bfd Add esbuild chunk and CommonJS cycle npm fixtures
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>
2026-10-19 12:17:13 -07:00
Chris Tate e7d5c67cdc Add Node 24 readable stream readers, controllers, and tee (#661) 2026-10-04 06:07:41 -05:00
Chris Tate 3aad3e8ebe Add Node 24 Web Stream pipelines and streaming encoding (#660) 2026-10-04 05:36:27 -05:00
Chris Tate 99613bce5a Add Node 24 writable Web Streams support (#659) 2026-10-04 04:54:43 -05:00
Chris Tate 095673351e Add Node 24 URL authority and protocol mutation support (#658) 2026-10-04 04:14:12 -05:00
Chris Tate 41b5eed650 Add Node 24 URL mutation support (#654)
* Add Node 24 URL mutation support

* Record preflight baselines for URL mutation fixtures
2026-10-04 01:57:10 -05:00
Chris Tate 4eb4e71d9b Expand Node 24 ESM metadata compatibility (#653)
* Expand Node 24 ESM metadata compatibility

* Fix ESM metadata lowering in native compiler bootstrap
2026-10-04 01:03:02 -05:00
Chris Tate 2dcd4b9e35 Add native util.types probes and reliable embedded brand checks (#652) 2026-10-03 23:49:39 -05:00
Chris Tate 44f7e7680e Add Node 24 util.styleText compatibility (#651) 2026-10-03 22:54:00 -05:00
Chris Tate ba79399aba Add Node 24 deep equality support (#650) 2026-10-03 21:56:40 -05:00
Chris Tate b7d5ea2722 Support Node 24 no-error assertions (#648) 2026-10-03 20:16:24 -05:00
Chris Tate eb83febcf2 Support Node 24 text utilities (#647) 2026-10-03 19:21:34 -05:00
Chris Tate b8344a6cb5 Support Node 24 system error utilities (#646)
* Support Node 24 system error utilities

* Wait for writable drain in the stream fixture
2026-10-03 18:34:51 -05:00
Chris Tate bc7c9f6bf6 Support Node 24 file URL buffers and platform options (#645)
* Support Node 24 file URL bytes and runtime platform options

* Record preflight baselines for file URL conversion fixtures
2026-10-03 17:33:04 -05:00
Chris Tate 7a0f38cbb3 Expand Node 24 diagnostics channel compatibility (#643) 2026-10-03 15:51:00 -05:00
Chris Tate 7a3794d8dd fix: emit TypeScript runtime package sources (#641) 2026-10-03 15:43:26 -05:00
Chris Tate 91ab7a68ce Expand Node 24 CommonJS module compatibility (#642) 2026-10-03 15:12:07 -05:00
Chris Tate 6aa1370507 feat: support Node 24 MIME utilities (#640)
* feat: support Node 24 MIME utilities in the dynamic runtime

* test: record frontend baselines for MIME fixtures
2026-10-03 14:23:41 -05:00
Chris Tate 64a66ddd46 Expand Node 24 promise timer compatibility (#637) 2026-10-03 13:00:14 -05:00
Chris Tate b76aa555f0 Expand Node 24 async event consumption compatibility (#636) 2026-10-03 12:18:14 -05:00
Chris Tate 724fa7591b Expand Node 24 EventEmitter listener lifecycle compatibility (#635) 2026-10-03 11:37:49 -05:00