@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,2255 @@
|
|
|
1
|
+
/* THE program lifecycle: load → preflight — tsconfig adoption, the tsc
|
|
2
|
+
* gate with its project-world second chance, the import/export form
|
|
3
|
+
* fences, CommonJS require discipline, and the module evaluation order —
|
|
4
|
+
* on the native TypeScript compiler (one spawned tsgo server behind
|
|
5
|
+
* Ts7Host, through the ts7/ adapter) with scriptc's OWN resolver
|
|
6
|
+
* (resolve.ts) standing in for ts.resolveModuleName /
|
|
7
|
+
* ts.resolveTypeReferenceDirective, which typescript@7.0.2 no longer
|
|
8
|
+
* ships.
|
|
9
|
+
*
|
|
10
|
+
* HISTORY. This began as a deliberately MECHANICAL PORT of the
|
|
11
|
+
* typescript@5.9.3 checkPreflight (the adapter's design: swap the ts
|
|
12
|
+
* import, keep every spelling), shipped alongside it for phases 2-3 with
|
|
13
|
+
* the order-parity suite holding both lanes to identical diagnostics and
|
|
14
|
+
* module order over the whole corpus. The phase-4 flip deleted the 5.9.3
|
|
15
|
+
* pipeline; its recorded verdicts live on as the canary baselines
|
|
16
|
+
* (test/ts7/baselines/order-parity.json), and the rationale prose the old
|
|
17
|
+
* program.ts carried was folded in here where behavior demands it.
|
|
18
|
+
*
|
|
19
|
+
* Differences from the retired 5.9.3 lane, each deliberate and each
|
|
20
|
+
* pinned by the canary baselines:
|
|
21
|
+
* - Module resolution is resolve.ts (answer-parity proven by its suite).
|
|
22
|
+
* - Diagnostics carry pos/end and flat text + messageChain; toPassthrough
|
|
23
|
+
* renders through the adapter's flattenDiagnosticMessageText, whose
|
|
24
|
+
* output shape matches 5.9.3's flattener byte for byte.
|
|
25
|
+
* - Finding 5 (tsgo models CommonJS strictly): TS2309 joins the JS-file
|
|
26
|
+
* relaxation here — 5.9.3 never fires it on JS CJS files, tsgo does on
|
|
27
|
+
* the table-then-member pattern. The EXPORT-IDENTITY fences do not live
|
|
28
|
+
* in tsc codes at all: cjsExportDiscardReason (lowering) names every
|
|
29
|
+
* genuinely discarded export, so relaxing the checker's stricter
|
|
30
|
+
* modeling loses nothing.
|
|
31
|
+
* - isNodeEsmFile: 5.9.3 under bundler sets impliedNodeFormat ONLY for
|
|
32
|
+
* .mjs/.cjs-family files (probed) and falls back to syntactic module
|
|
33
|
+
* detection for .js/.ts; 7's client SourceFile has no impliedNodeFormat,
|
|
34
|
+
* so this lane spells exactly that observed behavior: extensions first,
|
|
35
|
+
* then externalModuleIndicator.
|
|
36
|
+
* - Malformed tsconfig.json: tsgo's parseConfigFile swallows JSONC syntax
|
|
37
|
+
* errors (recovers to empty options) where 5.9.3 reported a passthrough
|
|
38
|
+
* diagnostic, so this lane validates the config's JSONC syntax itself
|
|
39
|
+
* (jsoncSyntaxError) and fails preflight like 5.9.3 does — with
|
|
40
|
+
* JSON.parse's wording rather than tsc's, the one message-text delta on
|
|
41
|
+
* that path (no snapshot pins it). */
|
|
42
|
+
import { builtinModules } from "node:module";
|
|
43
|
+
import { dirname, resolve } from "node:path";
|
|
44
|
+
import * as ts from "./ts7/adapter.js";
|
|
45
|
+
import { strictNullChecksFloorDiag, tscPassthroughDiag, unsupportedDiag, } from "../diagnostics/diagnostic.js";
|
|
46
|
+
import { isNodeModulesPath, nearestPkgJsonPath, projectDtsRuntimeSibling, resolveBareModule, resolveProjectImport, resolveRelativeModule, resolveTypeDirective, setProjectRealm } from "./resolve.js";
|
|
47
|
+
import { probeNodeImportRefusal, probeNodeRequireRefusal } from "./npm.js";
|
|
48
|
+
import { isNpmStaticPackage, npmStaticActive, npmStaticFsShadow, npmStaticPackageOfPath, reportNpmStaticOffender, setNpmStaticPackages } from "./npm-static.js";
|
|
49
|
+
import { provenanceEntryFor, provenancePaths } from "./provenance-registry.js";
|
|
50
|
+
import { cjsLexerVisibleNames } from "./cjs-lexer.js";
|
|
51
|
+
import { ADOPTED_OPTIONS, ambientDtsPath, builtinDefaultImportModule, canonicalBuiltinModule, clearWorkspacePackages, fallbackDtsPath, isJsSourceFileName, isNodeTypesPath, isRelativeSpecifier, JS_ANY_OPERATOR_CODES, JS_RELAXED_TSC_CODES, overridesDtsPath, registerWorkspacePackage, SUPPORTED_NODE_MODULES, workspacePackageOfPath, } from "./shared.js";
|
|
52
|
+
const BASE_OPTIONS = {
|
|
53
|
+
strict: true,
|
|
54
|
+
};
|
|
55
|
+
const FORCED_OPTIONS = {
|
|
56
|
+
target: ts.ScriptTarget.ESNext,
|
|
57
|
+
module: ts.ModuleKind.ESNext,
|
|
58
|
+
moduleResolution: ts.ModuleResolutionKind.Bundler,
|
|
59
|
+
lib: ["lib.es2025.d.ts"],
|
|
60
|
+
types: [],
|
|
61
|
+
allowImportingTsExtensions: true,
|
|
62
|
+
allowJs: true,
|
|
63
|
+
checkJs: true,
|
|
64
|
+
resolveJsonModule: true,
|
|
65
|
+
noEmit: true,
|
|
66
|
+
};
|
|
67
|
+
/** JSONC syntax validation for a tsconfig text: tsgo's parseConfigFile
|
|
68
|
+
* RECOVERS silently over syntax errors (probed: a hard-broken file answers
|
|
69
|
+
* empty options, no diagnostic), where 5.9.3's readConfigFile reported the
|
|
70
|
+
* first parse error — a preflight-visible difference (broken config: fail
|
|
71
|
+
* loudly, never adopt silently). tsconfig's grammar is JSON plus comments
|
|
72
|
+
* and trailing commas, so stripping exactly those and handing the rest to
|
|
73
|
+
* JSON.parse decides validity without either TypeScript's parser. The
|
|
74
|
+
* MESSAGE is JSON.parse's, not 5.9.3's ("'}' expected.") — the one
|
|
75
|
+
* remaining wording delta on this path, unpinned by any snapshot. */
|
|
76
|
+
function jsoncSyntaxError(text) {
|
|
77
|
+
let out = "";
|
|
78
|
+
for (let i = 0; i < text.length; i++) {
|
|
79
|
+
const ch = text[i];
|
|
80
|
+
if (ch === '"') {
|
|
81
|
+
const from = i;
|
|
82
|
+
i++;
|
|
83
|
+
while (i < text.length && text[i] !== '"')
|
|
84
|
+
i += text[i] === "\\" ? 2 : 1;
|
|
85
|
+
out += text.slice(from, i + 1);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ch === "/" && text[i + 1] === "/") {
|
|
89
|
+
while (i < text.length && text[i] !== "\n")
|
|
90
|
+
i++;
|
|
91
|
+
out += "\n";
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (ch === "/" && text[i + 1] === "*") {
|
|
95
|
+
const close = text.indexOf("*/", i + 2);
|
|
96
|
+
i = close < 0 ? text.length : close + 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
out += ch;
|
|
100
|
+
}
|
|
101
|
+
// Trailing commas: `,` followed only by whitespace before } or ].
|
|
102
|
+
out = out.replace(/,(\s*[}\]])/g, "$1");
|
|
103
|
+
if (out.trim() === "")
|
|
104
|
+
return null; // an empty config file is legal
|
|
105
|
+
try {
|
|
106
|
+
JSON.parse(out);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
return e instanceof Error ? e.message : String(e);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
/** The project's tsconfig adoption in the 7 world: tsgo's own config parser
|
|
114
|
+
* (extends chains resolved server-side), the ADOPTED_OPTIONS subset taken,
|
|
115
|
+
* the rest forced, the strictNullChecks floor enforced. */
|
|
116
|
+
function adoptProjectConfig7(host, entryPath) {
|
|
117
|
+
const configFile = ts.findConfigFile(dirname(entryPath), ts.sys.fileExists) ?? null;
|
|
118
|
+
if (!configFile) {
|
|
119
|
+
return { configFile, options: { ...BASE_OPTIONS, ...FORCED_OPTIONS }, diags: [] };
|
|
120
|
+
}
|
|
121
|
+
const diags = [];
|
|
122
|
+
const syntaxError = jsoncSyntaxError(ts.sys.readFile(configFile) ?? "");
|
|
123
|
+
if (syntaxError !== null) {
|
|
124
|
+
diags.push(tscPassthroughDiag(syntaxError, { file: configFile, start: 0, end: 0 }));
|
|
125
|
+
return { configFile, options: { ...BASE_OPTIONS, ...FORCED_OPTIONS }, diags };
|
|
126
|
+
}
|
|
127
|
+
const parsed = host.parseConfigFile(configFile);
|
|
128
|
+
const adopted = {};
|
|
129
|
+
for (const key of ADOPTED_OPTIONS) {
|
|
130
|
+
const value = parsed.options[key];
|
|
131
|
+
if (value !== undefined)
|
|
132
|
+
adopted[key] = value;
|
|
133
|
+
}
|
|
134
|
+
const nullChecks = adopted["strictNullChecks"] ?? adopted["strict"] ?? false;
|
|
135
|
+
if (nullChecks !== true) {
|
|
136
|
+
diags.push(strictNullChecksFloorDiag(configFile));
|
|
137
|
+
adopted["strictNullChecks"] = true;
|
|
138
|
+
}
|
|
139
|
+
return { configFile, options: { ...BASE_OPTIONS, ...adopted, ...FORCED_OPTIONS }, diags };
|
|
140
|
+
}
|
|
141
|
+
/** The target project's @types/node, resolved with the OWN resolver's
|
|
142
|
+
* type-directive lookup anchored at the ENTRY (typeRoots-free — exactly the
|
|
143
|
+
* secondary lookup 5.9.3 ran with typeRoots emptied). */
|
|
144
|
+
function resolveNodeTypes7(entryPath) {
|
|
145
|
+
const file = resolveTypeDirective("node", entryPath);
|
|
146
|
+
return file !== null && isNodeTypesPath(file) ? file : null;
|
|
147
|
+
}
|
|
148
|
+
function loadProgram7(host, entryPath) {
|
|
149
|
+
const config = adoptProjectConfig7(host, entryPath);
|
|
150
|
+
const nodeTypes = config.configFile ? resolveNodeTypes7(entryPath) : null;
|
|
151
|
+
// skipLibCheck is FORCED with @types/node in the program: checking a
|
|
152
|
+
// third-party lib's internals against OUR lib choice (es2025, no DOM) is
|
|
153
|
+
// not scriptc's fence and drowns real diagnostics in hundreds of
|
|
154
|
+
// .d.ts-internal errors. Fence discipline never depended on it: the
|
|
155
|
+
// lowerer checks provenance and forms at every use site.
|
|
156
|
+
let options = nodeTypes ? { ...config.options, skipLibCheck: true } : { ...config.options };
|
|
157
|
+
// --npm-static: opted-in packages' shipped JS must be TYPE-INCLUDED (not
|
|
158
|
+
// just resolved) — without maxNodeModuleJsDepth, node_modules JS types as
|
|
159
|
+
// an implicit-any module (TS7016) and nothing infers. Only flagged
|
|
160
|
+
// compiles pay this; flagless builds keep the exact historical options.
|
|
161
|
+
if (npmStaticActive())
|
|
162
|
+
options.maxNodeModuleJsDepth = 4;
|
|
163
|
+
// --provenance-sources: the registered entries become tsconfig "paths"
|
|
164
|
+
// so tsgo's OWN resolution of the bare specifiers lands on the same
|
|
165
|
+
// source files the preflight resolver answers — the checker types the
|
|
166
|
+
// driver against the package's real TypeScript, not its shipped .d.ts.
|
|
167
|
+
const paths = provenancePaths();
|
|
168
|
+
if (paths !== null)
|
|
169
|
+
options = { ...options, paths };
|
|
170
|
+
const coreRoots = [entryPath, ambientDtsPath(), nodeTypes ?? fallbackDtsPath()];
|
|
171
|
+
const program = ts.createProgram([...coreRoots, overridesDtsPath()], options, host);
|
|
172
|
+
const entry = program.getSourceFile(entryPath);
|
|
173
|
+
if (!entry)
|
|
174
|
+
throw new Error(`could not load ${entryPath}`);
|
|
175
|
+
let projectWorld = null;
|
|
176
|
+
return {
|
|
177
|
+
program,
|
|
178
|
+
entry,
|
|
179
|
+
moduleOrder: [],
|
|
180
|
+
configDiags: config.diags,
|
|
181
|
+
projectWorld: () => (projectWorld ??= ts.createProgram(coreRoots, options, host)),
|
|
182
|
+
disposeAll: () => {
|
|
183
|
+
projectWorld?.dispose();
|
|
184
|
+
program.dispose();
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
/** The pipeline surface: ONE tsgo program serves preflight AND the
|
|
189
|
+
* lowering. The caller runs checkPreflight, hands program/entry/
|
|
190
|
+
* moduleOrder straight to lowerToIr, and MUST dispose() when done — the
|
|
191
|
+
* spawned tsgo server must not outlive the compile. */
|
|
192
|
+
export function loadProgram(entryPath, opts) {
|
|
193
|
+
// Absolute from the start: tsgo's world is absolute-path-keyed (the CLI
|
|
194
|
+
// resolves before calling; this covers direct API callers too).
|
|
195
|
+
entryPath = resolve(entryPath);
|
|
196
|
+
setNpmStaticPackages(opts?.npmStatic ?? []);
|
|
197
|
+
// Workspace-package registrations reset per load (same discipline as the
|
|
198
|
+
// npm-static set), then the opted-in names are probed UP FRONT: a
|
|
199
|
+
// workspace-linked opt-in resolves to files whose realpaths carry no
|
|
200
|
+
// node_modules segment, and the tsgo host's fs shadow must already
|
|
201
|
+
// recognize those directories while the PROGRAM is created (hiding the
|
|
202
|
+
// package's declaration twins along its internal relative edges).
|
|
203
|
+
// Non-opted workspace packages register lazily as preflight's own
|
|
204
|
+
// resolver discovers their import edges.
|
|
205
|
+
clearWorkspacePackages();
|
|
206
|
+
for (const pkg of opts?.npmStatic ?? []) {
|
|
207
|
+
const probe = resolveBareModule(entryPath, pkg);
|
|
208
|
+
if (probe?.workspaceDir !== undefined)
|
|
209
|
+
registerWorkspacePackage(probe.packageName, probe.workspaceDir);
|
|
210
|
+
}
|
|
211
|
+
// Declaration-twin hiding is scoped to the entry's own package realm
|
|
212
|
+
// (see projectDtsRuntimeSibling) — set before the host exists so the
|
|
213
|
+
// program never sees an out-of-realm twin hidden.
|
|
214
|
+
setProjectRealm(entryPath);
|
|
215
|
+
// Two fs shadows compose: --npm-static's per-package hiding, and the
|
|
216
|
+
// always-on project declaration-TWIN hiding (a .d.ts beside runtime JS
|
|
217
|
+
// outside node_modules — prettier's src/index.d.ts — must not exist for
|
|
218
|
+
// the checker, so its resolution lands on the JS Node actually loads;
|
|
219
|
+
// resolve.ts answers the same sibling for scriptc's own edges).
|
|
220
|
+
const npmShadow = npmStaticFsShadow();
|
|
221
|
+
const fsShadow = {
|
|
222
|
+
readFile: (path) => npmShadow?.readFile(path),
|
|
223
|
+
hideFile: (path) => (npmShadow?.hideFile(path) ?? false) || projectDtsRuntimeSibling(path) !== null,
|
|
224
|
+
};
|
|
225
|
+
const host = new ts.Ts7Host({ cwd: dirname(entryPath), fsShadow });
|
|
226
|
+
const load = loadProgram7(host, entryPath);
|
|
227
|
+
return {
|
|
228
|
+
...load,
|
|
229
|
+
dispose: () => {
|
|
230
|
+
load.disposeAll();
|
|
231
|
+
host.close();
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
/** tsc diagnostics (syntax + types), the supported-import fence, and the
|
|
236
|
+
* module evaluation order: fills load.moduleOrder (the SourceFiles
|
|
237
|
+
* themselves — the lowering consumes them directly) and returns the
|
|
238
|
+
* preflight diagnostics. The lowerer runs only on programs that pass. */
|
|
239
|
+
export function checkPreflight(load) {
|
|
240
|
+
const { diags, moduleOrder, startupCrash } = preflight7(load);
|
|
241
|
+
load.moduleOrder = moduleOrder;
|
|
242
|
+
load.startupCrash = startupCrash;
|
|
243
|
+
return diags;
|
|
244
|
+
}
|
|
245
|
+
/* Finding 5: tsgo's strict CommonJS modeling fires checker codes on JS
|
|
246
|
+
* shapes 5.9.3 accepted, and the real hazards those shapes carry are fenced
|
|
247
|
+
* by scriptc's OWN analysis, never by these codes — so for JS FILES ONLY
|
|
248
|
+
* they join the relaxation (TS sources keep the full gate; the order-parity
|
|
249
|
+
* harness holds both lanes to identical verdicts, so a case where 5.9.3
|
|
250
|
+
* itself raises one of these on a JS file would fail there, not slip by):
|
|
251
|
+
* - TS2309 ("export assignment with other exported elements") on the
|
|
252
|
+
* table-then-member pattern — cjsExportDiscardReason names every export
|
|
253
|
+
* Node actually discards.
|
|
254
|
+
* - TS2323 ("cannot redeclare exported variable") on symbol-keyed expando
|
|
255
|
+
* assignments in CJS classes (`this[kSym] = v` under `module.exports =
|
|
256
|
+
* Class`) — 5.9.3 synthesized expando symbols, tsgo trips its
|
|
257
|
+
* redeclaration check instead (the corpus's countdown fixture). */
|
|
258
|
+
const TS7_JS_RELAXED_EXTRA = new Set([2309, 2323]);
|
|
259
|
+
function suppressedJsStrictness7(d) {
|
|
260
|
+
const file = d.fileName;
|
|
261
|
+
if (!file || !isJsSourceFileName(file))
|
|
262
|
+
return false;
|
|
263
|
+
if (JS_RELAXED_TSC_CODES.has(d.code) || TS7_JS_RELAXED_EXTRA.has(d.code))
|
|
264
|
+
return true;
|
|
265
|
+
// The 8xxx range is TypeScript's JSDoc-specific diagnostic family
|
|
266
|
+
// (malformed tags, signature/tag mismatches): comment semantics with no
|
|
267
|
+
// runtime meaning in a JS file.
|
|
268
|
+
if (d.code >= 8000 && d.code <= 8999)
|
|
269
|
+
return true;
|
|
270
|
+
if (JS_ANY_OPERATOR_CODES.has(d.code)) {
|
|
271
|
+
return ts.flattenDiagnosticMessageText(d, "\n").includes("'any'");
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
/** The specifier of a `require("...")` call: the callee is the bare
|
|
276
|
+
* identifier `require` and the single argument a string literal. The one
|
|
277
|
+
* recognizer preflight edges, global collection, and statement lowering
|
|
278
|
+
* share — anything require-shaped it does NOT match (computed specifiers,
|
|
279
|
+
* extra arguments) is not a module edge and fences at its use site. */
|
|
280
|
+
function requireSpecOf7(node) {
|
|
281
|
+
if (!ts.isCallExpression(node))
|
|
282
|
+
return null;
|
|
283
|
+
if (!ts.isIdentifier(node.expression) || node.expression.text !== "require")
|
|
284
|
+
return null;
|
|
285
|
+
if (node.arguments.length !== 1)
|
|
286
|
+
return null;
|
|
287
|
+
const arg = node.arguments[0];
|
|
288
|
+
return ts.isStringLiteral(arg) ? arg.text : null;
|
|
289
|
+
}
|
|
290
|
+
/** True for statements that are CommonJS require IMPORTS at a module's top
|
|
291
|
+
* level: `const x = require("s")` / `const { a, b } = require("s")`
|
|
292
|
+
* (every declarator a require), and the bare side-effect `require("s");`.
|
|
293
|
+
* These lower to NOTHING — the bindings are alias plumbing (tsc models
|
|
294
|
+
* them as import aliases) and the module edge lives in the order walk. */
|
|
295
|
+
function isRequireStatement7(stmt) {
|
|
296
|
+
if (ts.isExpressionStatement(stmt))
|
|
297
|
+
return requireSpecOf7(stmt.expression) !== null;
|
|
298
|
+
if (!ts.isVariableStatement(stmt))
|
|
299
|
+
return false;
|
|
300
|
+
const decls = stmt.declarationList.declarations;
|
|
301
|
+
return decls.length > 0 && decls.every((d) => d.initializer !== undefined && requireSpecOf7(d.initializer) !== null);
|
|
302
|
+
}
|
|
303
|
+
/** The require() occurrences of one top-level statement: the declaration
|
|
304
|
+
* forms and the bare side-effect call. Empty for everything else. */
|
|
305
|
+
function requiresOf7(stmt) {
|
|
306
|
+
if (ts.isExpressionStatement(stmt)) {
|
|
307
|
+
const spec = requireSpecOf7(stmt.expression);
|
|
308
|
+
return spec !== null ? [{ spec, node: stmt.expression, decl: null }] : [];
|
|
309
|
+
}
|
|
310
|
+
if (!ts.isVariableStatement(stmt))
|
|
311
|
+
return [];
|
|
312
|
+
const out = [];
|
|
313
|
+
for (const decl of stmt.declarationList.declarations) {
|
|
314
|
+
const spec = decl.initializer !== undefined ? requireSpecOf7(decl.initializer) : null;
|
|
315
|
+
if (spec !== null)
|
|
316
|
+
out.push({ spec, node: decl, decl });
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
/** True for top-level statements that cannot run user code: directives,
|
|
321
|
+
* empty statements, hoisted declarations, require statements themselves,
|
|
322
|
+
* and literal-initialized variables. A require preceded ONLY by these can
|
|
323
|
+
* never have its bindings observed early — nothing above it executes. */
|
|
324
|
+
function purePrefixStmt7(s) {
|
|
325
|
+
if (ts.isEmptyStatement(s) || ts.isFunctionDeclaration(s))
|
|
326
|
+
return true;
|
|
327
|
+
if (ts.isExpressionStatement(s) && ts.isStringLiteral(s.expression))
|
|
328
|
+
return true; // directive
|
|
329
|
+
if (isRequireStatement7(s))
|
|
330
|
+
return true;
|
|
331
|
+
if (ts.isVariableStatement(s)) {
|
|
332
|
+
return s.declarationList.declarations.every((d) => d.initializer === undefined ||
|
|
333
|
+
requireSpecOf7(d.initializer) !== null ||
|
|
334
|
+
ts.isStringLiteralLike(d.initializer) ||
|
|
335
|
+
ts.isNumericLiteral(d.initializer) ||
|
|
336
|
+
d.initializer.kind === ts.SyntaxKind.TrueKeyword ||
|
|
337
|
+
d.initializer.kind === ts.SyntaxKind.FalseKeyword ||
|
|
338
|
+
d.initializer.kind === ts.SyntaxKind.NullKeyword);
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
/** Can code that runs BEFORE the require statement at index `k` reach one
|
|
343
|
+
* of the require's bindings? Node initializes them AT the require (TDZ —
|
|
344
|
+
* earlier access is a ReferenceError), but the lowering aliases reads
|
|
345
|
+
* through to the exporter's storage with no TDZ state, so a reachable
|
|
346
|
+
* early read would silently diverge. Conservative reachability: earlier
|
|
347
|
+
* statements' whole subtrees (arrows and function expressions included)
|
|
348
|
+
* read the binding directly, or reference a hoisted function/class whose
|
|
349
|
+
* body (transitively through other hoisted declarations) reads it — a
|
|
350
|
+
* referenced function value may be invoked immediately by whatever takes
|
|
351
|
+
* it. Asynchronously-scheduled callbacks run after the whole module body,
|
|
352
|
+
* hence after the require: never an early read. Returns the first
|
|
353
|
+
* reachable binding's name, or null when position is provably free.
|
|
354
|
+
* (The CheckerFacade memoizes symbol queries, and 7's client dedupes
|
|
355
|
+
* symbol identity by server handle, so the Map-keyed-by-Symbol discipline
|
|
356
|
+
* carries over from the 5.9.3 original unchanged.) */
|
|
357
|
+
function requireTdzRisk7(program, sf, k, decl) {
|
|
358
|
+
const checker = program.getTypeChecker();
|
|
359
|
+
const bound = [];
|
|
360
|
+
if (ts.isIdentifier(decl.name))
|
|
361
|
+
bound.push(decl.name);
|
|
362
|
+
else if (ts.isObjectBindingPattern(decl.name)) {
|
|
363
|
+
for (const el of decl.name.elements)
|
|
364
|
+
if (el.name !== undefined && ts.isIdentifier(el.name))
|
|
365
|
+
bound.push(el.name);
|
|
366
|
+
}
|
|
367
|
+
const bindings = new Map();
|
|
368
|
+
for (const id of bound) {
|
|
369
|
+
const sym = checker.getSymbolAtLocation(id);
|
|
370
|
+
if (sym)
|
|
371
|
+
bindings.set(sym, id.text);
|
|
372
|
+
}
|
|
373
|
+
if (bindings.size === 0)
|
|
374
|
+
return null;
|
|
375
|
+
const stmts = sf.statements;
|
|
376
|
+
const hoisted = new Map();
|
|
377
|
+
for (const s of stmts) {
|
|
378
|
+
if ((ts.isFunctionDeclaration(s) || ts.isClassDeclaration(s)) && s.name) {
|
|
379
|
+
const sym = checker.getSymbolAtLocation(s.name);
|
|
380
|
+
if (sym)
|
|
381
|
+
hoisted.set(sym, s);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
let hit = null;
|
|
385
|
+
const scanned = new Set();
|
|
386
|
+
const work = [];
|
|
387
|
+
const scan = (root) => {
|
|
388
|
+
// Iterative walk (walkPreorder): a statement's subtree can be
|
|
389
|
+
// pathologically deep (the binderBinaryExpressionStress chains), and
|
|
390
|
+
// this scan must survive it so the nesting fence can answer later.
|
|
391
|
+
ts.walkPreorder(root, (n) => {
|
|
392
|
+
if (ts.isIdentifier(n)) {
|
|
393
|
+
const sym = checker.getSymbolAtLocation(n);
|
|
394
|
+
if (sym) {
|
|
395
|
+
const name = bindings.get(sym);
|
|
396
|
+
if (name !== undefined) {
|
|
397
|
+
hit = name;
|
|
398
|
+
return "stop";
|
|
399
|
+
}
|
|
400
|
+
const target = hoisted.get(sym);
|
|
401
|
+
if (target && !scanned.has(target)) {
|
|
402
|
+
scanned.add(target);
|
|
403
|
+
work.push(target);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
return undefined;
|
|
408
|
+
});
|
|
409
|
+
};
|
|
410
|
+
for (let i = 0; i < k && hit === null; i++) {
|
|
411
|
+
const s = stmts[i];
|
|
412
|
+
if (ts.isFunctionDeclaration(s))
|
|
413
|
+
continue;
|
|
414
|
+
scan(s);
|
|
415
|
+
}
|
|
416
|
+
while (hit === null && work.length > 0) {
|
|
417
|
+
scan(work.pop());
|
|
418
|
+
}
|
|
419
|
+
return hit;
|
|
420
|
+
}
|
|
421
|
+
/** Bare `require("...")` EXPRESSION STATEMENTS anywhere below the top
|
|
422
|
+
* level of a file — inside top-level ifs/blocks/loops and inside function
|
|
423
|
+
* bodies (the lazy-require idiom). No bindings, pure side-effect loads. */
|
|
424
|
+
function nestedBareRequiresOf7(sf) {
|
|
425
|
+
const out = [];
|
|
426
|
+
// Iterative walk (walkPreorder): this sweep sees every JS file whole,
|
|
427
|
+
// including pathologically deep expression chains that must live to be
|
|
428
|
+
// fenced by name, not crash a recursive visit.
|
|
429
|
+
ts.walkPreorder(sf, (n) => {
|
|
430
|
+
if (ts.isExpressionStatement(n) &&
|
|
431
|
+
!ts.isSourceFile(n.parent) &&
|
|
432
|
+
ts.isCallExpression(n.expression) &&
|
|
433
|
+
requireSpecOf7(n.expression) !== null) {
|
|
434
|
+
out.push(n.expression);
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
return out;
|
|
438
|
+
}
|
|
439
|
+
/** True when Node would treat this file as an ES MODULE (never defining
|
|
440
|
+
* require/__dirname there): .mjs always, .cjs never, and .js/.ts by
|
|
441
|
+
* syntactic module detection — spelled to 5.9.3's OBSERVED bundler-mode
|
|
442
|
+
* behavior (it set impliedNodeFormat only for the mjs/cjs families; 7's
|
|
443
|
+
* client SourceFile has no impliedNodeFormat at all). */
|
|
444
|
+
function isNodeEsmFile7(sf) {
|
|
445
|
+
if (sf.fileName.endsWith(".cjs") || sf.fileName.endsWith(".cts"))
|
|
446
|
+
return false;
|
|
447
|
+
if (sf.fileName.endsWith(".mjs") || sf.fileName.endsWith(".mts"))
|
|
448
|
+
return true;
|
|
449
|
+
return ts.isExternalModule(sf);
|
|
450
|
+
}
|
|
451
|
+
/** A JavaScript source file Node treats as CommonJS (not an ES module):
|
|
452
|
+
* ESM namespace/default machinery must not claim it — its export surface
|
|
453
|
+
* is module.exports, read through the CJS interop paths and their own
|
|
454
|
+
* fences. */
|
|
455
|
+
function isCjsJsFile7(sf) {
|
|
456
|
+
return isJsSourceFileName(sf.fileName) && !isNodeEsmFile7(sf);
|
|
457
|
+
}
|
|
458
|
+
/** The exported spelling of isCjsJsFile7 (the lowering's gate for
|
|
459
|
+
* module.exports surfaces — `ts.isExternalModule` is NOT the CJS test:
|
|
460
|
+
* tsgo marks CJS files with export assignments as external modules). */
|
|
461
|
+
export function isCjsJsFile(sf) {
|
|
462
|
+
return isCjsJsFile7(sf);
|
|
463
|
+
}
|
|
464
|
+
/** The self-import TDZ fences: for `import { x } from "<self>"` (default
|
|
465
|
+
* and namespace forms included), any TOP-LEVEL reference lexically above
|
|
466
|
+
* the aliased declaration observes Node's temporal dead zone — the direct
|
|
467
|
+
* spelling of the same read is tsc's own TS2448, which the import
|
|
468
|
+
* indirection dodges, so preflight restores the bar with SC1030. Function
|
|
469
|
+
* aliases hoist (initialized at link — readable anywhere); references
|
|
470
|
+
* inside function-like bodies follow the direct spelling's existing
|
|
471
|
+
* policy (tsc accepts those too) and are not fenced here. Namespace
|
|
472
|
+
* bindings fence per MEMBER access (`self.x` above x's declaration);
|
|
473
|
+
* the bare namespace binding itself is never TDZ. */
|
|
474
|
+
function selfImportTdzFences7(program, sf, clause, diags) {
|
|
475
|
+
const checker = program.getTypeChecker();
|
|
476
|
+
const insideFunction = (node) => {
|
|
477
|
+
for (let p = node.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
|
|
478
|
+
if (ts.isFunctionLike(p))
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
return false;
|
|
482
|
+
};
|
|
483
|
+
// The aliased declaration's TDZ horizon: the position after which reads
|
|
484
|
+
// are initialized. Hoisted functions have none.
|
|
485
|
+
const tdzEndOf = (aliased) => {
|
|
486
|
+
const d = checker.valueDeclarationOf(aliased) ?? checker.declarationsOf(aliased)[0];
|
|
487
|
+
if (d === undefined || d.getSourceFile() !== sf)
|
|
488
|
+
return null;
|
|
489
|
+
if (ts.isFunctionDeclaration(d))
|
|
490
|
+
return null;
|
|
491
|
+
return d.getEnd();
|
|
492
|
+
};
|
|
493
|
+
const fence = (ref, name) => {
|
|
494
|
+
diags.push(unsupportedDiag("SC1030", { file: sf.fileName, start: ref.getStart(sf), end: ref.getEnd() }, `the self-import binding '${name}' read above its declaration (the import aliases this module's OWN binding — Node initializes it when the declaration executes, so this read throws ReferenceError; move the read below the declaration)`));
|
|
495
|
+
};
|
|
496
|
+
const checkNamed = (bindingName) => {
|
|
497
|
+
const sym = checker.getSymbolAtLocation(bindingName);
|
|
498
|
+
if (sym === undefined || !(sym.flags & ts.SymbolFlags.Alias))
|
|
499
|
+
return;
|
|
500
|
+
const end = tdzEndOf(checker.getAliasedSymbol(sym));
|
|
501
|
+
if (end === null)
|
|
502
|
+
return;
|
|
503
|
+
const visit = (node) => {
|
|
504
|
+
if (ts.isIdentifier(node) &&
|
|
505
|
+
node !== bindingName &&
|
|
506
|
+
node.text === bindingName.text &&
|
|
507
|
+
node.getStart(sf) < end &&
|
|
508
|
+
!insideFunction(node) &&
|
|
509
|
+
checker.getSymbolAtLocation(node) === sym) {
|
|
510
|
+
fence(node, bindingName.text);
|
|
511
|
+
}
|
|
512
|
+
ts.forEachChild(node, visit);
|
|
513
|
+
};
|
|
514
|
+
visit(sf);
|
|
515
|
+
};
|
|
516
|
+
if (clause.name !== undefined)
|
|
517
|
+
checkNamed(clause.name);
|
|
518
|
+
if (clause.namedBindings !== undefined && ts.isNamedImports(clause.namedBindings)) {
|
|
519
|
+
for (const el of clause.namedBindings.elements) {
|
|
520
|
+
if (!el.isTypeOnly)
|
|
521
|
+
checkNamed(el.name);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
if (clause.namedBindings !== undefined && ts.isNamespaceImport(clause.namedBindings)) {
|
|
525
|
+
const nsName = clause.namedBindings.name;
|
|
526
|
+
const nsSym = checker.getSymbolAtLocation(nsName);
|
|
527
|
+
if (nsSym === undefined)
|
|
528
|
+
return;
|
|
529
|
+
const visit = (node) => {
|
|
530
|
+
if (ts.isPropertyAccessExpression(node) &&
|
|
531
|
+
ts.isIdentifier(node.expression) &&
|
|
532
|
+
node.expression.text === nsName.text &&
|
|
533
|
+
ts.isIdentifier(node.name) &&
|
|
534
|
+
!insideFunction(node) &&
|
|
535
|
+
checker.getSymbolAtLocation(node.expression) === nsSym) {
|
|
536
|
+
const memberSym = checker.getSymbolAtLocation(node.name);
|
|
537
|
+
if (memberSym !== undefined) {
|
|
538
|
+
const end = tdzEndOf(memberSym);
|
|
539
|
+
if (end !== null && node.getStart(sf) < end)
|
|
540
|
+
fence(node.expression, `${nsName.text}.${node.name.text}`);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
ts.forEachChild(node, visit);
|
|
544
|
+
};
|
|
545
|
+
visit(sf);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
/** True when every reference to the namespace-import binding `nsName`
|
|
549
|
+
* (outside its own declaration) is exactly a bare expression statement's
|
|
550
|
+
* expression — the shape whose lowering is Node's no-op. A self-import's
|
|
551
|
+
* references all live in the importing file, so one file-local scan
|
|
552
|
+
* answers completely. */
|
|
553
|
+
function nsBindingUsesAreBareStatements7(program, sf, nsName) {
|
|
554
|
+
const checker = program.getTypeChecker();
|
|
555
|
+
const bindingSym = checker.getSymbolAtLocation(nsName);
|
|
556
|
+
if (bindingSym === undefined)
|
|
557
|
+
return false;
|
|
558
|
+
let bareOnly = true;
|
|
559
|
+
const visit = (node) => {
|
|
560
|
+
if (!bareOnly)
|
|
561
|
+
return;
|
|
562
|
+
if (ts.isIdentifier(node) &&
|
|
563
|
+
node !== nsName &&
|
|
564
|
+
node.text === nsName.text &&
|
|
565
|
+
checker.getSymbolAtLocation(node) === bindingSym) {
|
|
566
|
+
if (!(node.parent !== undefined && ts.isExpressionStatement(node.parent) && node.parent.expression === node)) {
|
|
567
|
+
bareOnly = false;
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
ts.forEachChild(node, visit);
|
|
572
|
+
};
|
|
573
|
+
visit(sf);
|
|
574
|
+
return bareOnly;
|
|
575
|
+
}
|
|
576
|
+
/** True when the reference at `node` can only evaluate AFTER the module
|
|
577
|
+
* graph finished initializing: inside a function-like BODY, inside a
|
|
578
|
+
* parameter (default initializers evaluate at call time), or inside an
|
|
579
|
+
* INSTANCE field initializer (runs at construction, and no admitted top
|
|
580
|
+
* level constructs). Static field initializers, static blocks, heritage
|
|
581
|
+
* clauses, computed names, and decorators all run at the class statement
|
|
582
|
+
* itself — not deferred. */
|
|
583
|
+
function inDeferredPosition7(node) {
|
|
584
|
+
let child = node;
|
|
585
|
+
for (let p = node.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
|
|
586
|
+
if (p.kind === ts.SyntaxKind.Decorator)
|
|
587
|
+
return false;
|
|
588
|
+
if (ts.isClassStaticBlockDeclaration(p))
|
|
589
|
+
return false;
|
|
590
|
+
if (ts.isFunctionLike(p)) {
|
|
591
|
+
if (p.body === child)
|
|
592
|
+
return true;
|
|
593
|
+
if (ts.isParameter(child))
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
if (ts.isPropertyDeclaration(p) &&
|
|
597
|
+
p.initializer === child &&
|
|
598
|
+
(ts.getCombinedModifierFlags(p) & ts.ModifierFlags.Static) === 0) {
|
|
599
|
+
return true;
|
|
600
|
+
}
|
|
601
|
+
child = p;
|
|
602
|
+
}
|
|
603
|
+
return false;
|
|
604
|
+
}
|
|
605
|
+
/** True for identifiers in pure TYPE positions (annotations, `typeof`
|
|
606
|
+
* queries, interface/alias bodies) — erased at runtime, never a read. A
|
|
607
|
+
* CLASS `extends` heritage expression is the one TypeNode-shaped position
|
|
608
|
+
* that IS a runtime read. */
|
|
609
|
+
function inTypePosition7(node) {
|
|
610
|
+
for (let p = node.parent; p !== undefined && !ts.isSourceFile(p); p = p.parent) {
|
|
611
|
+
if (ts.isTypeNode(p)) {
|
|
612
|
+
const heritage = p.parent;
|
|
613
|
+
const isClassExtends = p.kind === ts.SyntaxKind.ExpressionWithTypeArguments &&
|
|
614
|
+
heritage !== undefined &&
|
|
615
|
+
ts.isHeritageClause(heritage) &&
|
|
616
|
+
heritage.token === ts.SyntaxKind.ExtendsKeyword &&
|
|
617
|
+
(ts.isClassDeclaration(heritage.parent) || ts.isClassExpression(heritage.parent));
|
|
618
|
+
if (!isClassExtends)
|
|
619
|
+
return true;
|
|
620
|
+
}
|
|
621
|
+
if (ts.isTypeAliasDeclaration(p) || ts.isInterfaceDeclaration(p))
|
|
622
|
+
return true;
|
|
623
|
+
}
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
/** The first top-level construct of `sf` that could execute USER code
|
|
627
|
+
* during a cycle's init window, or null when the module's top level is
|
|
628
|
+
* declaration-only ("inert"). The expression whitelist is deliberately
|
|
629
|
+
* conservative — anything that could CALL user code (directly, through a
|
|
630
|
+
* callback-invoking builtin, a getter, an iterator, or an implicit
|
|
631
|
+
* coercion of a user object) disqualifies the module's cycles. */
|
|
632
|
+
function nonInertTopLevel7(program, sf) {
|
|
633
|
+
const checker = program.getTypeChecker();
|
|
634
|
+
const PRIM = ts.TypeFlags.StringLike |
|
|
635
|
+
ts.TypeFlags.NumberLike |
|
|
636
|
+
ts.TypeFlags.BooleanLike |
|
|
637
|
+
ts.TypeFlags.BigIntLike |
|
|
638
|
+
ts.TypeFlags.ESSymbolLike |
|
|
639
|
+
ts.TypeFlags.EnumLike |
|
|
640
|
+
ts.TypeFlags.Undefined |
|
|
641
|
+
ts.TypeFlags.Null |
|
|
642
|
+
ts.TypeFlags.Void |
|
|
643
|
+
ts.TypeFlags.Never;
|
|
644
|
+
/** Coercion-safe: the expression's type is primitive in every arm, so
|
|
645
|
+
* ToString/ToNumber/ToPrimitive can never reach a user valueOf. */
|
|
646
|
+
const primitiveTyped = (e) => {
|
|
647
|
+
const t = checker.getTypeAtLocation(e);
|
|
648
|
+
const parts = t.isUnionType() ? t.getTypes() : [t];
|
|
649
|
+
return parts.every((p) => (p.flags & PRIM) !== 0);
|
|
650
|
+
};
|
|
651
|
+
/** The identifier's symbol lives entirely in declaration files — a
|
|
652
|
+
* global builtin (process, WeakSet, Object, …) whose property reads and
|
|
653
|
+
* calls are runtime-implemented, never user code. */
|
|
654
|
+
const dtsRooted = (e) => {
|
|
655
|
+
let root = e;
|
|
656
|
+
while (ts.isPropertyAccessExpression(root) || ts.isElementAccessExpression(root))
|
|
657
|
+
root = root.expression;
|
|
658
|
+
if (ts.isMetaProperty(root))
|
|
659
|
+
return true; // import.meta
|
|
660
|
+
if (!ts.isIdentifier(root))
|
|
661
|
+
return false;
|
|
662
|
+
let sym = checker.getSymbolAtLocation(root);
|
|
663
|
+
if (sym === undefined)
|
|
664
|
+
return false;
|
|
665
|
+
if (sym.flags & ts.SymbolFlags.Alias)
|
|
666
|
+
sym = checker.getAliasedSymbol(sym);
|
|
667
|
+
const decls = checker.declarationsOf(sym);
|
|
668
|
+
return decls.length > 0 && decls.every((d) => d.getSourceFile().isDeclarationFile);
|
|
669
|
+
};
|
|
670
|
+
const hasDecorator = (n) => (n.modifiers ?? []).some((m) => m.kind === ts.SyntaxKind.Decorator);
|
|
671
|
+
/** No function-like node anywhere in the subtree — arguments to builtin
|
|
672
|
+
* calls must not smuggle a callback the builtin could invoke. */
|
|
673
|
+
const containsFunctionLike = (n) => {
|
|
674
|
+
let found = false;
|
|
675
|
+
ts.walkPreorder(n, (c) => {
|
|
676
|
+
if (ts.isFunctionLike(c) || ts.isClassExpression(c) || ts.isClassDeclaration(c)) {
|
|
677
|
+
found = true;
|
|
678
|
+
return "stop";
|
|
679
|
+
}
|
|
680
|
+
return undefined;
|
|
681
|
+
});
|
|
682
|
+
return found;
|
|
683
|
+
};
|
|
684
|
+
const inertClass = (c) => {
|
|
685
|
+
if (hasDecorator(c))
|
|
686
|
+
return c;
|
|
687
|
+
for (const h of c.heritageClauses ?? []) {
|
|
688
|
+
if (h.token !== ts.SyntaxKind.ExtendsKeyword)
|
|
689
|
+
continue;
|
|
690
|
+
for (const t of h.types)
|
|
691
|
+
if (!inert(t.expression))
|
|
692
|
+
return t.expression;
|
|
693
|
+
}
|
|
694
|
+
for (const m of c.members) {
|
|
695
|
+
if (ts.isClassStaticBlockDeclaration(m))
|
|
696
|
+
return m;
|
|
697
|
+
if (hasDecorator(m))
|
|
698
|
+
return m;
|
|
699
|
+
const memberName = m.name;
|
|
700
|
+
if (memberName !== undefined && ts.isComputedPropertyName(memberName)) {
|
|
701
|
+
if (!inert(memberName.expression) || !primitiveTyped(memberName.expression))
|
|
702
|
+
return memberName;
|
|
703
|
+
}
|
|
704
|
+
if (ts.isPropertyDeclaration(m) &&
|
|
705
|
+
(ts.getCombinedModifierFlags(m) & ts.ModifierFlags.Static) !== 0 &&
|
|
706
|
+
m.initializer !== undefined &&
|
|
707
|
+
!inert(m.initializer)) {
|
|
708
|
+
return m.initializer;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
return null;
|
|
712
|
+
};
|
|
713
|
+
const COERCING_FREE = new Set([
|
|
714
|
+
ts.SyntaxKind.EqualsEqualsEqualsToken,
|
|
715
|
+
ts.SyntaxKind.ExclamationEqualsEqualsToken,
|
|
716
|
+
ts.SyntaxKind.AmpersandAmpersandToken,
|
|
717
|
+
ts.SyntaxKind.BarBarToken,
|
|
718
|
+
ts.SyntaxKind.QuestionQuestionToken,
|
|
719
|
+
ts.SyntaxKind.CommaToken,
|
|
720
|
+
ts.SyntaxKind.InstanceOfKeyword,
|
|
721
|
+
]);
|
|
722
|
+
const inert = (e) => {
|
|
723
|
+
if (ts.isStringLiteral(e) ||
|
|
724
|
+
ts.isNumericLiteral(e) ||
|
|
725
|
+
ts.isBigIntLiteral(e) ||
|
|
726
|
+
ts.isRegularExpressionLiteral(e) ||
|
|
727
|
+
ts.isNoSubstitutionTemplateLiteral(e) ||
|
|
728
|
+
e.kind === ts.SyntaxKind.TrueKeyword ||
|
|
729
|
+
e.kind === ts.SyntaxKind.FalseKeyword ||
|
|
730
|
+
e.kind === ts.SyntaxKind.NullKeyword ||
|
|
731
|
+
ts.isIdentifier(e) ||
|
|
732
|
+
ts.isMetaProperty(e) ||
|
|
733
|
+
ts.isArrowFunction(e) ||
|
|
734
|
+
ts.isFunctionExpression(e)) {
|
|
735
|
+
return true;
|
|
736
|
+
}
|
|
737
|
+
if (ts.isClassExpression(e))
|
|
738
|
+
return inertClass(e) === null;
|
|
739
|
+
if (ts.isParenthesizedExpression(e) || ts.isAsExpression(e) || ts.isSatisfiesExpression(e) || ts.isNonNullExpression(e)) {
|
|
740
|
+
return inert(e.expression);
|
|
741
|
+
}
|
|
742
|
+
if (ts.isPropertyAccessExpression(e))
|
|
743
|
+
return dtsRooted(e);
|
|
744
|
+
if (ts.isElementAccessExpression(e)) {
|
|
745
|
+
return dtsRooted(e) && inert(e.argumentExpression) && primitiveTyped(e.argumentExpression);
|
|
746
|
+
}
|
|
747
|
+
if (ts.isTemplateExpression(e)) {
|
|
748
|
+
return e.templateSpans.every((s) => inert(s.expression) && primitiveTyped(s.expression));
|
|
749
|
+
}
|
|
750
|
+
if (ts.isVoidExpression(e) || ts.isTypeOfExpression(e))
|
|
751
|
+
return inert(e.expression);
|
|
752
|
+
if (ts.isPrefixUnaryExpression(e)) {
|
|
753
|
+
if (e.operator === ts.SyntaxKind.ExclamationToken)
|
|
754
|
+
return inert(e.operand);
|
|
755
|
+
if (e.operator === ts.SyntaxKind.PlusToken ||
|
|
756
|
+
e.operator === ts.SyntaxKind.MinusToken ||
|
|
757
|
+
e.operator === ts.SyntaxKind.TildeToken) {
|
|
758
|
+
return inert(e.operand) && primitiveTyped(e.operand);
|
|
759
|
+
}
|
|
760
|
+
return false;
|
|
761
|
+
}
|
|
762
|
+
if (ts.isBinaryExpression(e)) {
|
|
763
|
+
if (!inert(e.left) || !inert(e.right))
|
|
764
|
+
return false;
|
|
765
|
+
if (COERCING_FREE.has(e.operatorToken.kind))
|
|
766
|
+
return true;
|
|
767
|
+
return primitiveTyped(e.left) && primitiveTyped(e.right);
|
|
768
|
+
}
|
|
769
|
+
if (ts.isConditionalExpression(e))
|
|
770
|
+
return inert(e.condition) && inert(e.whenTrue) && inert(e.whenFalse);
|
|
771
|
+
if (ts.isArrayLiteralExpression(e))
|
|
772
|
+
return e.elements.every((el) => !ts.isSpreadElement(el) && inert(el));
|
|
773
|
+
if (ts.isObjectLiteralExpression(e)) {
|
|
774
|
+
return e.properties.every((p) => {
|
|
775
|
+
if (ts.isShorthandPropertyAssignment(p))
|
|
776
|
+
return true;
|
|
777
|
+
if (!ts.isPropertyAssignment(p) || ts.isComputedPropertyName(p.name))
|
|
778
|
+
return false;
|
|
779
|
+
return inert(p.initializer);
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
if (ts.isCallExpression(e) || ts.isNewExpression(e)) {
|
|
783
|
+
if (!dtsRooted(e.expression) || !ts.isIdentifier(chainRoot7(e.expression)))
|
|
784
|
+
return false;
|
|
785
|
+
const args = e.arguments ?? [];
|
|
786
|
+
// A CALLABLE argument is admissible when it is itself dts-rooted:
|
|
787
|
+
// a builtin-owned function value (vercel's `promisify(fs.readFile)`
|
|
788
|
+
// at every cycle member's top level) is runtime-implemented — even
|
|
789
|
+
// if the builtin callee invokes it, no user code runs and no
|
|
790
|
+
// cluster binding is observable. Function literals and user
|
|
791
|
+
// callables keep the refusal.
|
|
792
|
+
return args.every((a) => inert(a) &&
|
|
793
|
+
!containsFunctionLike(a) &&
|
|
794
|
+
(checker.getCallSignatures(checker.getTypeAtLocation(a)).length === 0 || dtsRooted(a)));
|
|
795
|
+
}
|
|
796
|
+
return false;
|
|
797
|
+
};
|
|
798
|
+
for (const stmt of sf.statements) {
|
|
799
|
+
if (ts.isImportDeclaration(stmt) ||
|
|
800
|
+
ts.isExportDeclaration(stmt) ||
|
|
801
|
+
ts.isImportEqualsDeclaration(stmt) ||
|
|
802
|
+
ts.isFunctionDeclaration(stmt) ||
|
|
803
|
+
ts.isInterfaceDeclaration(stmt) ||
|
|
804
|
+
ts.isTypeAliasDeclaration(stmt) ||
|
|
805
|
+
ts.isEmptyStatement(stmt) ||
|
|
806
|
+
(ts.getCombinedModifierFlags(stmt) & ts.ModifierFlags.Ambient) !== 0) {
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
if (ts.isEnumDeclaration(stmt)) {
|
|
810
|
+
const bad = stmt.members.find((m) => m.initializer !== undefined && !inert(m.initializer));
|
|
811
|
+
if (bad !== undefined)
|
|
812
|
+
return bad;
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (ts.isClassDeclaration(stmt)) {
|
|
816
|
+
const bad = inertClass(stmt);
|
|
817
|
+
if (bad !== null)
|
|
818
|
+
return bad;
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
if (ts.isVariableStatement(stmt)) {
|
|
822
|
+
for (const d of stmt.declarationList.declarations) {
|
|
823
|
+
if (!ts.isIdentifier(d.name))
|
|
824
|
+
return d.name; // patterns can invoke getters/iterators
|
|
825
|
+
if (d.initializer !== undefined && !inert(d.initializer))
|
|
826
|
+
return d.initializer;
|
|
827
|
+
}
|
|
828
|
+
continue;
|
|
829
|
+
}
|
|
830
|
+
if (ts.isExpressionStatement(stmt)) {
|
|
831
|
+
if (!inert(stmt.expression))
|
|
832
|
+
return stmt;
|
|
833
|
+
continue;
|
|
834
|
+
}
|
|
835
|
+
if (ts.isExportAssignment(stmt) && !stmt.isExportEquals) {
|
|
836
|
+
if (!inert(stmt.expression))
|
|
837
|
+
return stmt;
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
return stmt;
|
|
841
|
+
}
|
|
842
|
+
return null;
|
|
843
|
+
}
|
|
844
|
+
/** The head of a property/element-access chain. */
|
|
845
|
+
function chainRoot7(e) {
|
|
846
|
+
let root = e;
|
|
847
|
+
while (ts.isPropertyAccessExpression(root) || ts.isElementAccessExpression(root))
|
|
848
|
+
root = root.expression;
|
|
849
|
+
return root;
|
|
850
|
+
}
|
|
851
|
+
/** The first use of a binding this cycle-closing import introduces that
|
|
852
|
+
* sits OUTSIDE a deferred position (a read there can observe the
|
|
853
|
+
* partially-initialized exporter), or null when every use defers.
|
|
854
|
+
* Re-export statements bind nothing locally — always null: the alias is
|
|
855
|
+
* link-time plumbing, never an evaluation-time read. Export specifiers
|
|
856
|
+
* of an imported binding are the same alias story, not reads. */
|
|
857
|
+
function backEdgeUseOffence7(program, sf, stmt) {
|
|
858
|
+
if (!ts.isImportDeclaration(stmt) || stmt.importClause === undefined)
|
|
859
|
+
return null;
|
|
860
|
+
const checker = program.getTypeChecker();
|
|
861
|
+
const clause = stmt.importClause;
|
|
862
|
+
const bindingNames = [];
|
|
863
|
+
if (clause.name !== undefined)
|
|
864
|
+
bindingNames.push(clause.name);
|
|
865
|
+
if (clause.namedBindings !== undefined) {
|
|
866
|
+
if (ts.isNamespaceImport(clause.namedBindings))
|
|
867
|
+
bindingNames.push(clause.namedBindings.name);
|
|
868
|
+
else
|
|
869
|
+
for (const el of clause.namedBindings.elements)
|
|
870
|
+
if (!el.isTypeOnly)
|
|
871
|
+
bindingNames.push(el.name);
|
|
872
|
+
}
|
|
873
|
+
for (const bindingName of bindingNames) {
|
|
874
|
+
const sym = checker.getSymbolAtLocation(bindingName);
|
|
875
|
+
if (sym === undefined)
|
|
876
|
+
continue;
|
|
877
|
+
let offence = null;
|
|
878
|
+
const visit = (node) => {
|
|
879
|
+
if (offence !== null || ts.isImportDeclaration(node))
|
|
880
|
+
return;
|
|
881
|
+
if (ts.isIdentifier(node) &&
|
|
882
|
+
node.text === bindingName.text &&
|
|
883
|
+
!(node.parent !== undefined && ts.isExportSpecifier(node.parent)) &&
|
|
884
|
+
checker.getSymbolAtLocation(node) === sym &&
|
|
885
|
+
!inTypePosition7(node) &&
|
|
886
|
+
!inDeferredPosition7(node)) {
|
|
887
|
+
offence = { name: bindingName.text, node };
|
|
888
|
+
return;
|
|
889
|
+
}
|
|
890
|
+
ts.forEachChild(node, visit);
|
|
891
|
+
};
|
|
892
|
+
visit(sf);
|
|
893
|
+
if (offence !== null)
|
|
894
|
+
return offence;
|
|
895
|
+
}
|
|
896
|
+
return null;
|
|
897
|
+
}
|
|
898
|
+
/** Resolves an import specifier from `from` to a source file of the
|
|
899
|
+
* program, or null (unresolvable / outside the program). Exported (as
|
|
900
|
+
* resolveImport) for the lowering: CommonJS require statements lower to
|
|
901
|
+
* guarded %init calls of exactly the module preflight resolved here. */
|
|
902
|
+
function resolveImport7(program, from, specifier) {
|
|
903
|
+
const resolved = resolveRelativeModule(from.fileName, specifier);
|
|
904
|
+
if (resolved === null)
|
|
905
|
+
return null;
|
|
906
|
+
return program.getSourceFile(resolved) ?? null;
|
|
907
|
+
}
|
|
908
|
+
/** An import that resolves into node_modules: the package's shipped .d.ts
|
|
909
|
+
* is the type surface, and the package's shipped JS runs in the dynamic
|
|
910
|
+
* island under --dynamic. Resolution rides the own resolver (resolve.ts).
|
|
911
|
+
* Null for relative and node: specifiers, and for anything that doesn't
|
|
912
|
+
* resolve into node_modules. */
|
|
913
|
+
function resolveNpmImport7(fromFileName, specifier) {
|
|
914
|
+
if (isRelativeSpecifier(specifier) || specifier.startsWith("node:")) {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
// --provenance-sources: a registered specifier is NOT an npm import —
|
|
918
|
+
// its attested source compiles as program modules (resolveProjectImport
|
|
919
|
+
// answers the entry), so no island embed and no .d.ts type surface.
|
|
920
|
+
if (provenanceEntryFor(specifier) !== null)
|
|
921
|
+
return null;
|
|
922
|
+
const resolved = resolveBareModule(fromFileName, specifier);
|
|
923
|
+
if (!resolved)
|
|
924
|
+
return null;
|
|
925
|
+
if (!isNodeModulesPath(resolved.typesFile)) {
|
|
926
|
+
// A workspace-linked package (the node_modules entry is a symlink into
|
|
927
|
+
// the project — every monorepo tool's internal-package install): Node
|
|
928
|
+
// resolves and runs it exactly like any installed package, so it IS an
|
|
929
|
+
// npm import; the realpath'd answer escaping node_modules must not
|
|
930
|
+
// refuse the edge. Registered so path-keyed package attribution
|
|
931
|
+
// (shared.ts) recognizes the package's real files. Anything else
|
|
932
|
+
// whose answer left node_modules stays refused.
|
|
933
|
+
if (resolved.workspaceDir === undefined)
|
|
934
|
+
return null;
|
|
935
|
+
registerWorkspacePackage(resolved.packageName, resolved.workspaceDir);
|
|
936
|
+
}
|
|
937
|
+
return {
|
|
938
|
+
packageName: resolved.packageName,
|
|
939
|
+
...(resolved.version !== undefined ? { version: resolved.version } : {}),
|
|
940
|
+
typesFile: resolved.typesFile,
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
function locOf7(node) {
|
|
944
|
+
const sf = node.getSourceFile();
|
|
945
|
+
return { file: sf.fileName, start: node.getStart(sf), end: node.getEnd() };
|
|
946
|
+
}
|
|
947
|
+
/** The PROGRAM source file an opted-in --npm-static package's resolved
|
|
948
|
+
* entry maps to, or null with the package marked an offender (resolution
|
|
949
|
+
* still answering declarations, or the file missing from the type-checked
|
|
950
|
+
* program — a path-identity mismatch between the two resolvers). */
|
|
951
|
+
function npmStaticProgramDep(program, packageName, resolvedFile) {
|
|
952
|
+
if (!isJsSourceFileName(resolvedFile)) {
|
|
953
|
+
reportNpmStaticOffender(packageName, `its import resolves to '${resolvedFile}', not to runtime JavaScript`);
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
const dep = program.getSourceFile(resolvedFile) ?? null;
|
|
957
|
+
if (dep === null) {
|
|
958
|
+
reportNpmStaticOffender(packageName, `its entry '${resolvedFile}' did not join the type-checked program`);
|
|
959
|
+
return null;
|
|
960
|
+
}
|
|
961
|
+
return dep;
|
|
962
|
+
}
|
|
963
|
+
/** `const process = require('node:process')` (identifier binding) and the
|
|
964
|
+
* bare side-effect `require('node:process')`: Node's process module IS
|
|
965
|
+
* the global process object, so the binding is a stdlib-global alias
|
|
966
|
+
* (surfaces.ts registers it; reads lower through the process surface) and
|
|
967
|
+
* the bare load is a no-op. These forms are exempt from the SC1010
|
|
968
|
+
* builtin fence; every other shape (destructuring, subpath) keeps it. */
|
|
969
|
+
function processModuleAliasRequire7(spec, decl) {
|
|
970
|
+
if (spec !== "process" && spec !== "node:process")
|
|
971
|
+
return false;
|
|
972
|
+
return decl === null || ts.isIdentifier(decl.name);
|
|
973
|
+
}
|
|
974
|
+
/** The whole TS7-lane lifecycle for one entry: spawn (or share) a tsgo
|
|
975
|
+
* host, build the lowering-world program, run the ported preflight, and
|
|
976
|
+
* dispose EVERYTHING before returning — the CLI process must exit promptly,
|
|
977
|
+
* so no snapshot or tsgo child outlives the call. */
|
|
978
|
+
export function checkPreflightTs7(entryPath, sharedHost) {
|
|
979
|
+
const host = sharedHost ?? new ts.Ts7Host({ cwd: dirname(entryPath) });
|
|
980
|
+
try {
|
|
981
|
+
const load = loadProgram7(host, entryPath);
|
|
982
|
+
try {
|
|
983
|
+
const { diags, moduleOrder } = preflight7(load);
|
|
984
|
+
return { diags, moduleOrder: moduleOrder.map((sf) => sf.fileName) };
|
|
985
|
+
}
|
|
986
|
+
finally {
|
|
987
|
+
load.disposeAll();
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
finally {
|
|
991
|
+
if (!sharedHost)
|
|
992
|
+
host.close();
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
function preflight7(load) {
|
|
996
|
+
const { program, entry } = load;
|
|
997
|
+
const diags = [...load.configDiags];
|
|
998
|
+
// Workspace-linked packages register BEFORE the tsc gate. Their files
|
|
999
|
+
// live at realpaths OUTSIDE node_modules (the monorepo-tool symlink
|
|
1000
|
+
// shape), so nothing path-shaped marks them as npm surface — yet their
|
|
1001
|
+
// shipped JS is the identical foreign-tsconfig story as node_modules JS
|
|
1002
|
+
// (the island executes it under --dynamic; the author's own build
|
|
1003
|
+
// checked it, the program's author cannot fix it). The lazy
|
|
1004
|
+
// registration inside the import-fence walk below runs AFTER errorsOf,
|
|
1005
|
+
// too late for the gate, so one pass over the program's import edges
|
|
1006
|
+
// registers them up front: every bare specifier any program file loads —
|
|
1007
|
+
// import/export declarations, dynamic import("literal") (tsgo chases
|
|
1008
|
+
// those into the program too; vercel's CLI reaches @vercel/go exactly
|
|
1009
|
+
// that way), require("literal") — resolved with the own resolver,
|
|
1010
|
+
// workspace answers recorded.
|
|
1011
|
+
{
|
|
1012
|
+
const supportedBuiltins = new Set(SUPPORTED_NODE_MODULES);
|
|
1013
|
+
const probed = new Set();
|
|
1014
|
+
const probe = (fromFile, spec) => {
|
|
1015
|
+
if (isRelativeSpecifier(spec) ||
|
|
1016
|
+
spec.startsWith("#") ||
|
|
1017
|
+
spec.startsWith("node:") ||
|
|
1018
|
+
supportedBuiltins.has(spec)) {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const key = `${dirname(fromFile)} ${spec}`;
|
|
1022
|
+
if (probed.has(key))
|
|
1023
|
+
return;
|
|
1024
|
+
probed.add(key);
|
|
1025
|
+
const r = resolveBareModule(fromFile, spec);
|
|
1026
|
+
if (r !== null && r.workspaceDir !== undefined)
|
|
1027
|
+
registerWorkspacePackage(r.packageName, r.workspaceDir);
|
|
1028
|
+
};
|
|
1029
|
+
for (const sf of program.getSourceFiles()) {
|
|
1030
|
+
if (sf.isDeclarationFile || sf.fileName.endsWith(".json"))
|
|
1031
|
+
continue;
|
|
1032
|
+
ts.walkPreorder(sf, (n) => {
|
|
1033
|
+
if ((ts.isImportDeclaration(n) || ts.isExportDeclaration(n)) &&
|
|
1034
|
+
n.moduleSpecifier !== undefined &&
|
|
1035
|
+
ts.isStringLiteral(n.moduleSpecifier)) {
|
|
1036
|
+
probe(sf.fileName, n.moduleSpecifier.text);
|
|
1037
|
+
return "skip";
|
|
1038
|
+
}
|
|
1039
|
+
if (ts.isCallExpression(n)) {
|
|
1040
|
+
const arg = n.arguments[0];
|
|
1041
|
+
const isImportCall = n.expression.kind === ts.SyntaxKind.ImportKeyword;
|
|
1042
|
+
const isRequireCall = ts.isIdentifier(n.expression) && n.expression.text === "require";
|
|
1043
|
+
if ((isImportCall || isRequireCall) && arg !== undefined && ts.isStringLiteralLike(arg)) {
|
|
1044
|
+
probe(sf.fileName, arg.text);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
return undefined;
|
|
1048
|
+
});
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
const toPassthrough = (d) => {
|
|
1052
|
+
const message = ts.flattenDiagnosticMessageText(d, "\n");
|
|
1053
|
+
// File-less (global/options) diagnostics anchor at the entry with a
|
|
1054
|
+
// zero span, exactly like the 5.9.3 lane's `d.start ?? 0` fallback.
|
|
1055
|
+
const file = d.fileName ?? entry.fileName;
|
|
1056
|
+
const start = d.fileName !== undefined ? d.pos : 0;
|
|
1057
|
+
const end = d.fileName !== undefined ? d.end : 0;
|
|
1058
|
+
return tscPassthroughDiag(message, { file, start, end });
|
|
1059
|
+
};
|
|
1060
|
+
/* TS7 checker change (not finding 5, same discipline): tsgo types a
|
|
1061
|
+
* NAMESPACE import as the spec's non-callable module namespace object
|
|
1062
|
+
* even when the module is declared with the callable `export =` shape —
|
|
1063
|
+
* 5.9.3 bound the callable value directly, so `import * as a from
|
|
1064
|
+
* "node:assert"; a(x)` typechecked there and draws TS2349 here. Those
|
|
1065
|
+
* callable module objects are scriptc's OWN declared surface, and calls
|
|
1066
|
+
* through the namespace binding are fenced per site by the lowering (the
|
|
1067
|
+
* assert-fences fixture pins the wording), so tsgo's extra TS2349 on
|
|
1068
|
+
* exactly those callees — an identifier that is a namespace-import
|
|
1069
|
+
* binding of a supported builtin module — is suppressed to the 5.9.3
|
|
1070
|
+
* verdict. Nothing else rides this: any other non-callable call keeps
|
|
1071
|
+
* tsc's voice in both lanes. */
|
|
1072
|
+
const builtinNamespaceNames = new Map();
|
|
1073
|
+
const namespaceCalleeSuppressed = (p, d) => {
|
|
1074
|
+
if (d.code !== 2349 || d.fileName === undefined)
|
|
1075
|
+
return false;
|
|
1076
|
+
let names = builtinNamespaceNames.get(d.fileName);
|
|
1077
|
+
if (!names) {
|
|
1078
|
+
names = new Set();
|
|
1079
|
+
builtinNamespaceNames.set(d.fileName, names);
|
|
1080
|
+
const sf = p.getSourceFile(d.fileName);
|
|
1081
|
+
for (const stmt of sf?.statements ?? []) {
|
|
1082
|
+
if (!ts.isImportDeclaration(stmt) || !ts.isStringLiteral(stmt.moduleSpecifier))
|
|
1083
|
+
continue;
|
|
1084
|
+
if (canonicalBuiltinModule(stmt.moduleSpecifier.text) === null)
|
|
1085
|
+
continue;
|
|
1086
|
+
const bindings = stmt.importClause?.namedBindings;
|
|
1087
|
+
if (bindings && ts.isNamespaceImport(bindings))
|
|
1088
|
+
names.add(bindings.name.text);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (names.size === 0)
|
|
1092
|
+
return false;
|
|
1093
|
+
const sf = p.getSourceFile(d.fileName);
|
|
1094
|
+
return sf !== undefined && names.has(sf.text.slice(d.pos, d.end));
|
|
1095
|
+
};
|
|
1096
|
+
/* --npm-static: checker errors INSIDE an opted-in package's files never
|
|
1097
|
+
* gate the build — the package's author checked that JS under a foreign
|
|
1098
|
+
* tsconfig (their own lib choices, their own strictness), the program's
|
|
1099
|
+
* author cannot fix it, and the JS design already names where inference
|
|
1100
|
+
* gaps land: the per-statement runtime fences (trust-but-verify — a
|
|
1101
|
+
* statement the checker could not prove compiles to its honest trap,
|
|
1102
|
+
* never to a silent guess). scriptc's OWN SC1xxx fences in those files
|
|
1103
|
+
* still count — preflight structure problems mark the package an
|
|
1104
|
+
* offender and it falls back to the island. */
|
|
1105
|
+
const npmStaticFileSuppressed = (d) => d.fileName !== undefined && npmStaticPackageOfPath(d.fileName) !== null;
|
|
1106
|
+
/* The same doctrine for node_modules JS the opt-in never NAMED:
|
|
1107
|
+
* maxNodeModuleJsDepth (set only on --npm-static loads) admits ANY
|
|
1108
|
+
* node_modules JavaScript the checker's resolution touches — e.g. an
|
|
1109
|
+
* ambient `declare module "punycode"` in @types/node now loses to an
|
|
1110
|
+
* INSTALLED punycode package's JS, which checkJs then checks. Those
|
|
1111
|
+
* files' errors are the identical foreign-tsconfig story (third-party
|
|
1112
|
+
* shipped JS the program's author cannot fix), they were invisible to
|
|
1113
|
+
* every flagless compile (depth 0 keeps node_modules JS out of the
|
|
1114
|
+
* program), and the packages themselves still island — so their checker
|
|
1115
|
+
* errors never gate a build. */
|
|
1116
|
+
/* WORKSPACE-LINKED shipped JS is the same story at a different path: the
|
|
1117
|
+
* package's files realpath OUTSIDE node_modules (so depth-0 exclusion
|
|
1118
|
+
* never saw them and allowJs+checkJs pulls them straight into the
|
|
1119
|
+
* program), but they are npm surface all the same — the island executes
|
|
1120
|
+
* them, the program's author cannot fix them (vercel's @vercel/go ships
|
|
1121
|
+
* an ncc bundle with dozens of checker errors). Registered up front (the
|
|
1122
|
+
* pass above), suppressed here unless --npm-static opted them into being
|
|
1123
|
+
* program modules. */
|
|
1124
|
+
const islandJsFile = (file) => isJsSourceFileName(file) &&
|
|
1125
|
+
npmStaticPackageOfPath(file) === null &&
|
|
1126
|
+
(isNodeModulesPath(file) || workspacePackageOfPath(file) !== null);
|
|
1127
|
+
const nodeModulesJsSuppressed = (d) => d.fileName !== undefined && islandJsFile(d.fileName);
|
|
1128
|
+
/* JSDoc TYPE positions in JS files are documentation Node never reads:
|
|
1129
|
+
* a name-resolution failure THERE (2304/2552 — prettier's utilities.js
|
|
1130
|
+
* spells a mapped type over a @template name it never declared) types
|
|
1131
|
+
* as the error-any and the per-site fences apply, same stance as the
|
|
1132
|
+
* strictness families above. A 2300 duplicate-identifier PAIR formed by
|
|
1133
|
+
* a @typedef and a same-named class/function is the same story — the
|
|
1134
|
+
* typedef half sits inside the comment, and the code half is suppressed
|
|
1135
|
+
* exactly when its partner (same file, same name) was comment-side, so
|
|
1136
|
+
* a REAL duplicate declaration (both halves in code — Node's own
|
|
1137
|
+
* SyntaxError) keeps tsc's voice. */
|
|
1138
|
+
const insideBlockComment = (text, pos) => {
|
|
1139
|
+
const open = text.lastIndexOf("/*", pos);
|
|
1140
|
+
if (open < 0)
|
|
1141
|
+
return false;
|
|
1142
|
+
const close = text.indexOf("*/", open + 2);
|
|
1143
|
+
return close === -1 || close >= pos;
|
|
1144
|
+
};
|
|
1145
|
+
const jsdocTypeSuppressed = (p, d, commentDup) => {
|
|
1146
|
+
if (d.fileName === undefined || !isJsSourceFileName(d.fileName))
|
|
1147
|
+
return false;
|
|
1148
|
+
if (d.code !== 2304 && d.code !== 2552 && d.code !== 2300 && d.code !== 1003)
|
|
1149
|
+
return false;
|
|
1150
|
+
if (d.pos === undefined)
|
|
1151
|
+
return false;
|
|
1152
|
+
const sf = p.getSourceFile(d.fileName);
|
|
1153
|
+
if (!sf)
|
|
1154
|
+
return false;
|
|
1155
|
+
if (insideBlockComment(sf.text, d.pos))
|
|
1156
|
+
return true;
|
|
1157
|
+
return d.code === 2300 && commentDup.has(`${d.fileName}:${sf.text.slice(d.pos, d.end)}`);
|
|
1158
|
+
};
|
|
1159
|
+
const errorsOf = (p) => {
|
|
1160
|
+
const all = ts.getPreEmitDiagnostics(p);
|
|
1161
|
+
// First pass: every comment-side 2300's (file, name) — the partners
|
|
1162
|
+
// the second pass forgives.
|
|
1163
|
+
const commentDup = new Set();
|
|
1164
|
+
for (const d of all) {
|
|
1165
|
+
if (d.code !== 2300 || d.fileName === undefined || d.pos === undefined || d.end === undefined)
|
|
1166
|
+
continue;
|
|
1167
|
+
if (!isJsSourceFileName(d.fileName))
|
|
1168
|
+
continue;
|
|
1169
|
+
const sf = p.getSourceFile(d.fileName);
|
|
1170
|
+
if (sf && insideBlockComment(sf.text, d.pos))
|
|
1171
|
+
commentDup.add(`${d.fileName}:${sf.text.slice(d.pos, d.end)}`);
|
|
1172
|
+
}
|
|
1173
|
+
return all.filter((d) => d.category === ts.DiagnosticCategory.Error &&
|
|
1174
|
+
!suppressedJsStrictness7(d) &&
|
|
1175
|
+
!npmStaticFileSuppressed(d) &&
|
|
1176
|
+
!nodeModulesJsSuppressed(d) &&
|
|
1177
|
+
!namespaceCalleeSuppressed(p, d) &&
|
|
1178
|
+
!jsdocTypeSuppressed(p, d, commentDup));
|
|
1179
|
+
};
|
|
1180
|
+
// The tsc gate, with a second chance: the LOWERING world's checker
|
|
1181
|
+
// includes the divergence overrides (JSON.parse(): unknown, the Promise
|
|
1182
|
+
// executor shape, ...), which are TIGHTER than the standard lib — a
|
|
1183
|
+
// project that typechecks clean under its own tsc can still error here
|
|
1184
|
+
// (any-typed JSON.parse results, most commonly). Those errors are OURS,
|
|
1185
|
+
// not the project's, so they must not fail preflight: when the lowering
|
|
1186
|
+
// world has errors, the PROJECT world (same program without the override
|
|
1187
|
+
// declarations) is built and consulted. Clean there → preflight passes
|
|
1188
|
+
// and the override-affected sites meet the lowerer's honest fences
|
|
1189
|
+
// instead (the SC1100 checked-cast family). Dirty there → the project-
|
|
1190
|
+
// world errors are the ones reported: they are reproducible with the
|
|
1191
|
+
// project's own tsc, which is what "fix type errors first" asks for.
|
|
1192
|
+
const tscErrors = errorsOf(program);
|
|
1193
|
+
if (tscErrors.length > 0) {
|
|
1194
|
+
for (const d of errorsOf(load.projectWorld()))
|
|
1195
|
+
diags.push(toPassthrough(d));
|
|
1196
|
+
}
|
|
1197
|
+
const ambient = ambientDtsPath();
|
|
1198
|
+
// node_modules JS that no --npm-static opt-in claims is NOT program
|
|
1199
|
+
// source even when maxNodeModuleJsDepth pulled it into the checker's
|
|
1200
|
+
// program (see nodeModulesJsSuppressed above): its execution home is the
|
|
1201
|
+
// island, so preflight's statement walks skip it — no import fences, no
|
|
1202
|
+
// module edges, no statement counts from files the lowering never lowers.
|
|
1203
|
+
const userFiles = program
|
|
1204
|
+
.getSourceFiles()
|
|
1205
|
+
.filter((sf) => sf.fileName !== ambient &&
|
|
1206
|
+
!sf.isDeclarationFile &&
|
|
1207
|
+
!sf.fileName.endsWith(".json") &&
|
|
1208
|
+
(!isNodeModulesPath(sf.fileName) || npmStaticPackageOfPath(sf.fileName) !== null) &&
|
|
1209
|
+
// Workspace-linked shipped JS (see islandJsFile): its execution
|
|
1210
|
+
// home is the island exactly like node_modules JS, so no import
|
|
1211
|
+
// fences, no module edges, no statement counts from it. Its .ts
|
|
1212
|
+
// files (a workspace package imported bare AND reached relatively)
|
|
1213
|
+
// stay program source — only the island-bound JS steps out.
|
|
1214
|
+
!islandJsFile(sf.fileName));
|
|
1215
|
+
const ambientModules = new Set(SUPPORTED_NODE_MODULES);
|
|
1216
|
+
// Ambient `declare module "name"` declarations anywhere in the program —
|
|
1217
|
+
// exact names and `*` patterns — so the import fence can say "type
|
|
1218
|
+
// surface only, no runtime module" instead of the generic package story.
|
|
1219
|
+
// Computed lazily: the scan only runs when a bare-import fence is about
|
|
1220
|
+
// to fire.
|
|
1221
|
+
let ambientModuleDecls = null;
|
|
1222
|
+
const ambientDeclared = (spec) => {
|
|
1223
|
+
if (ambientModuleDecls === null) {
|
|
1224
|
+
ambientModuleDecls = { names: new Set(), patterns: [] };
|
|
1225
|
+
for (const f of program.getSourceFiles()) {
|
|
1226
|
+
for (const s of f.statements) {
|
|
1227
|
+
if (!ts.isModuleDeclaration(s) || !ts.isStringLiteral(s.name))
|
|
1228
|
+
continue;
|
|
1229
|
+
const name = s.name.text;
|
|
1230
|
+
if (name.includes("*"))
|
|
1231
|
+
ambientModuleDecls.patterns.push(name);
|
|
1232
|
+
else
|
|
1233
|
+
ambientModuleDecls.names.add(name);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
if (ambientModuleDecls.names.has(spec))
|
|
1238
|
+
return true;
|
|
1239
|
+
return ambientModuleDecls.patterns.some((p) => {
|
|
1240
|
+
const star = p.indexOf("*");
|
|
1241
|
+
return (spec.length >= p.length - 1 &&
|
|
1242
|
+
spec.startsWith(p.slice(0, star)) &&
|
|
1243
|
+
spec.endsWith(p.slice(star + 1)));
|
|
1244
|
+
});
|
|
1245
|
+
};
|
|
1246
|
+
// Module edges, each carrying its BACK-EDGE admissibility: an edge that
|
|
1247
|
+
// closes a cycle is admissible when nothing can OBSERVE the partially-
|
|
1248
|
+
// initialized module through it — the importing statement binds nothing
|
|
1249
|
+
// (side-effect import) or binds only NAMESPACE objects (initialized at
|
|
1250
|
+
// link, never TDZ) whose every use is a bare expression statement (the
|
|
1251
|
+
// no-op lowering; no member is ever read). Node evaluates such cycles
|
|
1252
|
+
// benignly (the revisited module is a cache hit, and no binding read
|
|
1253
|
+
// can hit TDZ or a stale slot), so the guarded %init calls reproduce
|
|
1254
|
+
// its order exactly. Named/default bindings and `export ... from`
|
|
1255
|
+
// re-exports get the SECOND admission chance when they close a cycle —
|
|
1256
|
+
// the declaration-only init window analysis (see the benign-cycle
|
|
1257
|
+
// admission block above): cycles that fail it keep the SC1016 fence,
|
|
1258
|
+
// because a read through them mid-cycle observes TDZ (let/const) or
|
|
1259
|
+
// undefined (var) in Node — runtime evaluation-order semantics the
|
|
1260
|
+
// static story does not model.
|
|
1261
|
+
const edges = new Map();
|
|
1262
|
+
const refusals = new Map();
|
|
1263
|
+
const depPositions = new Map();
|
|
1264
|
+
for (const sf of userFiles) {
|
|
1265
|
+
const deps = [];
|
|
1266
|
+
edges.set(sf, deps);
|
|
1267
|
+
refusals.set(sf, []);
|
|
1268
|
+
depPositions.set(sf, []);
|
|
1269
|
+
for (const stmt of sf.statements) {
|
|
1270
|
+
if (ts.isImportEqualsDeclaration(stmt)) {
|
|
1271
|
+
// The ENTITY form (`import a = A`, `import x = N.y`) is namespace
|
|
1272
|
+
// alias plumbing — the lowerer resolves references through it and
|
|
1273
|
+
// fences what it cannot honor (lower-namespaces.ts). Only the
|
|
1274
|
+
// require form is a module edge, and its RUNTIME form stays fenced
|
|
1275
|
+
// (under the forced ESNext module world tsc itself rejects it, so
|
|
1276
|
+
// only nested/namespace-internal sites can reach the lowering's
|
|
1277
|
+
// fence); `import type x = require(...)` is pure type surface and
|
|
1278
|
+
// lowers to nothing.
|
|
1279
|
+
if (ts.isExternalModuleReference(stmt.moduleReference) && !stmt.isTypeOnly) {
|
|
1280
|
+
diags.push(unsupportedDiag("SC1013", locOf7(stmt), "import = require(...) assignments"));
|
|
1281
|
+
}
|
|
1282
|
+
continue;
|
|
1283
|
+
}
|
|
1284
|
+
if (ts.isExportAssignment(stmt)) {
|
|
1285
|
+
if (stmt.isExportEquals) {
|
|
1286
|
+
diags.push(unsupportedDiag("SC1012", locOf7(stmt), "export = assignments"));
|
|
1287
|
+
}
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
if (ts.isExportDeclaration(stmt)) {
|
|
1291
|
+
if (stmt.isTypeOnly || erasedTypeOnlyReexport(stmt))
|
|
1292
|
+
continue;
|
|
1293
|
+
if (!stmt.moduleSpecifier)
|
|
1294
|
+
continue;
|
|
1295
|
+
const fromSpec = ts.isStringLiteral(stmt.moduleSpecifier) ? stmt.moduleSpecifier.text : "";
|
|
1296
|
+
if (!isRelativeSpecifier(fromSpec)) {
|
|
1297
|
+
// NAMED re-exports from a SUPPORTED builtin pass (`export { ok }
|
|
1298
|
+
// from "node:assert"` — prettier's universal/assert facade): the
|
|
1299
|
+
// statement binds nothing locally and evaluates nothing (builtins
|
|
1300
|
+
// have no %init), and importers' references chase the alias to
|
|
1301
|
+
// the builtin's own declarations exactly as if they imported the
|
|
1302
|
+
// builtin directly — per-member support stays the lowering's
|
|
1303
|
+
// per-site business. Star and namespace re-exports and package
|
|
1304
|
+
// specifiers keep the fence.
|
|
1305
|
+
if (canonicalBuiltinModule(fromSpec) !== null &&
|
|
1306
|
+
stmt.exportClause !== undefined &&
|
|
1307
|
+
ts.isNamedExports(stmt.exportClause)) {
|
|
1308
|
+
continue;
|
|
1309
|
+
}
|
|
1310
|
+
diags.push(unsupportedDiag("SC1014", locOf7(stmt), "re-exports from packages or builtin modules"));
|
|
1311
|
+
continue;
|
|
1312
|
+
}
|
|
1313
|
+
const reDep = resolveImport7(program, sf, fromSpec);
|
|
1314
|
+
// `export * as ns from "./m"` re-exports the module NAMESPACE
|
|
1315
|
+
// object under a name: importers' `x.ns.member` reads resolve
|
|
1316
|
+
// statically through the same alias machinery as `import * as ns`
|
|
1317
|
+
// (moduleNsSymbolOf follows the chain to the module symbol), so a
|
|
1318
|
+
// RESOLVED user module lowers; everything else keeps the fence —
|
|
1319
|
+
// there is no namespace object to materialize.
|
|
1320
|
+
if (stmt.exportClause && ts.isNamespaceExport(stmt.exportClause)) {
|
|
1321
|
+
if (reDep === null || reDep.fileName.endsWith(".json") || isCjsJsFile7(reDep)) {
|
|
1322
|
+
diags.push(unsupportedDiag("SC1013", locOf7(stmt), "namespace re-exports (export * as ns) of this module form"));
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
}
|
|
1326
|
+
if (reDep && !reDep.fileName.endsWith(".json")) {
|
|
1327
|
+
deps.push({ dep: reDep, backAdmissible: () => false, stmt });
|
|
1328
|
+
depPositions.get(sf).push({ dep: reDep, pos: stmt.getStart(sf) });
|
|
1329
|
+
}
|
|
1330
|
+
continue;
|
|
1331
|
+
}
|
|
1332
|
+
if (!ts.isImportDeclaration(stmt))
|
|
1333
|
+
continue;
|
|
1334
|
+
if (erasedTypeOnlyImport(stmt))
|
|
1335
|
+
continue;
|
|
1336
|
+
// Import-like nodes whose specifier the parse could not shape into a
|
|
1337
|
+
// string literal — the jsdoc `/** @import x = require(...) */`
|
|
1338
|
+
// materialization and `import defer type * as ns` both arrive here
|
|
1339
|
+
// with no specifier text. Neither is a runtime module edge (both are
|
|
1340
|
+
// type-only forms), and where the form is malformed TS the SC0001
|
|
1341
|
+
// passthrough above already names it — never a crash here.
|
|
1342
|
+
const specNode = stmt.moduleSpecifier;
|
|
1343
|
+
if (specNode === undefined || !ts.isStringLiteral(specNode))
|
|
1344
|
+
continue;
|
|
1345
|
+
const spec = specNode.text;
|
|
1346
|
+
const isRelative = isRelativeSpecifier(spec);
|
|
1347
|
+
const isBare = !isRelative && !ambientModules.has(spec);
|
|
1348
|
+
// An import edge Node's own resolution refuses BEFORE any module
|
|
1349
|
+
// evaluates: recorded as a startup-crash candidate (the Node-order
|
|
1350
|
+
// walk below picks the first one Node would report; the program
|
|
1351
|
+
// compiles to exactly that crash — buildMain) with a compile-fence
|
|
1352
|
+
// fallback for candidates the entry never reaches. The bindings of a
|
|
1353
|
+
// refused edge never link, so every clause fence below is skipped —
|
|
1354
|
+
// their uses meet the lowering's own fences if the code demands
|
|
1355
|
+
// values from them.
|
|
1356
|
+
const refuse = (message, className, fallbackFeature) => {
|
|
1357
|
+
refusals.get(sf).push({
|
|
1358
|
+
crash: { message, className, loc: locOf7(stmt) },
|
|
1359
|
+
fallback: unsupportedDiag("SC1010", locOf7(stmt), fallbackFeature),
|
|
1360
|
+
pos: stmt.getStart(sf),
|
|
1361
|
+
});
|
|
1362
|
+
};
|
|
1363
|
+
// "#" specifiers can never name an npm package — they are the
|
|
1364
|
+
// imports-field family, resolved below.
|
|
1365
|
+
const npm = isBare && !spec.startsWith("#") ? resolveNpmImport7(sf.fileName, spec) : null;
|
|
1366
|
+
if (npm && isNodeTypesPath(npm.typesFile)) {
|
|
1367
|
+
diags.push(unsupportedDiag("SC1010", locOf7(stmt), `the '${spec}' module`));
|
|
1368
|
+
continue;
|
|
1369
|
+
}
|
|
1370
|
+
// --npm-static: an opted-in package's import is a PROGRAM-MODULE
|
|
1371
|
+
// edge — its resolution (types stripped) answered the shipped JS,
|
|
1372
|
+
// which allowJs + maxNodeModuleJsDepth pulled into the program, so
|
|
1373
|
+
// the entry joins the module order like any user module and the
|
|
1374
|
+
// clause fences below apply to it as to program JS. A miss (the
|
|
1375
|
+
// resolution still answers declarations, or the file never joined
|
|
1376
|
+
// the program) marks the package an OFFENDER: the frontend's
|
|
1377
|
+
// fallback loop rebuilds without it and the import islands as ever.
|
|
1378
|
+
let npmStaticDep = null;
|
|
1379
|
+
if (npm !== null) {
|
|
1380
|
+
if (isNpmStaticPackage(npm.packageName)) {
|
|
1381
|
+
npmStaticDep = npmStaticProgramDep(program, npm.packageName, npm.typesFile);
|
|
1382
|
+
if (npmStaticDep === null)
|
|
1383
|
+
continue;
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
// Types resolved, but Node's RUNTIME resolution can still refuse
|
|
1387
|
+
// the edge (a types-only package whose exports target ships no JS,
|
|
1388
|
+
// most commonly): that program is Node's startup crash, not an
|
|
1389
|
+
// island candidate — there is nothing to execute anywhere.
|
|
1390
|
+
const refusal = probeNodeImportRefusal(sf.fileName, spec);
|
|
1391
|
+
if (refusal !== null) {
|
|
1392
|
+
refuse(refusal.message, "%Error", `the '${spec}' package (its runtime resolution fails: ${refusal.message})`);
|
|
1393
|
+
}
|
|
1394
|
+
continue;
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
// PROJECT imports: package.json-mediated specifiers that resolve to
|
|
1398
|
+
// the program's own sources — `#alias` (the imports field) and
|
|
1399
|
+
// self-name references (the nearest package.json's name through its
|
|
1400
|
+
// exports). A source answer is an ordinary user-module edge, exactly
|
|
1401
|
+
// like a relative import (the checker resolved the bindings the same
|
|
1402
|
+
// way); a refused resolution is Node's startup crash with Node's
|
|
1403
|
+
// exact message; what keeps a compile fence: the unsupported
|
|
1404
|
+
// builtin and the types-only resolution (Node-hostable or ambiguous
|
|
1405
|
+
// — scriptc's own limitations, named as such).
|
|
1406
|
+
let projDep = null;
|
|
1407
|
+
if (isBare && npmStaticDep === null) {
|
|
1408
|
+
const resolved = resolveProjectImport(sf.fileName, spec);
|
|
1409
|
+
projDep = resolved !== null ? (program.getSourceFile(resolved) ?? null) : null;
|
|
1410
|
+
if (projDep !== null && projDep.isDeclarationFile) {
|
|
1411
|
+
diags.push(unsupportedDiag("SC1010", locOf7(stmt), `the '${spec}' import (it resolves only to type declarations — there is no runtime implementation to compile)`));
|
|
1412
|
+
continue;
|
|
1413
|
+
}
|
|
1414
|
+
if (projDep === null) {
|
|
1415
|
+
const nodeBuiltin = spec.startsWith("node:") || nodeBuiltinNames.has(spec);
|
|
1416
|
+
if (nodeBuiltin) {
|
|
1417
|
+
diags.push(unsupportedDiag("SC1010", locOf7(stmt), `the '${spec}' module`));
|
|
1418
|
+
continue;
|
|
1419
|
+
}
|
|
1420
|
+
if (spec === "#") {
|
|
1421
|
+
refuse(`Invalid module "#" is not a valid internal imports specifier name imported from ${sf.fileName}`, "%TypeError", `the '#' import (Node rejects the bare '#' specifier as invalid)`);
|
|
1422
|
+
continue;
|
|
1423
|
+
}
|
|
1424
|
+
if (spec.startsWith("#")) {
|
|
1425
|
+
const pkgJson = nearestPkgJsonPath(sf.fileName);
|
|
1426
|
+
refuse(`Package import specifier "${spec}" is not defined${pkgJson !== null ? ` in package ${pkgJson}` : ""} imported from ${sf.fileName}`, "%TypeError", `the '${spec}' import (no package.json imports-field entry maps it)`);
|
|
1427
|
+
continue;
|
|
1428
|
+
}
|
|
1429
|
+
const refusal = probeNodeImportRefusal(sf.fileName, spec);
|
|
1430
|
+
if (refusal !== null) {
|
|
1431
|
+
const ambientNote = ambientDeclared(spec)
|
|
1432
|
+
? `the '${spec}' module (it exists only as an ambient 'declare module' type surface — there is no runtime module to import)`
|
|
1433
|
+
: `the '${spec}' package (nothing installed resolves it)`;
|
|
1434
|
+
refuse(refusal.message, "%Error", ambientNote);
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
// Runtime-resolvable (or a shape the probe stays conservative
|
|
1438
|
+
// about) with no compilable types answer: scriptc's fence.
|
|
1439
|
+
diags.push(unsupportedDiag("SC1010", locOf7(stmt), ambientDeclared(spec)
|
|
1440
|
+
? `the '${spec}' module (it exists only as an ambient 'declare module' type surface — there is no runtime module to import)`
|
|
1441
|
+
: `the '${spec}' package (nothing installed resolves it)`));
|
|
1442
|
+
continue;
|
|
1443
|
+
}
|
|
1444
|
+
}
|
|
1445
|
+
const clause = stmt.importClause;
|
|
1446
|
+
const dep = isRelative ? resolveImport7(program, sf, spec) : (npmStaticDep ?? projDep);
|
|
1447
|
+
const isJson = dep !== null && dep.fileName.endsWith(".json");
|
|
1448
|
+
// SELF-imports: the bindings alias this module's OWN declarations,
|
|
1449
|
+
// so a TOP-LEVEL read lexically above the aliased declaration is
|
|
1450
|
+
// Node's TDZ ReferenceError at runtime (exactly what tsc's TS2448
|
|
1451
|
+
// refuses for the DIRECT spelling — imports are exempt from that
|
|
1452
|
+
// check, so the fence restores the same bar). Hoisted function
|
|
1453
|
+
// aliases initialize at link and read fine anywhere; reads inside
|
|
1454
|
+
// function bodies keep the direct spelling's existing policy.
|
|
1455
|
+
if (dep === sf && clause !== undefined) {
|
|
1456
|
+
selfImportTdzFences7(program, sf, clause, diags);
|
|
1457
|
+
}
|
|
1458
|
+
if (clause) {
|
|
1459
|
+
// Default imports of SUPPORTED builtins pass where the spelling is
|
|
1460
|
+
// legal: Node's default export of a CJS builtin IS the module
|
|
1461
|
+
// object, so the binding exposes exactly the namespace-import
|
|
1462
|
+
// surface and the lowering keys the same tables
|
|
1463
|
+
// (builtinNamespaceModuleOf's default-import twin). JS sources
|
|
1464
|
+
// always (Node never asks for interop flags); TS sources when the
|
|
1465
|
+
// adopted interop knobs made the checker accept the spelling
|
|
1466
|
+
// (vercel's `import os from 'os'` under esModuleInterop — the
|
|
1467
|
+
// program TYPECHECKED, so the form is the project's own legal
|
|
1468
|
+
// dialect). A TS project without interop flags keeps the fence:
|
|
1469
|
+
// the SC1012 wording beats the raw TS1259 at the same site. The
|
|
1470
|
+
// callable module objects (assert, events, test) stay allowed
|
|
1471
|
+
// everywhere.
|
|
1472
|
+
const opts = program.getCompilerOptions();
|
|
1473
|
+
const interopOn = opts.esModuleInterop === true || opts.allowSyntheticDefaultImports === true;
|
|
1474
|
+
const defaultOk = builtinDefaultImportModule(spec) !== null ||
|
|
1475
|
+
((isJsSourceFileName(sf.fileName) || interopOn) && canonicalBuiltinModule(spec) !== null);
|
|
1476
|
+
if (clause.name && !isJson && dep === null && !defaultOk) {
|
|
1477
|
+
diags.push(unsupportedDiag("SC1012", locOf7(clause.name)));
|
|
1478
|
+
}
|
|
1479
|
+
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings) && isJson) {
|
|
1480
|
+
diags.push(unsupportedDiag("SC1012", locOf7(clause.namedBindings), "named imports from JSON modules"));
|
|
1481
|
+
}
|
|
1482
|
+
if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
|
|
1483
|
+
// `import * as ns from "./m"` of a RESOLVED user module lowers:
|
|
1484
|
+
// ns.member accesses resolve statically through the checker to
|
|
1485
|
+
// the exporter's own registrations (lower-namespaces.ts's
|
|
1486
|
+
// module-namespace paths); the ns OBJECT as a first-class value
|
|
1487
|
+
// fences per site. What keeps a fence here: JSON modules (their
|
|
1488
|
+
// namespace object has no static story — default imports bake
|
|
1489
|
+
// the data), CommonJS JS modules (Node builds their namespace
|
|
1490
|
+
// from its lexer's static analysis, a different surface),
|
|
1491
|
+
// and unresolved specifiers. Supported builtins pass through as
|
|
1492
|
+
// before (their namespace members are the declared surface).
|
|
1493
|
+
if (isJson) {
|
|
1494
|
+
diags.push(unsupportedDiag("SC1013", locOf7(clause.namedBindings), "namespace imports of JSON modules"));
|
|
1495
|
+
}
|
|
1496
|
+
else if (dep !== null && isCjsJsFile7(dep)) {
|
|
1497
|
+
// A CJS namespace binding whose every use is a bare expression
|
|
1498
|
+
// statement (`cjs;` — the corpus's "the import linked"
|
|
1499
|
+
// assertion) carries no surface question: the statement lowers
|
|
1500
|
+
// to Node's no-op, the module still evaluates through its
|
|
1501
|
+
// ordinary init edge, and no member is ever read through the
|
|
1502
|
+
// lexer facade (self-imports of a CJS module included — the
|
|
1503
|
+
// partially-built exports are never observed). Any OTHER use
|
|
1504
|
+
// keeps the fence.
|
|
1505
|
+
if (!nsBindingUsesAreBareStatements7(program, sf, clause.namedBindings.name)) {
|
|
1506
|
+
diags.push(unsupportedDiag("SC1013", locOf7(clause.namedBindings), "namespace imports of CommonJS modules"));
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
else if (dep === null && !(!isRelative && ambientModules.has(spec))) {
|
|
1510
|
+
diags.push(unsupportedDiag("SC1013", locOf7(clause.namedBindings)));
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
if (dep && !isJson) {
|
|
1515
|
+
// Back-edge admissibility (see the edges declaration): no clause,
|
|
1516
|
+
// or namespace-only bindings whose uses are all bare statements.
|
|
1517
|
+
// Lazy — the file scan only runs if this edge ever closes a cycle.
|
|
1518
|
+
const importClause = clause;
|
|
1519
|
+
const importSf = sf;
|
|
1520
|
+
deps.push({
|
|
1521
|
+
dep,
|
|
1522
|
+
backAdmissible: () => importClause === undefined ||
|
|
1523
|
+
(importClause.name === undefined &&
|
|
1524
|
+
(importClause.namedBindings === undefined ||
|
|
1525
|
+
(ts.isNamespaceImport(importClause.namedBindings) &&
|
|
1526
|
+
nsBindingUsesAreBareStatements7(program, importSf, importClause.namedBindings.name)))),
|
|
1527
|
+
stmt,
|
|
1528
|
+
});
|
|
1529
|
+
depPositions.get(sf).push({ dep, pos: stmt.getStart(sf) });
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
if (isJsSourceFileName(sf.fileName)) {
|
|
1533
|
+
const stmts = sf.statements;
|
|
1534
|
+
let firstRunnable = -1;
|
|
1535
|
+
stmts.forEach((s, i) => {
|
|
1536
|
+
if (firstRunnable < 0 && !purePrefixStmt7(s))
|
|
1537
|
+
firstRunnable = i;
|
|
1538
|
+
});
|
|
1539
|
+
for (let k = 0; k < stmts.length; k++) {
|
|
1540
|
+
const stmt = stmts[k];
|
|
1541
|
+
for (const req of requiresOf7(stmt)) {
|
|
1542
|
+
const loc = { file: sf.fileName, start: req.node.getStart(sf), end: req.node.getEnd() };
|
|
1543
|
+
if (isNodeEsmFile7(sf)) {
|
|
1544
|
+
diags.push(unsupportedDiag("SC1013", loc, "require() in an ES module (Node throws ReferenceError — use import)"));
|
|
1545
|
+
continue;
|
|
1546
|
+
}
|
|
1547
|
+
if (req.decl && ts.isArrayBindingPattern(req.decl.name)) {
|
|
1548
|
+
diags.push(unsupportedDiag("SC1012", loc, "array-destructuring require() bindings"));
|
|
1549
|
+
continue;
|
|
1550
|
+
}
|
|
1551
|
+
if (req.decl && ts.isObjectBindingPattern(req.decl.name)) {
|
|
1552
|
+
const bad = req.decl.name.elements.find((e) => e.name === undefined || !ts.isIdentifier(e.name) || e.initializer !== undefined || e.dotDotDotToken !== undefined);
|
|
1553
|
+
if (bad) {
|
|
1554
|
+
diags.push(unsupportedDiag("SC1012", loc, "require() destructuring with defaults, rest, or nested patterns"));
|
|
1555
|
+
continue;
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
const isRelative = isRelativeSpecifier(req.spec);
|
|
1559
|
+
if (!isRelative) {
|
|
1560
|
+
if (canonicalBuiltinModule(req.spec) === null &&
|
|
1561
|
+
!processModuleAliasRequire7(req.spec, req.decl)) {
|
|
1562
|
+
// A binding-less require of a package NOTHING resolves:
|
|
1563
|
+
// Node throws MODULE_NOT_FOUND at the require site — the
|
|
1564
|
+
// lowering compiles exactly that catchable throw (the
|
|
1565
|
+
// optional-dependency try/require pattern), so no fence.
|
|
1566
|
+
// Binding forms keep the fence: their downstream reads
|
|
1567
|
+
// would need the module that never loads.
|
|
1568
|
+
if (req.decl !== null || probeNodeRequireRefusal(sf.fileName, req.spec) === null) {
|
|
1569
|
+
diags.push(unsupportedDiag("SC1010", loc, `the '${req.spec}' module`));
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
continue;
|
|
1573
|
+
}
|
|
1574
|
+
const dep = resolveImport7(program, sf, req.spec);
|
|
1575
|
+
if (dep && dep.fileName.endsWith(".json")) {
|
|
1576
|
+
diags.push(unsupportedDiag("SC1012", loc, "require() of JSON modules"));
|
|
1577
|
+
continue;
|
|
1578
|
+
}
|
|
1579
|
+
const tdzName = firstRunnable >= 0 && firstRunnable < k && req.decl
|
|
1580
|
+
? requireTdzRisk7(program, sf, k, req.decl)
|
|
1581
|
+
: null;
|
|
1582
|
+
if (tdzName !== null) {
|
|
1583
|
+
diags.push(unsupportedDiag("SC1013", loc, `require() below code that can reach its binding '${tdzName}' (Node initializes the binding AT the require and throws ReferenceError on any earlier access, which is not modeled — move the require above the code that can run first)`));
|
|
1584
|
+
continue;
|
|
1585
|
+
}
|
|
1586
|
+
if (dep)
|
|
1587
|
+
deps.push({ dep, backAdmissible: () => false });
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1590
|
+
if (!ts.isExternalModule(sf)) {
|
|
1591
|
+
for (const call of nestedBareRequiresOf7(sf)) {
|
|
1592
|
+
const spec = requireSpecOf7(call);
|
|
1593
|
+
const loc = { file: sf.fileName, start: call.getStart(sf), end: call.getEnd() };
|
|
1594
|
+
if (!isRelativeSpecifier(spec)) {
|
|
1595
|
+
if (canonicalBuiltinModule(spec) === null && !processModuleAliasRequire7(spec, null)) {
|
|
1596
|
+
// Binding-less by construction — same require-site throw
|
|
1597
|
+
// channel as the statement-level form above.
|
|
1598
|
+
if (probeNodeRequireRefusal(sf.fileName, spec) === null) {
|
|
1599
|
+
diags.push(unsupportedDiag("SC1010", loc, `the '${spec}' module`));
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
const dep = resolveImport7(program, sf, spec);
|
|
1605
|
+
if (dep && dep.fileName.endsWith(".json")) {
|
|
1606
|
+
diags.push(unsupportedDiag("SC1012", loc, "require() of JSON modules"));
|
|
1607
|
+
continue;
|
|
1608
|
+
}
|
|
1609
|
+
if (dep)
|
|
1610
|
+
deps.push({ dep, backAdmissible: () => false });
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
// Node's evaluation order: depth-first postorder from the entry, each
|
|
1616
|
+
// module once, back-edges answered from the cache (a module already
|
|
1617
|
+
// evaluating is skipped) — which the guarded %init calls reproduce
|
|
1618
|
+
// exactly. Cycles are admitted only where Node's partially-initialized
|
|
1619
|
+
// semantics are UNOBSERVABLE (JS's partial-initialization is otherwise a
|
|
1620
|
+
// silent-misbehavior trap, so SC1016 stays for the rest):
|
|
1621
|
+
// - a DIRECT SELF-IMPORT (the package self-name reference resolving to
|
|
1622
|
+
// the importing module itself — Node's self-reference rule): the
|
|
1623
|
+
// imported bindings alias the module's OWN top-level bindings with
|
|
1624
|
+
// identical timing, evaluation happens once — always benign;
|
|
1625
|
+
// - a longer cycle whose closing edge is backAdmissible (see the edges
|
|
1626
|
+
// declaration: nothing binds, or only namespace objects bound to
|
|
1627
|
+
// bare-statement uses — no read can hit TDZ or a stale slot);
|
|
1628
|
+
// - a cycle passing the DECLARATION-ONLY INIT WINDOW analysis (the
|
|
1629
|
+
// benign-cycle admission block): every module of the cycle's
|
|
1630
|
+
// strongly-connected component has an inert top level and the
|
|
1631
|
+
// closing edge's bindings are used only in deferred positions, so no
|
|
1632
|
+
// read can execute before every member initialized.
|
|
1633
|
+
// The cycle cluster (SCC) memo: computed once, on the first back edge
|
|
1634
|
+
// the cheap per-edge rule does not admit. Tarjan over the same edges
|
|
1635
|
+
// the order walk uses (self-edges skipped — Node's self-reference rule
|
|
1636
|
+
// handles those above).
|
|
1637
|
+
let sccOf = null;
|
|
1638
|
+
const computeSccs = () => {
|
|
1639
|
+
const out = new Map();
|
|
1640
|
+
const index = new Map();
|
|
1641
|
+
const low = new Map();
|
|
1642
|
+
const onStack = new Set();
|
|
1643
|
+
const tstack = [];
|
|
1644
|
+
let next = 0;
|
|
1645
|
+
const strongconnect = (v) => {
|
|
1646
|
+
index.set(v, next);
|
|
1647
|
+
low.set(v, next);
|
|
1648
|
+
next++;
|
|
1649
|
+
tstack.push(v);
|
|
1650
|
+
onStack.add(v);
|
|
1651
|
+
for (const e of edges.get(v) ?? []) {
|
|
1652
|
+
const w = e.dep;
|
|
1653
|
+
if (w === v)
|
|
1654
|
+
continue;
|
|
1655
|
+
if (!index.has(w)) {
|
|
1656
|
+
strongconnect(w);
|
|
1657
|
+
low.set(v, Math.min(low.get(v), low.get(w)));
|
|
1658
|
+
}
|
|
1659
|
+
else if (onStack.has(w)) {
|
|
1660
|
+
low.set(v, Math.min(low.get(v), index.get(w)));
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
if (low.get(v) === index.get(v)) {
|
|
1664
|
+
const comp = [];
|
|
1665
|
+
for (;;) {
|
|
1666
|
+
const w = tstack.pop();
|
|
1667
|
+
onStack.delete(w);
|
|
1668
|
+
comp.push(w);
|
|
1669
|
+
if (w === v)
|
|
1670
|
+
break;
|
|
1671
|
+
}
|
|
1672
|
+
for (const w of comp)
|
|
1673
|
+
out.set(w, comp);
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
for (const sf of edges.keys())
|
|
1677
|
+
if (!index.has(sf))
|
|
1678
|
+
strongconnect(sf);
|
|
1679
|
+
return out;
|
|
1680
|
+
};
|
|
1681
|
+
const lineOf = (node) => {
|
|
1682
|
+
const nsf = node.getSourceFile();
|
|
1683
|
+
return `${nsf.fileName}:${ts.getLineAndCharacterOfPosition(nsf, node.getStart(nsf)).line + 1}`;
|
|
1684
|
+
};
|
|
1685
|
+
// Cluster verdict memo (keyed by the component array identity): the
|
|
1686
|
+
// reason the cluster's cycles stay fenced, or null when its every
|
|
1687
|
+
// member passes the inert-top-level bar.
|
|
1688
|
+
const sccVerdict = new Map();
|
|
1689
|
+
const cycleAdmissionReason = (importer, e) => {
|
|
1690
|
+
if (e.stmt === undefined)
|
|
1691
|
+
return "the cycle closes through a require() edge";
|
|
1692
|
+
sccOf ??= computeSccs();
|
|
1693
|
+
const comp = sccOf.get(e.dep);
|
|
1694
|
+
if (comp === undefined || comp.length < 2 || !comp.includes(importer)) {
|
|
1695
|
+
return "the cycle's module cluster could not be analyzed";
|
|
1696
|
+
}
|
|
1697
|
+
if (!sccVerdict.has(comp)) {
|
|
1698
|
+
let reason = null;
|
|
1699
|
+
for (const m of comp) {
|
|
1700
|
+
if (isCjsJsFile7(m)) {
|
|
1701
|
+
reason = `${m.fileName} is a CommonJS module — admission covers ES-module cycles only`;
|
|
1702
|
+
break;
|
|
1703
|
+
}
|
|
1704
|
+
const off = nonInertTopLevel7(program, m);
|
|
1705
|
+
if (off !== null) {
|
|
1706
|
+
reason = `top-level code at ${lineOf(off)} can run user code during the cycle's init window — only declaration-only module bodies are admitted`;
|
|
1707
|
+
break;
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
sccVerdict.set(comp, reason);
|
|
1711
|
+
}
|
|
1712
|
+
const clusterReason = sccVerdict.get(comp);
|
|
1713
|
+
if (clusterReason !== null)
|
|
1714
|
+
return clusterReason;
|
|
1715
|
+
const use = backEdgeUseOffence7(program, importer, e.stmt);
|
|
1716
|
+
if (use !== null) {
|
|
1717
|
+
return `the cycle-crossing binding '${use.name}' is read at ${lineOf(use.node)}, outside any function body — a read during the init window observes the partially-initialized module (Node's TDZ ReferenceError / stale var), which is not modeled`;
|
|
1718
|
+
}
|
|
1719
|
+
return null;
|
|
1720
|
+
};
|
|
1721
|
+
const order = [];
|
|
1722
|
+
const state = new Map();
|
|
1723
|
+
const stack = [];
|
|
1724
|
+
const visit = (sf) => {
|
|
1725
|
+
state.set(sf, "visiting");
|
|
1726
|
+
stack.push(sf.fileName);
|
|
1727
|
+
for (const e of edges.get(sf) ?? []) {
|
|
1728
|
+
if (e.dep === sf)
|
|
1729
|
+
continue; // self-import: a Node cache hit, not a cycle
|
|
1730
|
+
const s = state.get(e.dep);
|
|
1731
|
+
if (s === "done")
|
|
1732
|
+
continue;
|
|
1733
|
+
if (s === "visiting") {
|
|
1734
|
+
// A back-edge: Node answers it from the cache (the module is
|
|
1735
|
+
// already evaluating) — benign exactly when nothing can observe
|
|
1736
|
+
// the partial initialization through this edge's bindings, by the
|
|
1737
|
+
// cheap per-edge rule or the declaration-only init window rule.
|
|
1738
|
+
if (!e.backAdmissible()) {
|
|
1739
|
+
const reason = cycleAdmissionReason(sf, e);
|
|
1740
|
+
if (reason !== null) {
|
|
1741
|
+
const cycleStart = stack.indexOf(e.dep.fileName);
|
|
1742
|
+
const cycle = [...stack.slice(cycleStart), e.dep.fileName].join(" → ");
|
|
1743
|
+
diags.push(unsupportedDiag("SC1016", { file: e.dep.fileName, start: 0, end: 0 }, `circular imports (${cycle}; ${reason})`));
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
continue;
|
|
1747
|
+
}
|
|
1748
|
+
visit(e.dep);
|
|
1749
|
+
}
|
|
1750
|
+
stack.pop();
|
|
1751
|
+
state.set(sf, "done");
|
|
1752
|
+
order.push(sf);
|
|
1753
|
+
};
|
|
1754
|
+
visit(entry);
|
|
1755
|
+
// The startup crash Node's RESOLUTION phase reports: modules resolve
|
|
1756
|
+
// their request specifiers in source order, children linking depth-first
|
|
1757
|
+
// as each resolves (ModuleJob.syncLink) — a preorder walk over the
|
|
1758
|
+
// resolved edges, statement-interleaved, so the first refusal found here
|
|
1759
|
+
// is the one Node reports. Refusal candidates in modules the entry never
|
|
1760
|
+
// reaches keep their compile-fence fallback instead (Node never resolves
|
|
1761
|
+
// them). A found resolution crash wins over the CJS link check below:
|
|
1762
|
+
// Node's fetch/resolve phase runs before instantiate.
|
|
1763
|
+
let resolveCrash = null;
|
|
1764
|
+
{
|
|
1765
|
+
const seen = new Set();
|
|
1766
|
+
const walk = (sf) => {
|
|
1767
|
+
if (seen.has(sf))
|
|
1768
|
+
return null;
|
|
1769
|
+
seen.add(sf);
|
|
1770
|
+
const steps = [
|
|
1771
|
+
...(depPositions.get(sf) ?? []),
|
|
1772
|
+
...(refusals.get(sf) ?? []).map((r) => ({ pos: r.pos, crash: r.crash })),
|
|
1773
|
+
].sort((a, b) => a.pos - b.pos);
|
|
1774
|
+
for (const step of steps) {
|
|
1775
|
+
if (step.crash !== undefined)
|
|
1776
|
+
return step.crash;
|
|
1777
|
+
if (step.dep !== undefined && step.dep !== sf) {
|
|
1778
|
+
const hit = walk(step.dep);
|
|
1779
|
+
if (hit !== null)
|
|
1780
|
+
return hit;
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
return null;
|
|
1784
|
+
};
|
|
1785
|
+
resolveCrash = walk(entry);
|
|
1786
|
+
// The crash channel engages only for programs that OTHERWISE pass
|
|
1787
|
+
// preflight: when any other diagnostic already fails the build, every
|
|
1788
|
+
// refusal keeps its compile-fence fallback instead — the report names
|
|
1789
|
+
// each import's story rather than silently dropping edges whose crash
|
|
1790
|
+
// will never compile. (No reachable refusal → same: fallbacks only.)
|
|
1791
|
+
if (resolveCrash === null || diags.length > 0) {
|
|
1792
|
+
resolveCrash = null;
|
|
1793
|
+
for (const cands of refusals.values()) {
|
|
1794
|
+
for (const c of cands)
|
|
1795
|
+
diags.push(c.fallback);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
const linkCrash = resolveCrash !== null ? null : cjsNamedImportLinkCheck(program, entry, order, diags);
|
|
1800
|
+
return { diags, moduleOrder: order, startupCrash: resolveCrash ?? linkCrash };
|
|
1801
|
+
}
|
|
1802
|
+
/** Walks the entry's ESM instantiate graph exactly as V8 does — children
|
|
1803
|
+
* in request order, each module's own imports checked AFTER its children
|
|
1804
|
+
* (postorder), regular imports sorted by LOCAL name (code units), then
|
|
1805
|
+
* `export ... from` re-exports in source order — and answers the first
|
|
1806
|
+
* named import of a lexer-INVISIBLE CommonJS export as Node's link-time
|
|
1807
|
+
* SyntaxError (the caller compiles the program to that startup crash).
|
|
1808
|
+
* ESM modules OUTSIDE the instantiate graph (reached only through
|
|
1809
|
+
* require() of an ES module from CommonJS) instantiate mid-evaluation in
|
|
1810
|
+
* Node, where the SyntaxError interleaves with output already produced —
|
|
1811
|
+
* not modeled: those get a pointed fence instead. */
|
|
1812
|
+
function cjsNamedImportLinkCheck(program, entry, moduleOrder, diags) {
|
|
1813
|
+
const lexMemo = new Map();
|
|
1814
|
+
const isRelative = isRelativeSpecifier;
|
|
1815
|
+
// Relative specifiers resolve as ever; PROJECT imports (#alias/self-name
|
|
1816
|
+
// — the same package.json-mediated edges preflight admits) join them so
|
|
1817
|
+
// a named import THROUGH one of a CommonJS module keeps Node's lexer
|
|
1818
|
+
// check. Builtin/npm targets contribute nothing here — except opted-in
|
|
1819
|
+
// --npm-static packages, whose CJS entries face Node's lexer exactly
|
|
1820
|
+
// like program CJS files (their JS IS the program now).
|
|
1821
|
+
const resolveEdge = (from, spec) => {
|
|
1822
|
+
if (isRelative(spec))
|
|
1823
|
+
return resolveImport7(program, from, spec);
|
|
1824
|
+
const npmStatic = npmStaticDepSf7(program, from, spec);
|
|
1825
|
+
if (npmStatic !== null)
|
|
1826
|
+
return npmStatic;
|
|
1827
|
+
const p = resolveProjectImport(from.fileName, spec);
|
|
1828
|
+
return p !== null ? (program.getSourceFile(p) ?? null) : null;
|
|
1829
|
+
};
|
|
1830
|
+
// Reexport targets union in only when they resolve to CommonJS program
|
|
1831
|
+
// files (Node's cjsPreparseModuleExports rule).
|
|
1832
|
+
const resolveCjsDep = (from, spec) => {
|
|
1833
|
+
const dep = resolveEdge(from, spec);
|
|
1834
|
+
return dep !== null && isCjsJsFile7(dep) ? dep : null;
|
|
1835
|
+
};
|
|
1836
|
+
const visible = (dep, name) =>
|
|
1837
|
+
// `default` is the module.exports binding itself — always provided.
|
|
1838
|
+
// (cjs-lexer.ts lexes SOURCE TEXT — only strings cross into the
|
|
1839
|
+
// typescript5 island; the SourceFile is just the memo/resolve handle.)
|
|
1840
|
+
name === "default" || cjsLexerVisibleNames(dep, (d) => d.text, resolveCjsDep, lexMemo).has(name);
|
|
1841
|
+
/** The statement's resolved LOCAL CommonJS dependency, when it is an
|
|
1842
|
+
* import/re-export from one. */
|
|
1843
|
+
const cjsDepOf = (sf, spec) => {
|
|
1844
|
+
const dep = resolveEdge(sf, spec);
|
|
1845
|
+
return dep !== null && isCjsJsFile7(dep) ? dep : null;
|
|
1846
|
+
};
|
|
1847
|
+
/** The first lexer-invisible CJS name request of ONE module, in V8's
|
|
1848
|
+
* check order: all regular named imports sorted by local binding name,
|
|
1849
|
+
* then `export { x } from` elements in source order. */
|
|
1850
|
+
const firstInvisibleOf = (sf) => {
|
|
1851
|
+
const entries = [];
|
|
1852
|
+
for (const stmt of sf.statements) {
|
|
1853
|
+
if (!ts.isImportDeclaration(stmt) || stmt.moduleSpecifier === undefined || !ts.isStringLiteral(stmt.moduleSpecifier))
|
|
1854
|
+
continue;
|
|
1855
|
+
const clause = stmt.importClause;
|
|
1856
|
+
if (!clause || clause.phaseModifier === ts.SyntaxKind.TypeKeyword)
|
|
1857
|
+
continue;
|
|
1858
|
+
if (!clause.namedBindings || !ts.isNamedImports(clause.namedBindings))
|
|
1859
|
+
continue;
|
|
1860
|
+
const spec = stmt.moduleSpecifier.text;
|
|
1861
|
+
const dep = cjsDepOf(sf, spec);
|
|
1862
|
+
if (dep === null)
|
|
1863
|
+
continue;
|
|
1864
|
+
for (const e of clause.namedBindings.elements) {
|
|
1865
|
+
if (e.isTypeOnly)
|
|
1866
|
+
continue;
|
|
1867
|
+
const nameNode = e.propertyName ?? e.name;
|
|
1868
|
+
entries.push({ local: e.name.text, exportName: nameNode.text, nameNode, spec, dep });
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
entries.sort((a, b) => (a.local < b.local ? -1 : a.local > b.local ? 1 : 0));
|
|
1872
|
+
for (const e of entries) {
|
|
1873
|
+
if (!visible(e.dep, e.exportName))
|
|
1874
|
+
return { exportName: e.exportName, spec: e.spec, nameNode: e.nameNode, sf };
|
|
1875
|
+
}
|
|
1876
|
+
for (const stmt of sf.statements) {
|
|
1877
|
+
if (!ts.isExportDeclaration(stmt) || stmt.isTypeOnly)
|
|
1878
|
+
continue;
|
|
1879
|
+
if (stmt.moduleSpecifier === undefined || !ts.isStringLiteral(stmt.moduleSpecifier))
|
|
1880
|
+
continue;
|
|
1881
|
+
if (!stmt.exportClause || !ts.isNamedExports(stmt.exportClause))
|
|
1882
|
+
continue;
|
|
1883
|
+
const spec = stmt.moduleSpecifier.text;
|
|
1884
|
+
const dep = cjsDepOf(sf, spec);
|
|
1885
|
+
if (dep === null)
|
|
1886
|
+
continue;
|
|
1887
|
+
for (const e of stmt.exportClause.elements) {
|
|
1888
|
+
if (e.isTypeOnly)
|
|
1889
|
+
continue;
|
|
1890
|
+
const nameNode = e.propertyName ?? e.name;
|
|
1891
|
+
if (!visible(dep, nameNode.text))
|
|
1892
|
+
return { exportName: nameNode.text, spec, nameNode, sf };
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return null;
|
|
1896
|
+
};
|
|
1897
|
+
// The instantiate graph: from an ESM entry, every import/re-export
|
|
1898
|
+
// request in source order, recursing into ESM dependencies only (CJS
|
|
1899
|
+
// and JSON modules are leaves — their own requires never join V8's
|
|
1900
|
+
// linking). A CommonJS ENTRY has no instantiate phase at all.
|
|
1901
|
+
const visited = new Set();
|
|
1902
|
+
const dfs = (sf) => {
|
|
1903
|
+
if (visited.has(sf))
|
|
1904
|
+
return null;
|
|
1905
|
+
visited.add(sf);
|
|
1906
|
+
for (const stmt of sf.statements) {
|
|
1907
|
+
if (!ts.isImportDeclaration(stmt) && !(ts.isExportDeclaration(stmt) && !stmt.isTypeOnly))
|
|
1908
|
+
continue;
|
|
1909
|
+
if (ts.isImportDeclaration(stmt) && stmt.importClause?.phaseModifier === ts.SyntaxKind.TypeKeyword)
|
|
1910
|
+
continue;
|
|
1911
|
+
const specNode = stmt.moduleSpecifier;
|
|
1912
|
+
if (specNode === undefined || !ts.isStringLiteral(specNode))
|
|
1913
|
+
continue;
|
|
1914
|
+
const spec = specNode.text;
|
|
1915
|
+
const dep = resolveEdge(sf, spec);
|
|
1916
|
+
if (dep === null || dep.fileName.endsWith(".json") || !isNodeEsmFile7(dep))
|
|
1917
|
+
continue;
|
|
1918
|
+
const bad = dfs(dep);
|
|
1919
|
+
if (bad !== null)
|
|
1920
|
+
return bad;
|
|
1921
|
+
}
|
|
1922
|
+
return firstInvisibleOf(sf);
|
|
1923
|
+
};
|
|
1924
|
+
const bad = isNodeEsmFile7(entry) ? dfs(entry) : null;
|
|
1925
|
+
// ESM modules only require() reaches: same invisibility, but Node's
|
|
1926
|
+
// SyntaxError fires mid-evaluation at the require — a pointed fence
|
|
1927
|
+
// instead of a mismodeled crash position.
|
|
1928
|
+
for (const sf of moduleOrder) {
|
|
1929
|
+
if (!isNodeEsmFile7(sf) || visited.has(sf))
|
|
1930
|
+
continue;
|
|
1931
|
+
const b = firstInvisibleOf(sf);
|
|
1932
|
+
if (b !== null) {
|
|
1933
|
+
diags.push(unsupportedDiag("SC1013", locOf7(b.nameNode), `a named import of a CommonJS export Node's lexer cannot detect, reached through require() of an ES module (Node throws its SyntaxError mid-evaluation at that require; import the CommonJS module's default and read members off it instead)`));
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
if (bad === null)
|
|
1937
|
+
return null;
|
|
1938
|
+
// Node's message: V8's generic missing-export SyntaxError, rewritten to
|
|
1939
|
+
// the CommonJS hint form only when the failing SPECIFIER STRING is also
|
|
1940
|
+
// one of the ENTRY module's own requests and the entry's resolution of
|
|
1941
|
+
// it is CommonJS (module_job.js consults the ROOT job's moduleRequests/
|
|
1942
|
+
// commonJsDeps — a deep module's failure keeps the generic wording).
|
|
1943
|
+
let flavored = false;
|
|
1944
|
+
for (const stmt of entry.statements) {
|
|
1945
|
+
if (!ts.isImportDeclaration(stmt) && !(ts.isExportDeclaration(stmt) && !stmt.isTypeOnly))
|
|
1946
|
+
continue;
|
|
1947
|
+
if (ts.isImportDeclaration(stmt) && stmt.importClause?.phaseModifier === ts.SyntaxKind.TypeKeyword)
|
|
1948
|
+
continue;
|
|
1949
|
+
const specNode = stmt.moduleSpecifier;
|
|
1950
|
+
if (specNode === undefined || !ts.isStringLiteral(specNode) || specNode.text !== bad.spec)
|
|
1951
|
+
continue;
|
|
1952
|
+
if (cjsDepOf(entry, bad.spec) !== null) {
|
|
1953
|
+
flavored = true;
|
|
1954
|
+
break;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
const loc = locOf7(bad.nameNode);
|
|
1958
|
+
if (!flavored) {
|
|
1959
|
+
return { message: `The requested module '${bad.spec}' does not provide an export named '${bad.exportName}'`, className: "%SyntaxError", loc };
|
|
1960
|
+
}
|
|
1961
|
+
// The destructuring hint: Node regexes the failing name's SOURCE LINE
|
|
1962
|
+
// for its brace group (greedy, first `{` to last `}` on the line) and
|
|
1963
|
+
// swaps ` as ` for `: ` — reproduced byte for byte, quirks included
|
|
1964
|
+
// (multi-line import statements simply lose the hint).
|
|
1965
|
+
const text = bad.sf.text;
|
|
1966
|
+
const pos = bad.nameNode.getStart(bad.sf);
|
|
1967
|
+
const lineStart = text.lastIndexOf("\n", pos - 1) + 1;
|
|
1968
|
+
const lineEnd = text.indexOf("\n", pos);
|
|
1969
|
+
let line = text.slice(lineStart, lineEnd < 0 ? text.length : lineEnd);
|
|
1970
|
+
if (line.endsWith("\r"))
|
|
1971
|
+
line = line.slice(0, -1);
|
|
1972
|
+
const braces = /{.*}/.exec(line);
|
|
1973
|
+
const destructuring = braces === null ? null : braces[0].replace(/\s+as\s+/g, ": ");
|
|
1974
|
+
return {
|
|
1975
|
+
message: `Named export '${bad.exportName}' not found. The requested module '${bad.spec}' is a CommonJS module, which may not support all module.exports as named exports.\n` +
|
|
1976
|
+
"CommonJS modules can always be imported via the default export, for example using:\n\n" +
|
|
1977
|
+
`import pkg from '${bad.spec}';\n` +
|
|
1978
|
+
(destructuring !== null ? `const ${destructuring} = pkg;\n` : ""),
|
|
1979
|
+
className: "%SyntaxError",
|
|
1980
|
+
loc,
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
/* node's builtin-module name list, for the SC1010 wording decision ("the
|
|
1984
|
+
* 'fs' module" vs the generic package message) — shared verbatim with the
|
|
1985
|
+
* retired 5.9.3 lane's builtinModules use. */
|
|
1986
|
+
const nodeBuiltinNames = new Set(builtinModules);
|
|
1987
|
+
/* ── the lowering-facing surface ─────────────────────────────────────────
|
|
1988
|
+
* A family of world-typed helpers the LOWERING imports (require
|
|
1989
|
+
* recognizers, the CJS export-identity analysis, ordered imports,
|
|
1990
|
+
* resolution, locations), exported under the names the 5.9.3 program.ts
|
|
1991
|
+
* established (the phase-2 port kept every spelling); the world-neutral
|
|
1992
|
+
* helpers re-export from shared.ts. */
|
|
1993
|
+
export { ambientDtsPath, builtinDefaultImportModule, canonicalBuiltinModule, fallbackDtsPath, isNodeTypesPath, npmPackageNameOf, overridesDtsPath, SUPPORTED_BUILTIN_MODULES, workspacePackageOfPath, } from "./shared.js";
|
|
1994
|
+
export { requireSpecOf7 as requireSpecOf, isRequireStatement7 as isRequireStatement, requiresOf7 as requiresOf, isNodeEsmFile7 as isNodeEsmFile, resolveImport7 as resolveImport, resolveNpmImport7 as resolveNpmImport, locOf7 as locOf, };
|
|
1995
|
+
/** True for JavaScript source files (.js/.mjs/.cjs) — the files whose
|
|
1996
|
+
* types come entirely from inference (checkJs + JSDoc) and whose classes
|
|
1997
|
+
* declare fields by constructor assignment. */
|
|
1998
|
+
export function isJsSourceFile(sf) {
|
|
1999
|
+
return isJsSourceFileName(sf.fileName);
|
|
2000
|
+
}
|
|
2001
|
+
/** `import type …` declarations and named clauses whose every element is
|
|
2002
|
+
* `type`-qualified are ERASED by tsc at emit — Node's module graph has no
|
|
2003
|
+
* edge there, so module order, cycle detection, init calls, and the
|
|
2004
|
+
* startup-refusal probe must not count them either (zod v4's core modules
|
|
2005
|
+
* are "cyclic" only through `import type * as ns` namespaces — 27 phantom
|
|
2006
|
+
* SC1016 sites against 1 real value-level cycle; and a type-only import
|
|
2007
|
+
* of a module Node cannot resolve is NOT a startup crash — Node never
|
|
2008
|
+
* resolves it). Originated as the provenance-sources pilot's flag-gated
|
|
2009
|
+
* prototype (SCRIPTC_TYPE_ONLY_EDGES), promoted to always-on with the
|
|
2010
|
+
* full-suite differential green. Side-effect imports (no clause) stay
|
|
2011
|
+
* real edges. */
|
|
2012
|
+
function erasedTypeOnlyImport(stmt) {
|
|
2013
|
+
const clause = stmt.importClause;
|
|
2014
|
+
if (clause === undefined)
|
|
2015
|
+
return false; // side-effect import: a real edge
|
|
2016
|
+
if (clause.phaseModifier === ts.SyntaxKind.TypeKeyword)
|
|
2017
|
+
return true;
|
|
2018
|
+
return (clause.name === undefined &&
|
|
2019
|
+
clause.namedBindings !== undefined &&
|
|
2020
|
+
ts.isNamedImports(clause.namedBindings) &&
|
|
2021
|
+
clause.namedBindings.elements.length > 0 &&
|
|
2022
|
+
clause.namedBindings.elements.every((e) => e.isTypeOnly));
|
|
2023
|
+
}
|
|
2024
|
+
/** The re-export twin: `export { type X } from "./m"` with every element
|
|
2025
|
+
* `type`-qualified erases at emit exactly like `export type { X } from`
|
|
2026
|
+
* (which stmt.isTypeOnly already covers) — no module edge. */
|
|
2027
|
+
function erasedTypeOnlyReexport(stmt) {
|
|
2028
|
+
return (stmt.exportClause !== undefined &&
|
|
2029
|
+
ts.isNamedExports(stmt.exportClause) &&
|
|
2030
|
+
stmt.exportClause.elements.length > 0 &&
|
|
2031
|
+
stmt.exportClause.elements.every((e) => e.isTypeOnly));
|
|
2032
|
+
}
|
|
2033
|
+
/** A file's import declarations — and value re-exports with a specifier
|
|
2034
|
+
* (`export { x } from "./y.js"`, `export * from "./y.js"`), which Node
|
|
2035
|
+
* evaluates exactly like imports — in SOURCE ORDER, each with its resolved
|
|
2036
|
+
* user-module dependency (null for npm/builtin/JSON specifiers — those are
|
|
2037
|
+
* not module-order edges). The per-file %init header (lowerFileInit) uses
|
|
2038
|
+
* this to interleave island imports with user-module init calls exactly
|
|
2039
|
+
* where Node would evaluate them. CommonJS require statements are
|
|
2040
|
+
* deliberately NOT here: Node evaluates a require AT ITS STATEMENT, inline
|
|
2041
|
+
* in the module body, so the statement lowering emits their guarded %init
|
|
2042
|
+
* calls in place instead of hoisting them. */
|
|
2043
|
+
export function orderedImportsOf(program, sf) {
|
|
2044
|
+
const out = [];
|
|
2045
|
+
for (const stmt of sf.statements) {
|
|
2046
|
+
if (ts.isExportDeclaration(stmt) && (stmt.isTypeOnly || erasedTypeOnlyReexport(stmt) || !stmt.moduleSpecifier))
|
|
2047
|
+
continue;
|
|
2048
|
+
if (!ts.isImportDeclaration(stmt) && !ts.isExportDeclaration(stmt))
|
|
2049
|
+
continue;
|
|
2050
|
+
if (ts.isImportDeclaration(stmt) && erasedTypeOnlyImport(stmt))
|
|
2051
|
+
continue;
|
|
2052
|
+
if (!stmt.moduleSpecifier || !ts.isStringLiteral(stmt.moduleSpecifier))
|
|
2053
|
+
continue;
|
|
2054
|
+
const spec = stmt.moduleSpecifier.text;
|
|
2055
|
+
const isRelative = isRelativeSpecifier(spec);
|
|
2056
|
+
// Relative edges as ever; PROJECT imports (#alias/self-name — the
|
|
2057
|
+
// package.json-mediated specifiers preflight admits as user-module
|
|
2058
|
+
// edges) resolve to the same dep so the importer's %init header calls
|
|
2059
|
+
// theirs. Builtin and npm bare specifiers stay null (resolveProjectImport
|
|
2060
|
+
// answers only inside the project) — EXCEPT opted-in --npm-static
|
|
2061
|
+
// packages, whose entries are program modules the header must init.
|
|
2062
|
+
const dep = isRelative
|
|
2063
|
+
? resolveImport7(program, sf, spec)
|
|
2064
|
+
: (npmStaticDepSf7(program, sf, spec) ?? resolveProjectImportSf7(program, sf, spec));
|
|
2065
|
+
const isJson = dep !== null && dep.fileName.endsWith(".json");
|
|
2066
|
+
out.push({ stmt, dep: isJson ? null : dep });
|
|
2067
|
+
}
|
|
2068
|
+
return out;
|
|
2069
|
+
}
|
|
2070
|
+
/** The program source file a bare specifier reaches when it names an
|
|
2071
|
+
* opted-in --npm-static package (the shipped-JS entry preflight admitted
|
|
2072
|
+
* as a module edge), else null. No offender reporting here — preflight
|
|
2073
|
+
* already classified the import; this is the lookup the module-order and
|
|
2074
|
+
* lowering paths share. */
|
|
2075
|
+
function npmStaticDepSf7(program, sf, spec) {
|
|
2076
|
+
if (!npmStaticActive() || isRelativeSpecifier(spec))
|
|
2077
|
+
return null;
|
|
2078
|
+
if (spec.startsWith("node:") || spec.startsWith("#"))
|
|
2079
|
+
return null;
|
|
2080
|
+
const npm = resolveNpmImport7(sf.fileName, spec);
|
|
2081
|
+
if (npm === null || !isNpmStaticPackage(npm.packageName))
|
|
2082
|
+
return null;
|
|
2083
|
+
if (!isJsSourceFileName(npm.typesFile))
|
|
2084
|
+
return null;
|
|
2085
|
+
return program.getSourceFile(npm.typesFile) ?? null;
|
|
2086
|
+
}
|
|
2087
|
+
/** resolveProjectImport lifted to SourceFile answers: the program's file
|
|
2088
|
+
* for a `#alias`/self-name resolution, or null (unresolved, or resolved
|
|
2089
|
+
* outside the program — declaration files included; callers that admit
|
|
2090
|
+
* d.ts answers probe resolveProjectImport directly). */
|
|
2091
|
+
function resolveProjectImportSf7(program, sf, spec) {
|
|
2092
|
+
if (spec.startsWith("node:"))
|
|
2093
|
+
return null;
|
|
2094
|
+
const p = resolveProjectImport(sf.fileName, spec);
|
|
2095
|
+
if (p === null)
|
|
2096
|
+
return null;
|
|
2097
|
+
const dep = program.getSourceFile(p) ?? null;
|
|
2098
|
+
return dep !== null && !dep.isDeclarationFile ? dep : null;
|
|
2099
|
+
}
|
|
2100
|
+
/** True when `expr` is the `module.exports` property access. */
|
|
2101
|
+
export function isModuleExportsAccess(expr) {
|
|
2102
|
+
return (ts.isPropertyAccessExpression(expr) &&
|
|
2103
|
+
!expr.questionDotToken &&
|
|
2104
|
+
ts.isIdentifier(expr.expression) &&
|
|
2105
|
+
expr.expression.text === "module" &&
|
|
2106
|
+
expr.name.text === "exports");
|
|
2107
|
+
}
|
|
2108
|
+
/** Classifies a statement as a CommonJS EXPORT assignment:
|
|
2109
|
+
* - table: `module.exports = { ... }` — the whole export table at once
|
|
2110
|
+
* (obj is null when the RHS is not an object literal — the
|
|
2111
|
+
* recognized-but-unsupported form, fenced by the caller);
|
|
2112
|
+
* - member: `exports.f = <expr>` / `module.exports.f = <expr>`.
|
|
2113
|
+
* Null for everything else. Callers gate on top-level position in a JS
|
|
2114
|
+
* module file — these forms anywhere else keep their generic fences. */
|
|
2115
|
+
export function cjsExportAssignmentOf(stmt) {
|
|
2116
|
+
if (!ts.isExpressionStatement(stmt))
|
|
2117
|
+
return null;
|
|
2118
|
+
const e = stmt.expression;
|
|
2119
|
+
if (!ts.isBinaryExpression(e) || e.operatorToken.kind !== ts.SyntaxKind.EqualsToken)
|
|
2120
|
+
return null;
|
|
2121
|
+
if (isModuleExportsAccess(e.left)) {
|
|
2122
|
+
return { kind: "table", obj: ts.isObjectLiteralExpression(e.right) ? e.right : null, expr: e };
|
|
2123
|
+
}
|
|
2124
|
+
if (ts.isPropertyAccessExpression(e.left) && !e.left.questionDotToken) {
|
|
2125
|
+
const recv = e.left.expression;
|
|
2126
|
+
const isExports = ts.isIdentifier(recv) && recv.text === "exports";
|
|
2127
|
+
if (isExports || isModuleExportsAccess(recv)) {
|
|
2128
|
+
return { kind: "member", name: e.left.name, value: e.right, expr: e };
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
return null;
|
|
2132
|
+
}
|
|
2133
|
+
/** Why Node would DISCARD this CommonJS export statement, or null when it
|
|
2134
|
+
* survives (Node's export-object identity rules — checker-free, pure
|
|
2135
|
+
* AST): `module.exports = {...}` REPLACES the export object, so members
|
|
2136
|
+
* attached before it (either spelling), any earlier table, and
|
|
2137
|
+
* `exports.f =` after it (exports still references the replaced object)
|
|
2138
|
+
* are never seen by importers. Lowering fences these instead of shipping
|
|
2139
|
+
* the checker's everything-is-live answer — the checker/runtime split is
|
|
2140
|
+
* exactly the silent-divergence trap. */
|
|
2141
|
+
export function cjsExportDiscardReason(stmt) {
|
|
2142
|
+
const sf = stmt.parent;
|
|
2143
|
+
if (!ts.isSourceFile(sf))
|
|
2144
|
+
return null;
|
|
2145
|
+
const mine = cjsExportAssignmentOf(stmt);
|
|
2146
|
+
if (!mine)
|
|
2147
|
+
return null;
|
|
2148
|
+
const stmts = sf.statements;
|
|
2149
|
+
let lastTable = -1;
|
|
2150
|
+
for (let i = 0; i < stmts.length; i++) {
|
|
2151
|
+
if (cjsExportAssignmentOf(stmts[i])?.kind === "table")
|
|
2152
|
+
lastTable = i;
|
|
2153
|
+
}
|
|
2154
|
+
if (lastTable < 0)
|
|
2155
|
+
return null;
|
|
2156
|
+
const myIdx = stmts.indexOf(stmt);
|
|
2157
|
+
if (mine.kind === "table") {
|
|
2158
|
+
return myIdx < lastTable
|
|
2159
|
+
? "multiple 'module.exports =' assignments (Node keeps only the last one — this table is discarded)"
|
|
2160
|
+
: null;
|
|
2161
|
+
}
|
|
2162
|
+
if (myIdx < lastTable) {
|
|
2163
|
+
return "export members attached before a 'module.exports =' assignment (Node replaces the export object — importers never see this member; attach it to module.exports AFTER the table)";
|
|
2164
|
+
}
|
|
2165
|
+
const left = mine.expr.left;
|
|
2166
|
+
return ts.isIdentifier(left.expression) && left.expression.text === "exports"
|
|
2167
|
+
? "'exports.f =' after a 'module.exports =' assignment ('exports' still references the replaced object — Node discards this member; write module.exports.f instead)"
|
|
2168
|
+
: null;
|
|
2169
|
+
}
|
|
2170
|
+
/** The object literal a `module.exports = <expr>` statement's RHS
|
|
2171
|
+
* RESOLVES to (syntactically): the literal itself; `new Proxy(target,
|
|
2172
|
+
* handler)` — Node programs export identity-forwarding proxies over a
|
|
2173
|
+
* table, and the TARGET is the table (traps cannot run in a compiled
|
|
2174
|
+
* program: every member access is statically resolved against the
|
|
2175
|
+
* target's own type); or an identifier naming a top-level const whose
|
|
2176
|
+
* initializer is the literal. Null for everything else. */
|
|
2177
|
+
export function cjsExportTargetLiteral(e, sf) {
|
|
2178
|
+
while (ts.isParenthesizedExpression(e))
|
|
2179
|
+
e = e.expression;
|
|
2180
|
+
if (ts.isObjectLiteralExpression(e))
|
|
2181
|
+
return e;
|
|
2182
|
+
if (ts.isNewExpression(e) &&
|
|
2183
|
+
ts.isIdentifier(e.expression) &&
|
|
2184
|
+
e.expression.text === "Proxy" &&
|
|
2185
|
+
(e.arguments?.length ?? 0) >= 1) {
|
|
2186
|
+
return cjsExportTargetLiteral(e.arguments[0], sf);
|
|
2187
|
+
}
|
|
2188
|
+
if (ts.isIdentifier(e)) {
|
|
2189
|
+
for (const stmt of sf.statements) {
|
|
2190
|
+
if (!ts.isVariableStatement(stmt))
|
|
2191
|
+
continue;
|
|
2192
|
+
if ((stmt.declarationList.flags & ts.NodeFlags.Const) === 0)
|
|
2193
|
+
continue;
|
|
2194
|
+
for (const d of stmt.declarationList.declarations) {
|
|
2195
|
+
if (d.name !== undefined && ts.isIdentifier(d.name) && d.name.text === e.text && d.initializer) {
|
|
2196
|
+
let init = d.initializer;
|
|
2197
|
+
while (ts.isParenthesizedExpression(init))
|
|
2198
|
+
init = init.expression;
|
|
2199
|
+
return ts.isObjectLiteralExpression(init) ? init : null;
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
}
|
|
2203
|
+
}
|
|
2204
|
+
return null;
|
|
2205
|
+
}
|
|
2206
|
+
/** The class expression a JS module's WHOLE export is (`module.exports =
|
|
2207
|
+
* class …{}`): the kept (last) top-level `module.exports =` statement's
|
|
2208
|
+
* paren-unwrapped RHS when it is a class expression, together with the
|
|
2209
|
+
* statement it sits in (in-file `module.exports` reads gate on source
|
|
2210
|
+
* order against it). Null when the kept export is anything else. */
|
|
2211
|
+
export function cjsClassExprWholeExportOf(sf) {
|
|
2212
|
+
let last = null;
|
|
2213
|
+
for (const stmt of sf.statements) {
|
|
2214
|
+
const cjs = cjsExportAssignmentOf(stmt);
|
|
2215
|
+
if (!cjs || cjs.kind !== "table")
|
|
2216
|
+
continue;
|
|
2217
|
+
let rhs = cjs.expr.right;
|
|
2218
|
+
while (ts.isParenthesizedExpression(rhs))
|
|
2219
|
+
rhs = rhs.expression;
|
|
2220
|
+
// Any LATER table replaces the export object — a non-class one takes
|
|
2221
|
+
// the class answer back off the table.
|
|
2222
|
+
last = ts.isClassExpression(rhs) ? { classExpr: rhs, stmt } : null;
|
|
2223
|
+
}
|
|
2224
|
+
return last;
|
|
2225
|
+
}
|
|
2226
|
+
/** True when `obj` is the effective export-table literal of a JS module:
|
|
2227
|
+
* the object literal of a top-level `module.exports = { ... }` statement
|
|
2228
|
+
* directly, OR the RESOLVED TARGET of the module's kept `module.exports =`
|
|
2229
|
+
* statement (the identifier/Proxy forms — see cjsExportTargetLiteral).
|
|
2230
|
+
* The gate for the CJS export-property alias re-resolution
|
|
2231
|
+
* (resolveValueSymbol) and the accessor-read lift. */
|
|
2232
|
+
export function isCjsExportTableLiteral(obj) {
|
|
2233
|
+
const assign = obj.parent;
|
|
2234
|
+
if (ts.isBinaryExpression(assign) &&
|
|
2235
|
+
assign.right === obj &&
|
|
2236
|
+
isModuleExportsAccess(assign.left) &&
|
|
2237
|
+
ts.isExpressionStatement(assign.parent) &&
|
|
2238
|
+
ts.isSourceFile(assign.parent.parent)) {
|
|
2239
|
+
return isJsSourceFile(assign.parent.parent);
|
|
2240
|
+
}
|
|
2241
|
+
const decl = obj.parent;
|
|
2242
|
+
if (!ts.isVariableDeclaration(decl) || decl.initializer !== obj)
|
|
2243
|
+
return false;
|
|
2244
|
+
const sf = obj.getSourceFile();
|
|
2245
|
+
if (!isJsSourceFile(sf))
|
|
2246
|
+
return false;
|
|
2247
|
+
for (const stmt of sf.statements) {
|
|
2248
|
+
const cjs = cjsExportAssignmentOf(stmt);
|
|
2249
|
+
if (cjs?.kind === "table" && cjsExportDiscardReason(stmt) === null) {
|
|
2250
|
+
return cjsExportTargetLiteral(cjs.expr.right, sf) === obj;
|
|
2251
|
+
}
|
|
2252
|
+
}
|
|
2253
|
+
return false;
|
|
2254
|
+
}
|
|
2255
|
+
//# sourceMappingURL=program.js.map
|