mirror of
https://github.com/vercel-labs/scriptc.git
synced 2026-10-20 05:40:17 +08:00
Support Node 24 text utilities (#647)
This commit is contained in:
@@ -2,6 +2,6 @@
|
||||
"schemaVersion": 3,
|
||||
"nodeVersion": "24.15.0",
|
||||
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
|
||||
"artifactVersion": "b22c67d8306e45a80390",
|
||||
"artifactVersion": "5c443421620cc3431067",
|
||||
"rowCount": 3662
|
||||
}
|
||||
|
||||
@@ -1202,16 +1202,16 @@
|
||||
"apiEntries": 105,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 18,
|
||||
"partial": 20,
|
||||
"refused": 0,
|
||||
"not-implemented": 87,
|
||||
"not-implemented": 85,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
},
|
||||
"dynamic": {
|
||||
"supported": 16,
|
||||
"partial": 76,
|
||||
"supported": 18,
|
||||
"partial": 74,
|
||||
"refused": 0,
|
||||
"not-implemented": 13,
|
||||
"by-design": 0,
|
||||
@@ -85570,13 +85570,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Removes ANSI control sequences with the Node matcher and validates common non-string argument forms.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
"detail": "Available through the embedded Node compatibility shim; exact member and overload coverage may be narrower.",
|
||||
"status": "supported",
|
||||
"detail": "Removes ANSI control sequences with the Node matcher and validates non-string arguments.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
@@ -85626,13 +85626,13 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"detail": "No static compiler lowering is registered for this API yet.",
|
||||
"verification": "registry-gap"
|
||||
"status": "partial",
|
||||
"detail": "Converts native strings, primitives, and tested object conversion hooks to well-formed Unicode strings; native storage replaces unpaired surrogates at creation.",
|
||||
"verification": "test-backed"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
"detail": "Available through the embedded Node compatibility shim; exact member and overload coverage may be narrower.",
|
||||
"status": "supported",
|
||||
"detail": "Replaces unpaired surrogate code units and preserves Node string coercion, custom conversion hooks, and conversion errors.",
|
||||
"verification": "test-backed"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
"wasi": "by-design"
|
||||
},
|
||||
"features": [
|
||||
{ "chapter": "util", "symbols": ["util.stripVTControlCharacters", "util.toUSVString"], "status": "supported", "evidence": ["tests/fixtures/npm/cases/node24-text-utilities/main.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.getSystemErrorName", "util.getSystemErrorMessage", "util.getSystemErrorMap"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-system-errors/main.ts"] },
|
||||
{ "chapter": "url", "symbols": ["url.fileURLToPath", "url.fileURLToPathBuffer", "url.pathToFileURL"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-file-url-conversion/main.ts"] },
|
||||
{ "chapter": "diagnostics_channel", "symbols": ["start", "end", "asyncStart", "asyncEnd", "error", "TracingChannel", "channel.bindStore", "channel.unbindStore", "channel.runStores", "tracingChannel.subscribe", "tracingChannel.unsubscribe", "tracingChannel.traceSync", "tracingChannel.tracePromise", "tracingChannel.traceCallback"], "status": "partial", "evidence": ["tests/fixtures/npm/cases/node24-diagnostics-tracing/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-async/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-subscriber-throw/main.ts", "tests/fixtures/npm/cases/node24-diagnostics-transform-throw/main.ts"] },
|
||||
|
||||
@@ -4,24 +4,24 @@
|
||||
"nodeCommit": "848430679556aed0bd073f2bc263331ad84fa119",
|
||||
"summary": {
|
||||
"taskCount": 3368,
|
||||
"tierItemCount": 6647,
|
||||
"tierItemCount": 6645,
|
||||
"tiers": {
|
||||
"static": {
|
||||
"replace-refusal": 43,
|
||||
"verify-gap": 2512,
|
||||
"audit-partial": 657,
|
||||
"verify-gap": 2510,
|
||||
"audit-partial": 659,
|
||||
"classify": 2,
|
||||
"implement": 81
|
||||
},
|
||||
"dynamic": {
|
||||
"audit-partial": 848,
|
||||
"audit-partial": 846,
|
||||
"verify-gap": 2178,
|
||||
"replace-refusal": 112,
|
||||
"implement": 214
|
||||
}
|
||||
},
|
||||
"priorities": {
|
||||
"high": 4484,
|
||||
"high": 4482,
|
||||
"normal": 1411,
|
||||
"low": 752
|
||||
}
|
||||
@@ -95707,21 +95707,17 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:util",
|
||||
"tests": []
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "island-export:node:util.stripVTControlCharacters",
|
||||
"source": "compiler-feature:util.util.stripVTControlCharacters",
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/util-shims/main.ts"
|
||||
"tests/corpus/node24-text-utilities.ts",
|
||||
"tests/corpus/node24-text-utilities-validation.js",
|
||||
"tests/corpus/node24-text-utilities-coercion.js",
|
||||
"tests/corpus/node24-text-utilities.cjs",
|
||||
"tests/fixtures/node-types/text-utilities.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -95777,21 +95773,17 @@
|
||||
"priority": "high",
|
||||
"tiers": {
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"action": "verify-gap",
|
||||
"verification": "registry-gap",
|
||||
"confidence": "low",
|
||||
"source": "compiler-unmatched:util",
|
||||
"tests": []
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
"action": "audit-partial",
|
||||
"verification": "test-backed",
|
||||
"confidence": "high",
|
||||
"source": "island-export:node:util.toUSVString",
|
||||
"source": "compiler-feature:util.util.toUSVString",
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/util-shims/main.ts"
|
||||
"tests/corpus/node24-text-utilities.ts",
|
||||
"tests/corpus/node24-text-utilities-validation.js",
|
||||
"tests/corpus/node24-text-utilities-coercion.js",
|
||||
"tests/corpus/node24-text-utilities.cjs",
|
||||
"tests/fixtures/node-types/text-utilities.ts"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1323,16 +1323,16 @@
|
||||
"apiEntries": 105,
|
||||
"static": {
|
||||
"supported": 0,
|
||||
"partial": 18,
|
||||
"partial": 20,
|
||||
"refused": 0,
|
||||
"not-implemented": 87,
|
||||
"not-implemented": 85,
|
||||
"by-design": 0,
|
||||
"unreviewed": 0,
|
||||
"not-applicable": 0
|
||||
},
|
||||
"dynamic": {
|
||||
"supported": 16,
|
||||
"partial": 76,
|
||||
"supported": 18,
|
||||
"partial": 74,
|
||||
"refused": 0,
|
||||
"not-implemented": 13,
|
||||
"by-design": 0,
|
||||
@@ -32294,7 +32294,7 @@
|
||||
"nodeStability": null,
|
||||
"static": {
|
||||
"status": "not-applicable",
|
||||
"evidence": "compiler-chapter-policy:deprecations"
|
||||
"evidence": "compiler-feature:deprecations.util.toUSVString"
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "not-applicable",
|
||||
@@ -111273,11 +111273,16 @@
|
||||
"tests/corpus/node24-system-errors.ts",
|
||||
"tests/corpus/node24-text-codec-properties.ts",
|
||||
"tests/corpus/node24-text-encode-into.ts",
|
||||
"tests/corpus/node24-text-utilities-coercion.js",
|
||||
"tests/corpus/node24-text-utilities-validation.js",
|
||||
"tests/corpus/node24-text-utilities.cjs",
|
||||
"tests/corpus/node24-text-utilities.ts",
|
||||
"tests/corpus/text-codec-values/main.ts",
|
||||
"tests/fixtures/node-types/env-files.ts",
|
||||
"tests/fixtures/node-types/system-errors.ts",
|
||||
"tests/fixtures/node-types/text-codecs.mts",
|
||||
"tests/fixtures/node-types/text-encoding.ts",
|
||||
"tests/fixtures/node-types/text-utilities.ts",
|
||||
"tests/harness/project-config.test.ts"
|
||||
]
|
||||
},
|
||||
@@ -111291,6 +111296,7 @@
|
||||
"tests/fixtures/npm/cases/node24-parseargs/main.ts",
|
||||
"tests/fixtures/npm/cases/node24-system-errors/main.ts",
|
||||
"tests/fixtures/npm/cases/node24-text-encoding/main.ts",
|
||||
"tests/fixtures/npm/cases/node24-text-utilities/main.ts",
|
||||
"tests/fixtures/npm/cases/node24-util-adapters/main.ts",
|
||||
"tests/fixtures/npm/cases/util-shims/main.ts"
|
||||
]
|
||||
@@ -113252,14 +113258,21 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.util.stripVTControlCharacters",
|
||||
"tests": [
|
||||
"tests/corpus/node24-text-utilities.ts",
|
||||
"tests/corpus/node24-text-utilities-validation.js",
|
||||
"tests/corpus/node24-text-utilities-coercion.js",
|
||||
"tests/corpus/node24-text-utilities.cjs",
|
||||
"tests/fixtures/node-types/text-utilities.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
"evidence": "island-export:node:util.stripVTControlCharacters",
|
||||
"status": "supported",
|
||||
"evidence": "island-feature:util.util.stripVTControlCharacters",
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/util-shims/main.ts"
|
||||
"tests/fixtures/npm/cases/node24-text-utilities/main.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
@@ -113312,14 +113325,21 @@
|
||||
"inherited": true
|
||||
},
|
||||
"static": {
|
||||
"status": "not-implemented",
|
||||
"evidence": "compiler-unmatched:util"
|
||||
"status": "partial",
|
||||
"evidence": "compiler-feature:util.util.toUSVString",
|
||||
"tests": [
|
||||
"tests/corpus/node24-text-utilities.ts",
|
||||
"tests/corpus/node24-text-utilities-validation.js",
|
||||
"tests/corpus/node24-text-utilities-coercion.js",
|
||||
"tests/corpus/node24-text-utilities.cjs",
|
||||
"tests/fixtures/node-types/text-utilities.ts"
|
||||
]
|
||||
},
|
||||
"dynamic": {
|
||||
"status": "partial",
|
||||
"evidence": "island-export:node:util.toUSVString",
|
||||
"status": "supported",
|
||||
"evidence": "island-feature:util.util.toUSVString",
|
||||
"tests": [
|
||||
"tests/fixtures/npm/cases/util-shims/main.ts"
|
||||
"tests/fixtures/npm/cases/node24-text-utilities/main.ts"
|
||||
]
|
||||
},
|
||||
"anchorSource": "exact"
|
||||
|
||||
@@ -628,6 +628,12 @@ function publicDetail(tier) {
|
||||
if (source === "compiler-feature:util.util.getSystemErrorMap") {
|
||||
return "Returns a fresh Map of platform-specific error codes, names, and messages; entry tuples support updates but retain their fixed size.";
|
||||
}
|
||||
if (source === "compiler-feature:util.util.stripVTControlCharacters") {
|
||||
return "Removes ANSI control sequences with the Node matcher and validates common non-string argument forms.";
|
||||
}
|
||||
if (source === "compiler-feature:util.util.toUSVString") {
|
||||
return "Converts native strings, primitives, and tested object conversion hooks to well-formed Unicode strings; native storage replaces unpaired surrogates at creation.";
|
||||
}
|
||||
if (/^compiler-feature:util\.util\.getSystemError(?:Name|Message)$/.test(source)) {
|
||||
return "Converts platform-specific negative error codes to names or messages, with Node-compatible validation for common argument forms.";
|
||||
}
|
||||
@@ -690,6 +696,8 @@ function publicDetail(tier) {
|
||||
return "No dynamic-island implementation exists for this Node API family yet.";
|
||||
}
|
||||
if (source.startsWith("island-feature:")) {
|
||||
if (source === "island-feature:util.util.stripVTControlCharacters") return "Removes ANSI control sequences with the Node matcher and validates non-string arguments.";
|
||||
if (source === "island-feature:util.util.toUSVString") return "Replaces unpaired surrogate code units and preserves Node string coercion, custom conversion hooks, and conversion errors.";
|
||||
if (source.startsWith("island-feature:diagnostics_channel.")) return "Publishes synchronous, promise, and callback tracing lifecycle events with result/error identity, subscriber management, and store bindings. Bound stores follow the embedded engine's synchronous AsyncLocalStorage model and do not propagate across awaits.";
|
||||
if (source.startsWith("island-feature:timers.timersPromises.")) return "Provides cancellable promise timers, buffered interval iteration, scheduler waits and yields, and ref options over the native event loop; delay warnings remain outside this subset.";
|
||||
if (source.startsWith("island-feature:timers.")) return "Provides timer and immediate handles with reference control, refresh, cancellation, and disposal over the native event loop.";
|
||||
|
||||
@@ -102,6 +102,8 @@
|
||||
"stdlib.abort": ["tests/harness/fetch-conformance.test.ts"]
|
||||
},
|
||||
"features": [
|
||||
{ "chapter": "deprecations", "symbols": ["util.toUSVString"], "status": "not-applicable" },
|
||||
{ "chapter": "util", "symbols": ["util.stripVTControlCharacters", "util.toUSVString"], "status": "partial", "evidence": ["tests/corpus/node24-text-utilities.ts", "tests/corpus/node24-text-utilities-validation.js", "tests/corpus/node24-text-utilities-coercion.js", "tests/corpus/node24-text-utilities.cjs", "tests/fixtures/node-types/text-utilities.ts"] },
|
||||
{ "chapter": "util", "symbols": ["util.getSystemErrorName", "util.getSystemErrorMessage", "util.getSystemErrorMap"], "status": "partial", "evidence": ["tests/corpus/node24-system-errors.ts", "tests/corpus/node24-system-error-validation.js", "tests/corpus/node24-system-errors.cjs", "tests/fixtures/node-types/system-errors.ts"] },
|
||||
{ "chapter": "buffer", "symbols": ["buffer.isAscii", "buffer.isUtf8", "buffer.transcode"], "status": "partial", "evidence": ["tests/corpus/node24-buffer-encoding.ts", "tests/corpus/node24-buffer-encoding-errors.ts", "tests/fixtures/node-types/text-encoding.ts"] },
|
||||
{ "chapter": "util", "parent": "Class: `util.TextEncoder`", "signatures": ["`textEncoder.encodeInto(src, dest)`", "`encoding` Type: {string}"], "status": "partial", "evidence": ["tests/corpus/node24-text-encode-into.ts", "tests/corpus/node24-text-codec-properties.ts", "tests/fixtures/node-types/text-encoding.ts"] },
|
||||
|
||||
@@ -1887,6 +1887,8 @@ declare module "util" {
|
||||
export function getSystemErrorName(err: number): string;
|
||||
export function getSystemErrorMessage(err: number): string;
|
||||
export function getSystemErrorMap(): Map<number, [string, string]>;
|
||||
export function stripVTControlCharacters(str: string): string;
|
||||
export function toUSVString(string: string): string;
|
||||
export function parseEnv(content: string): { [key: string]: string | undefined };
|
||||
export type TextEncoder = globalThis.TextEncoder;
|
||||
export type TextDecoder = globalThis.TextDecoder;
|
||||
|
||||
@@ -60,6 +60,8 @@ export const LIB_FN_SYMS: Record<string, string> = {
|
||||
"util.getSystemErrorName": "scr_util_system_error_name",
|
||||
"util.getSystemErrorMessage": "scr_util_system_error_message",
|
||||
"util.systemErrorEntries": "scr_util_system_error_entries",
|
||||
"util.stripVTControlCharacters": "scr_util_strip_vt",
|
||||
"util.toUSVString": "scr_util_to_usv_string",
|
||||
"math.maxArr": "scr_math_max_arr",
|
||||
"math.minArr": "scr_math_min_arr",
|
||||
"math.hypotArr": "scr_math_hypot_arr",
|
||||
|
||||
@@ -28,6 +28,9 @@ export function builtinPrototypeMethod(lowerer: Lowerer, expr: ts.PropertyAccess
|
||||
export function isNativeBuiltinValueInitializer(lowerer: Lowerer, expr: ts.Expression | undefined, seen = new Set<ts.Symbol>()): boolean {
|
||||
if (!expr) return false;
|
||||
while (ts.isParenthesizedExpression(expr)) expr = expr.expression;
|
||||
const builtin = ts.isIdentifier(expr) ? lowerer.builtinImportOf(expr)
|
||||
: ts.isPropertyAccessExpression(expr) ? lowerer.builtinMemberOf(expr) : null;
|
||||
if (builtin?.module === "util" && ["stripVTControlCharacters", "toUSVString"].includes(builtin.member)) return true;
|
||||
// JS global snapshots may use an opaque builtin identity rather than
|
||||
// the ambient constructor's callable ABI. Store that actual value in
|
||||
// the checked representation, including optional capability probes.
|
||||
|
||||
@@ -3952,6 +3952,10 @@ export function lowerForkCall(lowerer: Lowerer, expr: ts.CallExpression, loc: Sr
|
||||
? { module: existing.module, member: existing.member }
|
||||
: lowerer.builtinImportOf(targetExpr);
|
||||
if (!target || !builtinModuleFnOf(lowerer, target.module, target.member)) return false;
|
||||
// These checked native functions retain a runtime slot in JavaScript so
|
||||
// aliases can escape without narrowing their arguments to string.
|
||||
if (isJsSourceFile(nameNode.getSourceFile()) && target.module === "util" &&
|
||||
["stripVTControlCharacters", "toUSVString"].includes(target.member)) return false;
|
||||
if (lowerer.checker.getCallSignatures(lowerer.typeOf(nameNode)).length === 0) return false;
|
||||
const symbol = lowerer.checker.getSymbolAtLocation(nameNode);
|
||||
if (!symbol) return false;
|
||||
|
||||
@@ -1390,12 +1390,14 @@ export function lowerUtilModuleCall(
|
||||
return stmts.length ? { kind: "seqExpr", stmts, result, type: result.type, loc } : result;
|
||||
}
|
||||
case "getSystemErrorName":
|
||||
case "getSystemErrorMessage": {
|
||||
case "getSystemErrorMessage":
|
||||
case "stripVTControlCharacters":
|
||||
case "toUSVString": {
|
||||
if (expr.arguments.some(ts.isSpreadElement)) lowerer.noLowering(`util.${bi.member} with spread arguments`, expr);
|
||||
const input = expr.arguments[0] ? lowerer.lowerExprExpecting(expr.arguments[0], DYN)
|
||||
: { kind: "dynFrom" as const, value: { kind: "unitLit" as const, unit: "undefined" as const, type: UNDEFINED_T, loc }, type: DYN, loc };
|
||||
if (expr.arguments.length <= 1) return { kind: "libCall", fn: `util.${bi.member}`, args: [input], type: STRING, loc };
|
||||
const saved = lowerer.declareHiddenLocal("%systemError", DYN);
|
||||
const saved = lowerer.declareHiddenLocal("%utilInput", DYN);
|
||||
return { kind: "seqExpr", stmts: [{ kind: "varDecl", localId: saved.id, init: input, loc },
|
||||
...expr.arguments.slice(1).map((arg): IrStmt => ({ kind: "exprStmt", expr: lowerer.lowerExpr(arg), loc }))],
|
||||
result: { kind: "libCall", fn: `util.${bi.member}`, args: [varRef(saved.id, DYN, loc)], type: STRING, loc }, type: STRING, loc };
|
||||
|
||||
@@ -888,6 +888,8 @@ export const BUILTIN_MODULE_FNS: Record<string, Record<string, BuiltinModuleFn |
|
||||
getSystemErrorName: { fn: "util.getSystemErrorName", params: [DYN], result: STRING, valueParams: exactValueParams(DYN) },
|
||||
getSystemErrorMessage: { fn: "util.getSystemErrorMessage", params: [DYN], result: STRING, valueParams: exactValueParams(DYN) },
|
||||
getSystemErrorMap: { fn: "util.systemErrorEntries", params: [], result: DYN },
|
||||
stripVTControlCharacters: { fn: "util.stripVTControlCharacters", params: [DYN], result: STRING, valueParams: exactValueParams(DYN) },
|
||||
toUSVString: { fn: "util.toUSVString", params: [DYN], result: STRING, valueParams: exactValueParams(DYN) },
|
||||
},
|
||||
buffer: {
|
||||
isAscii: { fn: "buffer.isAscii", params: [DYN], result: BOOL },
|
||||
|
||||
@@ -2577,6 +2577,8 @@ export type IrLibFn =
|
||||
| "util.getSystemErrorName"
|
||||
| "util.getSystemErrorMessage"
|
||||
| "util.systemErrorEntries"
|
||||
| "util.stripVTControlCharacters"
|
||||
| "util.toUSVString"
|
||||
/** ES Symbol values (scr_symbol.c — link-gated by moduleUsesSymbol).
|
||||
* sym.new: `Symbol(desc)` — a fresh runtime-unique identity (+1) whose
|
||||
* one arg is the description string (borrowed); sym.newAnon is the
|
||||
@@ -6809,7 +6811,7 @@ function scanRuntimeFeatures(mod: IrModule, stopAt?: keyof RuntimeFeatures): Run
|
||||
const expr = (node: IrExpr): boolean => {
|
||||
if (node.kind === "libCall") {
|
||||
const fn = node.fn;
|
||||
if (fn === "regexp.escape" || fn === "dyn.nativeRegexIs") features.regex = true;
|
||||
if (fn === "regexp.escape" || fn === "dyn.nativeRegexIs" || fn === "util.stripVTControlCharacters") features.regex = true;
|
||||
if (fn === "text.decodeLegacy" || fn === "text.decodeLegacyOptions" || fn === "text.decodeStream") features.legacyTextDecoder = true;
|
||||
if (fn.startsWith("fetch.")) features.fetch = true;
|
||||
if (PROCESS_EVENT_LIB_FNS.has(fn)) features.processEvents = true;
|
||||
@@ -7782,6 +7784,8 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
|
||||
"util.parseEnv",
|
||||
"util.getSystemErrorName",
|
||||
"util.getSystemErrorMessage",
|
||||
"util.stripVTControlCharacters",
|
||||
"util.toUSVString",
|
||||
// decodeURIComponent throws the spec's URIError on bad hex/invalid
|
||||
// UTF-8 octets (encodeURIComponent never throws — see the IrLibFn doc).
|
||||
"str.decodeUriComponent",
|
||||
|
||||
@@ -435,6 +435,8 @@ export const LIB_FN_SIGS: Record<IrLibFn, { argTypes: (IrType | null)[]; result:
|
||||
"util.getSystemErrorName": { argTypes: [DYN], result: STRING },
|
||||
"util.getSystemErrorMessage": { argTypes: [DYN], result: STRING },
|
||||
"util.systemErrorEntries": { argTypes: [], result: DYN },
|
||||
"util.stripVTControlCharacters": { argTypes: [DYN], result: STRING },
|
||||
"util.toUSVString": { argTypes: [DYN], result: STRING },
|
||||
"fs.statSync": { argTypes: [STRING], result: STATS_T },
|
||||
"fs.lstatSync": { argTypes: [STRING], result: STATS_T },
|
||||
"fs.fstatSync": { argTypes: [F64], result: STATS_T },
|
||||
|
||||
@@ -1989,6 +1989,18 @@
|
||||
"code": "SC2020",
|
||||
"note": "the lowered shapes are const bindings over child_process.execFile and fs.readFile; call the bound function directly"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.stripVTControlCharacters",
|
||||
"kind": "node-builtin",
|
||||
"name": "util.stripVTControlCharacters",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.toUSVString",
|
||||
"kind": "node-builtin",
|
||||
"name": "util.toUSVString",
|
||||
"status": "static"
|
||||
},
|
||||
{
|
||||
"id": "node-builtin.util.types",
|
||||
"kind": "node-builtin",
|
||||
|
||||
@@ -13540,6 +13540,42 @@
|
||||
"<repo>/tests/fixtures/node-types/system-errors.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/node24-text-utilities.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/node24-text-utilities.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/node24-text-utilities.cjs": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/node24-text-utilities.cjs"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/node24-text-utilities-validation.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/node24-text-utilities-validation.js"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/fixtures/npm/cases/node24-text-utilities/main.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/fixtures/npm/cases/node24-text-utilities/main.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/fixtures/node-types/text-utilities.ts": {
|
||||
"order": [
|
||||
"<repo>/tests/fixtures/node-types/text-utilities.ts"
|
||||
],
|
||||
"diags": []
|
||||
},
|
||||
"<repo>/tests/corpus/node24-text-utilities-coercion.js": {
|
||||
"order": [
|
||||
"<repo>/tests/corpus/node24-text-utilities-coercion.js"
|
||||
],
|
||||
"diags": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8697,13 +8697,9 @@ static const char isl_modules_bootstrap[] =
|
||||
" }\n"
|
||||
" };\n"
|
||||
" const isDeepStrictEqual = (a, b) => deepEquals(a, b, undefined);\n"
|
||||
" const ansiRe = /[\\u001b][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/\\\\#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/\\\\#&.:=?%@~_]*)*)?(?:|\\u001b\\|))|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))/g;\n"
|
||||
" const ansiRe = /[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/\\#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/\\#&.:=?%@~_]*)*)?(?:\\u0007|\\u001B\\u005C|\\u009C))|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))/g;\n"
|
||||
" const stripVTControlCharacters = (str) => {\n"
|
||||
" if (typeof str !== \"string\") {\n"
|
||||
" const e = new TypeError('The \"str\" argument must be of type string. Received ' + (str === null ? \"null\" : typeof str === \"object\" ? \"an instance of Object\" : typeof str === \"undefined\" ? \"undefined\" : \"type \" + typeof str));\n"
|
||||
" e.code = \"ERR_INVALID_ARG_TYPE\";\n"
|
||||
" throw e;\n"
|
||||
" }\n"
|
||||
" if (typeof str !== \"string\") envArgumentError(\"str\", \"of type string\", str);\n"
|
||||
" return str.replace(ansiRe, \"\");\n"
|
||||
" };\n"
|
||||
" const styleText = (fmt, text, options) => {\n"
|
||||
@@ -8866,7 +8862,33 @@ static const char isl_modules_bootstrap[] =
|
||||
" }\n"
|
||||
" return result;\n"
|
||||
" };\n"
|
||||
" const toUSVString = (s) => String(s).replace(/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g, \"�\");\n"
|
||||
" const toUSVString = (s) => {\n"
|
||||
" let primitive = s;\n"
|
||||
" const isObject = (v) => v !== null && (typeof v === 'object' || typeof v === 'function');\n"
|
||||
" if (isObject(s)) {\n"
|
||||
" const exotic = s[Symbol.toPrimitive];\n"
|
||||
" if (exotic !== undefined && exotic !== null) {\n"
|
||||
" if (typeof exotic !== 'function') {\n"
|
||||
" const kind = typeof exotic;\n"
|
||||
" const label = ['object', 'symbol', 'bigint'].includes(kind) ? kind : kind + ' ' + (kind === 'string' ? '\"' + exotic + '\"' : String(exotic));\n"
|
||||
" throw new TypeError(label + ' is not a function');\n"
|
||||
" }\n"
|
||||
" primitive = Reflect.apply(exotic, s, ['string']);\n"
|
||||
" if (isObject(primitive)) throw new TypeError('Cannot convert object to primitive value');\n"
|
||||
" } else {\n"
|
||||
" let converted = false;\n"
|
||||
" for (const name of ['toString', 'valueOf']) {\n"
|
||||
" const method = s[name];\n"
|
||||
" if (typeof method !== 'function') continue;\n"
|
||||
" primitive = Reflect.apply(method, s, []);\n"
|
||||
" if (!isObject(primitive)) { converted = true; break; }\n"
|
||||
" }\n"
|
||||
" if (!converted) throw new TypeError('Cannot convert object to primitive value');\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" if (typeof primitive === 'symbol') throw new TypeError('Cannot convert a Symbol value to a string');\n"
|
||||
" return `${primitive}`.replace(/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/g, \"�\");\n"
|
||||
" };\n"
|
||||
" const _extend = (target, source) => {\n"
|
||||
" if (source === null || typeof source !== \"object\") return target;\n"
|
||||
" for (const k of Object.keys(source)) target[k] = source[k];\n"
|
||||
|
||||
@@ -57,6 +57,7 @@ static void scr_json_oom(void) {
|
||||
|
||||
static SCR_TL ScrDyn *scr_iterator_symbol;
|
||||
static SCR_TL ScrDyn *scr_async_iterator_symbol;
|
||||
static SCR_TL ScrDyn *scr_to_primitive_symbol;
|
||||
static bool scr_dyn_generator(const ScrDyn *value) {
|
||||
return value && value->kind == SCR_DYN_TYPED_REF &&
|
||||
(!strncmp(value->v.typed_ref.type_key, "generator<", 10) || !strncmp(value->v.typed_ref.type_key, "async-generator<", 16));
|
||||
@@ -104,6 +105,17 @@ static void scr_async_iterator_symbol_cleanup(void) {
|
||||
scr_async_iterator_symbol = NULL;
|
||||
}
|
||||
|
||||
static void scr_to_primitive_symbol_cleanup(void) {
|
||||
scr_dyn_release(scr_to_primitive_symbol);
|
||||
scr_to_primitive_symbol = NULL;
|
||||
}
|
||||
|
||||
void scr_dyn_install_to_primitive_symbol(ScrDyn *key) {
|
||||
if (scr_to_primitive_symbol) return;
|
||||
scr_to_primitive_symbol = scr_dyn_retain(key);
|
||||
scr_atexit(scr_to_primitive_symbol_cleanup);
|
||||
}
|
||||
|
||||
void scr_dyn_install_async_iterator_symbol(ScrDyn *key) {
|
||||
if (scr_async_iterator_symbol) return;
|
||||
scr_async_iterator_symbol = scr_dyn_retain(key);
|
||||
@@ -2352,7 +2364,9 @@ const char *scr_dyn_specific_type(const ScrDyn *cb, char *detail, size_t cap) {
|
||||
}
|
||||
case SCR_DYN_NUM: {
|
||||
char num[32];
|
||||
size_t n = scr_f64_to_str(cb->v.num, num);
|
||||
size_t n;
|
||||
if (cb->v.num == 0 && signbit(cb->v.num)) { memcpy(num, "-0", 3); n = 2; }
|
||||
else n = scr_f64_to_str(cb->v.num, num);
|
||||
snprintf(detail, cap, "type number (%.*s)", (int)n, num);
|
||||
break;
|
||||
}
|
||||
@@ -4576,6 +4590,65 @@ ScrStr *scr_dyn_string_coerce_js(const ScrDyn *d) {
|
||||
return scr_dyn_string_coerce(d);
|
||||
}
|
||||
|
||||
ScrStr *scr_util_to_usv_string(const ScrDyn *input) {
|
||||
const ScrDyn *value = input ? input : scr_dyn_undefined();
|
||||
/* Native string producers already replace unpaired surrogate code units.
|
||||
* Preserve ToString's object protocol before using that storage invariant. */
|
||||
if (scr_to_primitive_symbol && scr_iterator_object(value)) {
|
||||
ScrDyn *method = scr_dyn_reflect_get((ScrDyn *)value, scr_to_primitive_symbol, (ScrDyn *)value);
|
||||
if (!method) return NULL;
|
||||
if (method->kind != SCR_DYN_UNDEF && method->kind != SCR_DYN_NULL) {
|
||||
if (!scr_dyn_is_callable(method)) {
|
||||
ScrJsonBuf buffer;
|
||||
scr_jb_init(&buffer);
|
||||
if (scr_iterator_object(method)) scr_jb_puts(&buffer, "object");
|
||||
else {
|
||||
ScrStr *type = scr_dyn_typeof(method);
|
||||
scr_jb_put_str(&buffer, type);
|
||||
scr_str_release(type);
|
||||
if (method->kind != SCR_DYN_SYMBOL && method->kind != SCR_DYN_BIGINT) scr_jb_puts(&buffer, " ");
|
||||
if (method->kind == SCR_DYN_STR) {
|
||||
scr_jb_puts(&buffer, "\"");
|
||||
scr_jb_put_str(&buffer, method->v.str);
|
||||
scr_jb_puts(&buffer, "\"");
|
||||
}
|
||||
else if (method->kind != SCR_DYN_SYMBOL && method->kind != SCR_DYN_BIGINT) {
|
||||
ScrStr *text = scr_dyn_string_coerce(method);
|
||||
scr_jb_put_str(&buffer, text);
|
||||
scr_str_release(text);
|
||||
}
|
||||
}
|
||||
scr_jb_puts(&buffer, " is not a function");
|
||||
ScrStr *message = scr_jb_finish(&buffer);
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message->data, message->len);
|
||||
scr_str_release(message);
|
||||
scr_dyn_release(method);
|
||||
return NULL;
|
||||
}
|
||||
ScrStr *hint = scr_str_new("string", 6);
|
||||
ScrDyn *argument = scr_dyn_new_str(hint);
|
||||
scr_str_release(hint);
|
||||
scr_dyn_this_push_dyn(value);
|
||||
ScrDyn *primitive = scr_dyn_call(method, &argument, 1, "Symbol.toPrimitive");
|
||||
scr_dyn_this_pop();
|
||||
scr_dyn_release(argument);
|
||||
scr_dyn_release(method);
|
||||
if (!primitive) return NULL;
|
||||
if (scr_dyn_to_primitive_result_is_object(primitive)) {
|
||||
static const char message[] = "Cannot convert object to primitive value";
|
||||
scr_throw_error_msg(SCR_ERR_TYPE, message, sizeof message - 1);
|
||||
scr_dyn_release(primitive);
|
||||
return NULL;
|
||||
}
|
||||
ScrStr *text = scr_dyn_string_coerce(primitive);
|
||||
scr_dyn_release(primitive);
|
||||
return text;
|
||||
}
|
||||
scr_dyn_release(method);
|
||||
}
|
||||
return scr_dyn_string_coerce_js(value);
|
||||
}
|
||||
|
||||
/* JS ToNumber over a checked-dynamic value, including OrdinaryToPrimitive's
|
||||
* NUMBER hint for object snapshots. This is the numeric twin of
|
||||
* scr_dyn_string_coerce_js above: valueOf precedes toString, inherited
|
||||
|
||||
@@ -719,6 +719,60 @@ ScrStr *scr_regex_replace(ScrStr *s, ScrRegex *re, ScrStr *rep) {
|
||||
return scr_replace_impl(s, re, rep);
|
||||
}
|
||||
|
||||
|
||||
/* The pinned Node ANSI matcher derives from ansi-regex, copyright (c)
|
||||
* Sindre Sorhus <sindresorhus@gmail.com>, used under the MIT license:
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a
|
||||
* copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation
|
||||
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
* and/or sell copies of the Software, and to permit persons to whom the
|
||||
* Software is furnished to do so, subject to the following conditions:
|
||||
* The above copyright notice and this permission notice shall be included
|
||||
* in all copies or substantial portions of the Software.
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
* DEALINGS IN THE SOFTWARE.
|
||||
* Keep the pattern equivalent to Node v24.15.0 internal/util/inspect.js. */
|
||||
#define SCR_VT_PATTERN "[\\u001B\\u009B][[\\]()#;?]*" \
|
||||
"(?:(?:(?:(?:;[-a-zA-Z\\d\\/\\#&.:=?%@~_]+)*" \
|
||||
"|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/\\#&.:=?%@~_]*)*)?" \
|
||||
"(?:\\u0007|\\u001B\\u005C|\\u009C))" \
|
||||
"|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))"
|
||||
|
||||
ScrStr *scr_util_strip_vt(const ScrDyn *input) {
|
||||
if (!input || input->kind != SCR_DYN_STR) {
|
||||
scr_dyn_arg_type_fail("str", "of type string", input ? input : scr_dyn_undefined());
|
||||
return NULL;
|
||||
}
|
||||
ScrStr *subject = input->v.str;
|
||||
bool escape = false;
|
||||
for (size_t i = 0; i < subject->len; i++) {
|
||||
if (subject->data[i] == '\x1b' || ((unsigned char)subject->data[i] == 0xc2 &&
|
||||
i + 1 < subject->len && (unsigned char)subject->data[i + 1] == 0x9b)) {
|
||||
escape = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!escape) return scr_str_retain(subject);
|
||||
static const struct { size_t rc, len, cap; char data[sizeof SCR_VT_PATTERN]; }
|
||||
source = { SIZE_MAX, sizeof SCR_VT_PATTERN - 1, sizeof SCR_VT_PATTERN - 1, SCR_VT_PATTERN };
|
||||
static const struct { size_t rc, len, cap; char data[2]; }
|
||||
flags = { SIZE_MAX, 1, 1, "g" };
|
||||
static SCR_TL ScrRegex template = { SIZE_MAX, (ScrStr *)&source, (ScrStr *)&flags, NULL, 0, NULL };
|
||||
ScrRegex *regex = scr_regex_literal(&template);
|
||||
ScrStr *empty = scr_str_new("", 0);
|
||||
ScrStr *result = scr_regex_replace(subject, regex, empty);
|
||||
scr_str_release(empty);
|
||||
scr_regex_release(regex);
|
||||
return result;
|
||||
}
|
||||
#undef SCR_VT_PATTERN
|
||||
|
||||
ScrStr *scr_regex_replace_all(ScrStr *s, ScrRegex *re, ScrStr *rep) {
|
||||
if (!(lre_get_flags(scr_regex_bc(re)) & LRE_FLAG_GLOBAL)) {
|
||||
/* Node's exact TypeError — a real instance now: catch bindings narrow
|
||||
|
||||
@@ -818,6 +818,11 @@ ScrStr *scr_str_pad_end(ScrStr *s, double maxLength, ScrStr *fill);
|
||||
* both are no-ops on well-formed input). Borrow s; toWellFormed +1. */
|
||||
bool scr_str_is_well_formed(ScrStr *s);
|
||||
ScrStr *scr_str_to_well_formed(ScrStr *s);
|
||||
/* Node text utilities: borrowed input, +1 result or NULL+pending. The VT
|
||||
* helper lives in the optional regex unit; USV conversion uses native strings. */
|
||||
ScrStr *scr_util_strip_vt(const struct ScrDyn *input);
|
||||
ScrStr *scr_util_to_usv_string(const struct ScrDyn *input);
|
||||
void scr_dyn_install_to_primitive_symbol(struct ScrDyn *key);
|
||||
|
||||
/* RegExp.escape(s) — ES2025 EncodeForRegExpEscape per code point: leading
|
||||
* ASCII alphanumeric hex-escapes, SyntaxCharacters and '/' take a
|
||||
|
||||
@@ -90,6 +90,11 @@ ScrSym *scr_sym_well_known(ScrStr *name) {
|
||||
scr_dyn_install_async_iterator_symbol(key);
|
||||
scr_dyn_release(key);
|
||||
}
|
||||
if (name->len == 11 && !memcmp(name->data, "toPrimitive", 11)) {
|
||||
ScrDyn *key = scr_dyn_new_symbol(symbol);
|
||||
scr_dyn_install_to_primitive_symbol(key);
|
||||
scr_dyn_release(key);
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { toUSVString as usv } from "node:util";
|
||||
|
||||
const calls = [];
|
||||
const exotic = { [Symbol.toPrimitive](hint) { calls.push(hint); return "\ud800"; } };
|
||||
console.log("exotic", JSON.stringify(usv(exotic)), calls.join(","));
|
||||
const ordinary = { toString() { calls.push("toString"); return {}; }, valueOf() { calls.push("valueOf"); return "\udfff"; } };
|
||||
console.log("ordinary", JSON.stringify(usv(ordinary)), calls.join(","));
|
||||
const sentinel = new Error("conversion failed");
|
||||
try { usv({ toString() { throw sentinel; } }); } catch (error) { console.log("throw-identity", error === sentinel); }
|
||||
try { usv({ [Symbol.toPrimitive]() { throw sentinel; } }); } catch (error) { console.log("exotic-throw-identity", error === sentinel); }
|
||||
try { usv({ [Symbol.toPrimitive]() { return Symbol("x"); } }); } catch (error) { console.log("symbol-result", error.name, error.message); }
|
||||
for (const method of [1, -0, NaN, Infinity, true, 2n, Symbol("x"), "bad", "a\nb", '"quoted"', {}, null, undefined]) {
|
||||
try { console.log("exotic-method", usv({ [Symbol.toPrimitive]: method })); }
|
||||
catch (error) { console.log("exotic-method", error.name, error.message); }
|
||||
}
|
||||
try { usv({ [Symbol.toPrimitive]() { return {}; } }); } catch (error) { console.log("exotic-object", error.name, error.message); }
|
||||
try { usv({ toString() { return {}; }, valueOf() { return {}; } }); } catch (error) { console.log("non-primitive", error.name, error.message); }
|
||||
const child = Object.create({ [Symbol.toPrimitive](hint) { return this.label + hint; } });
|
||||
child.label = "inherited:";
|
||||
console.log("inherited", usv(child));
|
||||
const getter = {};
|
||||
Object.defineProperty(getter, Symbol.toPrimitive, { get() { calls.push("get"); return function (hint) { return hint; }; } });
|
||||
console.log("getter", usv(getter), calls.join(","));
|
||||
@@ -0,0 +1,25 @@
|
||||
import { stripVTControlCharacters as strip, toUSVString as usv } from "node:util";
|
||||
const stripStored = strip;
|
||||
const usvStored = usv;
|
||||
|
||||
for (const value of [undefined, null, 0, -0, 1.5, NaN, Infinity, true, false, 2n, Symbol("x"), {}, [], [1, 2]]) {
|
||||
try { console.log("strip", strip(value)); }
|
||||
catch (error) { console.log(error.name, error.code, error.message); }
|
||||
try { console.log("usv", usv(value)); }
|
||||
catch (error) { console.log(error.name, error.code, error.message); }
|
||||
try { console.log("stored-strip", stripStored(value)); }
|
||||
catch (error) { console.log(error.name, error.code, error.message); }
|
||||
try { console.log("stored-usv", usvStored(value)); }
|
||||
catch (error) { console.log(error.name, error.code, error.message); }
|
||||
}
|
||||
try { strip(); } catch (error) { console.log("omitted", error.name, error.code, error.message); }
|
||||
console.log("omitted-usv", usv());
|
||||
console.log("identity", strip === stripStored, usv === usvStored);
|
||||
try { stripStored(); } catch (error) { console.log("omitted-stored", error.name, error.code, error.message); }
|
||||
console.log("omitted-stored-usv", usvStored());
|
||||
let order = "";
|
||||
function extra() { order += "extra"; return null; }
|
||||
console.log("extras", strip("\u001b[1mx", extra()), order);
|
||||
console.log("extras", usv("\ud800", extra()), order);
|
||||
try { strip(12, extra()); } catch (error) { console.log("validation-after-arguments", order, error.code); }
|
||||
try { usv(Symbol("x"), extra()); } catch (error) { console.log("conversion-after-arguments", order, error.name, error.message); }
|
||||
@@ -0,0 +1,7 @@
|
||||
const { stripVTControlCharacters: strip, toUSVString: usv } = require("node:util");
|
||||
const util = require("util");
|
||||
const stripValue = util.stripVTControlCharacters;
|
||||
const usvValue = util.toUSVString;
|
||||
console.log(strip("\u001b[1mhello\u001b[0m"), stripValue("\u001b]0;title\u001b\\hello"));
|
||||
console.log(usv("a\ud800b"), usvValue("😀"));
|
||||
console.log(strip === stripValue, usv === usvValue);
|
||||
@@ -0,0 +1,23 @@
|
||||
import { stripVTControlCharacters as strip, toUSVString as usv } from "node:util";
|
||||
import * as util from "util";
|
||||
|
||||
const stripValue = util.stripVTControlCharacters;
|
||||
const usvValue = util.toUSVString;
|
||||
for (const text of ["", "plain café 😀", "line\n\tend\r", "\u001b[31mred\u001b[0m", "\u009b1;32mgreen\u009b0m", "\u001b[2J\u001b[Hclear", "\u001b]0;window title\u0007tail", "\u001b]8;;https://example.com\u001b\\link\u001b]8;;\u001b\\", "\u001b]0;title\u009ctail", "\u001b[?25lhidden\u001b[?25h", "\u001b[38;2;1;2;3mRGB\u001b[0m", "\u001b7saved\u001b8", "\u001b[", "\u001b]unterminated", "\u001b\\", "embedded\u0000\u001b[1mNUL\u001b[0m"] ) {
|
||||
console.log(JSON.stringify(strip(text)), JSON.stringify(stripValue(text)));
|
||||
}
|
||||
for (const text of ["", "café 😀", "\ud800", "\udfff", "a\ud800z", "\ud800\ud800\udc00\udc00", "\ud83d\ude00", "\udc00\ud800", "\udbff\udfff"] ) {
|
||||
console.log(JSON.stringify(usv(text)), JSON.stringify(usvValue(text)));
|
||||
}
|
||||
// Repeated calls keep the shared regex bytecode's state independent.
|
||||
console.log(strip("\u001b[31mone"), strip("\u001b[32mtwo"), strip("three"));
|
||||
let digest = 0;
|
||||
for (const intro of ["\u001b", "\u009b"]) {
|
||||
for (const body of ["", "[", "]", "[31", "[1;2", "]0;title", "]8;;https://x", ";x/y#&.:=?%@~_", "[?25", "[12345", "[;;", "(B", "abc;xyz", "abc;", "[]#;?"]) {
|
||||
for (let code = 0; code < 160; code++) {
|
||||
const result = strip("前" + intro + body + String.fromCharCode(code) + "後");
|
||||
for (let i = 0; i < result.length; i++) digest = (digest * 31 + result.charCodeAt(i)) >>> 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log("control-matrix", digest);
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
import { stripVTControlCharacters, toUSVString } from "node:util";
|
||||
|
||||
const strip: (value: string) => string = stripVTControlCharacters;
|
||||
const usv: (value: string) => string = toUSVString;
|
||||
console.log(strip("\u001b[32mtyped\u001b[0m"));
|
||||
console.log(JSON.stringify(usv("\ud800😀\udfff")));
|
||||
@@ -0,0 +1,4 @@
|
||||
// @dynamic
|
||||
// @stderr
|
||||
import { reportTextUtilities } from "node24-text-utility-fixture";
|
||||
reportTextUtilities();
|
||||
@@ -0,0 +1 @@
|
||||
{"private":true,"type":"module","dependencies":{"node24-text-utility-fixture":"1.0.0"}}
|
||||
+1
@@ -0,0 +1 @@
|
||||
export function reportTextUtilities(): void;
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { stripVTControlCharacters as strip, toUSVString as usv } from "node:util";
|
||||
import * as util from "util";
|
||||
|
||||
export function reportTextUtilities() {
|
||||
console.log("identity", strip === util.stripVTControlCharacters, usv === util.toUSVString);
|
||||
for (const text of ["", "plain café 😀", "\u001b[31mred\u001b[0m", "\u009b1;32mgreen\u009b0m", "\u001b]0;title\u0007tail", "\u001b]8;;https://example.com\u001b\\link\u001b]8;;\u001b\\", "\u001b]0;title\u009ctail", "\u001b[?25lhide\u001b[?25h", "\u001b[", "\u001b]unterminated", "\u001b\\", "embedded\u0000\u001b[1mNUL"]) console.log("strip", JSON.stringify(strip(text)));
|
||||
for (const text of ["", "café 😀", "\ud800", "\udfff", "a\ud800z", "\ud800\ud800\udc00\udc00", "\ud83d\ude00", "\udc00\ud800", "\udbff\udfff"]) console.log("usv", JSON.stringify(usv(text)));
|
||||
for (const value of [undefined, null, 0, -0, 1.5, NaN, Infinity, true, false, 2n, Symbol("x"), {}, [], [1, 2], new String("boxed"), new Number(2), function sample() {}]) {
|
||||
try { console.log("strip-value", strip(value)); }
|
||||
catch (error) { console.log(error.name, error.code, error.message); }
|
||||
try { console.log("usv-value", usv(value)); }
|
||||
catch (error) { console.log(error.name, error.code, error.message); }
|
||||
}
|
||||
const calls = [];
|
||||
const exotic = { [Symbol.toPrimitive](hint) { calls.push(hint); return "\ud800"; } };
|
||||
console.log("exotic", JSON.stringify(usv(exotic)), calls.join(","));
|
||||
const ordinary = { toString() { calls.push("toString"); return {}; }, valueOf() { calls.push("valueOf"); return "\udfff"; } };
|
||||
console.log("ordinary", JSON.stringify(usv(ordinary)), calls.join(","));
|
||||
const sentinel = new Error("conversion failed");
|
||||
try { usv({ toString() { throw sentinel; } }); } catch (error) { console.log("throw-identity", error === sentinel); }
|
||||
try { usv({ [Symbol.toPrimitive]() { throw sentinel; } }); } catch (error) { console.log("exotic-throw-identity", error === sentinel); }
|
||||
try { usv({ [Symbol.toPrimitive]() { return Symbol("x"); } }); } catch (error) { console.log("symbol-result", error.name, error.message); }
|
||||
for (const method of [1, -0, NaN, Infinity, true, 2n, Symbol("x"), "bad", "a\nb", '"quoted"', {}, null, undefined]) {
|
||||
try { console.log("exotic-method", usv({ [Symbol.toPrimitive]: method })); }
|
||||
catch (error) { console.log("exotic-method", error.name, error.message); }
|
||||
}
|
||||
try { usv({ toString() { return {}; }, valueOf() { return {}; } }); } catch (error) { console.log("non-primitive", error.name, error.message); }
|
||||
try { usv({ [Symbol.toPrimitive]() { return {}; } }); } catch (error) { console.log("exotic-object", error.name, error.message); }
|
||||
try { strip(); } catch (error) { console.log("omitted", error.name, error.code, error.message); }
|
||||
console.log("omitted-usv", usv());
|
||||
// Exercise every control terminator and parameter class in the pinned matcher.
|
||||
let digest = 0;
|
||||
const bodies = ["", "[", "]", "[31", "[1;2", "]0;title", "]8;;https://x", ";x/y#&.:=?%@~_", "[?25", "[12345", "[;;", "(B", "abc;xyz", "abc;", "[]#;?"];
|
||||
for (const intro of ["\u001b", "\u009b"]) for (const body of bodies) {
|
||||
for (let code = 0; code < 160; code++) {
|
||||
const text = "前" + intro + body + String.fromCharCode(code) + "後";
|
||||
const result = strip(text);
|
||||
for (let i = 0; i < result.length; i++) digest = (digest * 31 + result.charCodeAt(i)) >>> 0;
|
||||
}
|
||||
}
|
||||
console.log("control-matrix", digest);
|
||||
}
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"name":"node24-text-utility-fixture","version":"1.0.0","type":"module","exports":"./index.js","types":"./index.d.ts"}
|
||||
@@ -250,6 +250,17 @@ test("node-types: URL factories match Node under @types/node", async () => {
|
||||
expect(native.stderr).toBe(node.stderr);
|
||||
});
|
||||
|
||||
test("node-types: text utilities match Node under @types/node", async () => {
|
||||
const outDir = outDirFor("node-text-utilities");
|
||||
const entry = join(nodeTypesDir, "text-utilities.ts");
|
||||
const result = await compile(entry, { outPath: join(outDir, "text-utilities"), outDir, sanitize });
|
||||
expect(result.ok, !result.ok ? JSON.stringify(result.diagnostics, null, 2) : "").toBe(true);
|
||||
if (!result.ok) return;
|
||||
const [native, node] = await Promise.all([execFileAsync(result.binaryPath), execFileAsync(process.execPath, ["--disable-warning=MODULE_TYPELESS_PACKAGE_JSON", entry])]);
|
||||
expect(native.stdout).toBe(node.stdout);
|
||||
expect(native.stderr).toBe(node.stderr);
|
||||
});
|
||||
|
||||
test("node-types: system error utilities match Node under @types/node", async () => {
|
||||
const outDir = outDirFor("node-system-errors");
|
||||
const entry = join(nodeTypesDir, "system-errors.ts");
|
||||
|
||||
Reference in New Issue
Block a user