@backtickjs/compiler 0.0.0-stage → 0.1.1
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 +21 -0
- package/README.md +25 -2
- package/dist/CodeInformation.d.ts +8 -0
- package/dist/CodeInformation.js +2 -0
- package/dist/CodeInformation.js.map +1 -0
- package/dist/SourceRange.d.ts +4 -0
- package/dist/SourceRange.js +2 -0
- package/dist/SourceRange.js.map +1 -0
- package/dist/buildMappings.d.ts +10 -0
- package/dist/buildMappings.js +21 -0
- package/dist/buildMappings.js.map +1 -0
- package/dist/compileModule.d.ts +25 -0
- package/dist/compileModule.js +78 -0
- package/dist/compileModule.js.map +1 -0
- package/dist/compileScript.d.ts +24 -0
- package/dist/compileScript.js +66 -0
- package/dist/compileScript.js.map +1 -0
- package/dist/diagnostics.d.ts +8 -0
- package/dist/diagnostics.js +2 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/emitScript.d.ts +52 -0
- package/dist/emitScript.js +201 -0
- package/dist/emitScript.js.map +1 -0
- package/dist/emitScripts.d.ts +10 -0
- package/dist/emitScripts.js +23 -0
- package/dist/emitScripts.js.map +1 -0
- package/dist/flattenScripts.d.ts +2 -0
- package/dist/flattenScripts.js +10 -0
- package/dist/flattenScripts.js.map +1 -0
- package/dist/hashText.d.ts +1 -0
- package/dist/hashText.js +18 -0
- package/dist/hashText.js.map +1 -0
- package/dist/hostElements.d.ts +3 -0
- package/dist/hostElements.js +28 -0
- package/dist/hostElements.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/isComponentTag.d.ts +1 -0
- package/dist/isComponentTag.js +15 -0
- package/dist/isComponentTag.js.map +1 -0
- package/dist/nodeFactory.d.ts +3 -0
- package/dist/nodeFactory.js +5 -0
- package/dist/nodeFactory.js.map +1 -0
- package/dist/parseFile.d.ts +34 -0
- package/dist/parseFile.js +200 -0
- package/dist/parseFile.js.map +1 -0
- package/dist/printVirtualCode.d.ts +9 -0
- package/dist/printVirtualCode.js +52 -0
- package/dist/printVirtualCode.js.map +1 -0
- package/dist/resolveBindings.d.ts +76 -0
- package/dist/resolveBindings.js +240 -0
- package/dist/resolveBindings.js.map +1 -0
- package/dist/rewriteFile.d.ts +12 -0
- package/dist/rewriteFile.js +22 -0
- package/dist/rewriteFile.js.map +1 -0
- package/dist/rewriteScript.d.ts +27 -0
- package/dist/rewriteScript.js +268 -0
- package/dist/rewriteScript.js.map +1 -0
- package/dist/segmentsToString.d.ts +9 -0
- package/dist/segmentsToString.js +4 -0
- package/dist/segmentsToString.js.map +1 -0
- package/dist/tagRoot.d.ts +2 -0
- package/dist/tagRoot.js +10 -0
- package/dist/tagRoot.js.map +1 -0
- package/dist/transform.d.ts +17 -0
- package/dist/transform.js +61 -0
- package/dist/transform.js.map +1 -0
- package/dist/transpile.d.ts +27 -0
- package/dist/transpile.js +41 -0
- package/dist/transpile.js.map +1 -0
- package/dist/unmangle.d.ts +2 -0
- package/dist/unmangle.js +9 -0
- package/dist/unmangle.js.map +1 -0
- package/dist/virtualScript.d.ts +22 -0
- package/dist/virtualScript.js +305 -0
- package/dist/virtualScript.js.map +1 -0
- package/dist/virtualize.d.ts +8 -0
- package/dist/virtualize.js +10 -0
- package/dist/virtualize.js.map +1 -0
- package/package.json +58 -3
- package/src/CodeInformation.ts +14 -0
- package/src/SourceRange.ts +4 -0
- package/src/buildMappings.ts +31 -0
- package/src/compileModule.ts +132 -0
- package/src/compileScript.ts +97 -0
- package/src/diagnostics.ts +9 -0
- package/src/emitScript.ts +297 -0
- package/src/emitScripts.ts +39 -0
- package/src/flattenScripts.ts +13 -0
- package/src/hashText.ts +17 -0
- package/src/hostElements.ts +34 -0
- package/src/index.ts +20 -0
- package/src/isComponentTag.ts +15 -0
- package/src/nodeFactory.ts +18 -0
- package/src/parseFile.ts +331 -0
- package/src/printVirtualCode.ts +93 -0
- package/src/resolveBindings.ts +417 -0
- package/src/rewriteFile.ts +52 -0
- package/src/rewriteScript.ts +413 -0
- package/src/segmentsToString.ts +16 -0
- package/src/tagRoot.ts +14 -0
- package/src/transform.ts +96 -0
- package/src/transpile.ts +49 -0
- package/src/unmangle.ts +10 -0
- package/src/virtualScript.ts +355 -0
- package/src/virtualize.ts +29 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
import type { ClientScript } from "./parseFile.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Every client script in a file, its scopes resolved by TypeScript's checker,
|
|
6
|
+
* so any syntax TypeScript reads is resolved as the language does. It produces
|
|
7
|
+
* two things:
|
|
8
|
+
*
|
|
9
|
+
* - `bindings`: every *bound* variable identifier — a declaration, an arrow
|
|
10
|
+
* parameter, or a reference that resolves to one of those — mapped to a
|
|
11
|
+
* stable, globally unique binding key: what a capture is threaded by, and
|
|
12
|
+
* what the virtual code renames a script's own bindings from.
|
|
13
|
+
*
|
|
14
|
+
* - `params`: for each script, its parameters in order (see `ResolvedParam`):
|
|
15
|
+
*
|
|
16
|
+
* - each splice it reads, a tag's (`<$Card>`) among them, with where it
|
|
17
|
+
* reads it and `bindings`: the script's own bindings a fragment landing
|
|
18
|
+
* at that hole can reach, what the hole must hand whatever arrives.
|
|
19
|
+
* Unlike counting what actually reached a hole in one bundle, it is a
|
|
20
|
+
* fact about the script alone.
|
|
21
|
+
* - each capture: a free variable it references but does not itself
|
|
22
|
+
* declare, which it must capture from the enclosing scope, as a binding
|
|
23
|
+
* key. Captures are ordered by first use, which falls out of the
|
|
24
|
+
* source-order walk. A name bound by no script at all is not a capture:
|
|
25
|
+
* the rewrite reads it as a global or reports it as unresolvable.
|
|
26
|
+
*
|
|
27
|
+
* Two filters, and both matter. Declared *above* the hole, not merely in
|
|
28
|
+
* scope: a hoisted declaration is in scope before the source reaches it,
|
|
29
|
+
* and handing it over would name a binding inside its own initializer. And
|
|
30
|
+
* captured by something, somewhere: a binding no fragment ever wants is
|
|
31
|
+
* dead weight in every thunk written for that hole.
|
|
32
|
+
*
|
|
33
|
+
* Scripts are checked as one text: each written `(() => …)`, a nested script
|
|
34
|
+
* where its placeholder stands, as it lands at runtime. A reference is free
|
|
35
|
+
* for the script it appears in exactly when the binding it resolves to was
|
|
36
|
+
* declared in an *enclosing* script (or in none at all).
|
|
37
|
+
*
|
|
38
|
+
* Uniqueness of keys has two axes. A per-file counter distinguishes bindings
|
|
39
|
+
* *within* a file; a hash of the file's text (see `hashText`) distinguishes
|
|
40
|
+
* bindings *across* files — necessary because the compiler runs one file at a
|
|
41
|
+
* time and so cannot hand out globally coordinated numbers. Together
|
|
42
|
+
* `<name>$<fileHash>$<n>` is
|
|
43
|
+
* unique across the whole program, so fragments composed from different scripts
|
|
44
|
+
* (even different files) never collide, and the serializer never has to rename a
|
|
45
|
+
* capture to dodge a same-named binding it threads through.
|
|
46
|
+
*
|
|
47
|
+
* Resolution spans scripts: a reference in a nested script
|
|
48
|
+
* (`cs`{ const x = 0; return ${cs`x`}; }``) resolves to the enclosing binding
|
|
49
|
+
* and so shares its key, keeping independently rewritten scripts consistent. A
|
|
50
|
+
* splice placeholder is not a variable — it evaluates host code in the enclosing
|
|
51
|
+
* scope — so it is never captured, though the pass descends into any scripts
|
|
52
|
+
* nested inside it so their references resolve against this scope chain.
|
|
53
|
+
*/
|
|
54
|
+
export type BindingResolution = Map<ts.Identifier, string>;
|
|
55
|
+
|
|
56
|
+
export interface ResolvedScopes {
|
|
57
|
+
bindings: BindingResolution;
|
|
58
|
+
// `params.get(script)[i]` is the script's parameter `i` (`$splice<i>`,
|
|
59
|
+
// `$capture<i>`): splices in the order the script first reads them, then
|
|
60
|
+
// captures
|
|
61
|
+
params: Map<ClientScript, ResolvedParam[]>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* One of a script's parameters as the compiler knows it: `ClientScript`'s `Param`,
|
|
66
|
+
* with the splice's key and where it is read in place of its host value.
|
|
67
|
+
* `refs` are a hole's placeholder, or an unbraced `$name`, a tag's name among
|
|
68
|
+
* them.
|
|
69
|
+
*/
|
|
70
|
+
export type ResolvedParam = ResolvedSplice | ResolvedCapture;
|
|
71
|
+
|
|
72
|
+
interface ResolvedSplice {
|
|
73
|
+
kind: "splice";
|
|
74
|
+
key: string;
|
|
75
|
+
bindings: string[];
|
|
76
|
+
refs: ts.Identifier[];
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
interface ResolvedCapture {
|
|
80
|
+
kind: "capture";
|
|
81
|
+
key: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// A script's parameters as the pass finds them, by key.
|
|
85
|
+
interface ScriptParams {
|
|
86
|
+
// in first-use order, tags among them
|
|
87
|
+
splices: ResolvedSplice[];
|
|
88
|
+
// in first-use order
|
|
89
|
+
captures: Map<string, ResolvedCapture>;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Where each piece of the combined text came from.
|
|
93
|
+
interface Piece {
|
|
94
|
+
start: number;
|
|
95
|
+
length: number;
|
|
96
|
+
script: ClientScript;
|
|
97
|
+
// the piece's offset in the script's placeholder text
|
|
98
|
+
from: number;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
interface Combined {
|
|
102
|
+
text: string;
|
|
103
|
+
pieces: Piece[];
|
|
104
|
+
// each script's enclosing script, for the top-level ones none
|
|
105
|
+
parents: Map<ClientScript, ClientScript | undefined>;
|
|
106
|
+
// where a script's own identifier stands in the combined text; a
|
|
107
|
+
// placeholder its nested scripts replace, where they start
|
|
108
|
+
positions: Map<ts.Identifier, number>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function resolveBindings(
|
|
112
|
+
ts: typeof import("typescript"),
|
|
113
|
+
scripts: ClientScript[],
|
|
114
|
+
fileHash: string,
|
|
115
|
+
): ResolvedScopes {
|
|
116
|
+
const combined = combine(ts, scripts);
|
|
117
|
+
const file = ts.createSourceFile(
|
|
118
|
+
"scripts.tsx",
|
|
119
|
+
combined.text,
|
|
120
|
+
ts.ScriptTarget.Latest,
|
|
121
|
+
true,
|
|
122
|
+
ts.ScriptKind.TSX,
|
|
123
|
+
);
|
|
124
|
+
const checker = checkerOf(ts, file);
|
|
125
|
+
const binding =
|
|
126
|
+
ts.SymbolFlags.Variable | ts.SymbolFlags.Function | ts.SymbolFlags.Class;
|
|
127
|
+
|
|
128
|
+
const scriptAt = (position: number): ClientScript | undefined =>
|
|
129
|
+
combined.pieces.find(
|
|
130
|
+
(piece) =>
|
|
131
|
+
position >= piece.start && position < piece.start + piece.length,
|
|
132
|
+
)?.script;
|
|
133
|
+
const own = new Map<ClientScript, Map<number, ts.Identifier>>();
|
|
134
|
+
// The script's own identifier written at a combined position.
|
|
135
|
+
const identifierAt = (position: number): ts.Identifier | undefined => {
|
|
136
|
+
const piece = combined.pieces.find(
|
|
137
|
+
(each) => position >= each.start && position < each.start + each.length,
|
|
138
|
+
);
|
|
139
|
+
if (piece === undefined) {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
let identifiers = own.get(piece.script);
|
|
143
|
+
if (identifiers === undefined) {
|
|
144
|
+
identifiers = identifiersOf(ts, piece.script.fileWithPlaceholders);
|
|
145
|
+
own.set(piece.script, identifiers);
|
|
146
|
+
}
|
|
147
|
+
return identifiers.get(piece.from + position - piece.start);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const scriptParams = new Map<ClientScript, ScriptParams>();
|
|
151
|
+
for (const script of combined.parents.keys()) {
|
|
152
|
+
scriptParams.set(script, {
|
|
153
|
+
splices: Object.values(script.splices).map(({ key, refs }) => ({
|
|
154
|
+
kind: "splice",
|
|
155
|
+
key,
|
|
156
|
+
bindings: [],
|
|
157
|
+
refs,
|
|
158
|
+
})),
|
|
159
|
+
captures: new Map(),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// A binding's key, its declaring script and where it is declared, minted
|
|
164
|
+
// the first time the pass meets it, so keys count up in source order.
|
|
165
|
+
const bindings: BindingResolution = new Map();
|
|
166
|
+
const keys = new Map<ts.Declaration, string>();
|
|
167
|
+
const owner = new Map<string, ClientScript>();
|
|
168
|
+
const declaredAt = new Map<string, ts.Declaration>();
|
|
169
|
+
let next = 0;
|
|
170
|
+
const keyOf = (declaration: ts.Declaration): string => {
|
|
171
|
+
let key = keys.get(declaration);
|
|
172
|
+
if (key === undefined) {
|
|
173
|
+
const name = ts.getNameOfDeclaration(declaration);
|
|
174
|
+
key = `${name && ts.isIdentifier(name) ? name.text : "_"}$${fileHash}$${next++}`;
|
|
175
|
+
keys.set(declaration, key);
|
|
176
|
+
owner.set(key, scriptAt(declaration.getStart(file))!);
|
|
177
|
+
declaredAt.set(key, declaration);
|
|
178
|
+
}
|
|
179
|
+
return key;
|
|
180
|
+
};
|
|
181
|
+
// The binding a name resolves to, where the scripts declare it.
|
|
182
|
+
const declarationOf = (
|
|
183
|
+
identifier: ts.Identifier,
|
|
184
|
+
): ts.Declaration | undefined => {
|
|
185
|
+
const symbol =
|
|
186
|
+
ts.isShorthandPropertyAssignment(identifier.parent) &&
|
|
187
|
+
identifier.parent.name === identifier
|
|
188
|
+
? checker.getShorthandAssignmentValueSymbol(identifier.parent)
|
|
189
|
+
: checker.getSymbolAtLocation(identifier);
|
|
190
|
+
return symbol !== undefined && symbol.flags & binding
|
|
191
|
+
? symbol.declarations?.find(
|
|
192
|
+
(declaration) => declaration.getSourceFile() === file,
|
|
193
|
+
)
|
|
194
|
+
: undefined;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Every binding some nested script captures, whatever hole it was written at.
|
|
198
|
+
const escaped = new Set<string>();
|
|
199
|
+
|
|
200
|
+
const visit = (node: ts.Node): void => {
|
|
201
|
+
// A type's names are never runtime bindings, but for a `typeof` and a
|
|
202
|
+
// class's `extends`, which read values.
|
|
203
|
+
if (
|
|
204
|
+
ts.isTypeNode(node) &&
|
|
205
|
+
!ts.isTypeQueryNode(node) &&
|
|
206
|
+
!(
|
|
207
|
+
ts.isExpressionWithTypeArguments(node) &&
|
|
208
|
+
ts.isHeritageClause(node.parent) &&
|
|
209
|
+
ts.isClassLike(node.parent.parent)
|
|
210
|
+
)
|
|
211
|
+
) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (ts.isIdentifier(node)) {
|
|
215
|
+
const script = scriptAt(node.getStart(file));
|
|
216
|
+
const declaration = declarationOf(node);
|
|
217
|
+
if (script === undefined) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (declaration === undefined) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
const key = keyOf(declaration);
|
|
224
|
+
const identifier = identifierAt(node.getStart(file));
|
|
225
|
+
if (identifier !== undefined) {
|
|
226
|
+
bindings.set(identifier, key);
|
|
227
|
+
}
|
|
228
|
+
// Captured by the script that reads it and by every one between it and
|
|
229
|
+
// the script that declared it: each hands it to the next.
|
|
230
|
+
const from = owner.get(key);
|
|
231
|
+
for (
|
|
232
|
+
let reader: ClientScript | undefined = script;
|
|
233
|
+
reader !== undefined && reader !== from;
|
|
234
|
+
reader = combined.parents.get(reader)
|
|
235
|
+
) {
|
|
236
|
+
const captures = scriptParams.get(reader)!.captures;
|
|
237
|
+
if (!captures.has(key)) {
|
|
238
|
+
captures.set(key, { kind: "capture", key });
|
|
239
|
+
}
|
|
240
|
+
escaped.add(key);
|
|
241
|
+
}
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
node.forEachChild(visit);
|
|
245
|
+
};
|
|
246
|
+
visit(file);
|
|
247
|
+
|
|
248
|
+
// Each splice where the script reads it, and what a fragment landing there
|
|
249
|
+
// may be handed: the script's own bindings in scope, by a name nothing
|
|
250
|
+
// shadows, whose declaration the source has passed.
|
|
251
|
+
for (const [script, { splices }] of scriptParams) {
|
|
252
|
+
for (const splice of splices) {
|
|
253
|
+
for (const ref of splice.refs) {
|
|
254
|
+
const position = combined.positions.get(ref)!;
|
|
255
|
+
const location = nodeAt(file, position);
|
|
256
|
+
splice.bindings = checker
|
|
257
|
+
.getSymbolsInScope(location, binding)
|
|
258
|
+
.flatMap((symbol) => {
|
|
259
|
+
const declaration = symbol.declarations?.find(
|
|
260
|
+
(each) => each.getSourceFile() === file,
|
|
261
|
+
);
|
|
262
|
+
if (
|
|
263
|
+
declaration === undefined ||
|
|
264
|
+
declared(ts, declaration).getEnd() > position
|
|
265
|
+
) {
|
|
266
|
+
return [];
|
|
267
|
+
}
|
|
268
|
+
const key = keyOf(declaration);
|
|
269
|
+
return owner.get(key) === script ? [key] : [];
|
|
270
|
+
})
|
|
271
|
+
.sort(
|
|
272
|
+
(a, b) =>
|
|
273
|
+
declaredAt.get(a)!.getStart(file) -
|
|
274
|
+
declaredAt.get(b)!.getStart(file),
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const params = new Map<ClientScript, ResolvedParam[]>();
|
|
281
|
+
for (const [script, { splices, captures }] of scriptParams) {
|
|
282
|
+
for (const splice of splices) {
|
|
283
|
+
// Narrowed only now: whether anything captures a binding is not known
|
|
284
|
+
// until every script that could has been read.
|
|
285
|
+
splice.bindings = splice.bindings.filter((key) => escaped.has(key));
|
|
286
|
+
}
|
|
287
|
+
params.set(script, [...splices, ...captures.values()]);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return { bindings, params };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// The scripts as one text, each `(() => text)`, a nested script written where
|
|
294
|
+
// its placeholder stands.
|
|
295
|
+
function combine(
|
|
296
|
+
ts: typeof import("typescript"),
|
|
297
|
+
scripts: ClientScript[],
|
|
298
|
+
): Combined {
|
|
299
|
+
const combined: Combined = {
|
|
300
|
+
text: "",
|
|
301
|
+
pieces: [],
|
|
302
|
+
parents: new Map(),
|
|
303
|
+
positions: new Map(),
|
|
304
|
+
};
|
|
305
|
+
const emit = (script: ClientScript, parent?: ClientScript): void => {
|
|
306
|
+
combined.parents.set(script, parent);
|
|
307
|
+
const identifiers = identifiersOf(ts, script.fileWithPlaceholders);
|
|
308
|
+
const holes = [...identifiers.values()].filter((identifier) => {
|
|
309
|
+
const splice = script.splices[identifier.text];
|
|
310
|
+
return splice?.kind === "braced" && splice.scripts.length > 0;
|
|
311
|
+
});
|
|
312
|
+
combined.text += "(() => ";
|
|
313
|
+
let at = 0;
|
|
314
|
+
const copy = (to: number): void => {
|
|
315
|
+
const start = combined.text.length;
|
|
316
|
+
combined.pieces.push({ start, length: to - at, script, from: at });
|
|
317
|
+
for (const [offset, identifier] of identifiers) {
|
|
318
|
+
if (offset >= at && offset < to) {
|
|
319
|
+
combined.positions.set(identifier, start + offset - at);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
combined.text += script.textWithPlaceholders.slice(at, to);
|
|
323
|
+
at = to;
|
|
324
|
+
};
|
|
325
|
+
for (const hole of holes) {
|
|
326
|
+
copy(hole.getStart(script.fileWithPlaceholders));
|
|
327
|
+
combined.positions.set(hole, combined.text.length);
|
|
328
|
+
const splice = script.splices[hole.text]!;
|
|
329
|
+
combined.text += "[";
|
|
330
|
+
splice.scripts.forEach((nested, index) => {
|
|
331
|
+
combined.text += index === 0 ? "" : ", ";
|
|
332
|
+
emit(nested, script);
|
|
333
|
+
});
|
|
334
|
+
combined.text += "]";
|
|
335
|
+
at = hole.getEnd();
|
|
336
|
+
}
|
|
337
|
+
copy(script.textWithPlaceholders.length);
|
|
338
|
+
combined.text += ")";
|
|
339
|
+
};
|
|
340
|
+
for (const script of scripts) {
|
|
341
|
+
emit(script);
|
|
342
|
+
combined.text += ";\n";
|
|
343
|
+
}
|
|
344
|
+
return combined;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// A checker over the one file: no libraries, as only the scripts' own
|
|
348
|
+
// bindings are asked about.
|
|
349
|
+
function checkerOf(
|
|
350
|
+
ts: typeof import("typescript"),
|
|
351
|
+
file: ts.SourceFile,
|
|
352
|
+
): ts.TypeChecker {
|
|
353
|
+
const host: ts.CompilerHost = {
|
|
354
|
+
getSourceFile: (name) => (name === file.fileName ? file : undefined),
|
|
355
|
+
getDefaultLibFileName: () => "lib.d.ts",
|
|
356
|
+
writeFile: () => {},
|
|
357
|
+
getCurrentDirectory: () => "/",
|
|
358
|
+
getCanonicalFileName: (name) => name,
|
|
359
|
+
useCaseSensitiveFileNames: () => true,
|
|
360
|
+
getNewLine: () => "\n",
|
|
361
|
+
fileExists: (name) => name === file.fileName,
|
|
362
|
+
readFile: () => undefined,
|
|
363
|
+
};
|
|
364
|
+
return ts
|
|
365
|
+
.createProgram(
|
|
366
|
+
[file.fileName],
|
|
367
|
+
{ noLib: true, noResolve: true, types: [], jsx: ts.JsxEmit.Preserve },
|
|
368
|
+
host,
|
|
369
|
+
)
|
|
370
|
+
.getTypeChecker();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// A script's own identifiers, by where they start in its placeholder text.
|
|
374
|
+
function identifiersOf(
|
|
375
|
+
ts: typeof import("typescript"),
|
|
376
|
+
file: ts.SourceFile,
|
|
377
|
+
): Map<number, ts.Identifier> {
|
|
378
|
+
const identifiers = new Map<number, ts.Identifier>();
|
|
379
|
+
const visit = (node: ts.Node): void => {
|
|
380
|
+
if (ts.isIdentifier(node)) {
|
|
381
|
+
identifiers.set(node.getStart(file), node);
|
|
382
|
+
}
|
|
383
|
+
node.forEachChild(visit);
|
|
384
|
+
};
|
|
385
|
+
visit(file);
|
|
386
|
+
return identifiers;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// The innermost node starting at a position.
|
|
390
|
+
function nodeAt(file: ts.SourceFile, position: number): ts.Node {
|
|
391
|
+
let found: ts.Node = file;
|
|
392
|
+
const visit = (node: ts.Node): void => {
|
|
393
|
+
if (node.getStart(file) <= position && position < node.getEnd()) {
|
|
394
|
+
found = node;
|
|
395
|
+
node.forEachChild(visit);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
file.forEachChild(visit);
|
|
399
|
+
return found;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// What declares a name, whole: a name a pattern destructures is declared when
|
|
403
|
+
// the declaration it is in is, initializer and all, not where the pattern ends.
|
|
404
|
+
function declared(
|
|
405
|
+
ts: typeof import("typescript"),
|
|
406
|
+
declaration: ts.Declaration,
|
|
407
|
+
): ts.Node {
|
|
408
|
+
let at: ts.Node = declaration;
|
|
409
|
+
while (
|
|
410
|
+
ts.isBindingElement(at) ||
|
|
411
|
+
ts.isObjectBindingPattern(at) ||
|
|
412
|
+
ts.isArrayBindingPattern(at)
|
|
413
|
+
) {
|
|
414
|
+
at = at.parent;
|
|
415
|
+
}
|
|
416
|
+
return at;
|
|
417
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
import type { Diagnostic } from "./diagnostics.js";
|
|
3
|
+
import { flattenScripts } from "./flattenScripts.js";
|
|
4
|
+
import { hashText } from "./hashText.js";
|
|
5
|
+
import { hostElementDiagnostics } from "./hostElements.js";
|
|
6
|
+
import type { ParsedFile } from "./parseFile.js";
|
|
7
|
+
import { type BindingResolution, resolveBindings } from "./resolveBindings.js";
|
|
8
|
+
import { type RewrittenScript, rewriteScript } from "./rewriteScript.js";
|
|
9
|
+
|
|
10
|
+
export interface RewrittenFile {
|
|
11
|
+
sourceFile: ts.SourceFile;
|
|
12
|
+
scripts: Map<ts.TaggedTemplateExpression, RewrittenScript>;
|
|
13
|
+
// every script's own bindings, by identifier (see `resolveBindings`)
|
|
14
|
+
bindings: BindingResolution;
|
|
15
|
+
diagnostics: Diagnostic[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function rewriteFile(
|
|
19
|
+
ts: typeof import("typescript"),
|
|
20
|
+
parsedFile: ParsedFile,
|
|
21
|
+
): RewrittenFile {
|
|
22
|
+
const sourceFile = parsedFile.sourceFile;
|
|
23
|
+
|
|
24
|
+
const scripts = new Map<ts.TaggedTemplateExpression, RewrittenScript>();
|
|
25
|
+
const diagnostics: Diagnostic[] = [];
|
|
26
|
+
|
|
27
|
+
// One hash per file, shared by binding keys and script locations: both need
|
|
28
|
+
// to distinguish this file from a same-named file in another codebase.
|
|
29
|
+
const fileHash = hashText(sourceFile.text);
|
|
30
|
+
|
|
31
|
+
const { bindings, params } = resolveBindings(
|
|
32
|
+
ts,
|
|
33
|
+
parsedFile.scripts,
|
|
34
|
+
fileHash,
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
for (const script of flattenScripts(parsedFile.scripts)) {
|
|
38
|
+
const rewritten = rewriteScript(
|
|
39
|
+
ts,
|
|
40
|
+
script,
|
|
41
|
+
fileHash,
|
|
42
|
+
bindings,
|
|
43
|
+
params.get(script),
|
|
44
|
+
);
|
|
45
|
+
scripts.set(script.sourceNode, rewritten);
|
|
46
|
+
diagnostics.push(...rewritten.diagnostics);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
diagnostics.push(...hostElementDiagnostics(ts, sourceFile));
|
|
50
|
+
|
|
51
|
+
return { sourceFile, scripts, bindings, diagnostics };
|
|
52
|
+
}
|