diff --git a/docs/content/docs/limitations.mdx b/docs/content/docs/limitations.mdx index ed2c5b1ec..a66b2ffd6 100644 --- a/docs/content/docs/limitations.mdx +++ b/docs/content/docs/limitations.mdx @@ -74,6 +74,8 @@ title: "Limitations" ../limitations/builtin-apis/legacy-url-api.mdx +../limitations/builtin-apis/deprecated-modules.mdx + ../limitations/builtin-apis/filesystem-flags.mdx ../limitations/builtin-apis/copy.mdx diff --git a/docs/content/limitations/builtin-apis/deprecated-modules.mdx b/docs/content/limitations/builtin-apis/deprecated-modules.mdx new file mode 100644 index 000000000..bb630b8c3 --- /dev/null +++ b/docs/content/limitations/builtin-apis/deprecated-modules.mdx @@ -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. diff --git a/docs/src/generated/node-v24-compatibility-meta.json b/docs/src/generated/node-v24-compatibility-meta.json index 93149c51c..a063052f3 100644 --- a/docs/src/generated/node-v24-compatibility-meta.json +++ b/docs/src/generated/node-v24-compatibility-meta.json @@ -2,6 +2,6 @@ "schemaVersion": 3, "nodeVersion": "24.22.0", "nodeCommit": "f14cd3aa7bf9ca8df2cc5b8bb14beb0f335b1d93", - "artifactVersion": "a1ff929d49885bd8c92e", + "artifactVersion": "7b345b86e2e5f616a7a0", "rowCount": 3880 } diff --git a/docs/src/generated/node-v24-compatibility.json b/docs/src/generated/node-v24-compatibility.json index 352c211b5..62eb8283e 100644 --- a/docs/src/generated/node-v24-compatibility.json +++ b/docs/src/generated/node-v24-compatibility.json @@ -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" } }, { diff --git a/internal/compatibility/generated/node-v24-backlog.json b/internal/compatibility/generated/node-v24-backlog.json index fff784adc..c4ce78314 100644 --- a/internal/compatibility/generated/node-v24-backlog.json +++ b/internal/compatibility/generated/node-v24-backlog.json @@ -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" + ] } } }, diff --git a/internal/compatibility/generated/node-v24-internal.json b/internal/compatibility/generated/node-v24-internal.json index 559bd48ca..58ffc3399 100644 --- a/internal/compatibility/generated/node-v24-internal.json +++ b/internal/compatibility/generated/node-v24-internal.json @@ -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" }, diff --git a/internal/compatibility/static-support.json b/internal/compatibility/static-support.json index d68011393..5332ddd4c 100644 --- a/internal/compatibility/static-support.json +++ b/internal/compatibility/static-support.json @@ -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"], diff --git a/packages/compiler/ambient/scriptc-node-fallback.d.ts b/packages/compiler/ambient/scriptc-node-fallback.d.ts index d87172f61..2c53bb523 100644 --- a/packages/compiler/ambient/scriptc-node-fallback.d.ts +++ b/packages/compiler/ambient/scriptc-node-fallback.d.ts @@ -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), diff --git a/packages/compiler/src/backend/llvm/lib-dispatch.ts b/packages/compiler/src/backend/llvm/lib-dispatch.ts index 50804bf5a..92dbaea76 100644 --- a/packages/compiler/src/backend/llvm/lib-dispatch.ts +++ b/packages/compiler/src/backend/llvm/lib-dispatch.ts @@ -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); diff --git a/packages/compiler/src/backend/llvm/lib-shared.ts b/packages/compiler/src/backend/llvm/lib-shared.ts index 9232cbd03..700044da2 100644 --- a/packages/compiler/src/backend/llvm/lib-shared.ts +++ b/packages/compiler/src/backend/llvm/lib-shared.ts @@ -192,6 +192,7 @@ export const LIB_FN_SYMS: Record = { "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 = { "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", diff --git a/packages/compiler/src/frontend/builtin-modules.ts b/packages/compiler/src/frontend/builtin-modules.ts index b45dce6d7..3a1acb934 100644 --- a/packages/compiler/src/frontend/builtin-modules.ts +++ b/packages/compiler/src/frontend/builtin-modules.ts @@ -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 — diff --git a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts index a9df9e662..c05430ad3 100644 --- a/packages/compiler/src/frontend/lowering/lower-builtin-values.ts +++ b/packages/compiler/src/frontend/lowering/lower-builtin-values.ts @@ -23,6 +23,7 @@ import { builtinModulesArrayLit, stdlibGlobalNameOf, } from "./surfaces.js"; +import { CRYPTO_CONSTANTS } from "./crypto-tables.js"; const PROTOTYPE_METHODS: Record = { 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 }; +} diff --git a/packages/compiler/src/frontend/lowering/lower-exprs.ts b/packages/compiler/src/frontend/lowering/lower-exprs.ts index 08e12518b..88a4918d8 100644 --- a/packages/compiler/src/frontend/lowering/lower-exprs.ts +++ b/packages/compiler/src/frontend/lowering/lower-exprs.ts @@ -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 }; diff --git a/packages/compiler/src/frontend/lowering/lower-modules.ts b/packages/compiler/src/frontend/lowering/lower-modules.ts index d18c4f547..b2f76cd24 100644 --- a/packages/compiler/src/frontend/lowering/lower-modules.ts +++ b/packages/compiler/src/frontend/lowering/lower-modules.ts @@ -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) { diff --git a/packages/compiler/src/frontend/lowering/lower-stmts.ts b/packages/compiler/src/frontend/lowering/lower-stmts.ts index 367c147f8..27f4169c1 100644 --- a/packages/compiler/src/frontend/lowering/lower-stmts.ts +++ b/packages/compiler/src/frontend/lowering/lower-stmts.ts @@ -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", diff --git a/packages/compiler/src/frontend/lowering/lowerer.ts b/packages/compiler/src/frontend/lowering/lowerer.ts index 3217802ad..8e6f64143 100644 --- a/packages/compiler/src/frontend/lowering/lowerer.ts +++ b/packages/compiler/src/frontend/lowering/lowerer.ts @@ -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. diff --git a/packages/compiler/src/frontend/lowering/surfaces.ts b/packages/compiler/src/frontend/lowering/surfaces.ts index 4c74e8b6a..72d6c09f8 100644 --- a/packages/compiler/src/frontend/lowering/surfaces.ts +++ b/packages/compiler/src/frontend/lowering/surfaces.ts @@ -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( diff --git a/packages/compiler/src/ir/builtin-effects.ts b/packages/compiler/src/ir/builtin-effects.ts index 2381413f8..ee5ba8c7c 100644 --- a/packages/compiler/src/ir/builtin-effects.ts +++ b/packages/compiler/src/ir/builtin-effects.ts @@ -466,6 +466,13 @@ export const MAY_THROW_LIB_FNS: ReadonlySet = 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]. diff --git a/packages/compiler/src/ir/builtin-signatures.ts b/packages/compiler/src/ir/builtin-signatures.ts index 5e219ecf6..e9f7778bc 100644 --- a/packages/compiler/src/ir/builtin-signatures.ts +++ b/packages/compiler/src/ir/builtin-signatures.ts @@ -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 }, diff --git a/packages/compiler/src/ir/runtime-features.ts b/packages/compiler/src/ir/runtime-features.ts index 80a9c9b57..ba97670ce 100644 --- a/packages/compiler/src/ir/runtime-features.ts +++ b/packages/compiler/src/ir/runtime-features.ts @@ -43,6 +43,8 @@ export interface RuntimeFeatures { } const DYN_ASYNC_LIB_FNS: ReadonlySet = new Set([ + // DEP0040 rides the dyn-async unit's warning machinery. + "punycode.warn", "fs.callbackValue", "fs.callbackCall", "fs.callbackGlob", diff --git a/packages/compiler/surface-manifest.json b/packages/compiler/surface-manifest.json index 63efde378..3ee171669 100644 --- a/packages/compiler/surface-manifest.json +++ b/packages/compiler/surface-manifest.json @@ -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", diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6122-punycode-conversions.ts.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6122-punycode-conversions.ts.json new file mode 100644 index 000000000..b15e4fe13 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6122-punycode-conversions.ts.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6122-punycode-conversions.ts" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6124-punycode-require-warnings.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6124-punycode-require-warnings.cjs.json new file mode 100644 index 000000000..dfd4aa912 --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6124-punycode-require-warnings.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6124-punycode-require-warnings.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6125-constants-module.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6125-constants-module.cjs.json new file mode 100644 index 000000000..ef8c7f2bf --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6125-constants-module.cjs.json @@ -0,0 +1,6 @@ +{ + "order": [ + "/tests/corpus/6125-constants-module.cjs" + ], + "diags": [] +} diff --git a/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json new file mode 100644 index 000000000..5de83361a --- /dev/null +++ b/packages/compiler/test/ts7/baselines/order-parity/tests/corpus/6126-punycode-dependency/main.cjs.json @@ -0,0 +1,18 @@ +{ + "order": [ + "/tests/corpus/6126-punycode-dependency/main.cjs" + ], + "diags": [ + { + "code": "SC1010", + "message": "the 'puny-dep' module is not supported yet", + "loc": { + "file": "/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" + } + ] +} diff --git a/packages/runtime/src/scr_async.h b/packages/runtime/src/scr_async.h index a391d2a26..f41949f6e 100644 --- a/packages/runtime/src/scr_async.h +++ b/packages/runtime/src/scr_async.h @@ -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. */ diff --git a/packages/runtime/src/scr_async_dyn.c b/packages/runtime/src/scr_async_dyn.c index a14ae6e6a..7a27f16f3 100644 --- a/packages/runtime/src/scr_async_dyn.c +++ b/packages/runtime/src/scr_async_dyn.c @@ -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. */ diff --git a/packages/runtime/src/scr_os.c b/packages/runtime/src/scr_os.c index 0bc50fb3f..0df57d2e1 100644 --- a/packages/runtime/src/scr_os.c +++ b/packages/runtime/src/scr_os.c @@ -27,6 +27,8 @@ #include #include #endif +#include +#include #ifdef __linux__ #include #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); +} diff --git a/packages/runtime/src/scr_os.h b/packages/runtime/src/scr_os.h index b40a8027a..6f8d9b616 100644 --- a/packages/runtime/src/scr_os.h +++ b/packages/runtime/src/scr_os.h @@ -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. */ diff --git a/packages/runtime/src/scr_url.c b/packages/runtime/src/scr_url.c index b6fdb9cd6..42a7ef607 100644 --- a/packages/runtime/src/scr_url.c +++ b/packages/runtime/src/scr_url.c @@ -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); } diff --git a/packages/runtime/src/scr_url.h b/packages/runtime/src/scr_url.h index 134893054..05e5702bb 100644 --- a/packages/runtime/src/scr_url.h +++ b/packages/runtime/src/scr_url.h @@ -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 */ diff --git a/tests/corpus/6122-punycode-conversions.ts b/tests/corpus/6122-punycode-conversions.ts new file mode 100644 index 000000000..061987a78 --- /dev/null +++ b/tests/corpus/6122-punycode-conversions.ts @@ -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; diff --git a/tests/corpus/6124-punycode-require-warnings.cjs b/tests/corpus/6124-punycode-require-warnings.cjs new file mode 100644 index 000000000..c3255e6b7 --- /dev/null +++ b/tests/corpus/6124-punycode-require-warnings.cjs @@ -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:)")); + } +} diff --git a/tests/corpus/6125-constants-module.cjs b/tests/corpus/6125-constants-module.cjs new file mode 100644 index 000000000..d7a547ac5 --- /dev/null +++ b/tests/corpus/6125-constants-module.cjs @@ -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")); diff --git a/tests/corpus/6126-punycode-dependency/main.cjs b/tests/corpus/6126-punycode-dependency/main.cjs new file mode 100644 index 000000000..530503a71 --- /dev/null +++ b/tests/corpus/6126-punycode-dependency/main.cjs @@ -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); diff --git a/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.d.ts b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.d.ts new file mode 100644 index 000000000..89b712210 --- /dev/null +++ b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.d.ts @@ -0,0 +1,2 @@ +declare function toAscii(domain: string): string; +export = toAscii; diff --git a/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.js b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.js new file mode 100644 index 000000000..417c44889 --- /dev/null +++ b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/index.js @@ -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); +}; diff --git a/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/package.json b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/package.json new file mode 100644 index 000000000..71edeb25c --- /dev/null +++ b/tests/corpus/6126-punycode-dependency/node_modules/puny-dep/package.json @@ -0,0 +1,5 @@ +{ + "name": "puny-dep", + "version": "1.0.0", + "main": "index.js" +}