@scriptc/compiler 0.0.4 → 0.0.6
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/dist/backend/cc.d.ts +16 -0
- package/dist/backend/cc.js +88 -0
- package/dist/backend/cc.js.map +1 -1
- package/dist/backend/emission/emit-async.js +2 -2
- package/dist/backend/emission/emit-async.js.map +1 -1
- package/dist/backend/emission/emit-exprs.js +27 -4
- package/dist/backend/emission/emit-exprs.js.map +1 -1
- package/dist/backend/emission/emit-shapes.js +2 -2
- package/dist/backend/emission/emit-shapes.js.map +1 -1
- package/dist/backend/emission/emit-walkers.js +54 -8
- package/dist/backend/emission/emit-walkers.js.map +1 -1
- package/dist/backend/emission/emitter.d.ts +1 -0
- package/dist/backend/emission/emitter.js +108 -0
- package/dist/backend/emission/emitter.js.map +1 -1
- package/dist/backend/emission/may-throw.js +12 -0
- package/dist/backend/emission/may-throw.js.map +1 -1
- package/dist/backend/llvm/dyn.js +23 -1
- package/dist/backend/llvm/dyn.js.map +1 -1
- package/dist/backend/llvm/emitter.js +150 -6
- package/dist/backend/llvm/emitter.js.map +1 -1
- package/dist/backend/llvm/walkers.d.ts +5 -0
- package/dist/backend/llvm/walkers.js +65 -5
- package/dist/backend/llvm/walkers.js.map +1 -1
- package/dist/diagnostics/diagnostic.d.ts +25 -0
- package/dist/diagnostics/diagnostic.js +86 -0
- package/dist/diagnostics/diagnostic.js.map +1 -1
- package/dist/frontend/lib-exports.d.ts +16 -0
- package/dist/frontend/lib-exports.js +35 -0
- package/dist/frontend/lib-exports.js.map +1 -0
- package/dist/frontend/lowering/lower-assert.js +41 -1
- package/dist/frontend/lowering/lower-assert.js.map +1 -1
- package/dist/frontend/lowering/lower-calls.js +2 -2
- package/dist/frontend/lowering/lower-calls.js.map +1 -1
- package/dist/frontend/lowering/lower-classes.js +1 -1
- package/dist/frontend/lowering/lower-classes.js.map +1 -1
- package/dist/frontend/lowering/lower-exprs.js +1 -1
- package/dist/frontend/lowering/lower-exprs.js.map +1 -1
- package/dist/frontend/lowering/lower-inspect.d.ts +7 -0
- package/dist/frontend/lowering/lower-inspect.js +130 -28
- package/dist/frontend/lowering/lower-inspect.js.map +1 -1
- package/dist/frontend/lowering/lower-island.js +1 -1
- package/dist/frontend/lowering/lower-island.js.map +1 -1
- package/dist/frontend/lowering/lower-mixins.js +4 -2
- package/dist/frontend/lowering/lower-mixins.js.map +1 -1
- package/dist/frontend/lowering/lower-stmts.js +107 -0
- package/dist/frontend/lowering/lower-stmts.js.map +1 -1
- package/dist/frontend/lowering/lowerer.d.ts +16 -2
- package/dist/frontend/lowering/lowerer.js +64 -15
- package/dist/frontend/lowering/lowerer.js.map +1 -1
- package/dist/frontend/npm-static-rewrite.d.ts +3 -0
- package/dist/frontend/npm-static-rewrite.js +677 -0
- package/dist/frontend/npm-static-rewrite.js.map +1 -0
- package/dist/frontend/npm-static.d.ts +8 -1
- package/dist/frontend/npm-static.js +159 -18
- package/dist/frontend/npm-static.js.map +1 -1
- package/dist/frontend/program.d.ts +6 -0
- package/dist/frontend/program.js +51 -14
- package/dist/frontend/program.js.map +1 -1
- package/dist/frontend/types.d.ts +68 -6
- package/dist/frontend/types.js +285 -57
- package/dist/frontend/types.js.map +1 -1
- package/dist/index.d.ts +22 -0
- package/dist/index.js +253 -17
- package/dist/index.js.map +1 -1
- package/dist/ir/nodes.d.ts +50 -4
- package/dist/ir/nodes.js +179 -13
- package/dist/ir/nodes.js.map +1 -1
- package/dist/ir/validate.js +61 -0
- package/dist/ir/validate.js.map +1 -1
- package/dist/library/profile.d.ts +52 -0
- package/dist/library/profile.js +232 -0
- package/dist/library/profile.js.map +1 -0
- package/package.json +2 -2
- package/surface-manifest.json +1 -1
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
/* --npm-static's bundler-emitted-CJS export surface: the getter-table
|
|
2
|
+
* shapes. A bundler-emitted CJS dist exports through runtime plumbing the
|
|
3
|
+
* checker cannot model — the `__export(target, { name: () => value, ... })`
|
|
4
|
+
* getter table behind `module.exports = __toCommonJS(target)`, the
|
|
5
|
+
* `Object.defineProperty(exports, 'n', { get })` family, and the
|
|
6
|
+
* transpiler star re-exports (`__exportStar(require('./x'), exports)`,
|
|
7
|
+
* `__reExport(target, require('./x'), module.exports)`). Node's CJS lexer
|
|
8
|
+
* SEES those named exports (cjs-lexer.ts mirrors it exactly), so named
|
|
9
|
+
* imports link under Node — but the inferred surface the opted-in
|
|
10
|
+
* resolution feeds the checker is an opaque object, and every import site
|
|
11
|
+
* reports "X has no exported member" (the SC0001 storm that used to gate
|
|
12
|
+
* the whole build).
|
|
13
|
+
*
|
|
14
|
+
* THE REWRITE. For an opted-in package's CJS file whose exports come from
|
|
15
|
+
* these shapes, the fs shadow (npm-static.ts) serves BOTH worlds — the
|
|
16
|
+
* checker and the lowering ride one program — a text rewrite that spells
|
|
17
|
+
* the same export surface in the plain CJS the whole existing machinery
|
|
18
|
+
* already models:
|
|
19
|
+
*
|
|
20
|
+
* - the recognized plumbing statements are SPACE-PADDED in place
|
|
21
|
+
* (newlines kept, so every original line/offset survives): the
|
|
22
|
+
* `module.exports = __toCommonJS(target)` assignment, the
|
|
23
|
+
* `__export(target, {...})` call, the star-pattern calls, the
|
|
24
|
+
* recognized defineProperty exports, and the dead esbuild annotation
|
|
25
|
+
* `0 && (module.exports = {...})` (left live it poisons the checker's
|
|
26
|
+
* alias resolution — multiple module.exports assignments make import
|
|
27
|
+
* aliases land on properties no lowering path resolves);
|
|
28
|
+
* - one canonical export table is APPENDED at the tail:
|
|
29
|
+
* `module.exports = { ...require("./star"), own: local, ... };`
|
|
30
|
+
* Each getter body the chase can follow types the export by its
|
|
31
|
+
* resolved VALUE (`() => localBinding` resolves to the binding's
|
|
32
|
+
* inferred type; member chains and scalar literals hoist to a tail
|
|
33
|
+
* const first), and every lexer-visible name the chase cannot follow
|
|
34
|
+
* falls back to a checked-dynamic `any` binding (value undefined —
|
|
35
|
+
* exactly what Node's import binds for a lexer-visible name the
|
|
36
|
+
* runtime export object never carries).
|
|
37
|
+
*
|
|
38
|
+
* LEXER PARITY IS THE CONTRACT: the rewritten text must answer the SAME
|
|
39
|
+
* name set to cjs-lexer.ts that the original answers to Node — named
|
|
40
|
+
* imports must link exactly where Node's would. The table is formatted
|
|
41
|
+
* for merve's byte rules (`name: IDENT,` — value ident runs directly
|
|
42
|
+
* followed by ','), star re-exports ride spread entries (recorded as
|
|
43
|
+
* reexports and resolved recursively, exactly Node's union), and the
|
|
44
|
+
* esbuild `__esModule: !0` marker is spelled as a deliberate SCAN STOPPER
|
|
45
|
+
* so it stays lexer-invisible (Node never lexes __esModule out of
|
|
46
|
+
* __toCommonJS) while member-access star entries after it are covered by
|
|
47
|
+
* the spread records.
|
|
48
|
+
*
|
|
49
|
+
* SNAPSHOT SEMANTICS (the documented divergence): the getter table reads
|
|
50
|
+
* its locals LIVE at every access; the canonical table captures values
|
|
51
|
+
* ONCE, at module tail — after every top-level statement ran, so ordinary
|
|
52
|
+
* modules see identical values, and only post-evaluation reassignment of
|
|
53
|
+
* an exported local (the lazy-cache counter idiom) can diverge. Star
|
|
54
|
+
* re-export targets load at module tail instead of their original
|
|
55
|
+
* statement positions (visible only to load-order-sensitive side
|
|
56
|
+
* effects). Node's __esModule marker survives on the rewritten object.
|
|
57
|
+
*
|
|
58
|
+
* Everything here is SYNTACTIC (typescript5 world, the cjs-lexer's
|
|
59
|
+
* sanctioned island): the chase decides only what TEXT to emit — the
|
|
60
|
+
* checker then infers the types from the emitted spellings. A file whose
|
|
61
|
+
* export mechanism the recognizers cannot fully account for is left
|
|
62
|
+
* untouched (null): its import-site errors stay, and the frontend's
|
|
63
|
+
* offender attribution degrades the PACKAGE to the island with a note. */
|
|
64
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
65
|
+
import { dirname, resolve as resolvePath } from "node:path";
|
|
66
|
+
import ts from "typescript5";
|
|
67
|
+
import { cjsLexedExportsOf, cjsLexerVisibleNames } from "./cjs-lexer.js";
|
|
68
|
+
/** True when `e` is exactly the `exports` identifier. */
|
|
69
|
+
function isExportsIdent(e) {
|
|
70
|
+
return ts.isIdentifier(e) && e.text === "exports";
|
|
71
|
+
}
|
|
72
|
+
/** True when `e` is exactly `module.exports`. */
|
|
73
|
+
function isModuleExports(e) {
|
|
74
|
+
return (ts.isPropertyAccessExpression(e) &&
|
|
75
|
+
e.questionDotToken === undefined &&
|
|
76
|
+
ts.isIdentifier(e.expression) &&
|
|
77
|
+
e.expression.text === "module" &&
|
|
78
|
+
ts.isIdentifier(e.name) &&
|
|
79
|
+
e.name.text === "exports");
|
|
80
|
+
}
|
|
81
|
+
/** The bare `require('spec')` call (identifier callee, one string arg). */
|
|
82
|
+
function bareRequireSpecOf(e) {
|
|
83
|
+
if (!ts.isCallExpression(e) || e.questionDotToken !== undefined)
|
|
84
|
+
return null;
|
|
85
|
+
if (!ts.isIdentifier(e.expression) || e.expression.text !== "require")
|
|
86
|
+
return null;
|
|
87
|
+
if (e.arguments.length !== 1)
|
|
88
|
+
return null;
|
|
89
|
+
const arg = e.arguments[0];
|
|
90
|
+
return ts.isStringLiteral(arg) ? arg.text : null;
|
|
91
|
+
}
|
|
92
|
+
/** The last NAME of a callee: bare identifier or any member chain's final
|
|
93
|
+
* name (`tslib_1.__exportStar` → "__exportStar"). */
|
|
94
|
+
function calleeNameOf(call) {
|
|
95
|
+
const callee = call.expression;
|
|
96
|
+
if (ts.isIdentifier(callee))
|
|
97
|
+
return callee.text;
|
|
98
|
+
if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name))
|
|
99
|
+
return callee.name.text;
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
/** A chaseable getter/descriptor VALUE: a plain identifier, an
|
|
103
|
+
* identifier-rooted member/element chain (string keys only), or a scalar
|
|
104
|
+
* literal. Answers the classification or null (not chaseable). */
|
|
105
|
+
function chaseValue(e) {
|
|
106
|
+
let cur = e;
|
|
107
|
+
while (ts.isParenthesizedExpression(cur))
|
|
108
|
+
cur = cur.expression;
|
|
109
|
+
if (ts.isIdentifier(cur))
|
|
110
|
+
return { kind: "ident", text: cur.text };
|
|
111
|
+
if (ts.isNumericLiteral(cur) ||
|
|
112
|
+
ts.isStringLiteral(cur) ||
|
|
113
|
+
ts.isNoSubstitutionTemplateLiteral(cur) ||
|
|
114
|
+
cur.kind === ts.SyntaxKind.TrueKeyword ||
|
|
115
|
+
cur.kind === ts.SyntaxKind.FalseKeyword ||
|
|
116
|
+
cur.kind === ts.SyntaxKind.NullKeyword) {
|
|
117
|
+
return { kind: "hoist", text: cur.getText() };
|
|
118
|
+
}
|
|
119
|
+
// ident-rooted member chains: this.x / a.b.c / a["k"].d — side-effect
|
|
120
|
+
// free reads the tail const can evaluate once.
|
|
121
|
+
let walk = cur;
|
|
122
|
+
while ((ts.isPropertyAccessExpression(walk) && walk.questionDotToken === undefined && ts.isIdentifier(walk.name)) ||
|
|
123
|
+
(ts.isElementAccessExpression(walk) && walk.questionDotToken === undefined && ts.isStringLiteral(walk.argumentExpression))) {
|
|
124
|
+
walk = walk.expression;
|
|
125
|
+
}
|
|
126
|
+
if (ts.isIdentifier(walk))
|
|
127
|
+
return { kind: "hoist", text: cur.getText() };
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
/** The getter body's returned expression for the two descriptor getter
|
|
131
|
+
* spellings (`get() { return X }` / `get: function () { return X }`) and
|
|
132
|
+
* the arrow forms the esbuild table uses (`() => X`, `() => { return X }`). */
|
|
133
|
+
function getterReturnOf(fn) {
|
|
134
|
+
let body;
|
|
135
|
+
if (ts.isArrowFunction(fn) && fn.parameters.length === 0)
|
|
136
|
+
body = fn.body;
|
|
137
|
+
else if (ts.isFunctionExpression(fn) && fn.parameters.length === 0)
|
|
138
|
+
body = fn.body;
|
|
139
|
+
else if (ts.isMethodDeclaration(fn) && fn.parameters.length === 0 && fn.body !== undefined)
|
|
140
|
+
body = fn.body;
|
|
141
|
+
else
|
|
142
|
+
return null;
|
|
143
|
+
if (body === undefined)
|
|
144
|
+
return null;
|
|
145
|
+
if (!ts.isBlock(body))
|
|
146
|
+
return body; // expression-bodied arrow
|
|
147
|
+
if (body.statements.length !== 1)
|
|
148
|
+
return null;
|
|
149
|
+
const ret = body.statements[0];
|
|
150
|
+
return ts.isReturnStatement(ret) && ret.expression !== undefined ? ret.expression : null;
|
|
151
|
+
}
|
|
152
|
+
/** The `__export(IDENT, { name: () => value, ... })` call's parts, or null.
|
|
153
|
+
* Every property must be a `name: <function>` assignment (identifier or
|
|
154
|
+
* string-literal name) — anything else is not the esbuild table. */
|
|
155
|
+
function exportCallOf(call) {
|
|
156
|
+
if (calleeNameOf(call) !== "__export")
|
|
157
|
+
return null;
|
|
158
|
+
if (call.arguments.length !== 2)
|
|
159
|
+
return null;
|
|
160
|
+
const [target, table] = call.arguments;
|
|
161
|
+
if (!ts.isIdentifier(target) || !ts.isObjectLiteralExpression(table))
|
|
162
|
+
return null;
|
|
163
|
+
const entries = [];
|
|
164
|
+
for (const prop of table.properties) {
|
|
165
|
+
if (!ts.isPropertyAssignment(prop))
|
|
166
|
+
return null;
|
|
167
|
+
let name;
|
|
168
|
+
if (ts.isIdentifier(prop.name))
|
|
169
|
+
name = prop.name.text;
|
|
170
|
+
else if (ts.isStringLiteral(prop.name))
|
|
171
|
+
name = prop.name.text;
|
|
172
|
+
else
|
|
173
|
+
return null;
|
|
174
|
+
entries.push([name, getterReturnOf(prop.initializer)]);
|
|
175
|
+
}
|
|
176
|
+
return { target: target.text, entries };
|
|
177
|
+
}
|
|
178
|
+
/** `Object.defineProperty(exports|module.exports, 'name', {...})` — the
|
|
179
|
+
* name plus the chased descriptor value (getter return or `value:`), or
|
|
180
|
+
* null when the statement is not that shape at all. `value: null` means
|
|
181
|
+
* recognized but unchaseable. */
|
|
182
|
+
function definePropertyExportOf(call) {
|
|
183
|
+
const callee = call.expression;
|
|
184
|
+
if (!ts.isPropertyAccessExpression(callee) ||
|
|
185
|
+
!ts.isIdentifier(callee.expression) ||
|
|
186
|
+
callee.expression.text !== "Object" ||
|
|
187
|
+
!ts.isIdentifier(callee.name) ||
|
|
188
|
+
callee.name.text !== "defineProperty") {
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
if (call.arguments.length !== 3)
|
|
192
|
+
return null;
|
|
193
|
+
const [recv, nameArg, desc] = call.arguments;
|
|
194
|
+
if (!isExportsIdent(recv) && !isModuleExports(recv))
|
|
195
|
+
return null;
|
|
196
|
+
if (!ts.isStringLiteral(nameArg) || !ts.isObjectLiteralExpression(desc))
|
|
197
|
+
return null;
|
|
198
|
+
const name = nameArg.text;
|
|
199
|
+
let value = null;
|
|
200
|
+
for (const p of desc.properties) {
|
|
201
|
+
if (ts.isPropertyAssignment(p) && ts.isIdentifier(p.name)) {
|
|
202
|
+
if (p.name.text === "value")
|
|
203
|
+
value = p.initializer;
|
|
204
|
+
else if (p.name.text === "get")
|
|
205
|
+
value = getterReturnOf(p.initializer);
|
|
206
|
+
}
|
|
207
|
+
else if (ts.isMethodDeclaration(p) && ts.isIdentifier(p.name) && p.name.text === "get") {
|
|
208
|
+
value = getterReturnOf(p);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const esModuleStamp = name === "__esModule" && value !== null && value.kind === ts.SyntaxKind.TrueKeyword;
|
|
212
|
+
return { name, value, esModuleStamp };
|
|
213
|
+
}
|
|
214
|
+
/** Resolves a RELATIVE CJS require target with Node's file-first probes
|
|
215
|
+
* (the dist-tree subset: exact, .js/.cjs, /index.js, package.json "main"
|
|
216
|
+
* for directories). Bare specifiers answer null — their names ride the
|
|
217
|
+
* spread entry and the program resolver. */
|
|
218
|
+
function resolveRelativeCjs(fromFile, spec) {
|
|
219
|
+
if (!spec.startsWith("./") && !spec.startsWith("../"))
|
|
220
|
+
return null;
|
|
221
|
+
const base = resolvePath(dirname(fromFile), spec);
|
|
222
|
+
const candidates = [base, `${base}.js`, `${base}.cjs`];
|
|
223
|
+
for (const c of candidates) {
|
|
224
|
+
try {
|
|
225
|
+
if (existsSync(c) && statSync(c).isFile() && /\.(js|cjs)$/.test(c))
|
|
226
|
+
return c;
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
/* keep probing */
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
if (existsSync(base) && statSync(base).isDirectory()) {
|
|
234
|
+
const pkgPath = resolvePath(base, "package.json");
|
|
235
|
+
if (existsSync(pkgPath)) {
|
|
236
|
+
const main = JSON.parse(readFileSync(pkgPath, "utf8")).main;
|
|
237
|
+
if (typeof main === "string") {
|
|
238
|
+
const m = resolvePath(base, main);
|
|
239
|
+
for (const c of [m, `${m}.js`, `${m}.cjs`, resolvePath(m, "index.js")]) {
|
|
240
|
+
if (existsSync(c) && statSync(c).isFile() && /\.(js|cjs)$/.test(c))
|
|
241
|
+
return c;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
const idx = resolvePath(base, "index.js");
|
|
246
|
+
if (existsSync(idx))
|
|
247
|
+
return idx;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
/* unresolved */
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
/** The Node-visible named-export set of a star-re-export TARGET file:
|
|
256
|
+
* its recursive lexer-visible names (relative edges only — exactly what
|
|
257
|
+
* the emitted member entries must enumerate). */
|
|
258
|
+
function starTargetNames(file) {
|
|
259
|
+
try {
|
|
260
|
+
return cjsLexerVisibleNames(file, (f) => readFileSync(f, "utf8"), (from, spec) => resolveRelativeCjs(from, spec));
|
|
261
|
+
}
|
|
262
|
+
catch {
|
|
263
|
+
return new Set();
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
/** Space-pads [start, end) of `text` preserving newlines — the recognized
|
|
267
|
+
* statement disappears while every original offset survives. */
|
|
268
|
+
function padSpan(text, start, end) {
|
|
269
|
+
let pad = "";
|
|
270
|
+
for (let i = start; i < end; i++)
|
|
271
|
+
pad += text[i] === "\n" ? "\n" : " ";
|
|
272
|
+
return text.slice(0, start) + pad + text.slice(end);
|
|
273
|
+
}
|
|
274
|
+
/** A valid identifier-run export name (the table can spell it bare). */
|
|
275
|
+
function isPlainName(name) {
|
|
276
|
+
return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name);
|
|
277
|
+
}
|
|
278
|
+
const RESERVED_KEYS = new Set(["__proto__"]);
|
|
279
|
+
/** The rewrite (see the header). Null = not a recognized bundle shape (or
|
|
280
|
+
* nothing to fix) — serve the file untouched. */
|
|
281
|
+
export function rewriteBundlerCjsExports(source, filePath) {
|
|
282
|
+
// Cheap gates before any parse: CJS bundle plumbing leaves textual
|
|
283
|
+
// fingerprints; files without any are never candidates.
|
|
284
|
+
if (!source.includes("__toCommonJS") &&
|
|
285
|
+
!source.includes("__exportStar") &&
|
|
286
|
+
!source.includes("__reExport") &&
|
|
287
|
+
!(source.includes("Object.defineProperty(exports") && source.includes("get"))) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
// parents ON: the helper sweep excludes declarator-name self references
|
|
291
|
+
// and the chase reads expression text through the tree
|
|
292
|
+
const sf = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.JS);
|
|
293
|
+
// ESM syntax → not CJS; leave alone.
|
|
294
|
+
for (const stmt of sf.statements) {
|
|
295
|
+
if (ts.isImportDeclaration(stmt) || ts.isExportDeclaration(stmt) || ts.isExportAssignment(stmt))
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const neutralize = [];
|
|
299
|
+
const exportTables = [];
|
|
300
|
+
const definePropEntries = [];
|
|
301
|
+
const memberStmts = [];
|
|
302
|
+
/** Star spec → require-load emission order (statement order). */
|
|
303
|
+
const starSpecsInOrder = [];
|
|
304
|
+
let esbuildMainTarget = null;
|
|
305
|
+
let tablePos = null;
|
|
306
|
+
let sawBundleShape = false;
|
|
307
|
+
const addStarSpec = (spec) => {
|
|
308
|
+
if (!starSpecsInOrder.includes(spec))
|
|
309
|
+
starSpecsInOrder.push(spec);
|
|
310
|
+
};
|
|
311
|
+
for (const stmt of sf.statements) {
|
|
312
|
+
if (!ts.isExpressionStatement(stmt))
|
|
313
|
+
continue;
|
|
314
|
+
const e = stmt.expression;
|
|
315
|
+
// `module.exports = <rhs>`
|
|
316
|
+
if (ts.isBinaryExpression(e) && e.operatorToken.kind === ts.SyntaxKind.EqualsToken && isModuleExports(e.left)) {
|
|
317
|
+
const rhs = e.right;
|
|
318
|
+
if (ts.isCallExpression(rhs) && calleeNameOf(rhs) === "__toCommonJS" && rhs.arguments.length === 1 && ts.isIdentifier(rhs.arguments[0])) {
|
|
319
|
+
esbuildMainTarget = rhs.arguments[0].text;
|
|
320
|
+
tablePos = stmt.getStart(sf);
|
|
321
|
+
neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() });
|
|
322
|
+
sawBundleShape = true;
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
// Any other real module.exports assignment: the file has an export
|
|
326
|
+
// mechanism the checker already models (or a shape outside the
|
|
327
|
+
// recognizers) — not ours to rewrite.
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
// member exports — `exports.N = RHS;` / `module.exports.N = RHS;`,
|
|
331
|
+
// chains included (`exports.a = exports.b = void 0;` — the tsc
|
|
332
|
+
// void-init preamble collects every name with the chain's final RHS).
|
|
333
|
+
{
|
|
334
|
+
let cur = e;
|
|
335
|
+
const names = [];
|
|
336
|
+
let viaBareExports = false;
|
|
337
|
+
while (ts.isBinaryExpression(cur) &&
|
|
338
|
+
cur.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
339
|
+
ts.isPropertyAccessExpression(cur.left) &&
|
|
340
|
+
cur.left.questionDotToken === undefined &&
|
|
341
|
+
ts.isIdentifier(cur.left.name) &&
|
|
342
|
+
(isExportsIdent(cur.left.expression) || isModuleExports(cur.left.expression))) {
|
|
343
|
+
names.push(cur.left.name.text);
|
|
344
|
+
if (isExportsIdent(cur.left.expression))
|
|
345
|
+
viaBareExports = true;
|
|
346
|
+
cur = cur.right;
|
|
347
|
+
}
|
|
348
|
+
if (names.length > 0) {
|
|
349
|
+
memberStmts.push({
|
|
350
|
+
names,
|
|
351
|
+
finalRhs: cur,
|
|
352
|
+
viaBareExports,
|
|
353
|
+
pos: stmt.getStart(sf),
|
|
354
|
+
start: stmt.getStart(sf),
|
|
355
|
+
end: stmt.getEnd(),
|
|
356
|
+
});
|
|
357
|
+
continue; // classified later (only when a rewrite actually happens)
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// the dead esbuild annotation: `0 && (module.exports = { ... });`
|
|
361
|
+
if (ts.isBinaryExpression(e) &&
|
|
362
|
+
e.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&
|
|
363
|
+
ts.isNumericLiteral(e.left) &&
|
|
364
|
+
e.left.text === "0") {
|
|
365
|
+
let inner = e.right;
|
|
366
|
+
while (ts.isParenthesizedExpression(inner))
|
|
367
|
+
inner = inner.expression;
|
|
368
|
+
if (ts.isBinaryExpression(inner) &&
|
|
369
|
+
inner.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
|
|
370
|
+
isModuleExports(inner.left) &&
|
|
371
|
+
ts.isObjectLiteralExpression(inner.right)) {
|
|
372
|
+
neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() });
|
|
373
|
+
sawBundleShape = true;
|
|
374
|
+
continue;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (!ts.isCallExpression(e))
|
|
378
|
+
continue;
|
|
379
|
+
const name = calleeNameOf(e);
|
|
380
|
+
// `__export(TARGET, { getter table });`
|
|
381
|
+
const table = exportCallOf(e);
|
|
382
|
+
if (table !== null) {
|
|
383
|
+
exportTables.push(table);
|
|
384
|
+
neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() });
|
|
385
|
+
sawBundleShape = true;
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
// `__exportStar(require('spec'), exports);` — the tsc/tslib spelling —
|
|
389
|
+
// and `__export(require('spec'))`, the legacy twin.
|
|
390
|
+
if ((name === "__exportStar" || name === "__export") && e.arguments.length >= 1) {
|
|
391
|
+
const spec = bareRequireSpecOf(e.arguments[0]);
|
|
392
|
+
if (spec !== null) {
|
|
393
|
+
addStarSpec(spec);
|
|
394
|
+
neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() });
|
|
395
|
+
sawBundleShape = true;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
// `__reExport(TARGET, require('spec')[, module.exports]);` — esbuild's
|
|
400
|
+
// `export * from` plumbing.
|
|
401
|
+
if (name === "__reExport" && e.arguments.length >= 2) {
|
|
402
|
+
const spec = bareRequireSpecOf(e.arguments[1]);
|
|
403
|
+
if (spec !== null) {
|
|
404
|
+
addStarSpec(spec);
|
|
405
|
+
neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() });
|
|
406
|
+
sawBundleShape = true;
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// Object.defineProperty(exports, 'n', {...})
|
|
411
|
+
const def = definePropertyExportOf(e);
|
|
412
|
+
if (def !== null) {
|
|
413
|
+
if (def.esModuleStamp) {
|
|
414
|
+
neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() });
|
|
415
|
+
// the canonical table re-stamps it (lexer-visibly — merve detects
|
|
416
|
+
// this defineProperty form, so parity wants a visible entry)
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
const chased = def.value !== null ? chaseValue(def.value) : null;
|
|
420
|
+
if (chased !== null) {
|
|
421
|
+
definePropEntries.push({ name: def.name, value: def.value });
|
|
422
|
+
neutralize.push({ start: stmt.getStart(sf), end: stmt.getEnd() });
|
|
423
|
+
sawBundleShape = true;
|
|
424
|
+
}
|
|
425
|
+
// unchaseable descriptor: LEAVE the statement (its runtime fence is
|
|
426
|
+
// loud where an undefined-valued entry would be a silent lie); the
|
|
427
|
+
// name still types through the any-fallback below.
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
if (!sawBundleShape)
|
|
432
|
+
return null;
|
|
433
|
+
const lexed = cjsLexedExportsOf(source, filePath);
|
|
434
|
+
if (lexed.exports.size === 0 && lexed.reexports.length === 0)
|
|
435
|
+
return null;
|
|
436
|
+
/* Star specs Node actually honors are the lexer's SURVIVING reexports
|
|
437
|
+
* (annotation spreads for esbuild bundles, star-pattern calls for tsc
|
|
438
|
+
* output). The statements walked above contribute load order; specs the
|
|
439
|
+
* lexer dropped (cleared by a later module.exports=) drop here too. */
|
|
440
|
+
const lexerSpecs = new Set(lexed.reexports);
|
|
441
|
+
const starSpecs = starSpecsInOrder.filter((s) => lexerSpecs.has(s));
|
|
442
|
+
for (const s of lexed.reexports) {
|
|
443
|
+
if (!starSpecs.includes(s))
|
|
444
|
+
starSpecs.push(s);
|
|
445
|
+
}
|
|
446
|
+
/* The entry list, first-wins per name: own getter-table entries, then
|
|
447
|
+
* defineProperty entries, then surviving top-level member exports, then
|
|
448
|
+
* (for names still uncovered) star enumerations in spec order, then the
|
|
449
|
+
* any fallback for every remaining lexer-visible name. */
|
|
450
|
+
const entries = new Map();
|
|
451
|
+
const claim = (name, spec) => {
|
|
452
|
+
if (name === "__esModule" || RESERVED_KEYS.has(name))
|
|
453
|
+
return;
|
|
454
|
+
if (!entries.has(name))
|
|
455
|
+
entries.set(name, spec);
|
|
456
|
+
};
|
|
457
|
+
const classify = (name, value) => {
|
|
458
|
+
const chased = value !== null ? chaseValue(value) : null;
|
|
459
|
+
if (chased === null)
|
|
460
|
+
return { name, kind: "any" };
|
|
461
|
+
if (chased.kind === "ident")
|
|
462
|
+
return { name, kind: "ident", value: chased.text };
|
|
463
|
+
return { name, kind: "hoist", value: chased.text };
|
|
464
|
+
};
|
|
465
|
+
// Own names: the getter table(s) — the one feeding module.exports first.
|
|
466
|
+
const tablesOrdered = [
|
|
467
|
+
...exportTables.filter((t) => t.target === esbuildMainTarget),
|
|
468
|
+
...exportTables.filter((t) => t.target !== esbuildMainTarget),
|
|
469
|
+
];
|
|
470
|
+
for (const t of tablesOrdered) {
|
|
471
|
+
for (const [name, value] of t.entries)
|
|
472
|
+
claim(name, classify(name, value));
|
|
473
|
+
}
|
|
474
|
+
for (const d of definePropEntries)
|
|
475
|
+
claim(d.name, classify(d.name, d.value));
|
|
476
|
+
/* Top-level member exports. Node's export-object identity rules decide
|
|
477
|
+
* each statement's fate against the ORIGINAL table (esbuildMainTarget's
|
|
478
|
+
* assignment): a member attached BEFORE it was discarded (the table
|
|
479
|
+
* replaced the object), a bare `exports.N =` AFTER it wrote the stale
|
|
480
|
+
* object — both keep only their lexer-visible NAME (value undefined,
|
|
481
|
+
* exactly Node's link answer: the any fallback below). A surviving
|
|
482
|
+
* member (`module.exports.N =` after the table, or any spelling when no
|
|
483
|
+
* table exists) re-emits its VALUE as a canonical entry — identifier
|
|
484
|
+
* RHS only; any other RHS would need a second evaluation, so the file
|
|
485
|
+
* is left alone (the offender path owns it). Every member statement is
|
|
486
|
+
* then NEUTRALIZED: the canonical tail table replaces the object, and
|
|
487
|
+
* a stray earlier attach would meet the lowering's discard fence. */
|
|
488
|
+
for (const m of memberStmts) {
|
|
489
|
+
let cur = m.finalRhs;
|
|
490
|
+
while (ts.isParenthesizedExpression(cur))
|
|
491
|
+
cur = cur.expression;
|
|
492
|
+
const isVoidInit = cur.kind === ts.SyntaxKind.VoidExpression || (ts.isIdentifier(cur) && cur.text === "undefined");
|
|
493
|
+
const discarded = tablePos !== null && (m.pos < tablePos || m.viaBareExports);
|
|
494
|
+
if (!isVoidInit) {
|
|
495
|
+
// Only side-effect-free RHS shapes may be neutralized (identifiers,
|
|
496
|
+
// scalar literals, ident-rooted member chains — chaseValue's set);
|
|
497
|
+
// anything else keeps the file untouched (the offender path owns
|
|
498
|
+
// it: neutralizing would drop an evaluation, keeping it meets the
|
|
499
|
+
// lowering's discard fence at load).
|
|
500
|
+
const chased = chaseValue(cur);
|
|
501
|
+
if (chased === null)
|
|
502
|
+
return null;
|
|
503
|
+
if (!discarded) {
|
|
504
|
+
for (const name of m.names)
|
|
505
|
+
claim(name, { name, kind: chased.kind, value: chased.text });
|
|
506
|
+
}
|
|
507
|
+
// discarded members keep only their lexer-visible NAME (the any
|
|
508
|
+
// fallback below) — value undefined, exactly Node's link answer
|
|
509
|
+
}
|
|
510
|
+
neutralize.push({ start: m.start, end: m.end });
|
|
511
|
+
}
|
|
512
|
+
/* Direct-vs-hoist for ident entries: only bindings declared by a
|
|
513
|
+
* top-level const/function/class may be spelled directly (alias
|
|
514
|
+
* plumbing needs an immutable target — a `var`/`let` in the table would
|
|
515
|
+
* fence the whole statement). Everything else snapshots via a tail
|
|
516
|
+
* const. */
|
|
517
|
+
const immutableTopLevel = new Set();
|
|
518
|
+
for (const stmt of sf.statements) {
|
|
519
|
+
if (ts.isFunctionDeclaration(stmt) && stmt.name !== undefined)
|
|
520
|
+
immutableTopLevel.add(stmt.name.text);
|
|
521
|
+
else if (ts.isClassDeclaration(stmt) && stmt.name !== undefined)
|
|
522
|
+
immutableTopLevel.add(stmt.name.text);
|
|
523
|
+
else if (ts.isVariableStatement(stmt) && (stmt.declarationList.flags & ts.NodeFlags.Const) !== 0) {
|
|
524
|
+
for (const d of stmt.declarationList.declarations) {
|
|
525
|
+
if (ts.isIdentifier(d.name))
|
|
526
|
+
immutableTopLevel.add(d.name.text);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
for (const spec of entries.values()) {
|
|
531
|
+
if (spec.kind === "ident" && !immutableTopLevel.has(spec.value))
|
|
532
|
+
spec.kind = "hoist";
|
|
533
|
+
}
|
|
534
|
+
// Star enumerations for names the own entries left uncovered.
|
|
535
|
+
const starMembers = [];
|
|
536
|
+
const requireConsts = [];
|
|
537
|
+
starSpecs.forEach((spec, i) => {
|
|
538
|
+
const target = resolveRelativeCjs(filePath, spec);
|
|
539
|
+
if (target === null)
|
|
540
|
+
return; // bare/unresolved: the spread entry carries it
|
|
541
|
+
const names = starTargetNames(target);
|
|
542
|
+
let refVar = null;
|
|
543
|
+
for (const n of names) {
|
|
544
|
+
if (n === "default" || n === "__esModule" || entries.has(n) || RESERVED_KEYS.has(n))
|
|
545
|
+
continue;
|
|
546
|
+
if (starMembers.some((m) => m.name === n))
|
|
547
|
+
continue; // earlier spec wins
|
|
548
|
+
if (refVar === null) {
|
|
549
|
+
refVar = `__scriptc_r${i}`;
|
|
550
|
+
requireConsts.push(`const ${refVar} = require(${JSON.stringify(spec)});`);
|
|
551
|
+
}
|
|
552
|
+
starMembers.push({ name: n, ref: refVar });
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
// The any fallback: every lexer-visible name nothing above covered.
|
|
556
|
+
for (const n of lexed.exports)
|
|
557
|
+
claim(n, { name: n, kind: "any" });
|
|
558
|
+
if (entries.size === 0 && starSpecs.length === 0)
|
|
559
|
+
return null;
|
|
560
|
+
/* Bundler HELPPER declarations left unused once the plumbing statements
|
|
561
|
+
* are gone would still fence at module load (`var __defProp =
|
|
562
|
+
* Object.defineProperty;` — a stdlib generic method as a value has no
|
|
563
|
+
* lowering, and a top-level declaration's fence throws when init runs
|
|
564
|
+
* it). Mark-and-sweep the RECOGNIZED helper names: a helper whose every
|
|
565
|
+
* reference lies inside an already-neutralized span (the plumbing
|
|
566
|
+
* calls, or another swept helper's declaration) is neutralized too.
|
|
567
|
+
* Helpers live code still calls (__toESM interop wrappers, __commonJS
|
|
568
|
+
* chunk factories) stay — their declarations are plain function values
|
|
569
|
+
* whose bodies defer per the JS function-poison rule. */
|
|
570
|
+
{
|
|
571
|
+
const HELPER_NAMES = new Set([
|
|
572
|
+
"__create", "__defProp", "__defProps", "__getOwnPropDesc", "__getOwnPropDescs",
|
|
573
|
+
"__getOwnPropNames", "__getOwnPropSymbols", "__getProtoOf", "__hasOwnProp",
|
|
574
|
+
"__propIsEnum", "__export", "__copyProps", "__reExport", "__toESM", "__toCommonJS",
|
|
575
|
+
"__exportStar", "__createBinding", "__setModuleDefault", "__importStar", "__importDefault",
|
|
576
|
+
]);
|
|
577
|
+
const helperDecls = new Map();
|
|
578
|
+
for (const stmt of sf.statements) {
|
|
579
|
+
if (!ts.isVariableStatement(stmt))
|
|
580
|
+
continue;
|
|
581
|
+
const decls = stmt.declarationList.declarations;
|
|
582
|
+
if (decls.length !== 1)
|
|
583
|
+
continue;
|
|
584
|
+
const d = decls[0];
|
|
585
|
+
if (ts.isIdentifier(d.name) && HELPER_NAMES.has(d.name.text)) {
|
|
586
|
+
helperDecls.set(d.name.text, { start: stmt.getStart(sf), end: stmt.getEnd() });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
// every identifier occurrence of a helper name outside its own
|
|
590
|
+
// declarator name node, position-recorded for the span test
|
|
591
|
+
const refs = new Map();
|
|
592
|
+
const record = (node) => {
|
|
593
|
+
ts.forEachChild(node, record);
|
|
594
|
+
if (ts.isIdentifier(node) && helperDecls.has(node.text)) {
|
|
595
|
+
const parent = node.parent;
|
|
596
|
+
if (parent !== undefined && ts.isVariableDeclaration(parent) && parent.name === node)
|
|
597
|
+
return;
|
|
598
|
+
const list = refs.get(node.text);
|
|
599
|
+
if (list === undefined)
|
|
600
|
+
refs.set(node.text, [node.getStart(sf)]);
|
|
601
|
+
else
|
|
602
|
+
list.push(node.getStart(sf));
|
|
603
|
+
}
|
|
604
|
+
};
|
|
605
|
+
record(sf);
|
|
606
|
+
const dead = [...neutralize];
|
|
607
|
+
const inDead = (pos) => dead.some((s) => pos >= s.start && pos < s.end);
|
|
608
|
+
const removed = new Set();
|
|
609
|
+
for (let changed = true; changed;) {
|
|
610
|
+
changed = false;
|
|
611
|
+
for (const [name, span] of helperDecls) {
|
|
612
|
+
if (removed.has(name))
|
|
613
|
+
continue;
|
|
614
|
+
// SELF-references inside the candidate's own declaration don't
|
|
615
|
+
// keep it alive (`var __createBinding = (this && this.__createBinding)
|
|
616
|
+
// || ...` — the tsc helper's UMD-style self probe).
|
|
617
|
+
const live = (refs.get(name) ?? []).filter((p) => p < span.start || p >= span.end);
|
|
618
|
+
if (live.every(inDead)) {
|
|
619
|
+
removed.add(name);
|
|
620
|
+
dead.push(span);
|
|
621
|
+
changed = true;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
neutralize.length = 0;
|
|
626
|
+
neutralize.push(...dead);
|
|
627
|
+
}
|
|
628
|
+
/* ── emission ─────────────────────────────────────────────────────── */
|
|
629
|
+
let text = source;
|
|
630
|
+
// pad from the END so earlier spans' offsets stay valid
|
|
631
|
+
for (const n of [...neutralize].sort((a, b) => b.start - a.start)) {
|
|
632
|
+
text = padSpan(text, n.start, n.end);
|
|
633
|
+
}
|
|
634
|
+
const lines = [""];
|
|
635
|
+
const hasAnyFallback = [...entries.values()].some((s) => s.kind === "any");
|
|
636
|
+
if (hasAnyFallback) {
|
|
637
|
+
// checked-dynamic undefined: an implicit-any IIFE result registers a
|
|
638
|
+
// dyn module global the table's alias plumbing can resolve.
|
|
639
|
+
lines.push("const __scriptc_any = (() => { let u; return u; })();");
|
|
640
|
+
}
|
|
641
|
+
lines.push(...requireConsts);
|
|
642
|
+
const hoists = [];
|
|
643
|
+
let hoistN = 0;
|
|
644
|
+
const keyOf = (name) => (isPlainName(name) ? name : JSON.stringify(name));
|
|
645
|
+
const plainParts = [];
|
|
646
|
+
for (const spec of entries.values()) {
|
|
647
|
+
if (spec.kind === "ident") {
|
|
648
|
+
plainParts.push(`${keyOf(spec.name)}: ${spec.value},`);
|
|
649
|
+
}
|
|
650
|
+
else if (spec.kind === "hoist") {
|
|
651
|
+
const v = `__scriptc_e${hoistN++}`;
|
|
652
|
+
hoists.push(`const ${v} = ${spec.value};`);
|
|
653
|
+
plainParts.push(`${keyOf(spec.name)}: ${v},`);
|
|
654
|
+
}
|
|
655
|
+
else {
|
|
656
|
+
plainParts.push(`${keyOf(spec.name)}: __scriptc_any,`);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
lines.push(...hoists);
|
|
660
|
+
/* The table. Spread entries first (REVERSED: Node's star copies are
|
|
661
|
+
* first-wins, an object literal's later-wins — reversing makes them
|
|
662
|
+
* agree), then the scan-visible plain entries, then — only where the
|
|
663
|
+
* original stamped it LEXER-DETECTABLY (the tsc defineProperty form;
|
|
664
|
+
* Node links `import { __esModule }` there) — the `__esModule: true`
|
|
665
|
+
* entry, then the member-access star entries whose names the spread
|
|
666
|
+
* records already cover. esbuild's marker lives inside __toCommonJS
|
|
667
|
+
* where Node's lexer never saw it, and spelling it here would flip the
|
|
668
|
+
* checker's default-import interop (an __esModule-stamped surface binds
|
|
669
|
+
* `import def` to exports.default) away from Node's CJS answer — so it
|
|
670
|
+
* is deliberately OMITTED from the canonical table. */
|
|
671
|
+
const spreadParts = [...starSpecs].reverse().map((s) => `...require(${JSON.stringify(s)}),`);
|
|
672
|
+
const esModulePart = lexed.exports.has("__esModule") ? "__esModule: true," : "";
|
|
673
|
+
const memberParts = starMembers.map((m) => `${keyOf(m.name)}: ${m.ref}${isPlainName(m.name) ? `.${m.name}` : `[${JSON.stringify(m.name)}]`},`);
|
|
674
|
+
lines.push(`module.exports = {${spreadParts.join("")}${plainParts.join("")}${esModulePart}${memberParts.join("")}};`);
|
|
675
|
+
return text + lines.join("\n") + "\n";
|
|
676
|
+
}
|
|
677
|
+
//# sourceMappingURL=npm-static-rewrite.js.map
|