@scriptc/compiler 0.0.0 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/ambient/package.json +3 -0
- package/ambient/scriptc-node-fallback.d.ts +3001 -0
- package/ambient/scriptc-overrides.d.ts +95 -0
- package/ambient/scriptc.d.ts +74 -0
- package/dist/backend/cc.d.ts +169 -0
- package/dist/backend/cc.js +897 -0
- package/dist/backend/cc.js.map +1 -0
- package/dist/backend/emission/emit-async.d.ts +145 -0
- package/dist/backend/emission/emit-async.js +996 -0
- package/dist/backend/emission/emit-async.js.map +1 -0
- package/dist/backend/emission/emit-exprs.d.ts +3 -0
- package/dist/backend/emission/emit-exprs.js +5949 -0
- package/dist/backend/emission/emit-exprs.js.map +1 -0
- package/dist/backend/emission/emit-island.d.ts +45 -0
- package/dist/backend/emission/emit-island.js +271 -0
- package/dist/backend/emission/emit-island.js.map +1 -0
- package/dist/backend/emission/emit-shapes.d.ts +142 -0
- package/dist/backend/emission/emit-shapes.js +575 -0
- package/dist/backend/emission/emit-shapes.js.map +1 -0
- package/dist/backend/emission/emit-stmts.d.ts +78 -0
- package/dist/backend/emission/emit-stmts.js +960 -0
- package/dist/backend/emission/emit-stmts.js.map +1 -0
- package/dist/backend/emission/emit-types.d.ts +65 -0
- package/dist/backend/emission/emit-types.js +652 -0
- package/dist/backend/emission/emit-types.js.map +1 -0
- package/dist/backend/emission/emit-walkers.d.ts +154 -0
- package/dist/backend/emission/emit-walkers.js +1587 -0
- package/dist/backend/emission/emit-walkers.js.map +1 -0
- package/dist/backend/emission/emitter.d.ts +452 -0
- package/dist/backend/emission/emitter.js +1249 -0
- package/dist/backend/emission/emitter.js.map +1 -0
- package/dist/backend/emission/may-throw.d.ts +15 -0
- package/dist/backend/emission/may-throw.js +250 -0
- package/dist/backend/emission/may-throw.js.map +1 -0
- package/dist/backend/llvm/blocks.d.ts +21 -0
- package/dist/backend/llvm/blocks.js +68 -0
- package/dist/backend/llvm/blocks.js.map +1 -0
- package/dist/backend/llvm/classes.d.ts +84 -0
- package/dist/backend/llvm/classes.js +438 -0
- package/dist/backend/llvm/classes.js.map +1 -0
- package/dist/backend/llvm/dyn.d.ts +123 -0
- package/dist/backend/llvm/dyn.js +2624 -0
- package/dist/backend/llvm/dyn.js.map +1 -0
- package/dist/backend/llvm/emitter.d.ts +3 -0
- package/dist/backend/llvm/emitter.js +10102 -0
- package/dist/backend/llvm/emitter.js.map +1 -0
- package/dist/backend/llvm/shapes.d.ts +78 -0
- package/dist/backend/llvm/shapes.js +754 -0
- package/dist/backend/llvm/shapes.js.map +1 -0
- package/dist/backend/llvm/unsupported.d.ts +6 -0
- package/dist/backend/llvm/unsupported.js +12 -0
- package/dist/backend/llvm/unsupported.js.map +1 -0
- package/dist/backend/llvm/walkers.d.ts +58 -0
- package/dist/backend/llvm/walkers.js +793 -0
- package/dist/backend/llvm/walkers.js.map +1 -0
- package/dist/backend/mangle.d.ts +124 -0
- package/dist/backend/mangle.js +232 -0
- package/dist/backend/mangle.js.map +1 -0
- package/dist/coverage/report.d.ts +65 -0
- package/dist/coverage/report.js +238 -0
- package/dist/coverage/report.js.map +1 -0
- package/dist/diagnostics/diagnostic.d.ts +140 -0
- package/dist/diagnostics/diagnostic.js +458 -0
- package/dist/diagnostics/diagnostic.js.map +1 -0
- package/dist/diagnostics/render.d.ts +11 -0
- package/dist/diagnostics/render.js +58 -0
- package/dist/diagnostics/render.js.map +1 -0
- package/dist/frontend/cjs-lexer.d.ts +20 -0
- package/dist/frontend/cjs-lexer.js +813 -0
- package/dist/frontend/cjs-lexer.js.map +1 -0
- package/dist/frontend/lowering/http2-constants.d.ts +1 -0
- package/dist/frontend/lowering/http2-constants.js +251 -0
- package/dist/frontend/lowering/http2-constants.js.map +1 -0
- package/dist/frontend/lowering/lib-boundary.d.ts +6 -0
- package/dist/frontend/lowering/lib-boundary.js +143 -0
- package/dist/frontend/lowering/lib-boundary.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.d.ts +18 -0
- package/dist/frontend/lowering/lower-assert.js +1269 -0
- package/dist/frontend/lowering/lower-assert.js.map +1 -0
- package/dist/frontend/lowering/lower-builtins.d.ts +511 -0
- package/dist/frontend/lowering/lower-builtins.js +5249 -0
- package/dist/frontend/lowering/lower-builtins.js.map +1 -0
- package/dist/frontend/lowering/lower-calls.d.ts +581 -0
- package/dist/frontend/lowering/lower-calls.js +6286 -0
- package/dist/frontend/lowering/lower-calls.js.map +1 -0
- package/dist/frontend/lowering/lower-classes.d.ts +727 -0
- package/dist/frontend/lowering/lower-classes.js +4701 -0
- package/dist/frontend/lowering/lower-classes.js.map +1 -0
- package/dist/frontend/lowering/lower-comptime.d.ts +53 -0
- package/dist/frontend/lowering/lower-comptime.js +244 -0
- package/dist/frontend/lowering/lower-comptime.js.map +1 -0
- package/dist/frontend/lowering/lower-containers.d.ts +486 -0
- package/dist/frontend/lowering/lower-containers.js +6345 -0
- package/dist/frontend/lowering/lower-containers.js.map +1 -0
- package/dist/frontend/lowering/lower-dgram.d.ts +20 -0
- package/dist/frontend/lowering/lower-dgram.js +319 -0
- package/dist/frontend/lowering/lower-dgram.js.map +1 -0
- package/dist/frontend/lowering/lower-emitter.d.ts +10 -0
- package/dist/frontend/lowering/lower-emitter.js +688 -0
- package/dist/frontend/lowering/lower-emitter.js.map +1 -0
- package/dist/frontend/lowering/lower-enums.d.ts +22 -0
- package/dist/frontend/lowering/lower-enums.js +235 -0
- package/dist/frontend/lowering/lower-enums.js.map +1 -0
- package/dist/frontend/lowering/lower-expando.d.ts +35 -0
- package/dist/frontend/lowering/lower-expando.js +276 -0
- package/dist/frontend/lowering/lower-expando.js.map +1 -0
- package/dist/frontend/lowering/lower-exprs.d.ts +524 -0
- package/dist/frontend/lowering/lower-exprs.js +8111 -0
- package/dist/frontend/lowering/lower-exprs.js.map +1 -0
- package/dist/frontend/lowering/lower-generators.d.ts +50 -0
- package/dist/frontend/lowering/lower-generators.js +421 -0
- package/dist/frontend/lowering/lower-generators.js.map +1 -0
- package/dist/frontend/lowering/lower-inspect.d.ts +20 -0
- package/dist/frontend/lowering/lower-inspect.js +1074 -0
- package/dist/frontend/lowering/lower-inspect.js.map +1 -0
- package/dist/frontend/lowering/lower-island.d.ts +108 -0
- package/dist/frontend/lowering/lower-island.js +650 -0
- package/dist/frontend/lowering/lower-island.js.map +1 -0
- package/dist/frontend/lowering/lower-mixins.d.ts +96 -0
- package/dist/frontend/lowering/lower-mixins.js +534 -0
- package/dist/frontend/lowering/lower-mixins.js.map +1 -0
- package/dist/frontend/lowering/lower-modules.d.ts +135 -0
- package/dist/frontend/lowering/lower-modules.js +1613 -0
- package/dist/frontend/lowering/lower-modules.js.map +1 -0
- package/dist/frontend/lowering/lower-namespaces.d.ts +168 -0
- package/dist/frontend/lowering/lower-namespaces.js +706 -0
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -0
- package/dist/frontend/lowering/lower-server.d.ts +90 -0
- package/dist/frontend/lowering/lower-server.js +3471 -0
- package/dist/frontend/lowering/lower-server.js.map +1 -0
- package/dist/frontend/lowering/lower-stmts.d.ts +361 -0
- package/dist/frontend/lowering/lower-stmts.js +5341 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -0
- package/dist/frontend/lowering/lower-stream.d.ts +88 -0
- package/dist/frontend/lowering/lower-stream.js +1533 -0
- package/dist/frontend/lowering/lower-stream.js.map +1 -0
- package/dist/frontend/lowering/lower-test.d.ts +27 -0
- package/dist/frontend/lowering/lower-test.js +353 -0
- package/dist/frontend/lowering/lower-test.js.map +1 -0
- package/dist/frontend/lowering/lowerer.d.ts +1748 -0
- package/dist/frontend/lowering/lowerer.js +6257 -0
- package/dist/frontend/lowering/lowerer.js.map +1 -0
- package/dist/frontend/lowering/surfaces.d.ts +272 -0
- package/dist/frontend/lowering/surfaces.js +1096 -0
- package/dist/frontend/lowering/surfaces.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +55 -0
- package/dist/frontend/npm-static.js +300 -0
- package/dist/frontend/npm-static.js.map +1 -0
- package/dist/frontend/npm.d.ts +330 -0
- package/dist/frontend/npm.js +1259 -0
- package/dist/frontend/npm.js.map +1 -0
- package/dist/frontend/program.d.ts +186 -0
- package/dist/frontend/program.js +2255 -0
- package/dist/frontend/program.js.map +1 -0
- package/dist/frontend/provenance-registry.d.ts +48 -0
- package/dist/frontend/provenance-registry.js +87 -0
- package/dist/frontend/provenance-registry.js.map +1 -0
- package/dist/frontend/provenance.d.ts +6 -0
- package/dist/frontend/provenance.js +459 -0
- package/dist/frontend/provenance.js.map +1 -0
- package/dist/frontend/resolve.d.ts +56 -0
- package/dist/frontend/resolve.js +682 -0
- package/dist/frontend/resolve.js.map +1 -0
- package/dist/frontend/shared.d.ts +67 -0
- package/dist/frontend/shared.js +241 -0
- package/dist/frontend/shared.js.map +1 -0
- package/dist/frontend/ts7/adapter.d.ts +7 -0
- package/dist/frontend/ts7/adapter.js +54 -0
- package/dist/frontend/ts7/adapter.js.map +1 -0
- package/dist/frontend/ts7/ast.d.ts +50 -0
- package/dist/frontend/ts7/ast.js +211 -0
- package/dist/frontend/ts7/ast.js.map +1 -0
- package/dist/frontend/ts7/census-check.d.ts +1 -0
- package/dist/frontend/ts7/census-check.js +12 -0
- package/dist/frontend/ts7/census-check.js.map +1 -0
- package/dist/frontend/ts7/checker.d.ts +140 -0
- package/dist/frontend/ts7/checker.js +544 -0
- package/dist/frontend/ts7/checker.js.map +1 -0
- package/dist/frontend/ts7/enums.d.ts +18 -0
- package/dist/frontend/ts7/enums.js +43 -0
- package/dist/frontend/ts7/enums.js.map +1 -0
- package/dist/frontend/ts7/program.d.ts +99 -0
- package/dist/frontend/ts7/program.js +278 -0
- package/dist/frontend/ts7/program.js.map +1 -0
- package/dist/frontend/ts7/world-check.d.ts +3 -0
- package/dist/frontend/ts7/world-check.js +34 -0
- package/dist/frontend/ts7/world-check.js.map +1 -0
- package/dist/frontend/types.d.ts +205 -0
- package/dist/frontend/types.js +2485 -0
- package/dist/frontend/types.js.map +1 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +429 -0
- package/dist/index.js.map +1 -0
- package/dist/ir/nodes.d.ts +4334 -0
- package/dist/ir/nodes.js +1906 -0
- package/dist/ir/nodes.js.map +1 -0
- package/dist/ir/serialize.d.ts +4 -0
- package/dist/ir/serialize.js +34 -0
- package/dist/ir/serialize.js.map +1 -0
- package/dist/ir/validate.d.ts +36 -0
- package/dist/ir/validate.js +4885 -0
- package/dist/ir/validate.js.map +1 -0
- package/package.json +30 -6
- package/README.md +0 -3
- package/index.js +0 -2
|
@@ -0,0 +1,1096 @@
|
|
|
1
|
+
/* Surface tables and provenance predicates of the lowerer: the island
|
|
2
|
+
* surface (Math/number/string/global functions marshalable through the
|
|
3
|
+
* dynamic island), the node builtin-module call tables and their fence
|
|
4
|
+
* hints, the string/array/map/set method-name sets, the unsupported-syntax
|
|
5
|
+
* diagnostic tables, and the "which world declared this symbol" predicates
|
|
6
|
+
* (ambient scriptc.d.ts / standard lib / @types/node provenance). */
|
|
7
|
+
import * as ts from "../ts7/adapter.js";
|
|
8
|
+
import { BOOL, BYTES_U8, CHILD_T, DYN, F64, RUNTIME_ERROR_CLASSES, SPAWNRES_T, STATS_T, STRING, URL_T, VOID, arrayOf } from "../../ir/nodes.js";
|
|
9
|
+
import { isNodeTypesPath, requireSpecOf } from "../program.js";
|
|
10
|
+
/** Statement-level constructs rejected wholesale, keyed by syntax kind. */
|
|
11
|
+
export const UNSUPPORTED_STMT = {
|
|
12
|
+
// Top-level ClassDeclarations are supported (collected in run()); one
|
|
13
|
+
// reaching lowerStmt is nested inside a function.
|
|
14
|
+
[ts.SyntaxKind.ClassDeclaration]: { code: "SC1090", feature: "class declarations inside functions" },
|
|
15
|
+
// DoStatement / SwitchStatement are supported; handled in lowerStmt.
|
|
16
|
+
// ForOfStatement is supported (arrays); handled in lowerStmt.
|
|
17
|
+
// ThrowStatement / TryStatement are supported (exceptions); handled in
|
|
18
|
+
// lowerStmt (catch bindings and jumps crossing a finally stay rejected).
|
|
19
|
+
// ForInStatement is supported (records/index-signature shapes via the
|
|
20
|
+
// Object.keys walk, arrays via index strings, globalThis empty) —
|
|
21
|
+
// handled in lowerStmt via lowerForIn; tuple/class/other receivers keep
|
|
22
|
+
// named SC1052 fences there.
|
|
23
|
+
// LabeledStatement is supported (labels on loops/switches directly, a
|
|
24
|
+
// labeled block wrapper for everything else) — handled in lowerStmt via
|
|
25
|
+
// lowerLabeled; what remains fenced is labeled jumps naming a loop whose
|
|
26
|
+
// lowering is a label-free desugar (SC1050 at the jump site).
|
|
27
|
+
// EnumDeclaration is supported (constant members fold; computed members
|
|
28
|
+
// fence) — handled in lowerStmt via lower-enums.ts.
|
|
29
|
+
[ts.SyntaxKind.ModuleDeclaration]: { code: "SC1090", feature: "namespaces" },
|
|
30
|
+
[ts.SyntaxKind.DebuggerStatement]: { code: "SC1090", feature: "debugger statements" },
|
|
31
|
+
// `with` blocks rewrite identifier resolution at runtime — statically
|
|
32
|
+
// uncompilable by design, and already an error in strict-mode JS.
|
|
33
|
+
[ts.SyntaxKind.WithStatement]: {
|
|
34
|
+
code: "SC1090",
|
|
35
|
+
feature: "'with' statements (runtime scope injection has no static resolution — bind the object to a variable and read members through it)",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
export const UNSUPPORTED_EXPR = {
|
|
39
|
+
// ArrowFunction / FunctionExpression are supported (closures); handled in
|
|
40
|
+
// lowerExpr before this table.
|
|
41
|
+
[ts.SyntaxKind.ClassExpression]: { code: "SC1020" },
|
|
42
|
+
// ObjectLiteralExpression is supported (records); handled in lowerExpr.
|
|
43
|
+
// ArrayLiteralExpression / ElementAccessExpression are supported (arrays);
|
|
44
|
+
// handled in lowerExpr.
|
|
45
|
+
// PropertyAccessExpression rejections are per-site in lowerExpr (the
|
|
46
|
+
// receiver lowers first — its blocker wins — and the residue names the
|
|
47
|
+
// property and receiver type; never a generic "property access").
|
|
48
|
+
// NewExpression / ThisKeyword are supported (classes); handled in lowerExpr.
|
|
49
|
+
// YieldExpression is supported (generators); handled in lowerExpr via
|
|
50
|
+
// lowerYield (value-position yield* and non-generator contexts keep
|
|
51
|
+
// their SC1071 fences there).
|
|
52
|
+
// TaggedTemplateExpression is supported (an interned per-site strings
|
|
53
|
+
// object + an ordinary call); handled in lowerExpr via lowerTaggedTemplate.
|
|
54
|
+
[ts.SyntaxKind.TypeOfExpression]: { code: "SC1090", feature: "typeof expressions" },
|
|
55
|
+
[ts.SyntaxKind.DeleteExpression]: { code: "SC1090", feature: "delete expressions" },
|
|
56
|
+
// RegularExpressionLiteral is supported (regex); handled in lowerExpr.
|
|
57
|
+
[ts.SyntaxKind.SpreadElement]: { code: "SC1090", feature: "spread arguments" },
|
|
58
|
+
// PostfixUnaryExpression is supported (expression-position ++/-- over
|
|
59
|
+
// f64 locals/globals — lowerIncDec); field/element receivers fence there.
|
|
60
|
+
};
|
|
61
|
+
/** The narrow-first hint every union fence shares — how to get from a
|
|
62
|
+
* union-typed value to the single arm the static compiler can use. One
|
|
63
|
+
* string so the fences speak with one voice (and match SC2003's hint). */
|
|
64
|
+
export const NARROW_FIRST = "narrow first: check a discriminant field, or compare with '!== undefined'/'!== null' for unit arms";
|
|
65
|
+
/* ── the options-record stance ───────────────────────────────────────────
|
|
66
|
+
* Node's object-options rule: an options record's unknown keys are simply
|
|
67
|
+
* IGNORED — the runtime reads the keys it documents and never validates
|
|
68
|
+
* the rest, so `http.request({ port, wibble: 1 })` runs identically to the
|
|
69
|
+
* wibble-free call. Rejecting a real Node program's option keys with a
|
|
70
|
+
* generic excess-property error is therefore itself a divergence, so the
|
|
71
|
+
* RECORD-shaped options parameters of the fallback surface carry an
|
|
72
|
+
* `[option: string]: unknown` index signature (every key typechecks) and
|
|
73
|
+
* each option WALK splits per key instead:
|
|
74
|
+
* - lowered keys take their lowering (bad VALUE shapes keep their
|
|
75
|
+
* per-key fences);
|
|
76
|
+
* - keys Node DOCUMENTS for the API but this compiler cannot honor
|
|
77
|
+
* fence BY NAME at the use site — an option that changes
|
|
78
|
+
* Node-observable behavior must work or fence, never silently drop;
|
|
79
|
+
* - undocumented keys DROP exactly as Node drops them, provided the
|
|
80
|
+
* value expression is side-effect-free (an effectful initializer
|
|
81
|
+
* fences: Node would have evaluated it, so skipping it would be
|
|
82
|
+
* observable).
|
|
83
|
+
* fenceOrDropOptionKey is that split's tail — option walks call it for
|
|
84
|
+
* every key outside their lowered set. */
|
|
85
|
+
/** True iff evaluating the option value can have no observable effect —
|
|
86
|
+
* the drop-safety test of the options-record stance. Literals, identifier
|
|
87
|
+
* reads (shorthand included), closures (created, never called — the key
|
|
88
|
+
* is unknown, so nothing can reach it), prefix +/-/! over these, and
|
|
89
|
+
* object/array literals of these all qualify; anything with a call,
|
|
90
|
+
* a property read (getters), or an assignment does not. */
|
|
91
|
+
export function sideEffectFreeOptionValue(node) {
|
|
92
|
+
let e = node;
|
|
93
|
+
while (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isTypeAssertion(e))
|
|
94
|
+
e = e.expression;
|
|
95
|
+
if (ts.isIdentifier(e))
|
|
96
|
+
return true; // includes `undefined`
|
|
97
|
+
if (ts.isStringLiteralLike(e) || ts.isNumericLiteral(e) || ts.isBigIntLiteral(e))
|
|
98
|
+
return true;
|
|
99
|
+
if (e.kind === ts.SyntaxKind.TrueKeyword || e.kind === ts.SyntaxKind.FalseKeyword ||
|
|
100
|
+
e.kind === ts.SyntaxKind.NullKeyword)
|
|
101
|
+
return true;
|
|
102
|
+
if (ts.isArrowFunction(e) || ts.isFunctionExpression(e))
|
|
103
|
+
return true;
|
|
104
|
+
if (ts.isPrefixUnaryExpression(e) &&
|
|
105
|
+
(e.operator === ts.SyntaxKind.MinusToken || e.operator === ts.SyntaxKind.PlusToken ||
|
|
106
|
+
e.operator === ts.SyntaxKind.ExclamationToken)) {
|
|
107
|
+
return sideEffectFreeOptionValue(e.operand);
|
|
108
|
+
}
|
|
109
|
+
if (ts.isObjectLiteralExpression(e)) {
|
|
110
|
+
return e.properties.every((p) => ts.isShorthandPropertyAssignment(p) ||
|
|
111
|
+
(ts.isPropertyAssignment(p) && !ts.isComputedPropertyName(p.name) &&
|
|
112
|
+
sideEffectFreeOptionValue(p.initializer)));
|
|
113
|
+
}
|
|
114
|
+
if (ts.isArrayLiteralExpression(e)) {
|
|
115
|
+
return e.elements.every((el) => !ts.isSpreadElement(el) && sideEffectFreeOptionValue(el));
|
|
116
|
+
}
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
/** The stance's tail: `key` is outside the walk's lowered set. Documented
|
|
120
|
+
* keys of the API fence by name (pointed per-key hints first, the walk's
|
|
121
|
+
* supported-options hint otherwise); undocumented keys drop like Node
|
|
122
|
+
* drops them when the value is side-effect-free, and fence when it is
|
|
123
|
+
* not. `prop` is the property entry (assignment or shorthand) for spans. */
|
|
124
|
+
export function fenceOrDropOptionKey(L, prop, key, api, documented, supportedHint, pointedHints) {
|
|
125
|
+
if (documented.has(key)) {
|
|
126
|
+
L.noLowering(`${api} option '${key}'`, prop, pointedHints?.[key] ?? supportedHint);
|
|
127
|
+
}
|
|
128
|
+
const value = ts.isPropertyAssignment(prop) ? prop.initializer : null;
|
|
129
|
+
if (value !== null && !sideEffectFreeOptionValue(value)) {
|
|
130
|
+
L.noLowering(`the undocumented ${api} option '${key}' with an effectful value`, prop, "Node ignores undocumented option keys and so does this compiler, but Node still evaluates the value — hoist it (const v = ...) or drop the entry");
|
|
131
|
+
}
|
|
132
|
+
// Dropped: Node's own ignore, byte-exact.
|
|
133
|
+
}
|
|
134
|
+
/** http.request/http.get's documented option keys (Node v24 — the
|
|
135
|
+
* `http.request(options)` and `net.socket.connect` tables); everything
|
|
136
|
+
* else on a client options literal is undocumented and drops. */
|
|
137
|
+
export const HTTP_CLIENT_DOCUMENTED_OPTIONS = new Set([
|
|
138
|
+
"agent", "auth", "createConnection", "defaultPort", "family", "headers",
|
|
139
|
+
"hints", "host", "hostname", "insecureHTTPParser", "joinDuplicateHeaders",
|
|
140
|
+
"localAddress", "localPort", "lookup", "maxHeaderSize", "method", "path",
|
|
141
|
+
"port", "protocol", "setDefaultHeaders", "setHost", "signal", "socketPath",
|
|
142
|
+
"timeout", "uniqueHeaders",
|
|
143
|
+
]);
|
|
144
|
+
/** http.createServer / http.Server's documented option keys (Node v24 —
|
|
145
|
+
* http.createServer options + the net.Server knobs it forwards). The
|
|
146
|
+
* lowered pair is requireHostHeader/joinDuplicateHeaders; the rest fence
|
|
147
|
+
* by name here, and unknown keys drop like Node drops them. */
|
|
148
|
+
export const HTTP_SERVER_DOCUMENTED_OPTIONS = new Set([
|
|
149
|
+
"IncomingMessage", "ServerResponse", "allowHalfOpen", "connectionsCheckingInterval",
|
|
150
|
+
"headersTimeout", "highWaterMark", "insecureHTTPParser", "joinDuplicateHeaders",
|
|
151
|
+
"keepAlive", "keepAliveInitialDelay", "keepAliveTimeout", "maxHeaderSize",
|
|
152
|
+
"noDelay", "pauseOnConnect", "rejectNonStandardBodyWrites", "requestTimeout",
|
|
153
|
+
"requireHostHeader", "uniqueHeaders",
|
|
154
|
+
]);
|
|
155
|
+
/** https.request adds tls.connect's client-side TLS knobs. */
|
|
156
|
+
export const HTTPS_CLIENT_DOCUMENTED_OPTIONS = new Set([
|
|
157
|
+
...HTTP_CLIENT_DOCUMENTED_OPTIONS,
|
|
158
|
+
"ca", "cert", "checkServerIdentity", "ciphers", "clientCertEngine", "crl",
|
|
159
|
+
"dhparam", "ecdhCurve", "highWaterMark", "honorCipherOrder", "key",
|
|
160
|
+
"maxVersion", "minVersion", "passphrase", "pfx", "privateKeyEngine",
|
|
161
|
+
"privateKeyIdentifier", "rejectUnauthorized", "secureOptions",
|
|
162
|
+
"secureProtocol", "servername", "session", "sessionIdContext",
|
|
163
|
+
"sessionTimeout", "sigalgs", "ticketKeys",
|
|
164
|
+
]);
|
|
165
|
+
/** tls.createServer / tls.createSecureContext / https.createServer /
|
|
166
|
+
* http2.createSecureServer's documented TLS-side option keys (Node v24 —
|
|
167
|
+
* tls.createSecureContext + tls.createServer + net.createServer). */
|
|
168
|
+
export const TLS_SERVER_DOCUMENTED_OPTIONS = new Set([
|
|
169
|
+
"ALPNProtocols", "ALPNCallback", "SNICallback", "allowHalfOpen", "ca",
|
|
170
|
+
"cert", "ciphers", "clientCertEngine", "crl", "dhparam", "ecdhCurve",
|
|
171
|
+
"enableTrace", "handshakeTimeout", "highWaterMark", "honorCipherOrder",
|
|
172
|
+
"keepAlive", "keepAliveInitialDelay", "key", "maxVersion", "minVersion",
|
|
173
|
+
"noDelay", "passphrase", "pauseOnConnect", "pfx", "privateKeyEngine",
|
|
174
|
+
"privateKeyIdentifier", "pskCallback", "pskIdentityHint",
|
|
175
|
+
"rejectUnauthorized", "requestCert", "requestOCSP", "secureContext",
|
|
176
|
+
"secureOptions", "secureProtocol", "sessionIdContext", "sessionTimeout",
|
|
177
|
+
"sigalgs", "ticketKeys",
|
|
178
|
+
]);
|
|
179
|
+
/** http2.createSecureServer's documented keys: the h2 knobs on top of the
|
|
180
|
+
* TLS server set. */
|
|
181
|
+
export const HTTP2_SECURE_SERVER_DOCUMENTED_OPTIONS = new Set([
|
|
182
|
+
...TLS_SERVER_DOCUMENTED_OPTIONS,
|
|
183
|
+
"allowHTTP1", "maxDeflateDynamicTableSize", "maxSettings",
|
|
184
|
+
"maxSessionMemory", "maxHeaderListPairs", "maxOutstandingPings",
|
|
185
|
+
"maxSendHeaderBlockLength", "origins", "paddingStrategy",
|
|
186
|
+
"peerMaxConcurrentStreams", "remoteCustomSettings", "selectPadding",
|
|
187
|
+
"settings", "streamResetBurst", "streamResetRate",
|
|
188
|
+
"unknownProtocolTimeout",
|
|
189
|
+
]);
|
|
190
|
+
/** fs.watch's documented option keys. */
|
|
191
|
+
export const FS_WATCH_DOCUMENTED_OPTIONS = new Set([
|
|
192
|
+
"persistent", "recursive", "encoding", "signal",
|
|
193
|
+
]);
|
|
194
|
+
/** fs.readdirSync's documented option keys. */
|
|
195
|
+
export const FS_READDIR_DOCUMENTED_OPTIONS = new Set([
|
|
196
|
+
"encoding", "withFileTypes", "recursive",
|
|
197
|
+
]);
|
|
198
|
+
/** fs.writeFileSync's documented option keys. */
|
|
199
|
+
export const FS_WRITE_FILE_DOCUMENTED_OPTIONS = new Set([
|
|
200
|
+
"encoding", "mode", "flag", "flush", "signal",
|
|
201
|
+
]);
|
|
202
|
+
/** dns.lookup's documented option keys. */
|
|
203
|
+
export const DNS_LOOKUP_DOCUMENTED_OPTIONS = new Set([
|
|
204
|
+
"family", "hints", "all", "order", "verbatim",
|
|
205
|
+
]);
|
|
206
|
+
/** readline.createInterface's documented option keys. */
|
|
207
|
+
export const READLINE_DOCUMENTED_OPTIONS = new Set([
|
|
208
|
+
"input", "output", "completer", "terminal", "history", "historySize",
|
|
209
|
+
"removeHistoryDuplicates", "prompt", "crlfDelay", "escapeCodeTimeout",
|
|
210
|
+
"tabSize", "signal",
|
|
211
|
+
]);
|
|
212
|
+
/** The lowered Array<T> method surface. Like STR_METHODS, membership is
|
|
213
|
+
* only half the check — the resolved symbol must be declared by the
|
|
214
|
+
* standard library (isStdlibMember); lib-declared array methods outside
|
|
215
|
+
* this set hit the SC2020 fence. */
|
|
216
|
+
export const ARRAY_METHODS = new Set([
|
|
217
|
+
"push",
|
|
218
|
+
"pop",
|
|
219
|
+
"concat",
|
|
220
|
+
"map",
|
|
221
|
+
"filter",
|
|
222
|
+
"forEach",
|
|
223
|
+
"find",
|
|
224
|
+
"findIndex",
|
|
225
|
+
"findLast",
|
|
226
|
+
"findLastIndex",
|
|
227
|
+
"some",
|
|
228
|
+
"every",
|
|
229
|
+
"at",
|
|
230
|
+
"flatMap",
|
|
231
|
+
"reduce",
|
|
232
|
+
"reduceRight",
|
|
233
|
+
"indexOf",
|
|
234
|
+
"includes",
|
|
235
|
+
"join",
|
|
236
|
+
"slice",
|
|
237
|
+
]);
|
|
238
|
+
/** The lowered Map<K, V> method surface. Like ARRAY_METHODS, membership is
|
|
239
|
+
* only half the check — the resolved symbol must be declared by the
|
|
240
|
+
* standard library (isStdlibMember). `size` is a property, handled in
|
|
241
|
+
* property-access position. */
|
|
242
|
+
export const MAP_METHODS = new Set(["get", "set", "has", "delete", "clear", "forEach"]);
|
|
243
|
+
/** The lowered Set<T> method surface — Map's minus get/set plus add.
|
|
244
|
+
* `size` is a property, handled in property-access position. */
|
|
245
|
+
export const SET_METHODS = new Set(["add", "has", "delete", "clear", "forEach"]);
|
|
246
|
+
/** The ES2025 Set composition surface (union/intersection/…): desugared
|
|
247
|
+
* to interned helper loops over the set iteration primitives — no user
|
|
248
|
+
* code runs mid-loop, so the walks need no enter/exit bracketing. The
|
|
249
|
+
* lib accepts any ReadonlySetLike argument; only a real Set lowers (the
|
|
250
|
+
* argument's has/size must be the builtins for the desugar to BE the
|
|
251
|
+
* spec's algorithm). */
|
|
252
|
+
export const SET_COMBINE_METHODS = new Set([
|
|
253
|
+
"union",
|
|
254
|
+
"intersection",
|
|
255
|
+
"difference",
|
|
256
|
+
"symmetricDifference",
|
|
257
|
+
"isSubsetOf",
|
|
258
|
+
"isSupersetOf",
|
|
259
|
+
"isDisjointFrom",
|
|
260
|
+
]);
|
|
261
|
+
export const COMPOUND_ASSIGN_OPS = {
|
|
262
|
+
[ts.SyntaxKind.PlusEqualsToken]: "+",
|
|
263
|
+
[ts.SyntaxKind.MinusEqualsToken]: "-",
|
|
264
|
+
[ts.SyntaxKind.AsteriskEqualsToken]: "*",
|
|
265
|
+
[ts.SyntaxKind.SlashEqualsToken]: "/",
|
|
266
|
+
[ts.SyntaxKind.AmpersandEqualsToken]: "&",
|
|
267
|
+
[ts.SyntaxKind.BarEqualsToken]: "|",
|
|
268
|
+
[ts.SyntaxKind.CaretEqualsToken]: "^",
|
|
269
|
+
[ts.SyntaxKind.LessThanLessThanEqualsToken]: "<<",
|
|
270
|
+
[ts.SyntaxKind.GreaterThanGreaterThanEqualsToken]: ">>",
|
|
271
|
+
[ts.SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken]: ">>>",
|
|
272
|
+
[ts.SyntaxKind.PercentEqualsToken]: "%",
|
|
273
|
+
[ts.SyntaxKind.AsteriskAsteriskEqualsToken]: "**",
|
|
274
|
+
};
|
|
275
|
+
/** String methods lowered to `strIntrinsic` (`.length` is handled in
|
|
276
|
+
* property-access position). Membership here is only half the check: the
|
|
277
|
+
* resolved symbol must also be declared by the standard library (see
|
|
278
|
+
* isStdlibMember), so a user-declared `slice` on some other type can never
|
|
279
|
+
* smuggle itself in by name. `minArgs`/`maxArgs` pin the LOWERED call
|
|
280
|
+
* forms: the lib declares wider signatures (optional position/fromIndex
|
|
281
|
+
* parameters) that typecheck but have no lowering — those forms are fenced
|
|
282
|
+
* per site instead of silently dropping arguments. */
|
|
283
|
+
export const STR_METHODS = {
|
|
284
|
+
charCodeAt: { method: "charCodeAt", result: F64, minArgs: 1, maxArgs: 1 },
|
|
285
|
+
charAt: { method: "charAt", result: STRING, minArgs: 1, maxArgs: 1 },
|
|
286
|
+
indexOf: { method: "indexOf", result: F64, minArgs: 1, maxArgs: 2 },
|
|
287
|
+
// includes with a position argument is indexOf's clamp exactly (the
|
|
288
|
+
// spec routes both through StringIndexOf) — the emitter composes
|
|
289
|
+
// scr_str_index_of(...) != -1 for the two-argument form.
|
|
290
|
+
includes: { method: "includes", result: BOOL, minArgs: 1, maxArgs: 2 },
|
|
291
|
+
startsWith: { method: "startsWith", result: BOOL, minArgs: 1, maxArgs: 1 },
|
|
292
|
+
endsWith: { method: "endsWith", result: BOOL, minArgs: 1, maxArgs: 1 },
|
|
293
|
+
slice: { method: "slice", result: STRING, minArgs: 0, maxArgs: 2 },
|
|
294
|
+
// substring: slice's clamp-and-swap sibling (negatives clamp to 0
|
|
295
|
+
// instead of counting from the end; start > end swaps).
|
|
296
|
+
substring: { method: "substring", result: STRING, minArgs: 1, maxArgs: 2 },
|
|
297
|
+
repeat: { method: "repeat", result: STRING, minArgs: 1, maxArgs: 1 },
|
|
298
|
+
trim: { method: "trim", result: STRING, minArgs: 0, maxArgs: 0 },
|
|
299
|
+
trimStart: { method: "trimStart", result: STRING, minArgs: 0, maxArgs: 0 },
|
|
300
|
+
trimEnd: { method: "trimEnd", result: STRING, minArgs: 0, maxArgs: 0 },
|
|
301
|
+
// The Annex B aliases (lib.es2019 declares them as trimStart/trimEnd's
|
|
302
|
+
// deprecated twins — same behavior, per spec).
|
|
303
|
+
trimLeft: { method: "trimStart", result: STRING, minArgs: 0, maxArgs: 0 },
|
|
304
|
+
trimRight: { method: "trimEnd", result: STRING, minArgs: 0, maxArgs: 0 },
|
|
305
|
+
// The STRING-separator split, no limit (the lib's limit parameter
|
|
306
|
+
// fences by arity here; regex separators lowered earlier as
|
|
307
|
+
// regexIntrinsic). Empty separator splits per UTF-16 code unit —
|
|
308
|
+
// astral halves become U+FFFD (SEMANTICS.md divergence 2, the same
|
|
309
|
+
// substitution the island's boundary marshal applied).
|
|
310
|
+
split: { method: "split", result: arrayOf(STRING), minArgs: 1, maxArgs: 1 },
|
|
311
|
+
// padStart/padEnd with the fill omitted: Node pads with " " — the
|
|
312
|
+
// lowering completes the default (lowerStringMethodCall).
|
|
313
|
+
padStart: { method: "padStart", result: STRING, minArgs: 1, maxArgs: 2 },
|
|
314
|
+
padEnd: { method: "padEnd", result: STRING, minArgs: 1, maxArgs: 2 },
|
|
315
|
+
// The lre-backed pair (ECMA Default Case Conversion, final sigma
|
|
316
|
+
// included — scr_regex.c): static now, no island needed; their presence
|
|
317
|
+
// flips the regex LINK switch (moduleUsesRegex).
|
|
318
|
+
toLowerCase: { method: "toLowerCase", result: STRING, minArgs: 0, maxArgs: 0 },
|
|
319
|
+
toUpperCase: { method: "toUpperCase", result: STRING, minArgs: 0, maxArgs: 0 },
|
|
320
|
+
// The ES2024 well-formedness pair — no-ops over the runtime's
|
|
321
|
+
// well-formed storage (constant true / the identity, per spec on
|
|
322
|
+
// well-formed input; where Node would answer false the program already
|
|
323
|
+
// diverged at the string's creation, SEMANTICS.md 2).
|
|
324
|
+
isWellFormed: { method: "isWellFormed", result: BOOL, minArgs: 0, maxArgs: 0 },
|
|
325
|
+
toWellFormed: { method: "toWellFormed", result: STRING, minArgs: 0, maxArgs: 0 },
|
|
326
|
+
};
|
|
327
|
+
export const ISL_N1 = { args: [F64], ret: F64 };
|
|
328
|
+
export const ISL_N2 = { args: [F64, F64], ret: F64 };
|
|
329
|
+
export const boundaryIntoIslandMsg = (typeName) => `passing a value of type '${typeName}' into dynamically-executed ('any'-typed) code ` +
|
|
330
|
+
`(only numbers, strings, booleans, typed arrays, URLs, undefined/null-armed unions, ` +
|
|
331
|
+
`and JSON-safe records/arrays/unions can cross the boundary)`;
|
|
332
|
+
export const boundaryOutOfIslandMsg = (typeName) => `using an 'any' value where '${typeName}' is expected ` +
|
|
333
|
+
`(an 'any' value can only exit to numbers, strings, booleans, JSON-safe records/arrays/unions, ` +
|
|
334
|
+
`and 'T | undefined' over those)`;
|
|
335
|
+
/** The island-backed surface — standard-library APIs with no static
|
|
336
|
+
* runtime implementation (Math.*, number/string methods beyond the
|
|
337
|
+
* intrinsic set, parseFloat, ...). ONE table drives both sides of the
|
|
338
|
+
* gate: under --dynamic each entry lowers to marshal → engine execution →
|
|
339
|
+
* validated exit to the declared return type; without the flag each use
|
|
340
|
+
* site is a per-site SC2012. Membership here is only half the check — the
|
|
341
|
+
* resolved symbol must be declared by the standard library (provenance,
|
|
342
|
+
* never names), so a user's own `parseFloat` or `.toFixed` takes the
|
|
343
|
+
* ordinary paths. The lib declares MORE than this table (extra members,
|
|
344
|
+
* optional-parameter forms): everything untabled falls through to the
|
|
345
|
+
* SC2020 lib fence — never an ICE, never a link error. Exported so the
|
|
346
|
+
* table-vs-lib consistency test can check every entry against the lib's
|
|
347
|
+
* own declarations — a drifted entry fails a test instead of surprising a
|
|
348
|
+
* user. */
|
|
349
|
+
export const ISLAND_SURFACE = {
|
|
350
|
+
/** `Math.<fn>(...)` lowers to callMethod(globalGet("Math"), fn, args);
|
|
351
|
+
* the readonly number props (`Math.PI`) to getProp(globalGet("Math")).
|
|
352
|
+
* min/max/atan2/hypot/pow are declared with exactly two parameters
|
|
353
|
+
* (rest/optional parameters aren't representable). */
|
|
354
|
+
math: {
|
|
355
|
+
// floor, min/max (two-arg), and random are STATIC now
|
|
356
|
+
// (STATIC_MATH_FNS below).
|
|
357
|
+
fns: {
|
|
358
|
+
abs: ISL_N1, acos: ISL_N1, asin: ISL_N1, atan: ISL_N1, atan2: ISL_N2,
|
|
359
|
+
cbrt: ISL_N1, ceil: ISL_N1, cos: ISL_N1, exp: ISL_N1,
|
|
360
|
+
hypot: ISL_N2, log: ISL_N1, log2: ISL_N1, log10: ISL_N1, pow: ISL_N2,
|
|
361
|
+
round: ISL_N1,
|
|
362
|
+
sign: ISL_N1, sin: ISL_N1, sqrt: ISL_N1, tan: ISL_N1, trunc: ISL_N1,
|
|
363
|
+
},
|
|
364
|
+
props: { PI: F64, E: F64 },
|
|
365
|
+
},
|
|
366
|
+
/** Methods on `number` receivers. The receiver marshals by value; the
|
|
367
|
+
* engine auto-boxes primitives on method calls, so `this` binds the
|
|
368
|
+
* number exactly as in JS. toString takes an EXPLICIT radix (radix-free
|
|
369
|
+
* conversion is the static template-literal path). */
|
|
370
|
+
number: {
|
|
371
|
+
toFixed: { args: [F64], ret: STRING },
|
|
372
|
+
toPrecision: { args: [F64], ret: STRING },
|
|
373
|
+
toString: { args: [F64], ret: STRING },
|
|
374
|
+
},
|
|
375
|
+
/** Methods on `string` receivers beyond the static strIntrinsic set
|
|
376
|
+
* (split/pad/trimStart/trimEnd moved to STR_METHODS — static now).
|
|
377
|
+
* replace/replaceAll take STRING patterns (no regex is declared). `at`
|
|
378
|
+
* returns `string`: out of range, the engine yields undefined and the
|
|
379
|
+
* validated exit refuses it with a catchable TypeError (the documented
|
|
380
|
+
* divergence from `string | undefined`). */
|
|
381
|
+
string: {
|
|
382
|
+
replace: { args: [STRING, STRING], ret: STRING },
|
|
383
|
+
replaceAll: { args: [STRING, STRING], ret: STRING },
|
|
384
|
+
at: { args: [F64], ret: STRING },
|
|
385
|
+
},
|
|
386
|
+
/** Global functions: parseInt and isNaN are STATIC (lower-calls.ts →
|
|
387
|
+
* num.parseInt/num.isNaN), and parseFloat/isFinite lower statically
|
|
388
|
+
* over EXACTLY-typed arguments (a string for parseFloat, a number for
|
|
389
|
+
* isFinite — num.parseFloat/number.isFinite); these island entries
|
|
390
|
+
* carry every OTHER argument shape, where the ToNumber/ToString
|
|
391
|
+
* coercions stay engine territory. */
|
|
392
|
+
globals: {
|
|
393
|
+
parseFloat: { args: [STRING], ret: F64 },
|
|
394
|
+
isFinite: { args: [F64], ret: BOOL },
|
|
395
|
+
},
|
|
396
|
+
};
|
|
397
|
+
/** Math members with a STATIC lowering — each is one C call that IS the
|
|
398
|
+
* JS operation, at the tabled arity (floor: libm's floor; min/max: the
|
|
399
|
+
* NaN-poisoning ±0-ordered scalar folds; random: arc4random-backed
|
|
400
|
+
* uniform [0,1) — SEMANTICS.md 62). Checked BEFORE the island table
|
|
401
|
+
* (lowerIslandMethodCall), so the tabled arities compile statically and
|
|
402
|
+
* other arities keep the island/fence story; unioned with the island
|
|
403
|
+
* table for the methods-as-values fence (lowerMathProperty). */
|
|
404
|
+
export const STATIC_MATH_FNS = {
|
|
405
|
+
floor: { fn: "math.floor", arity: 1 },
|
|
406
|
+
abs: { fn: "math.abs", arity: 1 },
|
|
407
|
+
round: { fn: "math.round", arity: 1 },
|
|
408
|
+
min: { fn: "math.min", arity: 2 },
|
|
409
|
+
max: { fn: "math.max", arity: 2 },
|
|
410
|
+
random: { fn: "math.random", arity: 0 },
|
|
411
|
+
};
|
|
412
|
+
/** The lowerable surface of the supported node builtin modules, keyed by
|
|
413
|
+
* CANONICAL module name (both "fs" and "node:fs" land on "fs" — see
|
|
414
|
+
* canonicalBuiltinModule). Like STR_METHODS, membership is only half the
|
|
415
|
+
* check — the identifier must be an IMPORT BINDING from that module's
|
|
416
|
+
* specifier (see builtinImportOf), so a user function named `readFileSync`
|
|
417
|
+
* can never smuggle itself in. Members @types/node declares beyond these
|
|
418
|
+
* tables typecheck and hit the SC2020-family fence at their use site. */
|
|
419
|
+
/** node:path's POSIX members — the bare-module table on posix targets and
|
|
420
|
+
* "path/posix" everywhere (Node's own aliasing: bare `path` IS the
|
|
421
|
+
* target platform's implementation, and each named namespace answers ITS
|
|
422
|
+
* platform's rules on any host). toNamespacedPath is the posix identity
|
|
423
|
+
* (Node: a non-op on posix systems). */
|
|
424
|
+
const PATH_MODULE_FNS = {
|
|
425
|
+
join: { fn: "path.join", params: [STRING], result: STRING, variadicPack: true },
|
|
426
|
+
resolve: { fn: "path.resolve", params: [STRING], result: STRING, variadicPack: true },
|
|
427
|
+
normalize: { fn: "path.normalize", params: [STRING], result: STRING },
|
|
428
|
+
dirname: { fn: "path.dirname", params: [STRING], result: STRING },
|
|
429
|
+
basename: { fn: "path.basename", params: [STRING, STRING], result: STRING, defaults: [""] },
|
|
430
|
+
extname: { fn: "path.extname", params: [STRING], result: STRING },
|
|
431
|
+
isAbsolute: { fn: "path.isAbsolute", params: [STRING], result: BOOL },
|
|
432
|
+
relative: { fn: "path.relative", params: [STRING, STRING], result: STRING },
|
|
433
|
+
toNamespacedPath: { fn: "path.toNamespacedPath", params: [STRING], result: STRING },
|
|
434
|
+
};
|
|
435
|
+
/** The win32 twins (scr_path.c's Node-v24 path.win32 port, byte-for-byte):
|
|
436
|
+
* "path/win32" everywhere, and the bare-module table when the build
|
|
437
|
+
* TARGETS win32 — Node on Windows is path.win32. */
|
|
438
|
+
const PATH_WIN32_MODULE_FNS = {
|
|
439
|
+
join: { fn: "path.win32Join", params: [STRING], result: STRING, variadicPack: true },
|
|
440
|
+
resolve: { fn: "path.win32Resolve", params: [STRING], result: STRING, variadicPack: true },
|
|
441
|
+
normalize: { fn: "path.win32Normalize", params: [STRING], result: STRING },
|
|
442
|
+
dirname: { fn: "path.win32Dirname", params: [STRING], result: STRING },
|
|
443
|
+
basename: { fn: "path.win32Basename", params: [STRING, STRING], result: STRING, defaults: [""] },
|
|
444
|
+
extname: { fn: "path.win32Extname", params: [STRING], result: STRING },
|
|
445
|
+
isAbsolute: { fn: "path.win32IsAbsolute", params: [STRING], result: BOOL },
|
|
446
|
+
relative: { fn: "path.win32Relative", params: [STRING, STRING], result: STRING },
|
|
447
|
+
toNamespacedPath: { fn: "path.win32ToNamespacedPath", params: [STRING], result: STRING },
|
|
448
|
+
};
|
|
449
|
+
export const BUILTIN_MODULE_FNS = {
|
|
450
|
+
fs: {
|
|
451
|
+
readFileSync: { fn: "fs.readFileSync", params: [STRING, STRING], result: STRING },
|
|
452
|
+
writeFileSync: { fn: "fs.writeFileSync", params: [STRING, STRING], result: VOID },
|
|
453
|
+
appendFileSync: { fn: "fs.appendFileSync", params: [STRING, STRING], result: VOID },
|
|
454
|
+
existsSync: { fn: "fs.existsSync", params: [STRING], result: BOOL },
|
|
455
|
+
mkdirSync: { fn: "fs.mkdirSync", params: [STRING], result: VOID },
|
|
456
|
+
rmSync: { fn: "fs.rmSync", params: [STRING], result: VOID },
|
|
457
|
+
rmdirSync: { fn: "fs.rmdirSync", params: [STRING], result: VOID },
|
|
458
|
+
readdirSync: { fn: "fs.readdirSync", params: [STRING], result: arrayOf(STRING) },
|
|
459
|
+
statSync: { fn: "fs.statSync", params: [STRING], result: STATS_T },
|
|
460
|
+
mkdtempSync: { fn: "fs.mkdtempSync", params: [STRING], result: STRING },
|
|
461
|
+
// accessSync's omitted mode completes to 0 (F_OK) in the special case
|
|
462
|
+
// in lowerBuiltinModuleCall — string `defaults` can't spell a number.
|
|
463
|
+
accessSync: { fn: "fs.accessSync", params: [STRING, F64], result: VOID },
|
|
464
|
+
unlinkSync: { fn: "fs.unlinkSync", params: [STRING], result: VOID },
|
|
465
|
+
chmodSync: { fn: "fs.chmodSync", params: [STRING, F64], result: VOID },
|
|
466
|
+
chownSync: { fn: "fs.chownSync", params: [STRING, F64, F64], result: VOID },
|
|
467
|
+
// The 2-argument form only: Node's mode flags (COPYFILE_EXCL, ...)
|
|
468
|
+
// land on the arity fence.
|
|
469
|
+
copyFileSync: { fn: "fs.copyFileSync", params: [STRING, STRING], result: VOID },
|
|
470
|
+
// statSync's no-follow sibling; stats.isSymbolicLink answers what the
|
|
471
|
+
// follow-free snapshot saw.
|
|
472
|
+
lstatSync: { fn: "fs.lstatSync", params: [STRING], result: STATS_T },
|
|
473
|
+
// realpath(3) — Node's realpathSync (failures spell syscall "lstat",
|
|
474
|
+
// Node's own message shape).
|
|
475
|
+
realpathSync: { fn: "fs.realpathSync", params: [STRING], result: STRING },
|
|
476
|
+
// The fd pair behind spawn's fd-stdio form (the daemon-log idiom:
|
|
477
|
+
// openSync(logPath, "a") → spawn stdio ["ignore", fd, fd] →
|
|
478
|
+
// closeSync). openSync takes Node's string flags ("r", "w", "a", the
|
|
479
|
+
// +/x variants — unknown flags throw Node's TypeError text); the
|
|
480
|
+
// numeric-mode third argument fences by arity.
|
|
481
|
+
openSync: { fn: "fs.openSync", params: [STRING, STRING], result: F64 },
|
|
482
|
+
// The 4-argument buffer form (fd, buffer, offset, length) — what the
|
|
483
|
+
// Node test harness's parseTestMetadata uses; the position parameter
|
|
484
|
+
// and options-object forms fence by arity/shape.
|
|
485
|
+
readSync: { fn: "fs.readSync", params: [F64, BYTES_U8, F64, F64], result: F64 },
|
|
486
|
+
closeSync: { fn: "fs.closeSync", params: [F64], result: VOID },
|
|
487
|
+
// Entirely special-cased (lowerFsWatchCall — the callback needs an
|
|
488
|
+
// adapter per listener shape); this entry only routes the dispatch.
|
|
489
|
+
watch: { fn: "fs.watch", params: [], result: VOID },
|
|
490
|
+
},
|
|
491
|
+
"fs/promises": {
|
|
492
|
+
// The sync operations behind already-settled promises: failures
|
|
493
|
+
// REJECT (catchable at the await) instead of throwing. readFile is
|
|
494
|
+
// utf8-fenced exactly like readFileSync (same special case below).
|
|
495
|
+
readFile: { fn: "fsp.readFile", params: [STRING, STRING], result: { kind: "promise", inner: STRING } },
|
|
496
|
+
writeFile: { fn: "fsp.writeFile", params: [STRING, STRING], result: { kind: "promise", inner: VOID } },
|
|
497
|
+
mkdir: { fn: "fsp.mkdir", params: [STRING], result: { kind: "promise", inner: VOID } },
|
|
498
|
+
readdir: { fn: "fsp.readdir", params: [STRING], result: { kind: "promise", inner: arrayOf(STRING) } },
|
|
499
|
+
rm: { fn: "fsp.rm", params: [STRING], result: { kind: "promise", inner: VOID } },
|
|
500
|
+
stat: { fn: "fsp.stat", params: [STRING], result: { kind: "promise", inner: STATS_T } },
|
|
501
|
+
unlink: { fn: "fsp.unlink", params: [STRING], result: { kind: "promise", inner: VOID } },
|
|
502
|
+
chmod: { fn: "fsp.chmod", params: [STRING, F64], result: { kind: "promise", inner: VOID } },
|
|
503
|
+
},
|
|
504
|
+
// The bare module's POSIX-target binding; a win32 target rebinds it to
|
|
505
|
+
// the win32 table (builtinModuleFnsOf — Node on Windows IS path.win32).
|
|
506
|
+
path: PATH_MODULE_FNS,
|
|
507
|
+
// Real Node modules ("node:path/posix" resolves), and the receivers of
|
|
508
|
+
// `path.posix.<member>` / `path.win32.<member>` through the namespace
|
|
509
|
+
// chain (builtinNamespaceModuleOf composes "path" + ".posix" to these
|
|
510
|
+
// keys). Both namespaces carry the FULL surface on every platform,
|
|
511
|
+
// Node's own rule: each answers its own platform's semantics anywhere.
|
|
512
|
+
"path/posix": PATH_MODULE_FNS,
|
|
513
|
+
"path/win32": PATH_WIN32_MODULE_FNS,
|
|
514
|
+
os: {
|
|
515
|
+
// One platform implementation: os.platform() === process.platform.
|
|
516
|
+
platform: { fn: "process.platform", params: [], result: STRING },
|
|
517
|
+
homedir: { fn: "os.homedir", params: [], result: STRING },
|
|
518
|
+
tmpdir: { fn: "os.tmpdir", params: [], result: STRING },
|
|
519
|
+
// uname(2)'s release field — Node's own implementation.
|
|
520
|
+
release: { fn: "os.release", params: [], result: STRING },
|
|
521
|
+
// uname(2)'s sysname field ("Darwin", "Linux", "Windows_NT") — the
|
|
522
|
+
// libFn/runtime pair predates this entry (the portless surface used
|
|
523
|
+
// them); this row makes the MODULE call reach them (test/common's
|
|
524
|
+
// isAIX/isIBMi getters are the canonical callers).
|
|
525
|
+
type: { fn: "os.type", params: [], result: STRING },
|
|
526
|
+
// Total physical memory in bytes — same predating-pair story.
|
|
527
|
+
totalmem: { fn: "os.totalmem", params: [], result: F64 },
|
|
528
|
+
// Entirely special-cased (lowerOsNetworkInterfacesCall): the result is
|
|
529
|
+
// the call site's mapped Dict<NetworkInterfaceInfo[]> shape, verified
|
|
530
|
+
// structurally there — this entry only routes the dispatch.
|
|
531
|
+
networkInterfaces: { fn: "os.networkInterfaces", params: [], result: VOID },
|
|
532
|
+
// Special-cased too (lowerOsUserInfoCall): the call site's mapped
|
|
533
|
+
// UserInfo record assembles field-by-field from scalar libCalls.
|
|
534
|
+
userInfo: { fn: "os.userName", params: [], result: VOID },
|
|
535
|
+
},
|
|
536
|
+
crypto: {
|
|
537
|
+
randomUUID: { fn: "crypto.randomUUID", params: [], result: STRING },
|
|
538
|
+
// A real Buffer result. The composed randomBytes(n).toString(enc)
|
|
539
|
+
// form KEEPS its one-libCall lowering (lowerCryptoComposedCall runs
|
|
540
|
+
// first and the Buffer never materializes there); this entry covers
|
|
541
|
+
// the bare calls and non-composed uses.
|
|
542
|
+
randomBytes: { fn: "crypto.randomBytes", params: [F64], result: BYTES_U8 },
|
|
543
|
+
},
|
|
544
|
+
zlib: {
|
|
545
|
+
// Buffer in, Buffer out, Node's default options; string inputs fence
|
|
546
|
+
// per site (see the zlib special case in lowerBuiltinModuleCall).
|
|
547
|
+
// cc.ts links libz only when these appear on the IR.
|
|
548
|
+
deflateSync: { fn: "zlib.deflateSync", params: [BYTES_U8], result: BYTES_U8 },
|
|
549
|
+
inflateSync: { fn: "zlib.inflateSync", params: [BYTES_U8], result: BYTES_U8 },
|
|
550
|
+
},
|
|
551
|
+
url: {
|
|
552
|
+
// fileURLToPath accepts a URL value OR a string — the call lowering
|
|
553
|
+
// picks the libFn by the ARGUMENT's static type (see the special case
|
|
554
|
+
// in lowerBuiltinModuleCall); the table entry carries the string form.
|
|
555
|
+
fileURLToPath: { fn: "url.fileURLToPathStr", params: [STRING], result: STRING },
|
|
556
|
+
pathToFileURL: { fn: "url.pathToFileURL", params: [STRING], result: URL_T },
|
|
557
|
+
},
|
|
558
|
+
child_process: {
|
|
559
|
+
// spawnSync's and spawn's call completions are entirely special-cased
|
|
560
|
+
// (an omitted args list completes to an empty string[]; spawnSync
|
|
561
|
+
// accepts exactly { encoding: "utf8" } as options, spawn requires
|
|
562
|
+
// exactly { stdio: "ignore" } — see lowerBuiltinModuleCall); the
|
|
563
|
+
// entries carry the canonical shapes.
|
|
564
|
+
spawnSync: { fn: "cp.spawnSync", params: [STRING, arrayOf(STRING)], result: SPAWNRES_T },
|
|
565
|
+
spawn: { fn: "cp.spawn", params: [STRING, arrayOf(STRING)], result: CHILD_T },
|
|
566
|
+
// execFileSync(file, args?, options?) and execSync(command, options?)
|
|
567
|
+
// share the cp.execSync runtime entry (execSync sets the shell flag);
|
|
568
|
+
// both call completions are special-cased in lowerBuiltinModuleCall.
|
|
569
|
+
// The entries carry canonical shapes for the coverage/fence machinery.
|
|
570
|
+
execFileSync: { fn: "cp.execSync", params: [STRING, arrayOf(STRING)], result: STRING },
|
|
571
|
+
execSync: { fn: "cp.execSync", params: [STRING], result: STRING },
|
|
572
|
+
},
|
|
573
|
+
// node:util lowers through TWO paths, neither tabled here: promisify
|
|
574
|
+
// only in the const-binding-over-execFile shape — recognized in
|
|
575
|
+
// lowerVarDecl / collectGlobals BEFORE any call lowering runs — and
|
|
576
|
+
// inspect/format/formatWithOptions through the util spoke
|
|
577
|
+
// (lower-inspect.ts: per-type synthesized traversal helpers,
|
|
578
|
+
// compile-time format strings), which both dispatch paths try before
|
|
579
|
+
// the member fence below.
|
|
580
|
+
util: {},
|
|
581
|
+
// node:string_decoder's surface is the StringDecoder CLASS — new/write/
|
|
582
|
+
// end are special-cased (lowerNew + lowerStringDecoderMethodCall); no
|
|
583
|
+
// function members exist to table.
|
|
584
|
+
string_decoder: {},
|
|
585
|
+
// node:readline: createInterface's options are entirely special-cased
|
|
586
|
+
// (exactly { input: process.stdin, output?: process.stdout } — see
|
|
587
|
+
// lowerBuiltinModuleCall); the entry carries the canonical shape. The
|
|
588
|
+
// interface's question/close/on lower through
|
|
589
|
+
// lowerReadlineMethodCall.
|
|
590
|
+
readline: {
|
|
591
|
+
createInterface: { fn: "rl.create", params: [], result: F64 },
|
|
592
|
+
},
|
|
593
|
+
// node:net and node:http lower ENTIRELY through the server spoke
|
|
594
|
+
// (lower-server.ts — every call shape is special-cased: closures, the
|
|
595
|
+
// optional connect host, writeHead's literal headers); the member list
|
|
596
|
+
// is NET_MODULE_FNS there. The modules still need keys here so future
|
|
597
|
+
// table entries have a home, and so the "recognized module, unlowered
|
|
598
|
+
// member" fence wording applies.
|
|
599
|
+
net: {
|
|
600
|
+
// The process-wide happy-eyeballs attempt budget: one runtime double
|
|
601
|
+
// in the core unit (never links scr_net.c by itself), default 250ms
|
|
602
|
+
// like Node's.
|
|
603
|
+
getDefaultAutoSelectFamilyAttemptTimeout: { fn: "net.getAutoSelTimeout", params: [], result: F64 },
|
|
604
|
+
setDefaultAutoSelectFamilyAttemptTimeout: { fn: "net.setAutoSelTimeout", params: [F64], result: VOID },
|
|
605
|
+
},
|
|
606
|
+
http: {},
|
|
607
|
+
// node:tls and node:https ride the same spoke (tls.createServer's
|
|
608
|
+
// options + secureConnection handler, https.createServer, and the
|
|
609
|
+
// https client's rejectUnauthorized/ca options are all special-cased
|
|
610
|
+
// in lower-server.ts).
|
|
611
|
+
tls: {},
|
|
612
|
+
https: {},
|
|
613
|
+
// node:dgram and node:dns lower ENTIRELY through the dgram spoke
|
|
614
|
+
// (lower-dgram.ts — createSocket's option literal, dns.lookup's
|
|
615
|
+
// options + callback, and the Socket method surface are all
|
|
616
|
+
// special-cased); the keys exist for the same reason net/http's do.
|
|
617
|
+
dgram: {},
|
|
618
|
+
dns: {},
|
|
619
|
+
// node:assert lowers ENTIRELY through the assert spoke (lower-assert.ts
|
|
620
|
+
// — every call shape is special-cased: optional messages complete, the
|
|
621
|
+
// comparisons pick per-type libCalls, deep equality synthesizes
|
|
622
|
+
// helpers); the keys exist so unlowered members fence module-qualified
|
|
623
|
+
// with the hints below.
|
|
624
|
+
assert: {},
|
|
625
|
+
"assert/strict": {},
|
|
626
|
+
// node:test lowers ENTIRELY through the test spoke (lower-test.ts —
|
|
627
|
+
// registrations, suites, hooks, the TestContext surface); the key
|
|
628
|
+
// exists so unlowered members fence module-qualified.
|
|
629
|
+
test: {},
|
|
630
|
+
// node:timers/promises — the delay-only setTimeout and bare setImmediate
|
|
631
|
+
// (void promises the shared timer heap settles). The arity completion
|
|
632
|
+
// (omitted delay = Node's 1ms) and the value/options fences are
|
|
633
|
+
// special-cased in lowerBuiltinModuleCall; these entries carry the
|
|
634
|
+
// canonical shapes and route the dispatch.
|
|
635
|
+
"timers/promises": {
|
|
636
|
+
setTimeout: { fn: "tp.setTimeout", params: [F64], result: { kind: "promise", inner: VOID } },
|
|
637
|
+
setImmediate: { fn: "tp.setImmediate", params: [], result: { kind: "promise", inner: VOID } },
|
|
638
|
+
},
|
|
639
|
+
// node:diagnostics_channel — the pub/sub core. channel() answers an f64
|
|
640
|
+
// handle (types.ts maps Channel to F64); the subscriber arguments box
|
|
641
|
+
// into the DOM (special-cased in lowerBuiltinModuleCall — the entries
|
|
642
|
+
// carry canonical shapes and route the dispatch). The Channel method
|
|
643
|
+
// surface lowers through lowerDcChannelMethodCall/lowerDcChannelProperty.
|
|
644
|
+
diagnostics_channel: {
|
|
645
|
+
channel: { fn: "dc.channel", params: [STRING], result: F64 },
|
|
646
|
+
subscribe: { fn: "dc.subscribe", params: [STRING, DYN], result: VOID },
|
|
647
|
+
unsubscribe: { fn: "dc.unsubscribe", params: [STRING, DYN], result: BOOL },
|
|
648
|
+
hasSubscribers: { fn: "dc.hasSubscribers", params: [STRING], result: BOOL },
|
|
649
|
+
// The string form's canonical shape; the collection form (an object
|
|
650
|
+
// literal of five Channels) is special-cased in lowerBuiltinModuleCall.
|
|
651
|
+
tracingChannel: { fn: "dc.tracingChannel", params: [STRING], result: F64 },
|
|
652
|
+
},
|
|
653
|
+
};
|
|
654
|
+
/** Builtin-module CONSTANTS (value reads of named imports): each lowers to
|
|
655
|
+
* a literal of its value's kind. The bare-module values are the TARGET
|
|
656
|
+
* platform's (builtinModuleConstOf swaps the win32 values in under a win32
|
|
657
|
+
* triple — Node's own platform conditionals); the named namespaces answer
|
|
658
|
+
* THEIR platform's constants everywhere. worker_threads/cluster carry the
|
|
659
|
+
* main-thread truths: a compiled binary IS the main thread (no JS-engine
|
|
660
|
+
* thread machinery) and never runs as a cluster worker (cluster forks the
|
|
661
|
+
* node binary itself) — Node's own answers for a directly-run script. */
|
|
662
|
+
export const BUILTIN_MODULE_CONSTS = {
|
|
663
|
+
path: { sep: "/", delimiter: ":" },
|
|
664
|
+
"path/posix": { sep: "/", delimiter: ":" },
|
|
665
|
+
"path/win32": { sep: "\\", delimiter: ";" },
|
|
666
|
+
os: { EOL: "\n" },
|
|
667
|
+
worker_threads: { isMainThread: true, threadId: 0 },
|
|
668
|
+
cluster: { isPrimary: true, isMaster: true, isWorker: false },
|
|
669
|
+
};
|
|
670
|
+
/** The win32-target overrides of the bare modules' constants: path's
|
|
671
|
+
* constants ARE the path/win32 namespace's, and os.EOL is CRLF (Node's
|
|
672
|
+
* `isWindows ? '\r\n' : '\n'`). */
|
|
673
|
+
const WIN32_TARGET_CONSTS = {
|
|
674
|
+
path: BUILTIN_MODULE_CONSTS["path/win32"],
|
|
675
|
+
os: { EOL: "\r\n" },
|
|
676
|
+
};
|
|
677
|
+
/** url's win32-target table: fileURLToPath is receiver-form-special-cased
|
|
678
|
+
* either way, and pathToFileURL swaps to its win32 IR flavor (the same
|
|
679
|
+
* runtime call, but in the may-throw seed — Node's win32 arm raises UNC
|
|
680
|
+
* TypeErrors where the posix arm never throws). */
|
|
681
|
+
const URL_WIN32_MODULE_FNS = {
|
|
682
|
+
fileURLToPath: { fn: "url.fileURLToPathStr", params: [STRING], result: STRING },
|
|
683
|
+
pathToFileURL: { fn: "url.pathToFileURLWin32", params: [STRING], result: URL_T },
|
|
684
|
+
};
|
|
685
|
+
/** BUILTIN_MODULE_FNS with the platform-conditional modules bound per
|
|
686
|
+
* TARGET: a win32 triple compiles Node-on-Windows semantics — `path` is
|
|
687
|
+
* path.win32 and url's bridge takes the win32 flavors. Fence wording is
|
|
688
|
+
* unaffected — callers keep naming the module the source spelled. */
|
|
689
|
+
export function builtinModuleFnsOf(L, module) {
|
|
690
|
+
if (L.targetPlatform === "win32") {
|
|
691
|
+
if (module === "path")
|
|
692
|
+
return BUILTIN_MODULE_FNS["path/win32"];
|
|
693
|
+
if (module === "url")
|
|
694
|
+
return URL_WIN32_MODULE_FNS;
|
|
695
|
+
}
|
|
696
|
+
return ownEntry(BUILTIN_MODULE_FNS, module);
|
|
697
|
+
}
|
|
698
|
+
/** Own-property lookup — lowerer.ts's own(), duplicated locally because
|
|
699
|
+
* lowerer.ts already imports surfaces.ts (a value import back would
|
|
700
|
+
* cycle). The tables here are plain object literals keyed by USER-written
|
|
701
|
+
* module/member names, so a bare index would also find Object.prototype
|
|
702
|
+
* members ("toString", "constructor", "__proto__"). */
|
|
703
|
+
function ownEntry(table, key) {
|
|
704
|
+
return Object.hasOwn(table, key) ? table[key] : undefined;
|
|
705
|
+
}
|
|
706
|
+
/** One MEMBER's table entry, own-property-safe (`path.toString` must not
|
|
707
|
+
* answer Object.prototype.toString as a BuiltinModuleFn). */
|
|
708
|
+
export function builtinModuleFnOf(L, module, member) {
|
|
709
|
+
const mod = builtinModuleFnsOf(L, module);
|
|
710
|
+
return mod ? ownEntry(mod, member) : undefined;
|
|
711
|
+
}
|
|
712
|
+
/** One member's fence hint, own-property-safe (a collision would print an
|
|
713
|
+
* inherited function into the diagnostic text). */
|
|
714
|
+
export function builtinFenceHintOf(module, member) {
|
|
715
|
+
const mod = ownEntry(BUILTIN_MODULE_FENCE_HINTS, module);
|
|
716
|
+
return mod ? ownEntry(mod, member) : undefined;
|
|
717
|
+
}
|
|
718
|
+
/** BUILTIN_MODULE_CONSTS with the win32-target overrides applied — the
|
|
719
|
+
* constants twin of builtinModuleFnsOf. */
|
|
720
|
+
export function builtinModuleConstOf(L, module, member) {
|
|
721
|
+
if (L.targetPlatform === "win32") {
|
|
722
|
+
const mod = ownEntry(WIN32_TARGET_CONSTS, module);
|
|
723
|
+
const w = mod ? ownEntry(mod, member) : undefined;
|
|
724
|
+
if (w !== undefined)
|
|
725
|
+
return w;
|
|
726
|
+
}
|
|
727
|
+
const mod = ownEntry(BUILTIN_MODULE_CONSTS, module);
|
|
728
|
+
return mod ? ownEntry(mod, member) : undefined;
|
|
729
|
+
}
|
|
730
|
+
/** The IR literal of a builtin-module constant — one place so both value
|
|
731
|
+
* paths (named-import bindings and namespace member reads) emit the same
|
|
732
|
+
* kinds. */
|
|
733
|
+
export function builtinConstLit(value, loc) {
|
|
734
|
+
if (typeof value === "string")
|
|
735
|
+
return { kind: "strLit", value, type: STRING, loc };
|
|
736
|
+
if (typeof value === "number")
|
|
737
|
+
return { kind: "numLit", value, type: F64, loc };
|
|
738
|
+
return { kind: "boolLit", value, type: BOOL, loc };
|
|
739
|
+
}
|
|
740
|
+
/** Member-specific hints for RECOGNIZED builtin modules whose member has
|
|
741
|
+
* no lowering. deflateSync/inflateSync lower now (Buffers are real);
|
|
742
|
+
* the rest of the zlib surface points at the lowered pair. */
|
|
743
|
+
export const ZLIB_HINT = "deflateSync and inflateSync are the lowered zlib surface";
|
|
744
|
+
/** The loose-equality quartet's shared hint: == coercion has no lowering
|
|
745
|
+
* anywhere in this compiler, and Node itself points at the strict forms. */
|
|
746
|
+
const ASSERT_LOOSE_HINT = "loose == equality has no lowering — the strict forms compare with " +
|
|
747
|
+
"Object.is/structural equality like Node's assert/strict module, where " +
|
|
748
|
+
"equal IS strictEqual";
|
|
749
|
+
const ASSERT_MODULE_HINTS = {
|
|
750
|
+
equal: ASSERT_LOOSE_HINT,
|
|
751
|
+
notEqual: ASSERT_LOOSE_HINT,
|
|
752
|
+
deepEqual: ASSERT_LOOSE_HINT,
|
|
753
|
+
notDeepEqual: ASSERT_LOOSE_HINT,
|
|
754
|
+
ifError: "test explicitly instead: assert.strictEqual(err, null) / " +
|
|
755
|
+
"assert.strictEqual(err, undefined)",
|
|
756
|
+
rejects: "await the promise inside assert.throws's callback story instead: " +
|
|
757
|
+
"try { await p; assert.fail(\"expected rejection\") } catch { ... }",
|
|
758
|
+
doesNotReject: "await the promise directly — an unexpected rejection already fails the test",
|
|
759
|
+
doesNotThrow: "call the function directly — an unexpected throw already fails the test",
|
|
760
|
+
AssertionError: "the class itself has no lowering — catch and test err.name === " +
|
|
761
|
+
'"AssertionError" or err.code === "ERR_ASSERTION"',
|
|
762
|
+
};
|
|
763
|
+
export const BUILTIN_MODULE_FENCE_HINTS = {
|
|
764
|
+
assert: ASSERT_MODULE_HINTS,
|
|
765
|
+
// The strict module's equal/notEqual/deepEqual/notDeepEqual ARE the
|
|
766
|
+
// strict comparisons (aliased in the spoke) — only the members with no
|
|
767
|
+
// lowering in either module fence here.
|
|
768
|
+
"assert/strict": {
|
|
769
|
+
ifError: ASSERT_MODULE_HINTS["ifError"],
|
|
770
|
+
rejects: ASSERT_MODULE_HINTS["rejects"],
|
|
771
|
+
doesNotReject: ASSERT_MODULE_HINTS["doesNotReject"],
|
|
772
|
+
doesNotThrow: ASSERT_MODULE_HINTS["doesNotThrow"],
|
|
773
|
+
AssertionError: ASSERT_MODULE_HINTS["AssertionError"],
|
|
774
|
+
},
|
|
775
|
+
util: {
|
|
776
|
+
promisify: "the one lowered shape is a const binding over child_process.execFile: " +
|
|
777
|
+
"const execFileAsync = promisify(execFile), then call execFileAsync directly",
|
|
778
|
+
// child_process.execFile itself exists to be promisified — the same
|
|
779
|
+
// story from the other end.
|
|
780
|
+
},
|
|
781
|
+
child_process: {
|
|
782
|
+
execFile: "the callback form has no lowering — promisify it: " +
|
|
783
|
+
"const execFileAsync = promisify(execFile) (from node:util), or use execFileSync",
|
|
784
|
+
},
|
|
785
|
+
crypto: {
|
|
786
|
+
createHash: "the one lowered shape is the composed chain " +
|
|
787
|
+
'createHash("sha256").update(data).digest("hex") — the Hash handle itself has no lowering',
|
|
788
|
+
},
|
|
789
|
+
zlib: {
|
|
790
|
+
gzipSync: ZLIB_HINT,
|
|
791
|
+
gunzipSync: ZLIB_HINT,
|
|
792
|
+
unzipSync: ZLIB_HINT,
|
|
793
|
+
brotliCompressSync: ZLIB_HINT,
|
|
794
|
+
brotliDecompressSync: ZLIB_HINT,
|
|
795
|
+
},
|
|
796
|
+
http2: {
|
|
797
|
+
connect: "HTTP/2 client sessions have no lowering — the lowered http2 surface is the SERVER side: " +
|
|
798
|
+
"createSecureServer({ allowHTTP1: true, cert, key }), which serves HTTP/1.1 only " +
|
|
799
|
+
"(ALPN never offers h2 — h2-capable clients negotiate down); an HTTP/1.1 client is https.request",
|
|
800
|
+
createServer: "cleartext (h2c) servers have no lowering — the lowered http2 surface is " +
|
|
801
|
+
"createSecureServer({ allowHTTP1: true, cert, key }), which serves HTTP/1.1 over TLS " +
|
|
802
|
+
"(plain HTTP/1.1 is http.createServer)",
|
|
803
|
+
},
|
|
804
|
+
tls: {
|
|
805
|
+
createSecureContext: "the lowered form is createSecureContext({ cert, key }) — an opaque SecureContext handle " +
|
|
806
|
+
"for SNI callbacks (http2.createSecureServer accepts SNICallback); other options fence by name",
|
|
807
|
+
connect: "the lowered TLS client is https.request({ hostname, port, path, method, ca?, rejectUnauthorized? }); " +
|
|
808
|
+
"raw tls.connect sockets have no lowering yet",
|
|
809
|
+
},
|
|
810
|
+
};
|
|
811
|
+
/** The MEMBER chokepoint of the lib fence: called from the property-read
|
|
812
|
+
* and method-call fallbacks after every lowering has passed. When the
|
|
813
|
+
* accessed member is stdlib-declared, the use is standard-library surface
|
|
814
|
+
* with no lowering — report SC2020 naming `<container>.<member>` (the
|
|
815
|
+
* receiver's global name when it IS a stdlib global like Math or Promise,
|
|
816
|
+
* its widened type text otherwise). Returns silently for non-stdlib
|
|
817
|
+
* members so the caller's generic rejection applies. */
|
|
818
|
+
export function stdlibMemberFence(L, access) {
|
|
819
|
+
const sym = L.checker.getSymbolAtLocation(access.name);
|
|
820
|
+
if (!sym || !L.isStdlibSymbol(sym))
|
|
821
|
+
return;
|
|
822
|
+
const member = access.name.text;
|
|
823
|
+
const recv = access.expression;
|
|
824
|
+
// Name the container the way the source reads: the global's name when
|
|
825
|
+
// the receiver IS a stdlib global (Math, process), the dotted path for
|
|
826
|
+
// a member of one (process.stdout — its TYPE text would be the useless
|
|
827
|
+
// 'WriteStream & { fd: 1; }'), the receiver's widened type text
|
|
828
|
+
// otherwise.
|
|
829
|
+
const globalPathOf = (e) => {
|
|
830
|
+
if (ts.isIdentifier(e) && L.isStdlibSymbol(L.checker.getSymbolAtLocation(e))) {
|
|
831
|
+
return e.text;
|
|
832
|
+
}
|
|
833
|
+
if (ts.isPropertyAccessExpression(e) && ts.isIdentifier(e.expression)) {
|
|
834
|
+
const base = globalPathOf(e.expression);
|
|
835
|
+
if (base !== null)
|
|
836
|
+
return `${base}.${e.name.text}`;
|
|
837
|
+
}
|
|
838
|
+
return null;
|
|
839
|
+
};
|
|
840
|
+
const container = globalPathOf(recv) ??
|
|
841
|
+
L.checker.typeToString(L.checker.getBaseTypeOfLiteralType(L.typeOf(recv)));
|
|
842
|
+
let hint;
|
|
843
|
+
const recvIr = L.mapTypeOf(L.typeOf(recv));
|
|
844
|
+
if (recvIr?.kind === "promise" && (member === "then" || member === "catch" || member === "finally")) {
|
|
845
|
+
hint =
|
|
846
|
+
"'await' is the supported way to chain (p.then(f) with one fulfillment handler, p.catch " +
|
|
847
|
+
"with an INLINE handler, and p.finally(cb) compile) — try { await p } catch (e) is the general form";
|
|
848
|
+
}
|
|
849
|
+
else if (recvIr?.kind === "f64" && member === "toString") {
|
|
850
|
+
hint = "radix-free conversion is a template literal away: `${x}`; toString(radix) runs under --dynamic";
|
|
851
|
+
}
|
|
852
|
+
else if (container === "Math" && (member === "min" || member === "max")) {
|
|
853
|
+
hint =
|
|
854
|
+
`Math.${member} lowers with exactly two arguments or one number[] spread — ` +
|
|
855
|
+
`nest calls (Math.${member}(a, Math.${member}(b, c))) or spread an array (Math.${member}(...xs))`;
|
|
856
|
+
}
|
|
857
|
+
else if (recvIr?.kind === "array" &&
|
|
858
|
+
member === "index" &&
|
|
859
|
+
(container === "RegExpExecArray" || container === "RegExpMatchArray")) {
|
|
860
|
+
hint =
|
|
861
|
+
"'.index' is supported on the const binding of a for-of over a DIRECT matchAll call " +
|
|
862
|
+
"(for (const m of s.matchAll(re)) { ... m.index ... }); stored rows are honest " +
|
|
863
|
+
"string[] slices without it";
|
|
864
|
+
}
|
|
865
|
+
else if (recvIr?.kind === "array" && member === "flat") {
|
|
866
|
+
hint =
|
|
867
|
+
"flat has no lowering (flatMap does) — flatten into an accumulator instead: " +
|
|
868
|
+
"for (const x of xs) for (const y of x) out.push(y)";
|
|
869
|
+
}
|
|
870
|
+
else if (recvIr?.kind === "object" &&
|
|
871
|
+
RUNTIME_ERROR_CLASSES.has(recvIr.className) &&
|
|
872
|
+
member === "stack") {
|
|
873
|
+
hint =
|
|
874
|
+
"stack traces are not captured (frames would need runtime bookkeeping); " +
|
|
875
|
+
"name, message, and toString() are available";
|
|
876
|
+
}
|
|
877
|
+
else if (container === "process.stdin" || (container === "process" && member === "stdin")) {
|
|
878
|
+
hint =
|
|
879
|
+
"isTTY, destroy(), on/once of the data/end/error events, and " +
|
|
880
|
+
"`for await (const chunk of process.stdin)` are the supported stdin surface " +
|
|
881
|
+
'(or read everything at once: readFileSync(0, "utf8") from node:fs)';
|
|
882
|
+
}
|
|
883
|
+
else if ((container === "process" && (member === "stdout" || member === "stderr")) ||
|
|
884
|
+
container === "process.stdout" ||
|
|
885
|
+
container === "process.stderr") {
|
|
886
|
+
// @types/node territory: the fallback declarations don't have these
|
|
887
|
+
// members at all (a type error), so this fence only fires with the
|
|
888
|
+
// project's real Node types adopted.
|
|
889
|
+
hint =
|
|
890
|
+
"console.log is the supported way to write a line to stdout; " +
|
|
891
|
+
"the stdout/stderr stream objects have no lowering";
|
|
892
|
+
}
|
|
893
|
+
else if (container === "console") {
|
|
894
|
+
hint = "console.log(number | string | boolean ...) is the supported console surface";
|
|
895
|
+
}
|
|
896
|
+
else if (member === "prototype") {
|
|
897
|
+
hint =
|
|
898
|
+
"prototype objects are not values here (method lookup is static) — call the method on an instance instead";
|
|
899
|
+
}
|
|
900
|
+
else if (recvIr?.kind === "func" &&
|
|
901
|
+
(member === "call" || member === "apply" || member === "bind")) {
|
|
902
|
+
hint =
|
|
903
|
+
"call the function directly — plain and arrow functions here never read `this`, so " +
|
|
904
|
+
"f.call(thisArg, ...args) is f(...args), and bind's partial application is an arrow: (...rest) => f(a, ...rest)";
|
|
905
|
+
}
|
|
906
|
+
else if (recvIr?.kind === "string" &&
|
|
907
|
+
(member === "match" || member === "matchAll" || member === "search")) {
|
|
908
|
+
hint =
|
|
909
|
+
"the regex-LITERAL argument forms lower (s.match(/re/)); a string pattern constructs " +
|
|
910
|
+
"a RegExp at runtime, which has no lowering";
|
|
911
|
+
}
|
|
912
|
+
else if (container === "Intl" || container.startsWith("Intl.") ||
|
|
913
|
+
["NumberFormat", "DateTimeFormat", "PluralRules", "Collator", "Locale",
|
|
914
|
+
"RelativeTimeFormat", "ListFormat", "Segmenter", "DisplayNames"].includes(container) ||
|
|
915
|
+
member === "toLocaleString" || member === "toLocaleDateString" ||
|
|
916
|
+
member === "toLocaleTimeString" || member === "toLocaleLowerCase" ||
|
|
917
|
+
member === "toLocaleUpperCase") {
|
|
918
|
+
hint =
|
|
919
|
+
"locale- and ICU-backed behavior lives outside the static runtime (the localeCompare " +
|
|
920
|
+
"stance: code-unit order, no collation/locale data) — format with template literals, " +
|
|
921
|
+
"toFixed, and toString instead";
|
|
922
|
+
}
|
|
923
|
+
else if (container === "Object" && member === "assign") {
|
|
924
|
+
hint =
|
|
925
|
+
"spread instead: { ...a, ...b } builds the merged record; what lowers: the empty-target " +
|
|
926
|
+
"literal-source shape (Object.assign({}, { ... }) IS the source literal) and merges INTO " +
|
|
927
|
+
"an index-signature record whose value slot every source value enters — " +
|
|
928
|
+
"other aliased targets are real mutation, and a function target (Object.assign(fn, { prop })) " +
|
|
929
|
+
"is a function-with-properties value the model cannot represent: bind the property separately";
|
|
930
|
+
}
|
|
931
|
+
L.noLowering(`${container}.${member}`, access, hint, sym);
|
|
932
|
+
}
|
|
933
|
+
/** True iff the accessed member is declared by the standard library —
|
|
934
|
+
* the same technique as isConsoleLog: trust declarations, not names. */
|
|
935
|
+
export function isStdlibMember(L, access) {
|
|
936
|
+
const direct = L.checker.getSymbolAtLocation(access.name);
|
|
937
|
+
if (direct)
|
|
938
|
+
return L.isStdlibSymbol(direct);
|
|
939
|
+
// An IMPLICIT-ANY instance body (the checker sees an `any` receiver —
|
|
940
|
+
// no member symbol resolves) or an ALIASED-TYPEOF narrow (the checker
|
|
941
|
+
// sees the un-narrowed union — `val.length` on String|Number has no
|
|
942
|
+
// property symbol): typeOf answers the bound/narrowed type — resolve
|
|
943
|
+
// the member from it, the same provenance answer the checker would
|
|
944
|
+
// give on a typed receiver (`path.startsWith` with path bound string
|
|
945
|
+
// IS String.prototype.startsWith).
|
|
946
|
+
if (L.implicitParamTypes !== null || L.aliasNarrowTypes.size > 0) {
|
|
947
|
+
const viaType = L.checker.getPropertyOfType(L.typeOf(access.expression), access.name.text);
|
|
948
|
+
return L.isStdlibSymbol(viaType ?? undefined);
|
|
949
|
+
}
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
/** The provenance check the CHILD receiver lowerings use: a stdlib
|
|
953
|
+
* member, OR a member the user's own child-shaped interface declares
|
|
954
|
+
* (the NgrokChildProcess idiom — mapType's duck rule admits an
|
|
955
|
+
* interface only when EVERY member names the lowered ChildProcess
|
|
956
|
+
* surface, so accepting its members here cannot widen the surface;
|
|
957
|
+
* the receiver-kind gate already proved the type maps to child). */
|
|
958
|
+
export function isChildSurfaceMember(L, access) {
|
|
959
|
+
if (isStdlibMember(L, access))
|
|
960
|
+
return true;
|
|
961
|
+
const sym = L.checker.getSymbolAtLocation(access.name);
|
|
962
|
+
const decl = sym ? L.checker.declarationsOf(sym)[0] : undefined;
|
|
963
|
+
const iface = decl?.parent;
|
|
964
|
+
if (!decl || !iface || !ts.isInterfaceDeclaration(iface))
|
|
965
|
+
return false;
|
|
966
|
+
if (L.isStdlibFile(decl.getSourceFile()))
|
|
967
|
+
return false;
|
|
968
|
+
return L.mapTypeOf(L.checker.getTypeAtLocation(iface.name))?.kind === "child";
|
|
969
|
+
}
|
|
970
|
+
/** True iff some declaration of the symbol lives in the standard library
|
|
971
|
+
* (shipped ambient or default lib — the provenance half of every
|
|
972
|
+
* supported-surface check). `some`, not `[0]`: divergence overrides merge
|
|
973
|
+
* with lib interfaces, so a member can carry declarations from both. A
|
|
974
|
+
* user's own declaration is in neither, so shadowing never matches. */
|
|
975
|
+
export function isStdlibSymbol(L, symbol) {
|
|
976
|
+
return !!symbol && L.checker.declarationsOf(symbol).some((d) => L.isStdlibFile(d.getSourceFile()));
|
|
977
|
+
}
|
|
978
|
+
/** The canonical stdlib-global name `expr` denotes, or null. Three
|
|
979
|
+
* spellings reach the same global (Node's own aliasing):
|
|
980
|
+
* - the bare identifier (`process`), name + provenance checked;
|
|
981
|
+
* - the `global` identifier — Node's alias of globalThis — and
|
|
982
|
+
* `globalThis` itself both canonicalize to "globalThis";
|
|
983
|
+
* - a property read off globalThis (`globalThis.process`,
|
|
984
|
+
* `global.process`) — `declare var` globals ARE properties of
|
|
985
|
+
* `typeof globalThis`, same symbol either way;
|
|
986
|
+
* - a local alias binding (`const process = globalThis.process`, the
|
|
987
|
+
* tamper-guard prologue) registered in stdlibGlobalAliases. */
|
|
988
|
+
export function stdlibGlobalNameOf(L, expr) {
|
|
989
|
+
if (ts.isParenthesizedExpression(expr))
|
|
990
|
+
return stdlibGlobalNameOf(L, expr.expression);
|
|
991
|
+
if (ts.isIdentifier(expr)) {
|
|
992
|
+
// `globalThis` itself: a reserved intrinsic — tsc rejects user
|
|
993
|
+
// bindings of the name, and its special symbol carries no ordinary
|
|
994
|
+
// declarations for the provenance check to see.
|
|
995
|
+
if (expr.text === "globalThis")
|
|
996
|
+
return "globalThis";
|
|
997
|
+
const symbol = L.checker.getSymbolAtLocation(expr);
|
|
998
|
+
if (!symbol)
|
|
999
|
+
return null;
|
|
1000
|
+
const alias = L.stdlibGlobalAliases.get(symbol);
|
|
1001
|
+
if (alias !== undefined)
|
|
1002
|
+
return alias;
|
|
1003
|
+
// An IMPORTED binding of a builtin module's re-exported global
|
|
1004
|
+
// (`import { Buffer } from "node:buffer"` — Node's module spelling
|
|
1005
|
+
// of the same object) resolves through the alias to the fallback
|
|
1006
|
+
// file's own export, so provenance and name check out exactly like
|
|
1007
|
+
// the bare-global spelling. A user module re-exporting its own
|
|
1008
|
+
// `Buffer` resolves to a user-file declaration and still misses.
|
|
1009
|
+
const resolved = symbol.flags & ts.SymbolFlags.Alias ? L.checker.getAliasedSymbol(symbol) : symbol;
|
|
1010
|
+
if (!L.isStdlibSymbol(resolved))
|
|
1011
|
+
return null;
|
|
1012
|
+
return resolved.name === "global" ? "globalThis" : resolved.name;
|
|
1013
|
+
}
|
|
1014
|
+
if (ts.isPropertyAccessExpression(expr) && !expr.questionDotToken) {
|
|
1015
|
+
if (stdlibGlobalNameOf(L, expr.expression) !== "globalThis")
|
|
1016
|
+
return null;
|
|
1017
|
+
const symbol = L.checker.getSymbolAtLocation(expr.name);
|
|
1018
|
+
if (!symbol || !L.isStdlibSymbol(symbol))
|
|
1019
|
+
return null;
|
|
1020
|
+
return symbol.name === "global" ? "globalThis" : symbol.name;
|
|
1021
|
+
}
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
/** True iff `expr` denotes THE standard-library global `name` — name AND
|
|
1025
|
+
* provenance, because neither alone suffices: several globals share
|
|
1026
|
+
* member names (Math.log must never lower as console.log), and a user
|
|
1027
|
+
* binding shadowing a global's name has a different, non-stdlib symbol.
|
|
1028
|
+
* All of stdlibGlobalNameOf's spellings answer (globalThis.process is
|
|
1029
|
+
* process; `const process = globalThis.process` aliases through). */
|
|
1030
|
+
export function isStdlibGlobal(L, expr, name) {
|
|
1031
|
+
return stdlibGlobalNameOf(L, expr) === name;
|
|
1032
|
+
}
|
|
1033
|
+
/** The member name of a `<global>.<member>` access whose receiver is THE
|
|
1034
|
+
* standard-library global `name` (console, JSON, process, Math). Null for
|
|
1035
|
+
* anything else, so property-lowering chains keep trying other
|
|
1036
|
+
* receivers. */
|
|
1037
|
+
export function stdlibGlobalMember(L, access, name) {
|
|
1038
|
+
if (access.questionDotToken)
|
|
1039
|
+
return null;
|
|
1040
|
+
return L.isStdlibGlobal(access.expression, name) ? access.name.text : null;
|
|
1041
|
+
}
|
|
1042
|
+
/** `const process = globalThis.process` (and any `const x = <stdlib
|
|
1043
|
+
* global>` snapshot — the suite harness's tamper-guard prologue): the
|
|
1044
|
+
* binding is pure alias plumbing. Nothing in a compiled program can
|
|
1045
|
+
* reassign a stdlib global, so the snapshot IS the global: the symbol
|
|
1046
|
+
* registers in stdlibGlobalAliases (every receiver check resolves
|
|
1047
|
+
* through it — see stdlibGlobalNameOf) and the declaration emits
|
|
1048
|
+
* nothing. Only the globals with lowered member surfaces alias this
|
|
1049
|
+
* way; aliasing, say, `Math` would change nothing (its members lower
|
|
1050
|
+
* by receiver too). Returns true when recognized. */
|
|
1051
|
+
export function stdlibGlobalAliasDecl(L, nameNode, init) {
|
|
1052
|
+
if (!init || !ts.isIdentifier(nameNode))
|
|
1053
|
+
return false;
|
|
1054
|
+
// `const process = require('node:process')`: Node's process MODULE is
|
|
1055
|
+
// the global process object (module.exports === globalThis.process),
|
|
1056
|
+
// so the binding aliases the global exactly like `const process =
|
|
1057
|
+
// globalThis.process`. Preflight admits exactly this shape
|
|
1058
|
+
// (processModuleAliasRequire7); commander's lib/command.js opens with
|
|
1059
|
+
// it.
|
|
1060
|
+
const requireSpec = requireSpecOf(init);
|
|
1061
|
+
const name = requireSpec === "process" || requireSpec === "node:process"
|
|
1062
|
+
? "process"
|
|
1063
|
+
: stdlibGlobalNameOf(L, init);
|
|
1064
|
+
if (name === null)
|
|
1065
|
+
return false;
|
|
1066
|
+
// Only alias OBJECT-shaped globals whose members lower by receiver
|
|
1067
|
+
// identity (process, console, globalThis itself). Function-valued
|
|
1068
|
+
// globals (setTimeout) taken as values are a different story — the
|
|
1069
|
+
// ordinary value paths (and their fences) apply.
|
|
1070
|
+
if (name !== "process" && name !== "console" && name !== "globalThis")
|
|
1071
|
+
return false;
|
|
1072
|
+
const symbol = L.checker.getSymbolAtLocation(nameNode);
|
|
1073
|
+
if (!symbol)
|
|
1074
|
+
return false;
|
|
1075
|
+
L.stdlibGlobalAliases.set(symbol, name);
|
|
1076
|
+
return true;
|
|
1077
|
+
}
|
|
1078
|
+
/** True iff the symbol is declared ONLY by the adopted @types/node
|
|
1079
|
+
* surface (no es-lib or shipped-ambient declaration merges in) — chooses
|
|
1080
|
+
* the SC2020 fence's wording: "typed by @types/node", not "standard
|
|
1081
|
+
* library". */
|
|
1082
|
+
export function nodeTypesOnlySymbol(L, sym) {
|
|
1083
|
+
const decls = sym ? L.checker.declarationsOf(sym) : undefined;
|
|
1084
|
+
if (!decls || decls.length === 0)
|
|
1085
|
+
return false;
|
|
1086
|
+
let viaNode = false;
|
|
1087
|
+
for (const d of decls) {
|
|
1088
|
+
const sf = d.getSourceFile();
|
|
1089
|
+
if (sf.isDeclarationFile && isNodeTypesPath(sf.fileName))
|
|
1090
|
+
viaNode = true;
|
|
1091
|
+
else if (L.isStdlibFile(sf))
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
return viaNode;
|
|
1095
|
+
}
|
|
1096
|
+
//# sourceMappingURL=surfaces.js.map
|