node:module: list node:test in builtinModules - #39700
Conversation
Node lists the modules that only resolve with the node: prefix under
that prefixed name in module.builtinModules. Bun already does this for
node:sqlite. node:test was missing from the list although
isBuiltin("node:test") is true and require("node:test") works.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (4)
Included review availability: Your plan provides up to 5 included reviews per hour; 0 remain after this review. WalkthroughChangesBuiltin module support
Possibly related PRs
Suggested reviewers: Merge Risk: 🔵 Low · up to The PR adds coverage for builtin-module consistency, but its CI workflow may run that lint under the wrong build configuration. The change is otherwise localized and mergeable with owner awareness or a follow-up to use the required debug-build test command. 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
Comment |
|
Updated 10:00 PM PT - Aug 19th, 2026
✅ @robobun, your commit f06a92c3f44de5ce9ed8afb4433407c6dc331071 passed in 🧪 To try this PR locally: bunx bun-pr 39700That installs a local version of the PR into your bun-39700 --bun |
StatusReproduced on main ( With this branch the debug build prints Fix: this PR (#39700). #33430 was an earlier attempt with a single table. It is closed. |
There was a problem hiding this comment.
LGTM — one string literal added to a static table, following the existing node:sqlite pattern.
What was reviewed:
- Counted the table: 77 entries after the addition, matching the updated
toHaveLength(77). isBuiltinModule.cppalready listsnode:test, so the newisBuiltin/process.getBuiltinModuleassertions hold.- Only other consumer of
node:-prefixed entries is the REPL'sgetBuiltinLibs(), which already filters them out (src/js/internal/repl/node-shims.js:184). - New tests assert the exact prefixed set and that bare
"test"stays out; they fail on main per the PR notes.
Extended reasoning...
Overview
Adds "node:test"_s to the static builtinModuleNames array in src/jsc/modules/NodeModuleModule.cpp, alongside the existing "node:sqlite"_s entry, plus a three-line comment explaining the prefix-only convention. Bumps the length assertion in node-module-module.test.js from 76 to 77 and adds two focused tests: one enumerating the node:-prefixed subset and asserting each is prefix-only and requireable, and one in node-test.test.ts asserting node:test is a builtin.
Security risks
None. The change is a compile-time string literal in a constexpr array; no user input, no allocation, no new code paths, no exception scopes touched.
Level of scrutiny
Low. This is a one-line data change to a lookup table that already contains an entry of the exact same shape (node:sqlite, added in #32498). The array is consumed only by getBuiltinModulesObject via countof(), so no hardcoded size to update elsewhere. I verified the entry count (77) by hand, confirmed isBuiltinModule.cpp:48 already recognizes node:test (so the new test's isBuiltin and process.getBuiltinModule assertions are consistent), and confirmed the REPL consumer at src/js/internal/repl/node-shims.js:184 filters node:-prefixed names so completion is unaffected.
Other factors
The PR description is unusually thorough — it enumerates the set difference against Node 26.3.0, justifies leaving out node:quic (not in stock Node builds), bun:wrap/bun:main (deliberately removed in #31831), and defers freezing the array as an independent change. Tests are placed in the correct existing files, use exact-value assertions (toEqual(["node:sqlite", "node:test"]) rather than toContain), and cover the negative contract (bare "test" absent, isBuiltin("test") false). No outstanding reviewer comments; no prior reviews from me.
|
Thanks, the review asks for no changes. @coderabbitai review (The first attempt hit the review rate limit before it started.) |
|
✅ Action performedReview finished.
|
module.builtinModules and module.isBuiltin() read two hand-written tables, and the module loader has a third marker for the builtins that resolve with the node: prefix only. A new module tends to reach the last two and not the first. The new source lint requires every name isBuiltin() accepts to be listed in builtinModules, with an allowlist that names the reasons for the deliberate exceptions. The runtime test now checks the array's properties instead of its length, and the copy of those assertions in node-test.test.ts is gone.
|
Update after a self-review of the first version. The head is now f06a92c.
The PR description describes the current state. |
|
@coderabbitai review The three commits after 8baf99b (the source lint, the test rework and the comment change) have not been reviewed yet. The earlier attempts hit the review rate limit. |
|
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
LGTM — one-line data addition to a static table, matching Node's behavior for prefix-only builtins.
What was reviewed:
- Verified
node:testis already inisBuiltinModule.cppandnode_entry_only_prefix!(), so this only closes the gap inbuiltinModules. - Checked the new source-lint's
notListedentries (bun:main,bun:wrap,node:quic) against both tables — all consistent; the regex parsers match the actual table declarations. - Confirmed the runtime test's new loop over
node:-prefixed entries only hitsnode:sqliteandnode:test, both requireable in-process; the removed.toHaveLength(76)is subsumed by the cross-table lint.
Extended reasoning...
Overview
The PR adds "node:test"_s to the builtinModuleNames[] table in src/jsc/modules/NodeModuleModule.cpp so that require("node:module").builtinModules includes it, matching Node 26. It also corrects the first sentence of the pre-existing comment above that table, adds a source-lint test that cross-checks the three hand-written builtin-name tables (builtinModuleNames, builtinModuleNamesSortedLength, node_entry_only_prefix!), wires the new file into the source-lints workflow path filter, and replaces the fragile .toHaveLength(76) runtime assertion with invariant-based checks.
Security risks
None. This is a metadata list of module names; adding an entry does not change what require() resolves — node:test was already resolvable and already accepted by isBuiltin(). The source-lint test only reads source files with regex.
Level of scrutiny
Low. The production change is a single string literal appended to a static constexpr ASCIILiteral[], following the exact precedent set by node:sqlite immediately above it. No control flow, no allocation, no exception paths are touched. The test and workflow changes are defensive tooling.
Other factors
- I verified the source-lint test's assumptions hold:
isBuiltinModule.cppcontainsbun:main,bun:wrap,node:quic,node:test,node:sqlite;HardcodedModule.rshasnode_entry_only_prefix!fornode:sqlite,node:test,node:quic. ThenotListedmap exactly covers the intended isBuiltin-only names, and the reverse check ensures the map cannot go stale. - The runtime test dropped the hardcoded length in favor of stronger invariants (every listed name passes
isBuiltin, prefix-only entries are not resolvable bare,process.getBuiltinModulematchesrequire). The lint test enforces completeness in the other direction, so no coverage is lost. - The comment-cop bot flagged the table comment three times; the author iterated down to a minimal one-sentence correction of the pre-existing comment, and all threads are resolved.
- The PR description references assertions in
node-test.test.tsthat are not in the final diff — presumably reverted in a later commit — but this does not affect the change's correctness or test coverage. - The PR author enumerated downstream consumers of
builtinModules(REPL,util.inspect,test-require-builtins,test-process-get-builtin) and explained why each is unaffected or improved.
|
Thanks. On the one observation: the PR description was rewritten for the current diff before this review ran and no longer mentions |
|
On the "wrong build configuration" note in the merge risk summary: the new test is a source lint. It reads three source files and never runs the bun binary, so the source-lints workflow runs it with a released bun on purpose. That is the documented setup for every test in |
Problem
require("node:module").builtinModuleslacks"node:test", althoughisBuiltin("node:test")is true andrequire("node:test")works. Node 26.3.0 lists"node:test".builtinModulesandisBuiltin()read two hand-written tables (src/jsc/modules/NodeModuleModule.cpp:51,src/jsc/bindings/isBuiltinModule.cpp:3). Nothing compared them. node:test:--testCLI mode, node:test/reporters, standalone execution, and reporter-output parity (+18 tests, test_runner 32%→55%) #34515 and js: add node:test/reporters module #23366 (node:test/reporters) update the second table only.Fix
"node:test"tobuiltinModuleNamesand fix the comment above it. Matches Node: a builtin that resolves only with thenode:prefix is listed under the prefixed name. Bun already does this fornode:sqlite(node:sqlite: implement the module and pass the Node v26.3.0 test suite #32498).test/internal/source-lints/builtin-module-tables.test.ts. Every name theisBuiltin()table accepts has to be inbuiltinModuleNames, unless an allowlist gives the reason:bun:mainandbun:wrapare internal (process: port Node.js v26.3.0 process compatibility tests and fix the gaps they surface (env exotic-object/TZ semantics, warnings pipeline + CLI flags, uncaught origin/exit codes, execve throw, threadCpuUsage/finalization/loadEnvFile, native-module identity; +26 tests) #31831), and stock Node 26 builds do not listnode:quic. It also checks the resolver's prefix-only entries against that table. The source-lints workflow now triggers onNodeModuleModule.cpptoo.test/js/node/module/node-module-module.test.jsfail on main and pass here. Other suites: see Notes.Background
module.builtinModulesis the public array of builtin names. Bundlers use it as the defaultexternallist. Bun's array also holds Bun's own modules and the packages Bun replaces, such asws.module.isBuiltin(name)has its own table. It also accepts internal names such asbun:wrap, whichBun.pluginmust refuse to override.node:xbut not asx. In Node 26:sea,sqlite,test,test/reporters. Bun hassqliteandtest(node_entry_only_prefix!,src/resolve_builtins/HardcodedModule.rs:472).test/internal/source-lints/) read the source tree and run on GitHub Actions without a build.Notes
bun -e 'const m=require("node:module"); console.log(m.isBuiltin("node:test"), m.builtinModules.includes("node:test"), typeof require("node:test"))'printstrue false function. Node printstrue true. With this change Bun printstrue true function.node:test#18140 (v1.2.6) listednode:test, require.resolve withpathsoption #18851 (v1.2.9) dropped it again and bumped the length assertion innode-module-module.test.jsfrom 77 to 76. That is why the runtime test no longer pins the length. It still asserts thatbun:wrapis not listed, which is what the length covered since process: port Node.js v26.3.0 process compatibility tests and fix the gaps they surface (env exotic-object/TZ semantics, warnings pipeline + CLI flags, uncaught origin/exit codes, execve throw, threadCpuUsage/finalization/loadEnvFile, native-module identity; +26 tests) #31831.test/js/bun/test/parallel/test-require-builtins.ts(it now also requiresnode:testin a subprocess),test/js/node/test/parallel/test-process-get-builtin.mjs(it now checksprocess.getBuiltinModule("node:test") === require("node:test")through the array),test-require-resolve.js,test-module-builtin.js,test/js/bun/resolve/builtin-esm-lazy-exports.test.ts,test/js/node/sqlite/node-sqlite.test.ts, the builtin-related tests intest/js/bun/repl/repl.test.ts, and all oftest/internal/source-lints/.--testCLI mode, node:test/reporters, standalone execution, and reporter-output parity (+18 tests, test_runner 32%→55%) #34515 (addnode:test/reporterstoisBuiltinModule.cppandHardcodedModule.rsonly): it fails with["node:test/reporters"]. Node 26.3.0 listsnode:test/reporters, so that is the wanted outcome. The lint also fails when an allowlist entry goes stale.node:sea,node:testandnode:test/reporters, Bun lists none of them.requireofnode:seaandnode:test/reportersthrowsERR_UNKNOWN_BUILTIN_MODULEin Bun. After this change the only entries Bun lacks are modules Bun does not have.node:quic: Bun resolves it without a flag and prints an ExperimentalWarning. The stock node 26.3.0 binary does not list it, also not with--experimental-quic, because QUIC is compiled out. To list it is a separate decision. The allowlist entry records the current one.stream/iterand third-party aliases), and the separate table behindprocess.binding("natives")insrc/jsc/bindings/ProcessBindingNatives.cpp. A table derived from the resolver'sAliasentries would remove the copies. That is a larger change than this fix.node:testentry by merging the two tables into one. That would listbun:wrapandbun:mainagain, and the branch conflicted with main, so I closed it. The freeze ofbuiltinModulesthat it also did is independent of this bug and is not part of this change.getBuiltinLibs()already filters outnode:names (src/js/internal/repl/node-shims.js:183).util.inspectonly looks up bare names taken from stack frames.no test proof · iteration 0 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/js/node/module/node-module-module.test.js