Skip to content

node:util: implement remaining top-level helper APIs #2514

Closed

Description

@andrewtdiz

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.md lists the helpers above as missing in the node:util section (docs/runtime-parity-gaps.md:694-710).
  • test-files/test_parity_util.ts probes 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, and stripVTControlCharacters (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

  1. proggeramlug commented on May 29, 2026

    @proggeramlug
    Contributor

    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()→Map of 85 entries, key = negative errno, value = [name, message] (e.g. -2 → ["ENOENT","no such file or directory"]). Unknown errno → getSystemErrorName returns "Unknown system error N".

    Key parity insight: messages are libc strerror on the host platform = Node's darwin messages, so std::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):

    • runtime fns in a new crates/perry-runtime/src/util_syserr.rs (avoid the hot object/* 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 a match on libc::E* consts → &str (else format!("Unknown system error {n}")), message via std::io::Error::from_raw_os_error(e).to_string().
    • wire: 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 runtime Map<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 same libc::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 *.cjs ref above). util is not ext-linked, so it builds/verifies in-tree.

  2. proggeramlug commented on May 29, 2026

    @proggeramlug
    Contributor

    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 breaks getSystemErrorName(-36) (would wrongly return "EINPROGRESS").

    So std::io::Error::from_raw_os_error / libc::strerror / a libc E* table all diverge. The correct implementation hardcodes libuv's UV_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-entry getSystemErrorMap). Unmapped errno → "Unknown system error -N". getSystemErrorName matched 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.h UV_ERRNO_MAP (name) + src/uv-common.c uv_strerror (message). Build the table, then probe a wide errno range vs node byte-for-byte and fix mismatches. util is a runtime module (verifiable in-tree); put it in crates/perry-runtime/src/util_syserr.rs (low conflict surface).

  3. andrewtdiz commented on May 30, 2026

    @andrewtdiz
    ContributorAuthor

    Current leaf split from this broad util tracker after the latest pass:

    util.toUSVString has since landed, so this tracker should not spawn another issue for that helper unless a fresh behavioral gap is found. util.setTraceSigInt remains broad-tracker scope for now because it needs signal-process harness coverage rather than the focused value-output probes used for the leaves above.

  4. proggeramlug commented on May 30, 2026

    @proggeramlug
    Contributor

    util.toUSVString fully 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.

  5. 6 remaining items

  6. andrewtdiz commented on May 31, 2026

    @andrewtdiz
    ContributorAuthor

    Fresh local evidence from the Node API compatibility sweep. Current origin/main is still build-red at 21246c8c9 (#3864), so this was verified on the previous buildable main:

    aed3a2b1b985 chore: complete stream tasklist reconciliation (#3790)
    

    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 debuglog logger shape:

    ./run_parity_tests.sh --suite node-suite --module util --filter util/debuglog/basic

    Report:

    /private/tmp/perry-node-compat-main/test-parity/reports/parity_report_20260531_132536.json
    

    Diff:

    Node:  numeric logger: function false enabled
    Perry: numeric logger: function false enabled,name
    
    Node:  logger keys: enabled
    Perry: logger keys: enabled,name
    

    The same sweep showed util/get-call-sites/basic passes when rerun sequentially, so the earlier parallel-run mismatch was harness contamination rather than a util gap.

  7. andrewtdiz commented on May 31, 2026

    @andrewtdiz
    ContributorAuthor

    Fresh current-main update:

    4e510532d hotfix: actually remove native_module.rs punycode/timers conflict marker (#3883)
    

    The curated node:util suite is now almost entirely green:

    perl -e 'alarm shift; exec @ARGV' 180 ./run_parity_tests.sh --suite node-suite --module util
    Parity Pass:   83
    Parity Fail:   1
    Compile Fail:  0
    Report: /private/tmp/perry-node-compat-main/test-parity/reports/parity_report_20260531_142519.json
    

    The one current suite failure is util.debuglog logger shape:

    Node:  numeric logger: function false enabled
    Perry: numeric logger: function false enabled,name
    
    Node:  logger keys: enabled
    Perry: logger keys: enabled,name
    

    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.inherits remains broken in #2673. A temporary current-main probe shows it is callable-shaped but does not wire the prototype chain like Node.

  8. andrewtdiz commented on May 31, 2026

    @andrewtdiz
    ContributorAuthor

    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

  9. andrewtdiz commented on Jun 3, 2026

    @andrewtdiz
    ContributorAuthor

    Claiming a focused node:util text-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 remaining styleText, TextEncoder.encodeInto, and util/types failures from today’s full util sweep for separate cuts.

  10. andrewtdiz commented on Jun 3, 2026

    @andrewtdiz
    ContributorAuthor

    Opened draft PR #4189 for the focused stripVTControlCharacters() Unicode slice: #4189

    What changed:

    • Preserve UTF-8 characters while the parser normalizes Unicode identifier escapes outside string/comment text.
    • Strip VT controls over JS string bytes so multibyte payload text is copied through unchanged.

    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 perry
    • cargo build --release -p perry
    • cargo fmt --all -- --check, git diff --check, ./scripts/check_file_size.sh

    This is intentionally scoped and does not close the full node:util issue. The remaining util failures I observed before this slice were style-text/basic, text-encoder-encode-into-validation, text-encoder-encode-into, and types/predicate-tail.

  11. andrewtdiz commented on Jun 3, 2026

    @andrewtdiz
    ContributorAuthor

    Picking up the next focused #2514 slice: TextEncoder.encodeInto() parity for the util fixtures. I’ll keep this separate from PR #4189.

  12. andrewtdiz commented on Jun 3, 2026

    @andrewtdiz
    ContributorAuthor

    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-validation
    • node-suite/util/text-encoder-encode-into

    So 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.

  13. andrewtdiz commented on Jun 3, 2026

    @andrewtdiz
    ContributorAuthor

    Picking up the next focused #2514 slice: node-suite/util/style-text/basic, specifically Node-compatible util.styleText() aliases and formatting behavior. This is separate from the Unicode literal PR #4189.

  14. andrewtdiz commented on Jun 3, 2026

    @andrewtdiz
    ContributorAuthor

    Opened PR #4192 for the util.styleText() slice:

    • [codex] fix util styleText aliases #4192
    • Adds Node 25 legacy alias handling for styleText() while keeping the canonical inspect color table separate.
    • Focused parity now passes: node-suite/util/style-text/basic.
    • Validation also passed: cargo fmt --all -- --check, git diff --check, ./scripts/check_file_size.sh, and cargo check -p perry-runtime -p perry.

    Continuing with the remaining #2514 util.types predicate-tail failure next.

  15. andrewtdiz commented on Jun 3, 2026

    @andrewtdiz
    ContributorAuthor

    Opened PR #4193 for the remaining util.types predicate-tail slice:

    • [codex] fix util arguments object predicate #4193
    • Fixes util.types.isArgumentsObject() for Perry's object-backed ECMAScript Arguments objects while preserving the array-backed synthetic arguments fallback.
    • Focused parity now passes: node-suite/util/types/predicate-tail.
    • Validation also passed: cargo fmt --all -- --check, git diff --check, ./scripts/check_file_size.sh, and cargo 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.

  16. proggeramlug commented on Jun 3, 2026

    @proggeramlug
    Contributor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions