Repository navigation
node:util: implement remaining top-level helper APIs #2514
Description
Summary
Perry has solid coverage for core node:util helpers such as format, formatWithOptions, inspect, promisify, callbackify, deprecate, parseArgs, isDeepStrictEqual, and stripVTControlCharacters, but several public top-level node:util helpers are still missing. These are separate from the MIMEType / MIMEParams class gap tracked in #2511.
Missing surface in this issue
util.debuglog(section[, callback])util.debug(section)util.getSystemErrorName(err)util.getSystemErrorMap()util.getSystemErrorMessage(err)util.parseEnv(content)util.styleText(format, text[, options])util.toUSVString(string)util.transferableAbortController()util.transferableAbortSignal(signal)util.aborted(signal, resource)util.convertProcessSignalToExitCode(signal)util.diff(actual, expected)util.setTraceSigInt(enable)
Evidence
docs/runtime-parity-gaps.mdlists the helpers above as missing in thenode:utilsection (docs/runtime-parity-gaps.md:694-710).test-files/test_parity_util.tsprobes these exact APIs by type or simple deterministic calls (test-files/test_parity_util.ts:13,test-files/test_parity_util.ts:25-27,test-files/test_parity_util.ts:46-50,test-files/test_parity_util.ts:60-70).- Current manifest/runtime dispatch only covers the smaller landed set:
inspect,format,formatWithOptions,promisify,callbackify,deprecate,inherits,isDeepStrictEqual,parseArgs, andstripVTControlCharacters(crates/perry-api-manifest/src/entries.rs:2371-2389;crates/perry-runtime/src/object/native_module_dispatch.rs:900-918). - Source search for the helper names above found no manifest/runtime dispatch entries outside the parity docs/probe.
Notes
This intentionally excludes APIs that appear stale in the generated gap table but are already implemented (formatWithOptions, stripVTControlCharacters) and excludes the MIME classes from #2511. Some newer APIs such as diff, setTraceSigInt, and convertProcessSignalToExitCode may be feature-detect-only initially, but the deterministic helpers (getSystemError*, parseEnv, toUSVString, debuglog) should have real Node-compatible behavior.
Duplicate check
Searched issues and PRs for node:util debuglog formatWithOptions getSystemError parseEnv styleText toUSVString, util getSystemErrorName getSystemErrorMap getSystemErrorMessage parseEnv, and deprecated util helper terms. No existing tracker or PR was found for this top-level helper cluster.
Verification note
I could not run a fresh local Perry parity probe in this workspace because there is no local perry binary. This issue is based on committed parity metadata and current source inspection.
Activity
proggeramlug commentedon May 29, 2026
Scoped plan for the
getSystemError*trio (a tractable, deterministic subset of this 14-fn issue), captured for a running start:Node ref (darwin, v25):
getSystemErrorName(-2)→"ENOENT";getSystemErrorMessage(-2)→"no such file or directory";getSystemErrorMap()→Mapof 85 entries, key = negative errno, value =[name, message](e.g.-2 → ["ENOENT","no such file or directory"]). Unknown errno →getSystemErrorNamereturns"Unknown system error N".Key parity insight: messages are libc
strerroron the host platform = Node's darwin messages, sostd::io::Error::from_raw_os_error(n).to_string()matches Node byte-for-byte on the same machine the parity test runs on. No hardcoded message strings needed.Tractable now —
getSystemErrorName+getSystemErrorMessage(single lookup, low conflict surface):crates/perry-runtime/src/util_syserr.rs(avoid the hotobject/*files):js_util_get_system_error_name(f64)->f64,js_util_get_system_error_message(f64)->f64. Take the (usually negative) errno,let e = (-n) as i32(Node passes negative), name via amatchonlibc::E*consts → &str (elseformat!("Unknown system error {n}")), message viastd::io::Error::from_raw_os_error(e).to_string().method("util","getSystemErrorName"/"getSystemErrorMessage")in manifest + native-table rows + dispatch arms (the hot files — rebase-prone under churn, push fast).Harder —
getSystemErrorMap(defer/separate): returns a runtimeMap<number,[name,message]>of the exact 85-entry uv errno set. Needs Map marshalling + the precise errno list (uv's set, not all of libc's). Build by iterating the samelibc::E*consts used by Name/Message and inserting[name, message]under the negative key; verify.size===85+ spot-check entries vs node.Verify each fn byte-for-byte vs
node --experimental-strip-types(the*.cjsref above). util is not ext-linked, so it builds/verifies in-tree.proggeramlug commentedon May 29, 2026
Correction to the getSystemError* plan above — it's harder than estimated:
Node's messages + name set come from libuv's
UV_ERRNO_MAP, NOT libc. Verified vs node (darwin):getSystemErrorMessage(-17)→"file already exists"(uv), but libc strerror is"File exists"— different wording, not just case.-21→"illegal operation on a directory"(uv) vs libc"Is a directory".-5→"i/o error"(uv) vs libc"Input/output error".-12→"not enough memory"(uv) vs libc"Cannot allocate memory".-36(darwin EINPROGRESS) →"Unknown system error -36"— uv doesn't map it, even though libc has errno 36. So a libc-derived table also breaksgetSystemErrorName(-36)(would wrongly return"EINPROGRESS").So
std::io::Error::from_raw_os_error/libc::strerror/ a libcE*table all diverge. The correct implementation hardcodes libuv'sUV_ERRNO_MAP—(errno_value, "ENAME", "uv message")for the ~70 uv-mapped codes — and uses it for all three functions (getSystemErrorName,getSystemErrorMessage, and the 85-entrygetSystemErrorMap). Unmapped errno →"Unknown system error -N".getSystemErrorNamematched node for the common uv-mapped codes (-2/-13/-21/-9) in a libc-table prototype, but fails on uv-unmapped ones — so don't ship the libc shortcut.Source of truth: libuv
include/uv.hUV_ERRNO_MAP(name) +src/uv-common.cuv_strerror(message). Build the table, then probe a wide errno range vsnodebyte-for-byte and fix mismatches. util is a runtime module (verifiable in-tree); put it incrates/perry-runtime/src/util_syserr.rs(low conflict surface).feat(node:util): getSystemErrorName/Message/Map (#2514)
feat(node:util): getSystemErrorName/Message/Map (#2514) (#2714)
feat(node:util): implement parseEnv (#2514) (#2739)
fix(util): implement util.toUSVString (#2514)
fix(util): implement util.toUSVString (#2514)
fix(util): implement util.toUSVString (#2514)
fix(util): implement util.toUSVString (#2514) (#3421)
andrewtdiz commentedon May 30, 2026
Current leaf split from this broad util tracker after the latest pass:
util.parseEnvdotenv parserutil.styleTextANSI formatting helperutil.abortedAbortSignal promise helperutil.debugloglogger factory andutil.debugaliasutil.convertProcessSignalToExitCodeprocess signal mappingutil.diffedit tuple helperutil.getCallSitesstack frame helperutil.toUSVStringhas since landed, so this tracker should not spawn another issue for that helper unless a fresh behavioral gap is found.util.setTraceSigIntremains broad-tracker scope for now because it needs signal-process harness coverage rather than the focused value-output probes used for the leaves above.fix(util): mirror toUSVString into deprecated sys alias (#2514) (#3436)
proggeramlug commentedon May 30, 2026
util.toUSVStringfully landed: implementation #3421 plus sys-alias mirror #3436 (which restored the sys_alias_mirrors_util_manifest test). Byte-for-byte identical to node, including lone-surrogate → single U+FFFD and non-string coercion. Remaining tracker helpers: styleText, transferableAbortController/Signal, aborted, diff, setTraceSigInt, convertProcessSignalToExitCode, debug.fix(util): implement util.styleText (#2514)
fix(util): implement util.styleText (#2514)
fix(util): implement util.styleText (#2514)
6 remaining items
andrewtdiz commentedon May 31, 2026
Fresh local evidence from the Node API compatibility sweep. Current
origin/mainis still build-red at21246c8c9(#3864), so this was verified on the previous buildable main:The current curated util suite is mostly green, so this issue's original missing-helper list is stale in a good way. A focused remaining util diff is
debugloglogger shape:Report:
Diff:
The same sweep showed
util/get-call-sites/basicpasses when rerun sequentially, so the earlier parallel-run mismatch was harness contamination rather than a util gap.andrewtdiz commentedon May 31, 2026
Fresh current-main update:
The curated
node:utilsuite is now almost entirely green:perl -e 'alarm shift; exec @ARGV' 180 ./run_parity_tests.sh --suite node-suite --module utilThe one current suite failure is
util.debugloglogger shape:So this broad tracker should no longer treat
format,formatWithOptions,inspect,promisify,callbackify,deprecate,parseArgs,parseEnv,styleText,stripVTControlCharacters,getCallSites,getSystemError*,diff, Abort helpers, util/types predicates, MIME classes, etc. as unproven gaps for the current curated surface; they all passed in this run.One separate util helper still needs its dedicated issue:
util.inheritsremains broken in #2673. A temporary current-main probe shows it is callable-shaped but does not wire the prototype chain like Node.andrewtdiz commentedon May 31, 2026
Progress on the util tracker: PR #3892 implements the native util.inherits path and fixes the util.debuglog logger name descriptor without closing this broader tracker. #3892
fix(util): implement native inherits (#3892)
andrewtdiz commentedon Jun 3, 2026
Claiming a focused
node:utiltext-decoding slice from this tracker.Scope: fix the current
util.stripVTControlCharacters()non-ASCII parity failure by decoding JS string values through the shared SSO/UTF-8-aware string helper before stripping VT sequences. I will leave the remainingstyleText,TextEncoder.encodeInto, andutil/typesfailures from today’s full util sweep for separate cuts.andrewtdiz commentedon Jun 3, 2026
Opened draft PR #4189 for the focused
stripVTControlCharacters()Unicode slice: #4189What changed:
Validation:
util --filter strip-vt-control-characters/non-ascii: 1 pass, 0 fail (parity_report_20260603_011944.json)util --filter strip-vt-control-characters: 3 pass, 0 fail (parity_report_20260603_012310.json)buffer --filter from/string-encodings: 1 pass, 0 fail (parity_report_20260603_012332.json)cargo test -p perry-parser normalize_cargo check -p perry-parser -p perry-runtime -p perrycargo build --release -p perrycargo fmt --all -- --check,git diff --check,./scripts/check_file_size.shThis is intentionally scoped and does not close the full
node:utilissue. The remaining util failures I observed before this slice werestyle-text/basic,text-encoder-encode-into-validation,text-encoder-encode-into, andtypes/predicate-tail.andrewtdiz commentedon Jun 3, 2026
Picking up the next focused #2514 slice:
TextEncoder.encodeInto()parity for the util fixtures. I’ll keep this separate from PR #4189.andrewtdiz commentedon Jun 3, 2026
Follow-up on the encodeInto slice: after checking the branch for PR #4189, both focused fixtures now pass there:
node-suite/util/text-encoder-encode-into-validationnode-suite/util/text-encoder-encode-intoSo I am not opening a duplicate encodeInto PR; those failures are covered by the Unicode literal preservation work in #4189. I am moving to the next remaining #2514 util slice.
andrewtdiz commentedon Jun 3, 2026
Picking up the next focused #2514 slice:
node-suite/util/style-text/basic, specifically Node-compatibleutil.styleText()aliases and formatting behavior. This is separate from the Unicode literal PR #4189.andrewtdiz commentedon Jun 3, 2026
Opened PR #4192 for the
util.styleText()slice:styleText()while keeping the canonical inspect color table separate.node-suite/util/style-text/basic.cargo fmt --all -- --check,git diff --check,./scripts/check_file_size.sh, andcargo check -p perry-runtime -p perry.Continuing with the remaining #2514
util.typespredicate-tail failure next.andrewtdiz commentedon Jun 3, 2026
Opened PR #4193 for the remaining
util.typespredicate-tail slice:util.types.isArgumentsObject()for Perry's object-backed ECMAScript Arguments objects while preserving the array-backed synthetic arguments fallback.node-suite/util/types/predicate-tail.cargo fmt --all -- --check,git diff --check,./scripts/check_file_size.sh, andcargo check -p perry-runtime -p perry.With #4189, #4192, and #4193, the known focused #2514 util failures from this pass are covered by open PRs.
proggeramlug commentedon Jun 3, 2026
Verified resolved against current main (built fresh from origin/main, 207 commits ahead of an older checkout) and compared byte-for-byte to node --experimental-strip-types. The enumerated node:util helpers are present and correct: getSystemErrorName(-2) is ENOENT, getSystemErrorMessage, getSystemErrorMap() returns a Map containing -2, parseEnv works, toUSVString, styleText, convertProcessSignalToExitCode(SIGINT) is 130, and debuglog returns a function. Closing as resolved.
test(parity): retire the 28 stale known_failures entries the ratchet …
test(parity): retire the 28 stale known_failures entries the ratchet …