@scriptc/compiler 0.0.9 → 0.0.11
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/ambient/scriptc-node-fallback.d.ts +54 -9
- package/dist/backend/emission/emit-exprs.js +170 -14
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-walkers.js +29 -2
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.js +25 -1
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/llvm/dyn.d.ts +1 -0
- package/dist/backend/llvm/dyn.js +83 -1
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +210 -13
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/coverage/surface-manifest.js +16 -2
- package/dist/coverage/surface-manifest.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +41 -14
- package/dist/diagnostics/diagnostic.js +79 -27
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/diagnostics/render.js +8 -0
- package/dist/diagnostics/render.js.map +1 -1
- package/dist/frontend/lowering/lib-boundary.js +11 -1
- package/dist/frontend/lowering/lib-boundary.js.map +1 -1
- package/dist/frontend/lowering/lower-builtins.d.ts +69 -0
- package/dist/frontend/lowering/lower-builtins.js +380 -4
- package/dist/frontend/lowering/lower-builtins.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.d.ts +41 -0
- package/dist/frontend/lowering/lower-calls.js +559 -11
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.d.ts +7 -0
- package/dist/frontend/lowering/lower-classes.js +51 -1
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-containers.d.ts +21 -0
- package/dist/frontend/lowering/lower-containers.js +262 -17
- package/dist/frontend/lowering/lower-containers.js.map +1 -1
- package/dist/frontend/lowering/lower-dgram.js +92 -17
- package/dist/frontend/lowering/lower-dgram.js.map +1 -1
- package/dist/frontend/lowering/lower-emitter.d.ts +70 -3
- package/dist/frontend/lowering/lower-emitter.js +436 -29
- package/dist/frontend/lowering/lower-emitter.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.d.ts +80 -5
- package/dist/frontend/lowering/lower-exprs.js +651 -23
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-modules.d.ts +8 -5
- package/dist/frontend/lowering/lower-modules.js +144 -19
- package/dist/frontend/lowering/lower-modules.js.map +1 -1
- package/dist/frontend/lowering/lower-namespaces.d.ts +39 -11
- package/dist/frontend/lowering/lower-namespaces.js +166 -24
- package/dist/frontend/lowering/lower-namespaces.js.map +1 -1
- package/dist/frontend/lowering/lower-server.js +211 -4
- package/dist/frontend/lowering/lower-server.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.d.ts +1 -5
- package/dist/frontend/lowering/lower-stmts.js +293 -10
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lower-stream.js +178 -10
- package/dist/frontend/lowering/lower-stream.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +53 -1
- package/dist/frontend/lowering/lowerer.js +210 -19
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/lowering/surfaces.d.ts +44 -0
- package/dist/frontend/lowering/surfaces.js +234 -3
- package/dist/frontend/lowering/surfaces.js.map +1 -1
- package/dist/frontend/npm.d.ts +12 -0
- package/dist/frontend/npm.js +30 -0
- package/dist/frontend/npm.js.map +1 -1
- package/dist/frontend/program.d.ts +25 -0
- package/dist/frontend/program.js +144 -135
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/shared.d.ts +1 -1
- package/dist/frontend/shared.js +1 -1
- package/dist/frontend/shared.js.map +1 -1
- package/dist/frontend/types.d.ts +13 -0
- package/dist/frontend/types.js +84 -7
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +157 -12
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +163 -13
- package/dist/ir/nodes.js +65 -6
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +64 -5
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/fence-eval.d.ts +77 -0
- package/dist/library/fence-eval.js +395 -0
- package/dist/library/fence-eval.js.map +1 -0
- package/dist/library/int-infer.d.ts +119 -0
- package/dist/library/int-infer.js +1301 -0
- package/dist/library/int-infer.js.map +1 -0
- package/dist/library/int-infer.test.d.ts +1 -0
- package/dist/library/int-infer.test.js +279 -0
- package/dist/library/int-infer.test.js.map +1 -0
- package/dist/library/profile.d.ts +43 -6
- package/dist/library/profile.js +179 -31
- package/dist/library/profile.js.map +1 -1
- package/dist/library/sidecar.d.ts +23 -0
- package/dist/library/sidecar.js +150 -16
- package/dist/library/sidecar.js.map +1 -1
- package/package.json +2 -2
- package/surface-manifest.json +208 -10
|
@@ -2,10 +2,16 @@
|
|
|
2
2
|
* crypto, child_process spawn/spawnSync and child/stats/spawn-result
|
|
3
3
|
* methods), JSON.parse/stringify, process properties/methods and
|
|
4
4
|
* process.env access, and console.log detection. */
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
5
6
|
import { builtinModules } from "node:module";
|
|
7
|
+
import { dirname, resolve } from "node:path";
|
|
6
8
|
import * as ts from "../ts7/adapter.js";
|
|
7
|
-
import { dynUndefinedExpr, nodeThrowExpr, own } from "./lowerer.js";
|
|
9
|
+
import { PoisonError, dynUndefinedExpr, ladderFenceExpr, nodeThrowExpr, own } from "./lowerer.js";
|
|
8
10
|
import { canonicalBuiltinModule, isJsSourceFile, locOf, requireSpecOf } from "../program.js";
|
|
11
|
+
import { isRelativeSpecifier } from "../shared.js";
|
|
12
|
+
import { probeNodeRequireRefusal } from "../npm.js";
|
|
13
|
+
import { isNpmStaticPackage } from "../npm-static.js";
|
|
14
|
+
import { invalidJsonModuleDiag, requiresDynamicImportDiag } from "../../diagnostics/diagnostic.js";
|
|
9
15
|
import { builtinModuleFnOf, FS_READDIR_DOCUMENTED_OPTIONS, FS_WATCH_DOCUMENTED_OPTIONS, FS_WRITE_FILE_DOCUMENTED_OPTIONS, QS_PARSE_DOCUMENTED_OPTIONS, QS_STRINGIFY_DOCUMENTED_OPTIONS, READLINE_DOCUMENTED_OPTIONS, builtinConstLit, fenceOrDropOptionKey, isChildSurfaceMember, } from "./surfaces.js";
|
|
10
16
|
import { conditionalSpreadOf, lowerDynObjectLiteral } from "./lower-exprs.js";
|
|
11
17
|
import { HTTP2_CONSTANTS } from "./http2-constants.js";
|
|
@@ -132,9 +138,17 @@ export function builtinMemberRequireDecl(nameNode, init) {
|
|
|
132
138
|
export function builtinNamespaceDestructureModuleOf(L, decl) {
|
|
133
139
|
if (decl.name === undefined || !ts.isObjectBindingPattern(decl.name) || decl.initializer === undefined)
|
|
134
140
|
return null;
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
141
|
+
// `const { join } = require("node:path")` through a createRequire
|
|
142
|
+
// binding: the call IS the namespace — same table keying.
|
|
143
|
+
let module = null;
|
|
144
|
+
const init = stripTypeCasts(decl.initializer);
|
|
145
|
+
if (ts.isCallExpression(init)) {
|
|
146
|
+
const cr = createRequireSpecOf(L, init);
|
|
147
|
+
module = cr !== null && cr.spec !== null ? canonicalBuiltinModule(cr.spec) : null;
|
|
148
|
+
}
|
|
149
|
+
else if (ts.isIdentifier(init)) {
|
|
150
|
+
module = L.builtinNamespaceModuleOf(init);
|
|
151
|
+
}
|
|
138
152
|
if (module === null)
|
|
139
153
|
return null;
|
|
140
154
|
for (const el of decl.name.elements) {
|
|
@@ -145,6 +159,223 @@ export function builtinNamespaceDestructureModuleOf(L, decl) {
|
|
|
145
159
|
}
|
|
146
160
|
return module;
|
|
147
161
|
}
|
|
162
|
+
/* ── node:module — createRequire's static erasure ─────────────────────
|
|
163
|
+
* The config/version-reading pattern real CLIs ship:
|
|
164
|
+
* import { createRequire } from "node:module";
|
|
165
|
+
* const require = createRequire(import.meta.url);
|
|
166
|
+
* const pkg = require("../package.json");
|
|
167
|
+
* A compiled program's module graph is fixed at build time, so the
|
|
168
|
+
* indirection ERASES where the required specifier is a static string
|
|
169
|
+
* literal naming something the compiler already handles: a builtin (the
|
|
170
|
+
* binding is a namespace import in const clothing), a relative .json
|
|
171
|
+
* document (the file bakes and parses like JSON.parse of its text), or
|
|
172
|
+
* an installed npm package (the island's require-condition entry under
|
|
173
|
+
* --dynamic). Dynamic specifiers and other targets fence by name. */
|
|
174
|
+
/** Strips the type-only wrappers the require pattern rides in typed
|
|
175
|
+
* code (`require("node:os") as typeof import("node:os")` — the
|
|
176
|
+
* fallback declarations answer `unknown`, so the cast IS the idiom),
|
|
177
|
+
* plus parens, legacy assertions, and non-null suffixes. */
|
|
178
|
+
export function stripTypeCasts(e) {
|
|
179
|
+
let cur = e;
|
|
180
|
+
while (ts.isParenthesizedExpression(cur) || ts.isAsExpression(cur) ||
|
|
181
|
+
ts.isTypeAssertion(cur) || ts.isNonNullExpression(cur)) {
|
|
182
|
+
cur = cur.expression;
|
|
183
|
+
}
|
|
184
|
+
return cur;
|
|
185
|
+
}
|
|
186
|
+
/** The `createRequire(<base>)` call over the node:module import binding
|
|
187
|
+
* with a supported base — import.meta.url, import.meta.filename, or
|
|
188
|
+
* __filename, every spelling of "this file" — or null. The base never
|
|
189
|
+
* LOWERS (import.meta has no value representation): it only names the
|
|
190
|
+
* file whose directory anchors the returned require's relative
|
|
191
|
+
* resolution, and every supported spelling names the call's own file. */
|
|
192
|
+
export function createRequireBaseCallOf(L, expr) {
|
|
193
|
+
const e = stripTypeCasts(expr);
|
|
194
|
+
if (!ts.isCallExpression(e) || e.questionDotToken)
|
|
195
|
+
return null;
|
|
196
|
+
if (!ts.isIdentifier(e.expression))
|
|
197
|
+
return null;
|
|
198
|
+
const bi = builtinImportOf(L, e.expression);
|
|
199
|
+
if (!bi || bi.module !== "module" || bi.member !== "createRequire")
|
|
200
|
+
return null;
|
|
201
|
+
if (e.arguments.length !== 1)
|
|
202
|
+
return null;
|
|
203
|
+
const base = stripTypeCasts(e.arguments[0]);
|
|
204
|
+
if (ts.isIdentifier(base) && base.text === "__filename")
|
|
205
|
+
return e;
|
|
206
|
+
if (ts.isPropertyAccessExpression(base) &&
|
|
207
|
+
!base.questionDotToken &&
|
|
208
|
+
ts.isMetaProperty(base.expression) &&
|
|
209
|
+
base.expression.keywordToken === ts.SyntaxKind.ImportKeyword &&
|
|
210
|
+
(base.name.text === "url" || base.name.text === "filename")) {
|
|
211
|
+
return e;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
/** True for `const require = createRequire(import.meta.url)` — the
|
|
216
|
+
* binding is compile-time plumbing (each call through it resolves per
|
|
217
|
+
* site) with no storage and no code; both declaration walks skip by
|
|
218
|
+
* this test. A reassignable (let/var) binding never matches — callers
|
|
219
|
+
* gate on constness like the other alias decls. */
|
|
220
|
+
export function createRequireBindingDecl(L, nameNode, init) {
|
|
221
|
+
if (!ts.isIdentifier(nameNode) || init === undefined)
|
|
222
|
+
return false;
|
|
223
|
+
return createRequireBaseCallOf(L, init) !== null;
|
|
224
|
+
}
|
|
225
|
+
/** The declaring source file when `callee` denotes a createRequire-made
|
|
226
|
+
* require: a CONST binding over createRequire(import.meta.url) (the
|
|
227
|
+
* binding's own file anchors resolution) or the inline
|
|
228
|
+
* `createRequire(import.meta.url)(...)` spelling (the call's file).
|
|
229
|
+
* Null off the pattern, so call chains keep trying. */
|
|
230
|
+
export function createRequireCalleeFileOf(L, callee) {
|
|
231
|
+
const e = stripTypeCasts(callee);
|
|
232
|
+
if (ts.isCallExpression(e)) {
|
|
233
|
+
return createRequireBaseCallOf(L, e) !== null ? e.getSourceFile() : null;
|
|
234
|
+
}
|
|
235
|
+
if (!ts.isIdentifier(e))
|
|
236
|
+
return null;
|
|
237
|
+
const symbol = L.checker.getSymbolAtLocation(e);
|
|
238
|
+
const decl = symbol ? L.checker.declarationsOf(symbol)[0] : undefined;
|
|
239
|
+
if (!decl || !ts.isVariableDeclaration(decl) || decl.initializer === undefined)
|
|
240
|
+
return null;
|
|
241
|
+
if (!ts.isVariableDeclarationList(decl.parent) || (decl.parent.flags & ts.NodeFlags.Const) === 0)
|
|
242
|
+
return null;
|
|
243
|
+
return createRequireBaseCallOf(L, decl.initializer) !== null ? decl.getSourceFile() : null;
|
|
244
|
+
}
|
|
245
|
+
/** The static require of `R("spec")` through a createRequire binding:
|
|
246
|
+
* the literal specifier plus the file anchoring relative resolution.
|
|
247
|
+
* Null when the callee is not a createRequire-made require; a matching
|
|
248
|
+
* callee with a non-literal (or missing) specifier answers spec null,
|
|
249
|
+
* so the call lowering fences by name instead of falling through to
|
|
250
|
+
* the generic call paths. */
|
|
251
|
+
export function createRequireSpecOf(L, call) {
|
|
252
|
+
if (call.questionDotToken)
|
|
253
|
+
return null;
|
|
254
|
+
const baseFile = createRequireCalleeFileOf(L, call.expression);
|
|
255
|
+
if (baseFile === null)
|
|
256
|
+
return null;
|
|
257
|
+
if (call.arguments.length !== 1)
|
|
258
|
+
return { spec: null, baseFile };
|
|
259
|
+
const a = call.arguments[0];
|
|
260
|
+
return { spec: ts.isStringLiteralLike(a) ? a.text : null, baseFile };
|
|
261
|
+
}
|
|
262
|
+
/** True for `const fs = require("node:fs")` through a createRequire
|
|
263
|
+
* binding — a builtin namespace import in const clothing: alias
|
|
264
|
+
* plumbing with no storage (uses resolve through
|
|
265
|
+
* builtinNamespaceModuleOf's createRequire arm); both declaration
|
|
266
|
+
* walks skip by this test. */
|
|
267
|
+
export function createRequireNamespaceDecl(L, nameNode, init) {
|
|
268
|
+
if (!ts.isIdentifier(nameNode) || init === undefined)
|
|
269
|
+
return false;
|
|
270
|
+
const call = stripTypeCasts(init);
|
|
271
|
+
if (!ts.isCallExpression(call))
|
|
272
|
+
return false;
|
|
273
|
+
const cr = createRequireSpecOf(L, call);
|
|
274
|
+
return cr !== null && cr.spec !== null && canonicalBuiltinModule(cr.spec) !== null;
|
|
275
|
+
}
|
|
276
|
+
/** `require("spec")` through a createRequire binding — the erasure per
|
|
277
|
+
* target. Builtins are reached here only OUTSIDE the const-namespace-
|
|
278
|
+
* binding shape (that declaration erases; member uses resolve through
|
|
279
|
+
* the namespace tables) and fence toward it. A relative .json document
|
|
280
|
+
* bakes: the file's text validates as JSON at compile time and the
|
|
281
|
+
* call lowers to json.parse over the baked literal — JSON.parse's
|
|
282
|
+
* checked-dynamic `unknown` stance, and exactly Node's value (require
|
|
283
|
+
* of JSON IS JSON.parse of the file; the per-call re-parse forgoes
|
|
284
|
+
* Node's module-cache identity, unobservable without mutation). A bare
|
|
285
|
+
* specifier NOTHING installed resolves compiles to Node's catchable
|
|
286
|
+
* MODULE_NOT_FOUND throw (the optional-dependency try/require
|
|
287
|
+
* pattern); an installed package loads through the island's
|
|
288
|
+
* require-condition entry under --dynamic (collectCreateRequires
|
|
289
|
+
* embedded it) and reports the requires-dynamic diagnostic in a static
|
|
290
|
+
* build. Null when the callee is not a createRequire require. */
|
|
291
|
+
export function lowerCreateRequireCall(L, call, loc) {
|
|
292
|
+
const cr = createRequireSpecOf(L, call);
|
|
293
|
+
if (cr === null)
|
|
294
|
+
return null;
|
|
295
|
+
if (cr.spec === null) {
|
|
296
|
+
L.noLowering("createRequire's require with this argument shape", call, "the compiled module graph is fixed at build time — the one lowered form is require(\"<static string literal>\")");
|
|
297
|
+
}
|
|
298
|
+
const spec = cr.spec;
|
|
299
|
+
if (canonicalBuiltinModule(spec) !== null) {
|
|
300
|
+
L.unsupported("SC1090", call, `module namespace objects as values (bind it first: const m = require("${spec}"), then access members through the binding)`);
|
|
301
|
+
}
|
|
302
|
+
if (spec.startsWith("#")) {
|
|
303
|
+
L.noLowering(`createRequire's require of the '${spec}' project import`, call, "imports-field specifiers have no require lowering yet — import the target statically");
|
|
304
|
+
}
|
|
305
|
+
if (isRelativeSpecifier(spec) || spec.startsWith("/")) {
|
|
306
|
+
if (!spec.endsWith(".json")) {
|
|
307
|
+
L.noLowering(`createRequire's require of '${spec}'`, call, "relative requires lower for .json documents only — a program module is a static import");
|
|
308
|
+
}
|
|
309
|
+
const abs = spec.startsWith("/")
|
|
310
|
+
? spec
|
|
311
|
+
: resolve(dirname(cr.baseFile.fileName), spec);
|
|
312
|
+
let text = null;
|
|
313
|
+
try {
|
|
314
|
+
text = readFileSync(abs, "utf8");
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
/* the fence below speaks */
|
|
318
|
+
}
|
|
319
|
+
if (text === null) {
|
|
320
|
+
L.noLowering(`createRequire's require of '${spec}' (no file at ${abs})`, call, "the required document resolves at build time — check the path against the requiring file");
|
|
321
|
+
}
|
|
322
|
+
try {
|
|
323
|
+
JSON.parse(text);
|
|
324
|
+
}
|
|
325
|
+
catch (e) {
|
|
326
|
+
L.pushDiag(invalidJsonModuleDiag(abs, e instanceof Error ? e.message : String(e), loc));
|
|
327
|
+
throw new PoisonError();
|
|
328
|
+
}
|
|
329
|
+
return {
|
|
330
|
+
kind: "libCall",
|
|
331
|
+
fn: "json.parse",
|
|
332
|
+
args: [{ kind: "strLit", value: text, type: STRING, loc }],
|
|
333
|
+
type: DYN,
|
|
334
|
+
loc,
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
// Bare package specifiers. --npm-static opt-ins are program modules —
|
|
338
|
+
// their exports bind through static imports, not a require value.
|
|
339
|
+
const pkgName = spec.startsWith("@")
|
|
340
|
+
? spec.split("/").slice(0, 2).join("/")
|
|
341
|
+
: spec.split("/")[0];
|
|
342
|
+
if (isNpmStaticPackage(pkgName)) {
|
|
343
|
+
L.noLowering(`createRequire's require of the --npm-static package '${pkgName}'`, call, "the opted-in package compiles as program modules — import it statically");
|
|
344
|
+
}
|
|
345
|
+
const refusal = probeNodeRequireRefusal(cr.baseFile.fileName, spec);
|
|
346
|
+
if (refusal !== null) {
|
|
347
|
+
// Node's require-site MODULE_NOT_FOUND, catchable — the compiled
|
|
348
|
+
// expression IS that throw (the typed dummy is abandoned by the
|
|
349
|
+
// pending check's unwind).
|
|
350
|
+
return nodeThrowExpr(0, "MODULE_NOT_FOUND", refusal.message, DYN, loc);
|
|
351
|
+
}
|
|
352
|
+
if (!L.dynamic) {
|
|
353
|
+
L.pushDiag(requiresDynamicImportDiag(pkgName, loc));
|
|
354
|
+
throw new PoisonError();
|
|
355
|
+
}
|
|
356
|
+
const res = L.createRequireImports.get(`${cr.baseFile.fileName}\u0000${spec}`);
|
|
357
|
+
if (res === undefined) {
|
|
358
|
+
// Collection never saw the site (a shape this walk and that walk
|
|
359
|
+
// disagree on) — fence rather than mis-embed.
|
|
360
|
+
L.noLowering(`createRequire's require of '${spec}'`, call, undefined);
|
|
361
|
+
}
|
|
362
|
+
if (res === "")
|
|
363
|
+
throw new PoisonError(); // reported at collection
|
|
364
|
+
// A CJS facade's default IS module.exports (Node's require answer);
|
|
365
|
+
// an ESM-resolved entry answers its namespace (Node's require(esm)).
|
|
366
|
+
const exportName = res.format === "esm" ? "*" : "default";
|
|
367
|
+
return {
|
|
368
|
+
kind: "libCall",
|
|
369
|
+
fn: "island.import",
|
|
370
|
+
args: [
|
|
371
|
+
{ kind: "strLit", value: res.entryKey, type: STRING, loc },
|
|
372
|
+
{ kind: "strLit", value: exportName, type: STRING, loc },
|
|
373
|
+
{ kind: "strLit", value: spec, type: STRING, loc },
|
|
374
|
+
],
|
|
375
|
+
type: JSVAL,
|
|
376
|
+
loc,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
148
379
|
/** True when `node` denotes the http2.constants OBJECT — any of its
|
|
149
380
|
* spellings: `http2.constants` through a namespace/require binding, a
|
|
150
381
|
* destructured or member-bound `constants` alias from the http2 module,
|
|
@@ -371,6 +602,151 @@ export function lowerFsToUnixTimestampCall(L, expr, bi, loc) {
|
|
|
371
602
|
}
|
|
372
603
|
return null;
|
|
373
604
|
}
|
|
605
|
+
/** The fs validation-ladder spoke (checked-dynamic lane, JS sources
|
|
606
|
+
* only — TypeScript keeps its compile fences): implemented-namespace
|
|
607
|
+
* calls whose misuse Node rejects with typed errors lower to fs.*Chk
|
|
608
|
+
* libCalls that replicate the validation ladder over DOM values and
|
|
609
|
+
* throw Node's exact ERR_INVALID_ARG_TYPE / ERR_INVALID_ARG_VALUE /
|
|
610
|
+
* ERR_OUT_OF_RANGE — the honest tail (the real operation where one
|
|
611
|
+
* exists, the compiler-rendered SC2020 fence otherwise) runs only
|
|
612
|
+
* after every validation passes, exactly Node's order. Null when this
|
|
613
|
+
* is not a claimed member/shape (the table or fence path stands). */
|
|
614
|
+
export function lowerFsLadderCall(L, expr, bi, loc) {
|
|
615
|
+
if (bi.module !== "fs" && bi.module !== "fs/promises")
|
|
616
|
+
return null;
|
|
617
|
+
if (!isJsSourceFile(expr.getSourceFile()))
|
|
618
|
+
return null;
|
|
619
|
+
const args = expr.arguments;
|
|
620
|
+
if (args.some(ts.isSpreadElement))
|
|
621
|
+
return null;
|
|
622
|
+
// Every ladder argument crosses as a DOM value; an argument that
|
|
623
|
+
// cannot leaves the historical fence in place.
|
|
624
|
+
const dynArg = (node) => {
|
|
625
|
+
if (!node)
|
|
626
|
+
return dynUndefinedExpr(loc);
|
|
627
|
+
const raw = L.lowerExpr(node);
|
|
628
|
+
if (raw.type.kind === "dyn")
|
|
629
|
+
return raw;
|
|
630
|
+
if (raw.kind === "unitLit" || L.dynConvertible(raw.type)) {
|
|
631
|
+
return { kind: "dynFrom", value: raw, type: DYN, loc };
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
};
|
|
635
|
+
const dynArgs = (nodes) => {
|
|
636
|
+
const out = [];
|
|
637
|
+
for (const n of nodes) {
|
|
638
|
+
const v = dynArg(n);
|
|
639
|
+
if (v === null)
|
|
640
|
+
return null;
|
|
641
|
+
out.push(v);
|
|
642
|
+
}
|
|
643
|
+
return out;
|
|
644
|
+
};
|
|
645
|
+
const resultT = L.mapTypeOf(L.typeOf(expr)) ?? DYN;
|
|
646
|
+
const chk = (fn, chkArgs, type) => ({ kind: "libCall", fn, args: chkArgs, type, loc });
|
|
647
|
+
if (bi.module === "fs/promises") {
|
|
648
|
+
if (bi.member !== "lchmod" || args.length !== 2)
|
|
649
|
+
return null;
|
|
650
|
+
const a = dynArgs([args[0], args[1]]);
|
|
651
|
+
return a && chk("fsp.lchmodChk", a, { kind: "promise", inner: VOID });
|
|
652
|
+
}
|
|
653
|
+
switch (bi.member) {
|
|
654
|
+
case "exists": {
|
|
655
|
+
// The REAL deprecated-API shape: the callback validates
|
|
656
|
+
// synchronously (Node's one throwing arm), invalid paths answer
|
|
657
|
+
// false THROUGH it, and the answer is asynchronous.
|
|
658
|
+
// A provably-non-file `new URL('<literal>')` path (the suite's
|
|
659
|
+
// https://foo probe): Node answers false through the callback
|
|
660
|
+
// synchronously — the DOM has no URL kind, so the path slot
|
|
661
|
+
// carries the unvalidatable token instead (construction of a
|
|
662
|
+
// parseable literal is effect-free; file: URLs keep the fence —
|
|
663
|
+
// they would need the real path conversion).
|
|
664
|
+
let pathNode = args[0];
|
|
665
|
+
let pathExpr = null;
|
|
666
|
+
if (pathNode && ts.isNewExpression(pathNode) && ts.isIdentifier(pathNode.expression) &&
|
|
667
|
+
pathNode.expression.text === "URL" && L.mapTypeOf(L.typeOf(pathNode))?.kind === "url" &&
|
|
668
|
+
pathNode.arguments?.length === 1 && ts.isStringLiteral(pathNode.arguments[0])) {
|
|
669
|
+
let parsed = null;
|
|
670
|
+
try {
|
|
671
|
+
parsed = new URL(pathNode.arguments[0].text);
|
|
672
|
+
}
|
|
673
|
+
catch {
|
|
674
|
+
parsed = null;
|
|
675
|
+
}
|
|
676
|
+
if (parsed === null || parsed.protocol === "file:")
|
|
677
|
+
return null;
|
|
678
|
+
pathExpr = dynUndefinedExpr(loc);
|
|
679
|
+
pathNode = undefined;
|
|
680
|
+
}
|
|
681
|
+
pathExpr ??= dynArg(pathNode);
|
|
682
|
+
const cbExpr = dynArg(args[1]);
|
|
683
|
+
if (pathExpr === null || cbExpr === null)
|
|
684
|
+
return null;
|
|
685
|
+
return chk("fs.existsChk", [pathExpr, cbExpr], DYN);
|
|
686
|
+
}
|
|
687
|
+
case "mkdtemp": {
|
|
688
|
+
// mkdtemp(prefix[, options], callback) — the callback is the
|
|
689
|
+
// LAST argument (makeCallback runs first, then the prefix).
|
|
690
|
+
const a = dynArgs([args[0], args.length >= 2 ? args[args.length - 1] : undefined]);
|
|
691
|
+
return a && chk("fs.mkdtempChk", [...a, ladderFenceExpr(L, "fs.mkdtemp", expr)], resultT);
|
|
692
|
+
}
|
|
693
|
+
case "mkdtempSync": {
|
|
694
|
+
// The table serves the plain string 1-arg form; the ladder takes
|
|
695
|
+
// every other shape — prefix/encoding validation, then the REAL
|
|
696
|
+
// mkdtemp when the options leave utf8 semantics.
|
|
697
|
+
if (args.length === 1 && L.mapTypeOf(L.typeOf(args[0]))?.kind === "string")
|
|
698
|
+
return null;
|
|
699
|
+
if (args.length > 2)
|
|
700
|
+
return null;
|
|
701
|
+
const a = dynArgs([args[0], args[1]]);
|
|
702
|
+
return a && chk("fs.mkdtempSyncChk", [...a, ladderFenceExpr(L, "fs.mkdtempSync with these options", expr)], STRING);
|
|
703
|
+
}
|
|
704
|
+
case "readFile": {
|
|
705
|
+
// readFile(path[, options], callback): callback, assertEncoding,
|
|
706
|
+
// path — then the async read fences.
|
|
707
|
+
const a = dynArgs([args[0], args.length >= 3 ? args[1] : undefined, args.length >= 2 ? args[args.length - 1] : undefined]);
|
|
708
|
+
return a && chk("fs.readFileChk", [...a, ladderFenceExpr(L, "fs.readFile", expr)], resultT);
|
|
709
|
+
}
|
|
710
|
+
case "opendirSync": {
|
|
711
|
+
const a = dynArgs([args[0], args[1]]);
|
|
712
|
+
return a && chk("fs.opendirChk", [...a, ladderFenceExpr(L, "fs.opendirSync", expr)], resultT);
|
|
713
|
+
}
|
|
714
|
+
case "watchFile": {
|
|
715
|
+
// watchFile(filename[, options], listener): the path first, the
|
|
716
|
+
// listener's function contract second; real watching fences.
|
|
717
|
+
const a = dynArgs([args[0], args.length >= 2 ? args[args.length - 1] : undefined]);
|
|
718
|
+
return a && chk("fs.watchFileChk", [...a, ladderFenceExpr(L, "fs.watchFile", expr)], resultT);
|
|
719
|
+
}
|
|
720
|
+
case "lchmod": {
|
|
721
|
+
// lchmod(path, mode, callback): callback, path, mode — macOS
|
|
722
|
+
// shapes (non-APPLE answers Node's not-a-function TypeError).
|
|
723
|
+
const a = dynArgs([args[0], args[1], args[2]]);
|
|
724
|
+
return a && chk("fs.lchmodChk", [...a, ladderFenceExpr(L, "fs.lchmod", expr)], resultT);
|
|
725
|
+
}
|
|
726
|
+
case "lchmodSync": {
|
|
727
|
+
if (args.length > 2)
|
|
728
|
+
return null;
|
|
729
|
+
const a = dynArgs([args[0], args[1]]);
|
|
730
|
+
return a && chk("fs.lchmodSyncChk", a, DYN);
|
|
731
|
+
}
|
|
732
|
+
case "read": {
|
|
733
|
+
// read(fd, buffer, offset, length, position, callback) — the
|
|
734
|
+
// positional form's full ladder; options-object forms keep the
|
|
735
|
+
// fence (their misuse arms are not in the target set).
|
|
736
|
+
if (args.length < 4)
|
|
737
|
+
return null;
|
|
738
|
+
const a = dynArgs([args[0], args[1], args[2], args[3], args.length >= 6 ? args[4] : undefined]);
|
|
739
|
+
return a && chk("fs.readChk", [...a, ladderFenceExpr(L, "fs.read", expr)], resultT);
|
|
740
|
+
}
|
|
741
|
+
case "createReadStream":
|
|
742
|
+
case "createWriteStream": {
|
|
743
|
+
const a = dynArgs([args[0], args[1]]);
|
|
744
|
+
return a && chk("fs.streamOptsChk", [...a, ladderFenceExpr(L, `fs.${bi.member}`, expr)], resultT);
|
|
745
|
+
}
|
|
746
|
+
default:
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
374
750
|
export function lowerBuiltinModuleCall(L, expr, bi, fn, loc) {
|
|
375
751
|
const name = expr.expression.getText();
|
|
376
752
|
if (bi.module === "child_process" && bi.member === "spawnSync") {
|