Compile the deprecated punycode and constants builtin modules

- node:punycode: encode, decode, toASCII, toUnicode, ucs2.decode,
  ucs2.encode and version, ported from the punycode.js 2.1.0 Node bundles
  (scr_url.c), with its RangeErrors. DEP0040 follows Node: a static ES
  import reports before any module evaluates and before the program can
  register a 'warning' listener; require() reports on the next tick, once
  for both spellings, not when a dependency under node_modules loads the
  module first, and not under process.noDeprecation.
- node:constants: the frozen object Node builds from os.constants'
  dlopen, errno, priority and signals groups, fs.constants (node_constants.cc
  order and guards) and crypto.constants (scr_os.c); its bindings read it as
  a checked-dynamic value.

These unblock tr46 (whatwg-url, node-fetch@2) and graceful-fs (fs-extra).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Malte Ubl
2026-10-18 06:23:56 -07:00
co-authored by Claude Opus 5.5
parent b4e25cdba0
commit 1c19d6613d
38 changed files with 1169 additions and 88 deletions
+2
View File
@@ -74,6 +74,8 @@ title: "Limitations"
<include>../limitations/builtin-apis/legacy-url-api.mdx</include>
<include>../limitations/builtin-apis/deprecated-modules.mdx</include>
<include>../limitations/builtin-apis/filesystem-flags.mdx</include>
<include>../limitations/builtin-apis/copy.mdx</include>
@@ -0,0 +1 @@
- **Deprecated `punycode` and `constants` modules:** `punycode.encode()`, `decode()`, `toASCII()`, `toUnicode()`, `ucs2.decode()`, `ucs2.encode()`, and `version` compile, with the `DEP0040` warning Node.js reports when the module loads. A `require("punycode")` made from inside `node_modules` doesn't warn; a `require()` used inside a larger expression, such as `require("punycode").toASCII(name)`, doesn't warn either. The `punycode.ucs2` object itself is unsupported as a value. The `constants` module is the frozen object Node.js builds from `os.constants` and `fs.constants`, plus the `crypto.constants` of Node.js 24.22.0.
@@ -2,6 +2,6 @@
"schemaVersion": 3,
"nodeVersion": "24.22.0",
"nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93",
"artifactVersion": "a1ff929d49885bd8c92e",
"artifactVersion": "7b345b86e2e5f616a7a0",
"rowCount": 3880
}
+24 -24
View File
@@ -584,9 +584,9 @@
"apiEntries": 9,
"static": {
"supported": 0,
"partial": 0,
"partial": 8,
"refused": 0,
"not-implemented": 9,
"not-implemented": 1,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -58489,7 +58489,7 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"status": "partial",
"detail": "Derived from the exact API rows in this chapter.",
"verification": "derived"
}
@@ -58512,9 +58512,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -58535,9 +58535,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -58558,9 +58558,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -58581,9 +58581,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -58627,9 +58627,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -58650,9 +58650,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -58673,9 +58673,9 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"detail": "No static compiler lowering is registered for this API yet.",
"verification": "registry-gap"
"status": "partial",
"detail": "Implemented for the call shapes accepted by the compiler lowering.",
"verification": "test-backed"
}
},
{
@@ -7,8 +7,8 @@
"tierItemCount": 3456,
"tiers": {
"static": {
"audit-partial": 1091,
"verify-gap": 2238,
"audit-partial": 1098,
"verify-gap": 2231,
"replace-refusal": 40,
"classify": 2,
"implement": 85
@@ -58534,12 +58534,16 @@
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:punycode",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.punycode.decode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
}
}
},
@@ -58559,12 +58563,16 @@
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:punycode",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.punycode.encode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
}
}
},
@@ -58584,12 +58592,16 @@
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:punycode",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.punycode.toASCII",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
}
}
},
@@ -58609,12 +58621,16 @@
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:punycode",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.punycode.toUnicode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
}
}
},
@@ -58659,12 +58675,16 @@
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:punycode",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.punycode.ucs2.decode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
}
}
},
@@ -58684,12 +58704,16 @@
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:punycode",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.punycode.ucs2.encode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
}
}
},
@@ -58709,12 +58733,16 @@
"priority": "low",
"tiers": {
"static": {
"status": "not-implemented",
"action": "verify-gap",
"verification": "registry-gap",
"confidence": "low",
"source": "compiler-unmatched:punycode",
"tests": []
"status": "partial",
"action": "audit-partial",
"verification": "test-backed",
"confidence": "high",
"source": "surface-manifest:node-builtin.punycode.version",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
}
}
},
@@ -611,9 +611,9 @@
"apiEntries": 9,
"static": {
"supported": 0,
"partial": 0,
"partial": 8,
"refused": 0,
"not-implemented": 9,
"not-implemented": 1,
"by-design": 0,
"unreviewed": 0,
"not-applicable": 0
@@ -81908,8 +81908,13 @@
"inherited": false
},
"static": {
"status": "not-implemented",
"evidence": "derived:descendants"
"status": "partial",
"evidence": "derived:descendants",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
},
"anchorSource": "chapter"
},
@@ -81931,8 +81936,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:punycode"
"status": "partial",
"evidence": "surface-manifest:node-builtin.punycode.decode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
},
"anchorSource": "exact"
},
@@ -81954,8 +81964,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:punycode"
"status": "partial",
"evidence": "surface-manifest:node-builtin.punycode.encode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
},
"anchorSource": "exact"
},
@@ -81977,8 +81992,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:punycode"
"status": "partial",
"evidence": "surface-manifest:node-builtin.punycode.toASCII",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
},
"anchorSource": "exact"
},
@@ -82000,8 +82020,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:punycode"
"status": "partial",
"evidence": "surface-manifest:node-builtin.punycode.toUnicode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
},
"anchorSource": "exact"
},
@@ -82046,8 +82071,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:punycode"
"status": "partial",
"evidence": "surface-manifest:node-builtin.punycode.ucs2.decode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
},
"anchorSource": "exact"
},
@@ -82069,8 +82099,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:punycode"
"status": "partial",
"evidence": "surface-manifest:node-builtin.punycode.ucs2.encode",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
},
"anchorSource": "exact"
},
@@ -82092,8 +82127,13 @@
"inherited": true
},
"static": {
"status": "not-implemented",
"evidence": "compiler-unmatched:punycode"
"status": "partial",
"evidence": "surface-manifest:node-builtin.punycode.version",
"tests": [
"tests/corpus/6122-punycode-conversions.ts",
"tests/corpus/6124-punycode-require-warnings.cjs",
"tests/corpus/6126-punycode-dependency/main.cjs"
]
},
"anchorSource": "chapter"
},
@@ -146,6 +146,7 @@
"node-builtin.process.rows": ["tests/corpus/2992-stream-rows.ts", "tests/harness/terminal-geometry.test.ts"],
"node-builtin.process.columns": ["tests/corpus/1448-stream-columns.ts", "tests/harness/terminal-geometry.test.ts"],
"node-builtin.process.exitCode": ["tests/corpus/2941-process-exit-code.ts", "tests/corpus/2942-process-exit-code-override.ts", "tests/corpus/2943-process-exit-code-invalid.ts", "tests/corpus/2944-process-exit-code-wrap.ts"],
"node-builtin.punycode": ["tests/corpus/6122-punycode-conversions.ts", "tests/corpus/6124-punycode-require-warnings.cjs", "tests/corpus/6126-punycode-dependency/main.cjs"],
"node-builtin.querystring": ["tests/corpus/957-builtins-namespace.ts", "tests/corpus/2917-builtin-callable-widths.ts"],
"node-builtin.readline": ["tests/corpus/1475-readline-closed-stdin.ts"],
"node-builtin.stream": ["tests/corpus/1685-stream-readable-basics.ts", "tests/corpus/1746-stream-for-await.ts", "tests/corpus/1814-stream-pipeline.cjs", "tests/corpus/2629-stream-consumers.ts", "tests/corpus/2861-readable-iterator-close.ts"],
+33
View File
@@ -3643,6 +3643,39 @@ declare module "node:querystring" {
export * from "querystring";
}
/* node:punycode (deprecated, DEP0040) — punycode.js 2.1.0: decode and
* toUnicode throw its RangeErrors on malformed input; ucs2 converts
* between strings and UTF-16 code point arrays. */
declare module "punycode" {
export function decode(string: string): string;
export function encode(string: string): string;
export function toUnicode(domain: string): string;
export function toASCII(domain: string): string;
export namespace ucs2 {
function decode(string: string): number[];
function encode(codePoints: readonly number[]): string;
}
export const version: string;
}
declare module "node:punycode" {
export * from "punycode";
}
/* node:constants (deprecated, DEP0008) — one frozen object: os.constants'
* dlopen, errno, priority and signals groups, fs.constants, and
* crypto.constants. A platform's missing constant reads as undefined. */
declare module "constants" {
const constants: { readonly [name: string]: number } & {
readonly defaultCoreCipherList: string;
readonly defaultCipherList: string;
};
export = constants;
}
declare module "node:constants" {
import constants = require("constants");
export = constants;
}
/* node:readline — the question/close slice: createInterface over exactly
* { input: process.stdin, output: process.stdout }, question(query, cb)
* (the query writes to stdout, the callback gets the next line's text),
@@ -680,6 +680,8 @@ export function emitLibCall(host: LlvmEmitterContext, e: LibCallExpr): LlValue {
case "events":
case "blob":
case "record":
case "punycode":
case "constants":
return host.emitGenericLibCall(e);
case "test":
return emitTestLibCall(host, e);
@@ -192,6 +192,7 @@ export const LIB_FN_SYMS: Record<string, string> = {
"os.cpusModel": "scr_os_cpus_model",
"os.cpusField": "scr_os_cpus_field",
"os.constants": "scr_os_constants",
"constants.object": "scr_node_constants",
"os.release": "scr_os_release",
"os.userName": "scr_os_user_name",
"os.userShell": "scr_os_user_shell",
@@ -631,6 +632,13 @@ export const LIB_FN_SYMS: Record<string, string> = {
"url.legacyFormat": "scr_url_legacy_format",
"url.legacyResolve": "scr_url_legacy_resolve",
"url.domainToASCII": "scr_url_domain_to_ascii",
"punycode.decode": "scr_punycode_decode",
"punycode.encode": "scr_punycode_encode",
"punycode.toASCII": "scr_punycode_to_ascii",
"punycode.toUnicode": "scr_punycode_to_unicode",
"punycode.ucs2Decode": "scr_punycode_ucs2_decode",
"punycode.ucs2Encode": "scr_str_from_code_point",
"punycode.warn": "scr_punycode_warn",
"url.domainToUnicode": "scr_url_domain_to_unicode",
"url.pathToFileURL": "scr_url_from_path",
"url.pathToFileURLPlatform": "scr_url_from_path_platform",
@@ -42,6 +42,8 @@ export const SUPPORTED_BUILTIN_MODULES = [
"module",
"console",
"process",
"punycode",
"constants",
] as const;
/** Builtins Node itself serves ONLY under the node: prefix —
@@ -23,6 +23,7 @@ import {
builtinModulesArrayLit,
stdlibGlobalNameOf,
} from "./surfaces.js";
import { CRYPTO_CONSTANTS } from "./crypto-tables.js";
const PROTOTYPE_METHODS: Record<string, readonly string[]> = {
Date: ["getTime", "valueOf", "toISOString", "toJSON"],
@@ -680,3 +681,34 @@ export function lowerBuiltinLoaderValue(lowerer: Lowerer, loc: SrcLoc): IrExpr {
loc,
};
}
/** The deprecated node:constants module object: the runtime merges
* os.constants' groups and fs.constants with crypto.constants, which a
* lifted function answers from the baked table the first time the object
* is built. */
export function builtinConstantsObject(lowerer: Lowerer, loc: SrcLoc): IrExpr {
const name = "%builtin.constants.crypto";
if (!lowerer.builtinCallableValueFns.has(name)) {
lowerer.builtinCallableValueFns.set(name, name);
const fields = Object.entries(CRYPTO_CONSTANTS).map(([key, value]) => ({
key: strLit(key, loc),
value: box(builtinConstLit(value, loc)),
}));
lowerer.liftedFns.push({
name,
params: [],
returnType: DYN,
locals: [],
body: [{ kind: "return", value: { kind: "dynObjLit", fields, type: DYN, loc }, loc }],
loc,
});
}
const table: IrExpr = {
kind: "closure",
fnName: name,
captures: [],
type: { kind: "func", params: [], ret: DYN },
loc,
};
return { kind: "libCall", fn: "constants.object", args: [box(table)], type: DYN, loc };
}
@@ -216,6 +216,7 @@ import {
} from "./class-prototypes.js";
import { isClassCallback } from "./class-callbacks.js";
import {
builtinConstantsObject,
builtinPrototypeMethod,
lowerArrayIsArrayValue,
lowerCheckedPredicateValue,
@@ -1164,6 +1165,17 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
if (bi.module === "os" && bi.member === "constants") {
return { kind: "libCall", fn: "os.constants", args: [], type: DYN, loc };
}
// `import { O_RDONLY } from "constants"`: the member of the frozen
// module object.
if (bi.module === "constants") {
return {
kind: "dynKeyGet",
value: builtinConstantsObject(lowerer, loc),
key: { kind: "strLit", value: bi.member, type: STRING, loc },
type: DYN,
loc,
};
}
// tls.rootCertificates: a runtime-valued module constant (the
// cached bundled-CA array) — the one member read that lowers
// to a libCall instead of a baked literal.
@@ -1206,6 +1218,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
// their access sites only. The CommonJS namespace binding
// (`const lib = require("./lib.js")`) gets the same fence.
const builtinNamespace = lowerer.builtinNamespaceModuleOf(expr);
if (builtinNamespace === "constants") return builtinConstantsObject(lowerer, loc);
if (builtinNamespace !== null) {
const type: IrType = { kind: "moduleNs", moduleId: `builtin:${builtinNamespace}` };
return { kind: "moduleNsRef", moduleId: type.moduleId, type, loc };
@@ -1495,6 +1508,7 @@ function lowerExprInner(lowerer: Lowerer, expr: ts.Expression): IrExpr {
return { kind: "moduleNsRef", moduleId: moduleNs.moduleId, type: moduleNs, loc };
}
const builtinNs = lowerer.builtinNamespaceModuleOf(expr);
if (builtinNs === "constants") return builtinConstantsObject(lowerer, loc);
if (builtinNs !== null) {
const type: IrType = { kind: "moduleNs", moduleId: `builtin:${builtinNs}` };
return { kind: "moduleNsRef", moduleId: type.moduleId, type, loc };
@@ -2656,6 +2656,7 @@ export function lowerFileInit(
loc: loc0,
});
}
if (sf === lowerer.entry) header.push(...lowerer.punycodeLinkWarning());
for (const { stmt, dep } of orderedImportsOf(lowerer.program, sf)) {
const npm = lowerer.npmInitActions.get(sf)?.get(stmt);
if (npm !== undefined && npm.length > 0) {
@@ -1557,7 +1557,9 @@ export function lowerVarStatement(lowerer: Lowerer, stmt: ts.VariableStatement):
// Relative requires keep the fence: their module-init-at-position
// semantics needs storage this model doesn't represent.
if (canonicalBuiltinModule(spec) !== null) {
return [];
return canonicalBuiltinModule(spec) === "punycode"
? [lowerer.punycodeRequireWarning(decl)]
: [];
}
lowerer.unsupported(
"SC1090",
@@ -128,6 +128,7 @@ import { resolve } from "node:path";
import { bindingSource } from "../binding-source.js";
import { tsgoPath } from "../dts-paths.js";
import * as ts from "../ts7/adapter.js";
import { erasedTypeOnlyImport } from "../type-only-imports.js";
import { literalValues } from "../literal-values.js";
import { discriminantOwners, literalUnionArm, type UnionLiteral } from "../union-discriminants.js";
import type { ScrDiagnostic } from "../../diagnostics/diagnostic.js";
@@ -3745,6 +3746,65 @@ export class Lowerer {
for (const fp of parts) if (!indexOf.has(fp.sf)) visit(fp.sf);
}
/** node:punycode's load at a require() call: the module body reports
* DEP0040 when it first runs, unless isInsideNodeModules() finds the
* requiring frame in a dependency, and process.emitWarning delivers the
* report on the next tick. Later loads are cache hits either way. */
punycodeRequireWarning(node: ts.Node): IrStmt {
const loc = locOf(node);
const fromDependency = /[\\/]node_modules[\\/]/.test(node.getSourceFile().fileName);
return {
kind: "exprStmt",
expr: {
kind: "libCall",
fn: "punycode.warn",
args: [
{ kind: "boolLit", value: false, type: BOOL, loc },
{ kind: "boolLit", value: !fromDependency, type: BOOL, loc },
],
type: VOID,
loc,
},
loc,
};
}
/** node:punycode's DEP0040 for a static ES import anywhere in the module
* graph: Node links builtins before any module evaluates, so the report
* comes first, before the program can register a 'warning' listener —
* whichever file imports it (a dependency's ES import has no requiring
* frame either). Prepended to the entry's %init. */
punycodeLinkWarning(): IrStmt[] {
const imports = this.moduleOrder.some((sf) =>
sf.statements.some(
(stmt) =>
((ts.isImportDeclaration(stmt) && !erasedTypeOnlyImport(stmt)) ||
(ts.isExportDeclaration(stmt) && !stmt.isTypeOnly)) &&
stmt.moduleSpecifier !== undefined &&
ts.isStringLiteral(stmt.moduleSpecifier) &&
canonicalBuiltinModule(stmt.moduleSpecifier.text) === "punycode",
),
);
if (!imports) return [];
const loc: SrcLoc = { file: this.entry.fileName, start: 0, end: 0 };
return [
{
kind: "exprStmt",
expr: {
kind: "libCall",
fn: "punycode.warn",
args: [
{ kind: "boolLit", value: true, type: BOOL, loc },
{ kind: "boolLit", value: true, type: BOOL, loc },
],
type: VOID,
loc,
},
loc,
},
];
}
/** The lowering of a CommonJS `require("./local")` occurrence: a call of
* the required module's run-once %init at exactly this statement's
* position — Node's inline evaluation, with the guard supplying the
@@ -3754,6 +3814,7 @@ export class Lowerer {
* their external dependencies by name). Null for everything else
* (builtins load nothing; the rest kept its preflight fence). */
requireInitStmt(spec: string, node: ts.Node): IrStmt | null {
if (canonicalBuiltinModule(spec) === "punycode") return this.punycodeRequireWarning(node);
// Project requires resolve within the program; another bare require can
// be a program-module edge when it names an opted-in --npm-static package
// (one package requiring another — the resolution answered its shipped
@@ -11286,7 +11347,23 @@ export class Lowerer {
return { module: "timers/promises", member: `scheduler.${access.name.text}` };
}
}
// punycode.ucs2.decode / encode: methods of the module's ucs2 object,
// dispatched as module members.
if (access.name.text === "decode" || access.name.text === "encode") {
const owner = access.expression;
const imported = ts.isIdentifier(owner)
? this.builtinImportOf(owner)
: ts.isPropertyAccessExpression(owner)
? this.builtinMemberOf(owner)
: null;
if (imported?.module === "punycode" && imported.member === "ucs2") {
return { module: "punycode", member: `ucs2.${access.name.text}` };
}
}
const module = this.builtinNamespaceModuleOf(access.expression);
// node:constants is one checked-dynamic object: its members read
// through the object itself (builtinConstantsObject), not the tables.
if (module === "constants") return null;
if (module !== null) return { module, member: access.name.text };
// The events module object IS the EventEmitter class, so the class's
// defaultMaxListeners is the module property.
@@ -1801,6 +1801,22 @@ export const BUILTIN_MODULE_FNS: Record<
valueParams: [{ mode: "required", type: STRING }],
},
},
// node:punycode (deprecated): punycode.js 2.1.0's conversions over
// strings (scr_url.c). ucs2.decode/encode are members of the module's
// ucs2 object, dispatched as module members (builtinMemberOf). The
// DEP0040 report rides the module's load sites (punycodeLoadWarning).
punycode: {
decode: { fn: "punycode.decode", params: [STRING], result: STRING },
encode: { fn: "punycode.encode", params: [STRING], result: STRING },
toASCII: { fn: "punycode.toASCII", params: [STRING], result: STRING },
toUnicode: { fn: "punycode.toUnicode", params: [STRING], result: STRING },
"ucs2.decode": { fn: "punycode.ucs2Decode", params: [STRING], result: arrayOf(F64) },
"ucs2.encode": { fn: "punycode.ucs2Encode", params: [arrayOf(F64)], result: STRING },
},
// node:constants (deprecated) is one frozen object (Object.assign of
// os.constants' groups, fs.constants and crypto.constants): bindings of
// the module read it as a checked-dynamic value (builtinConstantsObject).
constants: {},
// node:readline: createInterface's options are entirely special-cased
// (exactly { input: process.stdin, output?: process.stdout } — see
// lowerBuiltinModuleCall); the entry carries the canonical shape. The
@@ -1930,6 +1946,7 @@ export const BUILTIN_MODULE_CONSTS: Record<
os: { EOL: "\n", devNull: "/dev/null" },
worker_threads: { isMainThread: true, threadId: 0 },
cluster: { isPrimary: true, isMaster: true, isWorker: false },
punycode: { version: "2.1.0" },
// zlib's module-level copies of its constants (every member outside
// BROTLI_*, Node's own loop over zlib.constants).
zlib: Object.fromEntries(
@@ -466,6 +466,13 @@ export const MAY_THROW_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
"str.encodeUri",
"qs.escape",
"qs.stringify",
// node:punycode's RangeErrors (encode/toASCII only past 32-bit deltas)
// and String.fromCodePoint's for ucs2.encode.
"punycode.decode",
"punycode.encode",
"punycode.toASCII",
"punycode.toUnicode",
"punycode.ucs2Encode",
// The base64 globals: atob/btoa throw the catchable DOMException
// InvalidCharacterError on malformed input; the zero-argument form
// always throws Node's TypeError [ERR_MISSING_ARGS].
@@ -559,6 +559,10 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
/** os.constants: the per-thread null-prototype constants object (dlopen,
* errno, signals, priority, UV_UDP_REUSEADDR). Never throws. */
"os.constants": { argTypes: [], result: DYN },
/** The deprecated node:constants object (scr_os.c): the os groups and
* fs.constants merged with the borrowed baked crypto.constants table,
* frozen, one per thread. */
"constants.object": { argTypes: [DYN], result: DYN },
/** umask(2): arg < 0 reads without setting (umask has no read-only form
* — set 0, restore); otherwise sets and answers the PREVIOUS mask.
* Never throws. */
@@ -856,6 +860,19 @@ export const LIB_FN_SIGS = defineLibFnSignatures({
"url.legacyResolve": { argTypes: [DYN, DYN, BOOL], result: STRING },
"url.domainToASCII": { argTypes: [DYN, BOOL], result: STRING },
"url.domainToUnicode": { argTypes: [DYN, BOOL], result: STRING },
/** node:punycode (scr_url.c): punycode.js 2.1.0's string conversions;
* decode/toUnicode (and, past 32-bit deltas, encode/toASCII) throw the
* module's RangeErrors, ucs2Encode String.fromCodePoint's. warn marks the
* module's first load (scr_async_dyn.c) and reports DEP0040 then unless
* the second argument is false: at once for a static ES import, on the
* next tick for a require() call. */
"punycode.decode": { argTypes: [STRING], result: STRING },
"punycode.encode": { argTypes: [STRING], result: STRING },
"punycode.toASCII": { argTypes: [STRING], result: STRING },
"punycode.toUnicode": { argTypes: [STRING], result: STRING },
"punycode.ucs2Decode": { argTypes: [STRING], result: arrayOf(F64) },
"punycode.ucs2Encode": { argTypes: [arrayOf(F64)], result: STRING },
"punycode.warn": { argTypes: [BOOL, BOOL], result: VOID },
"url.fileURLToPathBuffer": { argTypes: [DYN, DYN], result: BYTES_U8 },
"url.pathToFileURLChecked": { argTypes: [DYN, DYN], result: URL_T },
"url.fileURLToPathStr": { argTypes: [STRING], result: STRING },
@@ -43,6 +43,8 @@ export interface RuntimeFeatures {
}
const DYN_ASYNC_LIB_FNS: ReadonlySet<IrLibFn> = new Set([
// DEP0040 rides the dyn-async unit's warning machinery.
"punycode.warn",
"fs.callbackValue",
"fs.callbackCall",
"fs.callbackGlob",
+57
View File
@@ -466,6 +466,13 @@
"status": "static",
"note": "recognized module (bare and node:-prefixed specifiers)"
},
{
"id": "node-builtin.constants",
"kind": "node-builtin",
"name": "constants",
"status": "static",
"note": "recognized module (bare and node:-prefixed specifiers)"
},
{
"id": "node-builtin.crypto",
"kind": "node-builtin",
@@ -2095,6 +2102,56 @@
"status": "static",
"note": "shared version dictionary containing node and openssl; other components are absent unless defined by the program"
},
{
"id": "node-builtin.punycode",
"kind": "node-builtin",
"name": "punycode",
"status": "static",
"note": "recognized module (bare and node:-prefixed specifiers)"
},
{
"id": "node-builtin.punycode.decode",
"kind": "node-builtin",
"name": "punycode.decode",
"status": "static"
},
{
"id": "node-builtin.punycode.encode",
"kind": "node-builtin",
"name": "punycode.encode",
"status": "static"
},
{
"id": "node-builtin.punycode.toASCII",
"kind": "node-builtin",
"name": "punycode.toASCII",
"status": "static"
},
{
"id": "node-builtin.punycode.toUnicode",
"kind": "node-builtin",
"name": "punycode.toUnicode",
"status": "static"
},
{
"id": "node-builtin.punycode.ucs2.decode",
"kind": "node-builtin",
"name": "punycode.ucs2.decode",
"status": "static"
},
{
"id": "node-builtin.punycode.ucs2.encode",
"kind": "node-builtin",
"name": "punycode.ucs2.encode",
"status": "static"
},
{
"id": "node-builtin.punycode.version",
"kind": "node-builtin",
"name": "punycode.version",
"status": "static",
"note": "constant value read"
},
{
"id": "node-builtin.querystring",
"kind": "node-builtin",
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6122-punycode-conversions.ts"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6124-punycode-require-warnings.cjs"
],
"diags": []
}
@@ -0,0 +1,6 @@
{
"order": [
"<repo>/tests/corpus/6125-constants-module.cjs"
],
"diags": []
}
@@ -0,0 +1,18 @@
{
"order": [
"<repo>/tests/corpus/6126-punycode-dependency/main.cjs"
],
"diags": [
{
"code": "SC1010",
"message": "the 'puny-dep' module is not supported yet",
"loc": {
"file": "<repo>/tests/corpus/6126-punycode-dependency/main.cjs",
"start": 347,
"end": 376
},
"milestone": "M4",
"hint": "relative imports (./file, ../dir/file), package.json-mediated project imports (#alias via the imports field, self-name references via exports), statically compiled npm packages, and the built-in fs, fs/promises, path, os, url, crypto, zlib, child_process, net, http, tls, https, http2, dgram, dns, util, util/types, string_decoder, querystring, readline, events, stream, stream/promises, stream/consumers, buffer, assert, assert/strict, worker_threads, cluster, tty, async_hooks, timers, timers/promises, diagnostics_channel, perf_hooks, module, and console modules (bare or node:-prefixed) and node:test (node:-prefixed only, like in Node) are supported"
}
]
}
+5
View File
@@ -212,6 +212,11 @@ void scr_process_on_warning(ScrDyn *fn);
void scr_process_off_warning(ScrDyn *fn);
void scr_process_emit_warning(ScrDyn *args);
void scr_emit_warning(const char *name, const char *code, ScrStr *message);
/* node:punycode's first load (scr_async_dyn.c): later calls are module
* cache hits. The first reports DEP0040 when `report` (false for a require
* made from node_modules) — at once for a static ES import, which Node
* links before any module evaluates, otherwise on the next tick. */
void scr_punycode_warn(bool immediate, bool report);
/* scr_emit_warning once a 'warning' listener exists (scr_lib.c; set by the
* listener registration): units that cannot link the dyn-async unit route
* their warnings through it, and print the default report otherwise. */
+21
View File
@@ -813,6 +813,27 @@ void scr_warning_emit_deferred(ScrDyn *w) {
scr_warning_defer(w, scr_warning_tick);
}
static SCR_TL bool scr_punycode_loaded = false;
void scr_punycode_warn(bool immediate, bool report) {
if (scr_punycode_loaded) return;
scr_punycode_loaded = true;
if (!report) return;
static const char text[] =
"The `punycode` module is deprecated. Please use a userland alternative instead.";
ScrStr *message = scr_str_new(text, sizeof text - 1);
ScrStr *code = scr_str_new("DEP0040", 7);
ScrDyn *w = scr_warning_new(message, "DeprecationWarning", code);
if (immediate) {
if (!scr_process_deprecation_flag_on(0)) scr_warning_dispatch(w);
} else {
scr_warning_emit_deferred(w);
}
scr_dyn_release(w);
scr_str_release(code);
scr_str_release(message);
}
/* The console members' warnings. Node's emitWarning emits on
* process.nextTick, so these queue in order and each rides one raw tick
* on the shared tick queue; any left at exit are dropped with it. */
+194
View File
@@ -27,6 +27,8 @@
#include <sys/utsname.h>
#include <unistd.h>
#endif
#include <fcntl.h>
#include <sys/stat.h>
#ifdef __linux__
#include <sys/sysinfo.h>
#endif
@@ -1286,3 +1288,195 @@ ScrDyn *scr_os_constants(void) {
scr_atexit(scr_os_constants_clear);
return scr_dyn_retain(os);
}
/* ── node:constants (deprecated, DEP0008) ──────────────────────────────
* Node's lib/constants.js: Object.assign({}, os.constants.dlopen, errno,
* priority, signals, fs.constants, crypto.constants), frozen, with an
* ordinary prototype. The fs group follows node_constants.cc's
* DefineFsConstants order and guards (libuv's UV_* values are fixed on
* POSIX); crypto_table (borrowed) is a compiler-emitted function that
* answers the baked crypto.constants table, called only on first use. One
* object per thread. */
#define SCR_FS_CONSTANT(o, name) scr_os_constant((o), #name, (double)(name))
static void scr_fs_constants(ScrDyn *o) {
scr_os_constant(o, "UV_FS_SYMLINK_DIR", 1);
scr_os_constant(o, "UV_FS_SYMLINK_JUNCTION", 2);
#ifdef O_RDONLY
SCR_FS_CONSTANT(o, O_RDONLY);
#endif
#ifdef O_WRONLY
SCR_FS_CONSTANT(o, O_WRONLY);
#endif
#ifdef O_RDWR
SCR_FS_CONSTANT(o, O_RDWR);
#endif
scr_os_constant(o, "UV_DIRENT_UNKNOWN", 0);
scr_os_constant(o, "UV_DIRENT_FILE", 1);
scr_os_constant(o, "UV_DIRENT_DIR", 2);
scr_os_constant(o, "UV_DIRENT_LINK", 3);
scr_os_constant(o, "UV_DIRENT_FIFO", 4);
scr_os_constant(o, "UV_DIRENT_SOCKET", 5);
scr_os_constant(o, "UV_DIRENT_CHAR", 6);
scr_os_constant(o, "UV_DIRENT_BLOCK", 7);
#ifdef S_IFMT
SCR_FS_CONSTANT(o, S_IFMT);
#endif
#ifdef S_IFREG
SCR_FS_CONSTANT(o, S_IFREG);
#endif
#ifdef S_IFDIR
SCR_FS_CONSTANT(o, S_IFDIR);
#endif
#ifdef S_IFCHR
SCR_FS_CONSTANT(o, S_IFCHR);
#endif
#ifdef S_IFBLK
SCR_FS_CONSTANT(o, S_IFBLK);
#endif
#ifdef S_IFIFO
SCR_FS_CONSTANT(o, S_IFIFO);
#endif
#ifdef S_IFLNK
SCR_FS_CONSTANT(o, S_IFLNK);
#endif
#ifdef S_IFSOCK
SCR_FS_CONSTANT(o, S_IFSOCK);
#endif
#ifdef O_CREAT
SCR_FS_CONSTANT(o, O_CREAT);
#endif
#ifdef O_EXCL
SCR_FS_CONSTANT(o, O_EXCL);
#endif
/* libuv's Windows-only open flags are 0 everywhere else. */
scr_os_constant(o, "UV_FS_O_FILEMAP", 0);
scr_os_constant(o, "UV_FS_O_TEMPORARY", 0);
scr_os_constant(o, "UV_FS_O_SHORT_LIVED", 0);
scr_os_constant(o, "UV_FS_O_SEQUENTIAL", 0);
scr_os_constant(o, "UV_FS_O_RANDOM", 0);
#ifdef O_NOCTTY
SCR_FS_CONSTANT(o, O_NOCTTY);
#endif
#ifdef O_TRUNC
SCR_FS_CONSTANT(o, O_TRUNC);
#endif
#ifdef O_APPEND
SCR_FS_CONSTANT(o, O_APPEND);
#endif
#ifdef O_DIRECTORY
SCR_FS_CONSTANT(o, O_DIRECTORY);
#endif
#ifdef O_NOATIME
SCR_FS_CONSTANT(o, O_NOATIME);
#endif
#ifdef O_NOFOLLOW
SCR_FS_CONSTANT(o, O_NOFOLLOW);
#endif
#ifdef O_SYNC
SCR_FS_CONSTANT(o, O_SYNC);
#endif
#ifdef O_DSYNC
SCR_FS_CONSTANT(o, O_DSYNC);
#endif
#ifdef O_SYMLINK
SCR_FS_CONSTANT(o, O_SYMLINK);
#endif
#ifdef O_DIRECT
SCR_FS_CONSTANT(o, O_DIRECT);
#endif
#ifdef O_NONBLOCK
SCR_FS_CONSTANT(o, O_NONBLOCK);
#endif
#ifdef S_IRWXU
SCR_FS_CONSTANT(o, S_IRWXU);
#endif
#ifdef S_IRUSR
SCR_FS_CONSTANT(o, S_IRUSR);
#endif
#ifdef S_IWUSR
SCR_FS_CONSTANT(o, S_IWUSR);
#endif
#ifdef S_IXUSR
SCR_FS_CONSTANT(o, S_IXUSR);
#endif
#ifdef S_IRWXG
SCR_FS_CONSTANT(o, S_IRWXG);
#endif
#ifdef S_IRGRP
SCR_FS_CONSTANT(o, S_IRGRP);
#endif
#ifdef S_IWGRP
SCR_FS_CONSTANT(o, S_IWGRP);
#endif
#ifdef S_IXGRP
SCR_FS_CONSTANT(o, S_IXGRP);
#endif
#ifdef S_IRWXO
SCR_FS_CONSTANT(o, S_IRWXO);
#endif
#ifdef S_IROTH
SCR_FS_CONSTANT(o, S_IROTH);
#endif
#ifdef S_IWOTH
SCR_FS_CONSTANT(o, S_IWOTH);
#endif
#ifdef S_IXOTH
SCR_FS_CONSTANT(o, S_IXOTH);
#endif
#ifdef F_OK
SCR_FS_CONSTANT(o, F_OK);
#endif
#ifdef R_OK
SCR_FS_CONSTANT(o, R_OK);
#endif
#ifdef W_OK
SCR_FS_CONSTANT(o, W_OK);
#endif
#ifdef X_OK
SCR_FS_CONSTANT(o, X_OK);
#endif
scr_os_constant(o, "UV_FS_COPYFILE_EXCL", 1);
scr_os_constant(o, "COPYFILE_EXCL", 1);
scr_os_constant(o, "UV_FS_COPYFILE_FICLONE", 2);
scr_os_constant(o, "COPYFILE_FICLONE", 2);
scr_os_constant(o, "UV_FS_COPYFILE_FICLONE_FORCE", 4);
scr_os_constant(o, "COPYFILE_FICLONE_FORCE", 4);
}
#undef SCR_FS_CONSTANT
static SCR_TL ScrDyn *scr_node_constants_value;
static void scr_node_constants_clear(void) {
ScrDyn *value = scr_node_constants_value;
scr_node_constants_value = NULL;
scr_dyn_release(value);
}
ScrDyn *scr_node_constants(const ScrDyn *crypto_table) {
if (scr_node_constants_value) return scr_dyn_retain(scr_node_constants_value);
ScrDyn *crypto = scr_dyn_call(crypto_table, NULL, 0, "constants");
if (!crypto) return NULL;
ScrDyn *o = scr_dyn_new_obj();
scr_os_dlopen_constants(o);
scr_os_errno_constants(o);
scr_os_constant(o, "PRIORITY_LOW", SCR_OS_PRIORITY_LOW);
scr_os_constant(o, "PRIORITY_BELOW_NORMAL", SCR_OS_PRIORITY_BELOW_NORMAL);
scr_os_constant(o, "PRIORITY_NORMAL", SCR_OS_PRIORITY_NORMAL);
scr_os_constant(o, "PRIORITY_ABOVE_NORMAL", SCR_OS_PRIORITY_ABOVE_NORMAL);
scr_os_constant(o, "PRIORITY_HIGH", SCR_OS_PRIORITY_HIGH);
scr_os_constant(o, "PRIORITY_HIGHEST", SCR_OS_PRIORITY_HIGHEST);
scr_os_signal_constants(o);
scr_fs_constants(o);
if (crypto && crypto->kind == SCR_DYN_OBJ) {
for (size_t i = 0; i < crypto->v.obj.len; i++) {
const ScrDynEntry *entry = &crypto->v.obj.entries[i];
if (entry->accessor || !entry->value) continue;
scr_dyn_obj_set(o, entry->key, entry->key_len, scr_dyn_retain(entry->value));
}
}
scr_dyn_release(crypto);
scr_dyn_release(scr_dyn_freeze(o));
scr_node_constants_value = o;
scr_atexit(scr_node_constants_clear);
return scr_dyn_retain(o);
}
+4
View File
@@ -34,6 +34,10 @@ ScrStr *scr_os_cpus_model(double i);
double scr_os_cpus_field(double i, double field);
/* os.constants: the per-thread null-prototype constants object (+1). */
ScrDyn *scr_os_constants(void);
/* The deprecated node:constants module object (scr_os.c): the os groups,
* fs.constants and the crypto table the borrowed function answers, merged
* and frozen (+1). */
ScrDyn *scr_node_constants(const ScrDyn *crypto_table);
double scr_os_available_parallelism(void); /* uv_available_parallelism: CPUs this process may use, >= 1 */
/* os.userInfo()'s field trio (pw_name / pw_shell / pw_dir — the passwd
* home, not the $HOME cascade). +1 fresh; abort on lookup failure. */
+339
View File
@@ -1988,3 +1988,342 @@ ScrBytes *scr_url_checked_to_path_buffer(const ScrDyn *value, const ScrDyn *opti
scr_url_release(url);
return result;
}
/* ── node:punycode (deprecated, DEP0040) ───────────────────────────────
* A port of the punycode.js 2.1.0 that Node 24 bundles as lib/punycode.js.
* It works on UTF-16 code units: ucs2.decode pairs surrogates and keeps
* lone ones, which is exactly a code-point walk over WTF-8 storage.
* Errors throw the module's RangeErrors ("Overflow: ...", "Illegal input
* >= 0x80 ...", "Invalid input"); decoded code points past U+10FFFF throw
* String.fromCodePoint's "Invalid code point N". All inputs are borrowed;
* results are +1, or NULL with the error pending. The DEP0040 report is
* scr_punycode_warn (scr_async_dyn.c). */
enum {
PC_MAX_INT = 2147483647,
PC_BASE = 36,
PC_TMIN = 1,
PC_TMAX = 26,
PC_SKEW = 38,
PC_DAMP = 700,
PC_INITIAL_BIAS = 72,
PC_INITIAL_N = 128
};
static void pc_error(const char *message) {
scr_throw_error_msg(SCR_ERR_RANGE, message, strlen(message));
}
#define PC_OVERFLOW "Overflow: input needs wider integers to process"
#define PC_NOT_BASIC "Illegal input >= 0x80 (not a basic code point)"
#define PC_INVALID "Invalid input"
typedef struct {
uint32_t *data;
size_t len, cap;
} PcPoints;
static void pc_push(PcPoints *p, uint32_t cp) {
if (p->len == p->cap) {
size_t cap = p->cap ? p->cap * 2 : 16;
uint32_t *grown = realloc(p->data, cap * sizeof *grown);
if (!grown) scr_trap("scriptc: out of memory\n");
p->data = grown;
p->cap = cap;
}
p->data[p->len++] = cp;
}
/* ucs2decode over WTF-8 storage: a pair is one 4-byte sequence, a lone
* surrogate one 3-byte sequence carrying its own value. */
static void pc_code_points(const char *s, size_t n, PcPoints *out) {
const unsigned char *b = (const unsigned char *)s;
for (size_t i = 0; i < n;) {
uint32_t c = b[i];
if (c < 0x80) {
i += 1;
} else if (c < 0xE0 && i + 1 < n) {
c = ((c & 0x1F) << 6) | (b[i + 1] & 0x3F);
i += 2;
} else if (c < 0xF0 && i + 2 < n) {
c = ((c & 0x0F) << 12) | ((uint32_t)(b[i + 1] & 0x3F) << 6) | (b[i + 2] & 0x3F);
i += 3;
} else if (i + 3 < n) {
c = ((c & 0x07) << 18) | ((uint32_t)(b[i + 1] & 0x3F) << 12) |
((uint32_t)(b[i + 2] & 0x3F) << 6) | (b[i + 3] & 0x3F);
i += 4;
} else {
i += 1;
}
pc_push(out, c);
}
}
static int64_t pc_adapt(int64_t delta, int64_t points, bool first) {
int64_t k = 0;
delta = first ? delta / PC_DAMP : delta >> 1;
delta += delta / points;
for (; delta > ((PC_BASE - PC_TMIN) * PC_TMAX) >> 1; k += PC_BASE) delta /= PC_BASE - PC_TMIN;
return k + (PC_BASE - PC_TMIN + 1) * delta / (delta + PC_SKEW);
}
static int64_t pc_basic_to_digit(unsigned char c) {
if (c >= 0x30 && c < 0x3A) return 26 + (c - 0x30);
if (c >= 0x41 && c < 0x5B) return c - 0x41;
if (c >= 0x61 && c < 0x7B) return c - 0x61;
return PC_BASE;
}
static char pc_digit_to_basic(int64_t digit) {
return (char)(digit + 22 + 75 * (digit < 26));
}
/* String.fromCodePoint(...points). */
static ScrStr *pc_from_points(const PcPoints *p) {
for (size_t i = 0; i < p->len; i++) {
if (p->data[i] > 0x10FFFF) {
char message[48];
int size = snprintf(message, sizeof message, "Invalid code point %u", (unsigned)p->data[i]);
scr_throw_error_msg(SCR_ERR_RANGE, message, (size_t)size);
return NULL;
}
}
char *out = malloc(p->len * 4 + 1);
if (!out) scr_trap("scriptc: out of memory\n");
size_t offset = 0;
for (size_t i = 0; i < p->len; i++) {
uint32_t point = p->data[i];
if (point >= 0xD800 && point <= 0xDBFF && i + 1 < p->len && p->data[i + 1] >= 0xDC00 &&
p->data[i + 1] <= 0xDFFF) {
point = 0x10000 + ((point - 0xD800) << 10) + (p->data[i + 1] - 0xDC00);
i++;
}
offset += scr_wtf8_put(out + offset, point);
}
ScrStr *result = scr_str_new(out, offset);
free(out);
return result;
}
/* punycode.decode over the bytes of one label. Any non-ASCII byte fails
* where its first code unit would: before the last delimiter it is not
* basic, after it it is no digit — so byte positions stand in for code
* unit positions until the first error. */
static ScrStr *pc_decode(const char *s, size_t n) {
PcPoints output = {0};
size_t basic = 0;
for (size_t k = n; k > 0; k--) {
if (s[k - 1] == '-') {
basic = k - 1;
break;
}
}
ScrStr *result = NULL;
for (size_t j = 0; j < basic; j++) {
if ((unsigned char)s[j] >= 0x80) {
pc_error(PC_NOT_BASIC);
goto done;
}
pc_push(&output, (unsigned char)s[j]);
}
int64_t i = 0, cp = PC_INITIAL_N, bias = PC_INITIAL_BIAS;
for (size_t index = basic > 0 ? basic + 1 : 0; index < n;) {
int64_t old = i;
for (int64_t w = 1, k = PC_BASE;; k += PC_BASE) {
if (index >= n) {
pc_error(PC_INVALID);
goto done;
}
int64_t digit = pc_basic_to_digit((unsigned char)s[index++]);
if (digit >= PC_BASE) {
pc_error(PC_INVALID);
goto done;
}
if (digit > (PC_MAX_INT - i) / w) {
pc_error(PC_OVERFLOW);
goto done;
}
i += digit * w;
int64_t t = k <= bias ? PC_TMIN : (k >= bias + PC_TMAX ? PC_TMAX : k - bias);
if (digit < t) break;
if (w > PC_MAX_INT / (PC_BASE - t)) {
pc_error(PC_OVERFLOW);
goto done;
}
w *= PC_BASE - t;
}
int64_t out = (int64_t)output.len + 1;
bias = pc_adapt(i - old, out, old == 0);
if (i / out > PC_MAX_INT - cp) {
pc_error(PC_OVERFLOW);
goto done;
}
cp += i / out;
i %= out;
pc_push(&output, 0);
memmove(output.data + i + 1, output.data + i, (output.len - 1 - (size_t)i) * sizeof *output.data);
output.data[i++] = (uint32_t)cp;
}
result = pc_from_points(&output);
done:
free(output.data);
return result;
}
/* punycode.encode of one label's code points, appended to buf. */
static bool pc_encode(const PcPoints *input, ScrJsonBuf *buf) {
int64_t cp = PC_INITIAL_N, delta = 0, bias = PC_INITIAL_BIAS;
size_t basic_length = 0;
for (size_t k = 0; k < input->len; k++) {
if (input->data[k] < 0x80) {
scr_jb_putc(buf, (char)input->data[k]);
basic_length++;
}
}
size_t handled = basic_length;
if (basic_length) scr_jb_putc(buf, '-');
while (handled < input->len) {
int64_t m = PC_MAX_INT;
for (size_t k = 0; k < input->len; k++)
if (input->data[k] >= cp && input->data[k] < m) m = input->data[k];
int64_t handled_plus_one = (int64_t)handled + 1;
if (m - cp > (PC_MAX_INT - delta) / handled_plus_one) {
pc_error(PC_OVERFLOW);
return false;
}
delta += (m - cp) * handled_plus_one;
cp = m;
for (size_t k = 0; k < input->len; k++) {
int64_t current = input->data[k];
if (current < cp && ++delta > PC_MAX_INT) {
pc_error(PC_OVERFLOW);
return false;
}
if (current == cp) {
int64_t q = delta;
for (int64_t j = PC_BASE;; j += PC_BASE) {
int64_t t = j <= bias ? PC_TMIN : (j >= bias + PC_TMAX ? PC_TMAX : j - bias);
if (q < t) break;
scr_jb_putc(buf, pc_digit_to_basic(t + (q - t) % (PC_BASE - t)));
q = (q - t) / (PC_BASE - t);
}
scr_jb_putc(buf, pc_digit_to_basic(q));
bias = pc_adapt(delta, handled_plus_one, handled == basic_length);
delta = 0;
handled++;
}
}
delta++;
cp++;
}
return true;
}
ScrStr *scr_punycode_decode(const ScrStr *input) { return pc_decode(input->data, input->len); }
ScrStr *scr_punycode_encode(const ScrStr *input) {
PcPoints points = {0};
pc_code_points(input->data, input->len, &points);
ScrJsonBuf buf;
scr_jb_init(&buf);
bool ok = pc_encode(&points, &buf);
free(points.data);
ScrStr *result = scr_jb_finish(&buf);
if (ok) return result;
scr_str_release(result);
return NULL;
}
ScrArr *scr_punycode_ucs2_decode(const ScrStr *input) {
PcPoints points = {0};
pc_code_points(input->data, input->len, &points);
ScrArr *out = scr_arr_new(SCR_ELEM_F64, points.len);
for (size_t i = 0; i < points.len; i++) scr_arr_push_f64(out, (double)points.data[i]);
free(points.data);
return out;
}
/* mapDomain: only the part after the first '@' maps (and only up to a
* second one — punycode.js keeps parts[0] and parts[1]); the RFC 3490
* separators U+002E, U+3002, U+FF0E and U+FF61 all split labels, and the
* labels rejoin with '.'. */
static ScrStr *pc_map_domain(const ScrStr *input, bool to_ascii) {
const char *s = input->data;
size_t n = input->len;
ScrJsonBuf buf;
scr_jb_init(&buf);
const char *at = memchr(s, '@', n);
if (at) {
scr_jb_write(&buf, s, (size_t)(at - s) + 1);
const char *rest = at + 1;
const char *second = memchr(rest, '@', n - (size_t)(rest - s));
n = (second ? (size_t)(second - rest) : n - (size_t)(rest - s));
s = rest;
}
size_t start = 0;
bool ok = true;
for (size_t i = 0; ok && i <= n;) {
size_t sep = 0;
if (i == n) sep = 1;
else if (s[i] == '.') sep = 1;
else if (i + 2 < n && (unsigned char)s[i] == 0xE3 && (unsigned char)s[i + 1] == 0x80 &&
(unsigned char)s[i + 2] == 0x82)
sep = 3;
else if (i + 2 < n && (unsigned char)s[i] == 0xEF &&
(((unsigned char)s[i + 1] == 0xBC && (unsigned char)s[i + 2] == 0x8E) ||
((unsigned char)s[i + 1] == 0xBD && (unsigned char)s[i + 2] == 0xA1)))
sep = 3;
if (!sep) {
i++;
continue;
}
const char *label = s + start;
size_t len = i - start;
bool ascii = true;
for (size_t k = 0; k < len; k++) ascii = ascii && (unsigned char)label[k] < 0x80;
if (to_ascii && !ascii) {
PcPoints points = {0};
pc_code_points(label, len, &points);
scr_jb_puts(&buf, "xn--");
ok = pc_encode(&points, &buf);
free(points.data);
} else if (!to_ascii && len >= 4 && memcmp(label, "xn--", 4) == 0) {
/* string.slice(4).toLowerCase(): ASCII letters fold; the one other
* code point whose lowercase is ASCII is KELVIN SIGN (U+212A → k).
* Any other non-ASCII stays non-ASCII and fails in decode either
* way, at the same position. */
ScrJsonBuf lower;
scr_jb_init(&lower);
for (size_t k = 4; k < len; k++) {
unsigned char c = (unsigned char)label[k];
if (c == 0xE2 && k + 2 < len && (unsigned char)label[k + 1] == 0x84 &&
(unsigned char)label[k + 2] == 0xAA) {
scr_jb_putc(&lower, 'k');
k += 2;
} else {
scr_jb_putc(&lower, (char)(c >= 'A' && c <= 'Z' ? c + 32 : c));
}
}
ScrStr *folded = scr_jb_finish(&lower);
ScrStr *decoded = pc_decode(folded->data, folded->len);
scr_str_release(folded);
if (decoded) {
scr_jb_put_str(&buf, decoded);
scr_str_release(decoded);
} else {
ok = false;
}
} else {
scr_jb_write(&buf, label, len);
}
if (ok && i < n) scr_jb_putc(&buf, '.');
i += sep;
start = i;
}
ScrStr *result = scr_jb_finish(&buf);
if (ok) return result;
scr_str_release(result);
return NULL;
}
ScrStr *scr_punycode_to_ascii(const ScrStr *input) { return pc_map_domain(input, true); }
ScrStr *scr_punycode_to_unicode(const ScrStr *input) { return pc_map_domain(input, false); }
+9
View File
@@ -53,6 +53,15 @@ ScrDyn *scr_url_legacy_parse(const ScrDyn *url, const ScrDyn *parse_query, const
ScrStr *scr_url_legacy_format(const ScrDyn *object, const ScrDyn *options, bool warn);
ScrStr *scr_url_legacy_resolve(const ScrDyn *from, const ScrDyn *to, bool warn);
ScrStr *scr_url_domain_to_ascii(const ScrDyn *domain, bool missing);
/* node:punycode (scr_url.c): punycode.js 2.1.0's encode/decode, toASCII/
* toUnicode (mapDomain over RFC 3490 separators) and ucs2.decode (UTF-16
* code points as an f64 array). Borrowed input; +1, or NULL with the
* module's RangeError pending. */
ScrStr *scr_punycode_decode(const ScrStr *input);
ScrStr *scr_punycode_encode(const ScrStr *input);
ScrStr *scr_punycode_to_ascii(const ScrStr *input);
ScrStr *scr_punycode_to_unicode(const ScrStr *input);
ScrArr *scr_punycode_ucs2_decode(const ScrStr *input);
ScrStr *scr_url_domain_to_unicode(const ScrDyn *domain, bool missing);
ScrStr *scr_url_to_path(ScrUrl *u); /* +1, or throws */
ScrStr *scr_url_str_to_path(ScrStr *s); /* +1, or throws */
+45
View File
@@ -0,0 +1,45 @@
// @exit: 1
// @no-node-shims
// node:punycode's conversions (punycode.js 2.1.0): encode/decode of bare
// labels, toASCII/toUnicode over domains and email addresses (only the part
// after '@' maps; every RFC 3490 separator splits labels), ucs2 code point
// arrays with astral pairs and lone surrogates, and the RangeErrors of
// malformed input. A static import links the module before anything runs,
// so its DEP0040 report precedes the program and no 'warning' listener the
// program registers sees it (the Node side runs without the preload shim,
// which would delay that report past the program's first turn). The report
// carries the process id, so the @exit directive keeps stderr out of the
// comparison.
import * as punycode from "node:punycode";
process.on("warning", (w) => console.log("listener", w.name));
const show = (label: string, f: () => unknown): void => {
try {
console.log(label, JSON.stringify(f()));
} catch (e) {
const err = e as Error;
console.log(label, "threw", err instanceof RangeError, err.name, err.message);
}
};
console.log(punycode.version);
for (const s of ["mañana", "bücher", "ü", "abc", "", "-> $1.00 <-", "ليهمابتكلموشعربي؟", "他们为什么不说中文", "😀x", "MaJiでKoiする5秒前"]) {
show(`encode ${s}`, () => punycode.encode(s));
show(`roundtrip ${s}`, () => punycode.decode(punycode.encode(s)));
}
for (const s of ["maana-pta", "bcher-kva", "tda", "abc-", "-abc", "ABC-kva", "a-b-c-kva", "ü-kva", "kva-ü", "99999999999", "zzzzzzzzzzzzz", "a-", "-"]) {
show(`decode ${s}`, () => punycode.decode(s));
}
for (const d of ["mañana.com", "bücher.de", "example.com", "", "a..b", "ex@mple@bücher.de", "user@bücher.de", "日本。jp", "a.b。c", "ÄÖÜ.test", "a.b."]) {
show(`toASCII ${d}`, () => punycode.toASCII(d));
}
for (const d of ["xn--maana-pta.com", "xn--bcher-kva.de", "XN--bcher-kva.de", "xn--BCHER-KVA.de", "example.com", "user@xn--bcher-kva.de", "xn--.x", "xn--ü.x", "xn--zzzzzzzzzzzzz.x", "xn--a-Kva.x"]) {
show(`toUnicode ${d}`, () => punycode.toUnicode(d));
}
show("ucs2.decode", () => punycode.ucs2.decode("aé😀\ud800b\udc00"));
show("ucs2.encode", () => punycode.ucs2.encode([97, 0xe9, 0x1f600, 0xd800, 0xdc00, 98]));
show("ucs2.encode join", () => punycode.ucs2.encode([0xd83d, 0xde00]).length);
show("ucs2.encode range", () => punycode.ucs2.encode([0x110000]));
show("ucs2 roundtrip", () => punycode.ucs2.encode(punycode.ucs2.decode("x😀\udfffy")) === "x😀\udfffy");
process.exitCode = 1;
@@ -0,0 +1,32 @@
// DEP0040 for require("punycode"): reported once, on the next tick after
// the requiring turn, to the stderr report and the 'warning' listeners;
// require("node:punycode") is the same module and does not report again.
// process.noDeprecation drops the report and the listener call. The program
// re-runs itself per case and prints each child's output with process ids
// normalized.
"use strict";
const { spawnSync } = require("child_process");
const mode = process.argv[2];
if (mode === "direct") {
process.on("warning", (w) => console.log("listener", w.name, w.code, w.message));
process.nextTick(() => console.log("earlier tick"));
console.log("before require");
const direct = require("punycode");
console.log("after require", direct.toASCII("bücher.de"));
const prefixed = require("node:punycode");
console.log("prefixed", prefixed.toUnicode("xn--bcher-kva.de"));
setTimeout(() => console.log("timer"), 0);
} else if (mode === "silenced") {
process.on("warning", (w) => console.log("listener", w.code));
process.noDeprecation = true;
const silencedModule = require("punycode");
console.log("silenced", silencedModule.encode("ü"));
} else {
for (const child of ["direct", "silenced"]) {
const run = spawnSync(process.execPath, [process.argv[1], child], { encoding: "utf8" });
console.log(`== ${child}: status ${run.status}`);
console.log(run.stdout);
console.log(run.stderr.replace(/\(node:\d+\)/g, "(node:<pid>)"));
}
}
+26
View File
@@ -0,0 +1,26 @@
// node:constants (deprecated in documentation only, so no warning): one
// frozen object merging os.constants' dlopen, errno, priority and signals
// groups with fs.constants and crypto.constants, in that order. Members
// read like the owning modules' constants, and a constant the platform
// lacks is absent (graceful-fs probes O_SYMLINK with hasOwnProperty).
const constants = require("constants");
const nodeConstants = require("node:constants");
const os = require("os");
console.log(typeof constants, Object.isFrozen(constants), constants === nodeConstants);
console.log(constants.O_RDONLY, constants.O_WRONLY, constants.O_RDWR);
console.log(constants.O_WRONLY | constants.O_CREAT | constants.O_TRUNC);
console.log(constants.S_IFMT, constants.S_IFREG, constants.S_IFDIR, constants.S_IRWXU);
console.log(constants.F_OK, constants.R_OK, constants.W_OK, constants.X_OK);
console.log(constants.COPYFILE_EXCL, constants.UV_FS_COPYFILE_FICLONE, constants.UV_DIRENT_DIR);
console.log(constants.SIGTERM === os.constants.signals.SIGTERM, constants.SIGINT);
console.log(constants.ENOENT === os.constants.errno.ENOENT, constants.EACCES);
console.log(constants.PRIORITY_HIGH, constants.PRIORITY_LOW, constants.RTLD_LAZY);
console.log(constants.TLS1_2_VERSION, constants.RSA_PKCS1_PADDING, typeof constants.defaultCoreCipherList);
console.log(constants.hasOwnProperty("O_SYMLINK") === ("O_SYMLINK" in constants));
console.log(constants.hasOwnProperty("O_RDWR"), "SIGKILL" in constants, "UV_UDP_REUSEADDR" in constants);
console.log(constants.hasOwnProperty("NOT_A_CONSTANT"), "dlopen" in constants, "errno" in constants);
const keys = Object.keys(constants);
console.log(keys.indexOf("RTLD_LAZY") < keys.indexOf("E2BIG"), keys.indexOf("E2BIG") < keys.indexOf("PRIORITY_LOW"));
console.log(keys.indexOf("PRIORITY_LOW") < keys.indexOf("SIGHUP"), keys.indexOf("SIGHUP") < keys.indexOf("O_RDONLY"));
console.log(keys.indexOf("O_RDONLY") < keys.indexOf("OPENSSL_VERSION_NUMBER"), keys.includes("POINT_CONVERSION_HYBRID"));
@@ -0,0 +1,12 @@
// A dependency under node_modules that requires node:punycode: Node's
// isInsideNodeModules() rule skips DEP0040 for it, so nothing reaches stderr
// or the 'warning' listeners, and the program's own later require of the
// cached module does not report either.
"use strict";
process.on("warning", (w) => console.log("listener", w.code));
const toAscii = require("puny-dep");
const punycode = require("punycode");
console.log(toAscii("mañana.com"), toAscii("example.org"));
console.log(punycode.toUnicode("xn--maana-pta.com"));
setTimeout(() => console.log("timer"), 0);
@@ -0,0 +1,2 @@
declare function toAscii(domain: string): string;
export = toAscii;
+8
View File
@@ -0,0 +1,8 @@
// A dependency requiring node:punycode: Node's isInsideNodeModules() rule
// skips DEP0040 for it.
"use strict";
const punycode = require("punycode");
module.exports = function toAscii(domain) {
return punycode.toASCII(domain);
};
@@ -0,0 +1,5 @@
{
"name": "puny-dep",
"version": "1.0.0",
"main": "index.js"
}