@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
package/src/index.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type { SourceMapping } from "./buildMappings.js";
|
|
2
|
+
export type { CodeInformation } from "./CodeInformation.js";
|
|
3
|
+
export type { Diagnostic } from "./diagnostics.js";
|
|
4
|
+
export type { EmittedScript } from "./emitScript.js";
|
|
5
|
+
export { emitScripts, type EmittedScriptAt } from "./emitScripts.js";
|
|
6
|
+
export { flattenScripts } from "./flattenScripts.js";
|
|
7
|
+
export type { ClientScript, ParsedFile, Splice } from "./parseFile.js";
|
|
8
|
+
export { parseSourceFile, parseSourceText } from "./parseFile.js";
|
|
9
|
+
export { type ResolvedParam, resolveBindings } from "./resolveBindings.js";
|
|
10
|
+
export type { Plugin } from "./compileScript.js";
|
|
11
|
+
export {
|
|
12
|
+
type CompileModuleOptions,
|
|
13
|
+
compileModule,
|
|
14
|
+
pluginsFrom,
|
|
15
|
+
} from "./compileModule.js";
|
|
16
|
+
export { type TransformOptions, transform } from "./transform.js";
|
|
17
|
+
export { transpile } from "./transpile.js";
|
|
18
|
+
export { mangle, unmangle } from "./unmangle.js";
|
|
19
|
+
export type { Virtualized } from "./virtualize.js";
|
|
20
|
+
export { virtualize } from "./virtualize.js";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Whether a tag names a component rather than one of the target's elements.
|
|
2
|
+
//
|
|
3
|
+
// TypeScript's own rule, spelled out because it does not publish it (`ts`'s
|
|
4
|
+
// `isIntrinsicJsxName` is internal): a name starting lowercase, or holding a
|
|
5
|
+
// `-` or a `:`, is the target's, and anything else is a value in scope. Written
|
|
6
|
+
// here so a tag reads the same to the compiler as it does to the checker, which
|
|
7
|
+
// applies this rule to the virtual code.
|
|
8
|
+
export function isComponentTag(tagName: string): boolean {
|
|
9
|
+
const first = tagName.charCodeAt(0);
|
|
10
|
+
const intrinsic =
|
|
11
|
+
(first >= 0x61 /* a */ && first <= 0x7a) /* z */ ||
|
|
12
|
+
tagName.includes("-") ||
|
|
13
|
+
tagName.includes(":");
|
|
14
|
+
return !intrinsic;
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
|
|
3
|
+
/** <receiver>.<method>(...args) */
|
|
4
|
+
export function call(
|
|
5
|
+
ts: typeof import("typescript"),
|
|
6
|
+
receiver: string,
|
|
7
|
+
method: string,
|
|
8
|
+
args: ts.Expression[],
|
|
9
|
+
): ts.CallExpression {
|
|
10
|
+
return ts.factory.createCallExpression(
|
|
11
|
+
ts.factory.createPropertyAccessExpression(
|
|
12
|
+
ts.factory.createIdentifier(receiver),
|
|
13
|
+
method,
|
|
14
|
+
),
|
|
15
|
+
undefined,
|
|
16
|
+
args,
|
|
17
|
+
);
|
|
18
|
+
}
|
package/src/parseFile.ts
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
import type { SourceRange } from "./SourceRange.js";
|
|
3
|
+
|
|
4
|
+
export interface ParsedFile {
|
|
5
|
+
sourceFile: ts.SourceFile;
|
|
6
|
+
scripts: ClientScript[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ClientScript {
|
|
10
|
+
sourceFile: ts.SourceFile;
|
|
11
|
+
sourceNode: ts.TaggedTemplateExpression;
|
|
12
|
+
textWithPlaceholders: string;
|
|
13
|
+
fileWithPlaceholders: ts.SourceFile;
|
|
14
|
+
splices: { [placeholder: string]: Splice };
|
|
15
|
+
|
|
16
|
+
toSourceRange: (node: ts.Node) => SourceRange;
|
|
17
|
+
// an offset in the placeholder text, in the host file
|
|
18
|
+
toSourceOffset: (offset: number) => number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A host value read into a client script: braced — a `${...}` template
|
|
22
|
+
// span — or unbraced, the `$x` shorthand, sugar for splicing the host
|
|
23
|
+
// binding named without the sigil (`$x` reads as `${x}`). Both stand in the
|
|
24
|
+
// placeholder text as an identifier — the spelling that keys the splice
|
|
25
|
+
// dictionary and the script's `params` (`key`) — and evaluate a host
|
|
26
|
+
// expression (`expression`).
|
|
27
|
+
//
|
|
28
|
+
// What the text spells, and only that. A component tag names a host binding
|
|
29
|
+
// too, but no sigil says so — which tags do is scope resolution's to answer —
|
|
30
|
+
// so the rewrite hands those to the script itself (see `rewriteScript`).
|
|
31
|
+
export type Splice = BracedSplice | UnbracedSplice;
|
|
32
|
+
|
|
33
|
+
export interface BracedSplice {
|
|
34
|
+
kind: "braced";
|
|
35
|
+
// the template span's host expression
|
|
36
|
+
expression: ts.Expression;
|
|
37
|
+
// the param's key — the `$0splice<n>` identifier standing in
|
|
38
|
+
// `textWithPlaceholders`, which also keys the splice dictionary
|
|
39
|
+
key: string;
|
|
40
|
+
// client scripts nested in the host expression
|
|
41
|
+
scripts: ClientScript[];
|
|
42
|
+
// where the script reads it, in the placeholder text
|
|
43
|
+
refs: ts.Identifier[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface UnbracedSplice {
|
|
47
|
+
kind: "unbraced";
|
|
48
|
+
// the host binding the shorthand names (synthesized, e.g. `x` for `$x`)
|
|
49
|
+
expression: ts.Identifier;
|
|
50
|
+
// the param's key — the `$x` spelling the shorthand stands as in the
|
|
51
|
+
// placeholder text, which also keys the splice dictionary
|
|
52
|
+
key: string;
|
|
53
|
+
// a shorthand names a single binding, so it nests no scripts
|
|
54
|
+
scripts: [];
|
|
55
|
+
// where the script reads it, in the placeholder text
|
|
56
|
+
refs: ts.Identifier[];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function parseSourceText(
|
|
60
|
+
ts: typeof import("typescript"),
|
|
61
|
+
filePath: string,
|
|
62
|
+
sourceText: string,
|
|
63
|
+
): ParsedFile {
|
|
64
|
+
const sourceFile = ts.createSourceFile(
|
|
65
|
+
filePath,
|
|
66
|
+
sourceText,
|
|
67
|
+
ts.ScriptTarget.Latest,
|
|
68
|
+
true,
|
|
69
|
+
);
|
|
70
|
+
return parseSourceFile(ts, sourceFile);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function parseSourceFile(
|
|
74
|
+
ts: typeof import("typescript"),
|
|
75
|
+
sourceFile: ts.SourceFile,
|
|
76
|
+
): ParsedFile {
|
|
77
|
+
const scripts = getDirectScripts(ts, null, sourceFile);
|
|
78
|
+
return { sourceFile, scripts };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getDirectScripts(
|
|
82
|
+
ts: typeof import("typescript"),
|
|
83
|
+
parent: Splice | null,
|
|
84
|
+
sourceFile: ts.SourceFile,
|
|
85
|
+
): ClientScript[] {
|
|
86
|
+
const node: ts.Node = parent ? parent.expression : sourceFile;
|
|
87
|
+
const taggedTemplates: ts.TaggedTemplateExpression[] = [];
|
|
88
|
+
|
|
89
|
+
const visit = (current: ts.Node): void => {
|
|
90
|
+
if (
|
|
91
|
+
ts.isTaggedTemplateExpression(current) &&
|
|
92
|
+
ts.isIdentifier(current.tag) &&
|
|
93
|
+
current.tag.text === "cs"
|
|
94
|
+
) {
|
|
95
|
+
taggedTemplates.push(current);
|
|
96
|
+
} else {
|
|
97
|
+
current.forEachChild(visit);
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
visit(node);
|
|
101
|
+
|
|
102
|
+
const scripts: ClientScript[] = [];
|
|
103
|
+
|
|
104
|
+
taggedTemplates.forEach((taggedTemplate) => {
|
|
105
|
+
const { textWithPlaceholders, mappings } = toTextWithPlaceholders(
|
|
106
|
+
ts,
|
|
107
|
+
taggedTemplate,
|
|
108
|
+
sourceFile,
|
|
109
|
+
);
|
|
110
|
+
const fileWithPlaceholders = ts.createSourceFile(
|
|
111
|
+
sourceFile.fileName,
|
|
112
|
+
textWithPlaceholders,
|
|
113
|
+
ts.ScriptTarget.Latest,
|
|
114
|
+
true,
|
|
115
|
+
);
|
|
116
|
+
// The dictionary's insertion order is evaluation order: emitted as the
|
|
117
|
+
// script's `params` array literal, the splices' host expressions run
|
|
118
|
+
// first to last when the `cs` expression itself evaluates — left to
|
|
119
|
+
// right in source order, like a real template literal's spans.
|
|
120
|
+
const splices = getDirectSplices(
|
|
121
|
+
ts,
|
|
122
|
+
taggedTemplate,
|
|
123
|
+
sourceFile,
|
|
124
|
+
fileWithPlaceholders,
|
|
125
|
+
);
|
|
126
|
+
const toSourceRange = (node: ts.Node): SourceRange => ({
|
|
127
|
+
start: toSourceOffset(mappings, node.getStart(fileWithPlaceholders)),
|
|
128
|
+
end: toSourceOffset(mappings, node.getEnd()),
|
|
129
|
+
});
|
|
130
|
+
scripts.push({
|
|
131
|
+
sourceFile,
|
|
132
|
+
sourceNode: taggedTemplate,
|
|
133
|
+
textWithPlaceholders,
|
|
134
|
+
fileWithPlaceholders,
|
|
135
|
+
toSourceRange,
|
|
136
|
+
toSourceOffset: (offset) => toSourceOffset(mappings, offset),
|
|
137
|
+
splices,
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return scripts;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// A splice per first reference in the placeholder text, in source order, and
|
|
145
|
+
// every reference to it: a `$0splice<n>` placeholder resolves to its template
|
|
146
|
+
// span's braced splice, and any other `$x` identifier mints an unbraced
|
|
147
|
+
// splice, deduplicated by spelling. Property, attribute and declaration names
|
|
148
|
+
// are not references (a `$`-prefixed declaration is rejected at rewrite time),
|
|
149
|
+
// and nested scripts are already placeholders in this text, so the walk scans
|
|
150
|
+
// only the script's own body.
|
|
151
|
+
function getDirectSplices(
|
|
152
|
+
ts: typeof import("typescript"),
|
|
153
|
+
taggedTemplate: ts.TaggedTemplateExpression,
|
|
154
|
+
sourceFile: ts.SourceFile,
|
|
155
|
+
fileWithPlaceholders: ts.SourceFile,
|
|
156
|
+
): { [placeholder: string]: Splice } {
|
|
157
|
+
// No prototype: a script's `valueOf` or `constructor` is no splice.
|
|
158
|
+
const splices: { [placeholder: string]: Splice } = Object.create(null);
|
|
159
|
+
|
|
160
|
+
const template = taggedTemplate.template;
|
|
161
|
+
const spans = ts.isTemplateExpression(template) ? template.templateSpans : [];
|
|
162
|
+
|
|
163
|
+
const visit = (node: ts.Node): void => {
|
|
164
|
+
if (ts.isPropertyAccessExpression(node)) {
|
|
165
|
+
visit(node.expression); // the name is not a reference
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (ts.isPropertyAssignment(node)) {
|
|
169
|
+
if (ts.isComputedPropertyName(node.name)) {
|
|
170
|
+
visit(node.name);
|
|
171
|
+
}
|
|
172
|
+
visit(node.initializer);
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (
|
|
176
|
+
ts.isVariableDeclaration(node) ||
|
|
177
|
+
ts.isParameter(node) ||
|
|
178
|
+
ts.isBindingElement(node)
|
|
179
|
+
) {
|
|
180
|
+
if (node.initializer) {
|
|
181
|
+
visit(node.initializer);
|
|
182
|
+
}
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
if (ts.isJsxAttribute(node)) {
|
|
186
|
+
if (node.initializer) {
|
|
187
|
+
visit(node.initializer); // the name is not a reference
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (ts.isCatchClause(node)) {
|
|
192
|
+
visit(node.block); // the catch binding is not a reference
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (ts.isIdentifier(node) && splices[node.text] != null) {
|
|
196
|
+
splices[node.text]!.refs.push(node);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (
|
|
200
|
+
ts.isIdentifier(node) &&
|
|
201
|
+
node.text.startsWith("$") &&
|
|
202
|
+
node.text.length > 1
|
|
203
|
+
) {
|
|
204
|
+
const key = node.text;
|
|
205
|
+
if (key.startsWith("$0splice")) {
|
|
206
|
+
// The numeric suffix indexes the template's spans. A `$0splice`
|
|
207
|
+
// spelling that resolves to no span (`$0spliceFoo`, an out-of-range
|
|
208
|
+
// index) is no splice at all — and no host binding either, since a
|
|
209
|
+
// name can't start with `$0`.
|
|
210
|
+
const index = Number(key.slice("$0splice".length));
|
|
211
|
+
const span = spans[index];
|
|
212
|
+
if (span != null) {
|
|
213
|
+
const splice: BracedSplice = {
|
|
214
|
+
kind: "braced",
|
|
215
|
+
expression: span.expression,
|
|
216
|
+
key,
|
|
217
|
+
scripts: [],
|
|
218
|
+
refs: [node],
|
|
219
|
+
};
|
|
220
|
+
splices[key] = splice;
|
|
221
|
+
splice.scripts = getDirectScripts(ts, splice, sourceFile);
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
splices[key] = {
|
|
225
|
+
kind: "unbraced",
|
|
226
|
+
expression: ts.factory.createIdentifier(key.slice(1)),
|
|
227
|
+
key,
|
|
228
|
+
scripts: [],
|
|
229
|
+
refs: [node],
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
node.forEachChild(visit);
|
|
235
|
+
};
|
|
236
|
+
visit(fileWithPlaceholders);
|
|
237
|
+
|
|
238
|
+
return splices;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
interface OffsetMapping {
|
|
242
|
+
placeholderStart: number;
|
|
243
|
+
length: number;
|
|
244
|
+
sourceStart: number;
|
|
245
|
+
verbatim: boolean;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function toTextWithPlaceholders(
|
|
249
|
+
ts: typeof import("typescript"),
|
|
250
|
+
taggedTemplate: ts.TaggedTemplateExpression,
|
|
251
|
+
sourceFile: ts.SourceFile,
|
|
252
|
+
): {
|
|
253
|
+
textWithPlaceholders: string;
|
|
254
|
+
mappings: OffsetMapping[];
|
|
255
|
+
} {
|
|
256
|
+
const sourceText = sourceFile.text;
|
|
257
|
+
const template = taggedTemplate.template;
|
|
258
|
+
|
|
259
|
+
const start = template.getStart(sourceFile) + 1; // past `
|
|
260
|
+
const end = template.getEnd() - 1; // before `
|
|
261
|
+
|
|
262
|
+
let textWithPlaceholders = "";
|
|
263
|
+
const mappings: OffsetMapping[] = [];
|
|
264
|
+
|
|
265
|
+
if (ts.isNoSubstitutionTemplateLiteral(template)) {
|
|
266
|
+
textWithPlaceholders = sourceText.slice(start, end);
|
|
267
|
+
mappings.push({
|
|
268
|
+
placeholderStart: 0,
|
|
269
|
+
length: textWithPlaceholders.length,
|
|
270
|
+
sourceStart: start,
|
|
271
|
+
verbatim: true,
|
|
272
|
+
});
|
|
273
|
+
} else {
|
|
274
|
+
let chunkStart = start;
|
|
275
|
+
|
|
276
|
+
template.templateSpans.forEach((span, index) => {
|
|
277
|
+
const placeholder = `$0splice${index}`;
|
|
278
|
+
const dollarBrace = span.expression.getFullStart() - 2; // before ${
|
|
279
|
+
const chunk = sourceText.slice(chunkStart, dollarBrace);
|
|
280
|
+
mappings.push({
|
|
281
|
+
placeholderStart: textWithPlaceholders.length,
|
|
282
|
+
length: chunk.length,
|
|
283
|
+
sourceStart: chunkStart,
|
|
284
|
+
verbatim: true,
|
|
285
|
+
});
|
|
286
|
+
textWithPlaceholders += chunk;
|
|
287
|
+
mappings.push({
|
|
288
|
+
placeholderStart: textWithPlaceholders.length,
|
|
289
|
+
length: placeholder.length,
|
|
290
|
+
sourceStart: dollarBrace,
|
|
291
|
+
verbatim: false,
|
|
292
|
+
});
|
|
293
|
+
textWithPlaceholders += placeholder;
|
|
294
|
+
chunkStart = span.literal.getStart(sourceFile) + 1; // past }
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
const tail = sourceText.slice(chunkStart, end);
|
|
298
|
+
mappings.push({
|
|
299
|
+
placeholderStart: textWithPlaceholders.length,
|
|
300
|
+
length: tail.length,
|
|
301
|
+
sourceStart: chunkStart,
|
|
302
|
+
verbatim: true,
|
|
303
|
+
});
|
|
304
|
+
textWithPlaceholders += tail;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return { textWithPlaceholders, mappings };
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function toSourceOffset(mappings: OffsetMapping[], pos: number): number {
|
|
311
|
+
for (const mapping of mappings) {
|
|
312
|
+
if (
|
|
313
|
+
mapping.verbatim &&
|
|
314
|
+
pos >= mapping.placeholderStart &&
|
|
315
|
+
pos <= mapping.placeholderStart + mapping.length
|
|
316
|
+
) {
|
|
317
|
+
return mapping.sourceStart + (pos - mapping.placeholderStart);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
for (const mapping of mappings) {
|
|
321
|
+
if (
|
|
322
|
+
!mapping.verbatim &&
|
|
323
|
+
pos >= mapping.placeholderStart &&
|
|
324
|
+
pos <= mapping.placeholderStart + mapping.length
|
|
325
|
+
) {
|
|
326
|
+
return mapping.sourceStart;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
const last = mappings[mappings.length - 1];
|
|
330
|
+
return last.sourceStart + last.length;
|
|
331
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
import { buildMappings, type SourceMapping } from "./buildMappings.js";
|
|
3
|
+
import type { ClientScript, ParsedFile, Splice } from "./parseFile.js";
|
|
4
|
+
import type { RewrittenFile } from "./rewriteFile.js";
|
|
5
|
+
import { type Segment, segmentsToString } from "./segmentsToString.js";
|
|
6
|
+
import { virtualScript } from "./virtualScript.js";
|
|
7
|
+
|
|
8
|
+
interface VirtualizedFile {
|
|
9
|
+
virtualCode: string;
|
|
10
|
+
mappings: SourceMapping[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function printVirtualCode(
|
|
14
|
+
ts: typeof import("typescript"),
|
|
15
|
+
parsedFile: ParsedFile,
|
|
16
|
+
rewrittenFile: RewrittenFile,
|
|
17
|
+
): VirtualizedFile {
|
|
18
|
+
const { sourceFile } = parsedFile;
|
|
19
|
+
const segments: Segment[] = [];
|
|
20
|
+
|
|
21
|
+
let cursor = 0;
|
|
22
|
+
for (const script of parsedFile.scripts) {
|
|
23
|
+
const start = script.sourceNode.getStart(sourceFile);
|
|
24
|
+
segments.push(...renderVerbatim(sourceFile.text, cursor, start - cursor));
|
|
25
|
+
segments.push(...renderScript(ts, sourceFile, rewrittenFile, script));
|
|
26
|
+
cursor = script.sourceNode.getEnd();
|
|
27
|
+
}
|
|
28
|
+
segments.push(
|
|
29
|
+
...renderVerbatim(sourceFile.text, cursor, sourceFile.text.length - cursor),
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
virtualCode: segmentsToString(segments),
|
|
34
|
+
mappings: buildMappings(segments),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function renderScript(
|
|
39
|
+
ts: typeof import("typescript"),
|
|
40
|
+
sourceFile: ts.SourceFile,
|
|
41
|
+
rewrittenFile: RewrittenFile,
|
|
42
|
+
script: ClientScript,
|
|
43
|
+
): Segment[] {
|
|
44
|
+
const rewritten = rewrittenFile.scripts.get(script.sourceNode);
|
|
45
|
+
if (rewritten === undefined) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
// Left as written, its errors already said: `never` fits wherever it
|
|
49
|
+
// stands, so nothing else is reported for it.
|
|
50
|
+
if (rewritten.leftAsWritten) {
|
|
51
|
+
return ["cs.lift(undefined as never)"];
|
|
52
|
+
}
|
|
53
|
+
return virtualScript(
|
|
54
|
+
ts,
|
|
55
|
+
script,
|
|
56
|
+
rewrittenFile.bindings,
|
|
57
|
+
rewritten.awaits,
|
|
58
|
+
(splice) => renderSplice(ts, sourceFile, rewrittenFile, splice),
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function renderSplice(
|
|
63
|
+
ts: typeof import("typescript"),
|
|
64
|
+
sourceFile: ts.SourceFile,
|
|
65
|
+
rewrittenFile: RewrittenFile,
|
|
66
|
+
splice: Splice,
|
|
67
|
+
): Segment[] {
|
|
68
|
+
const segments: Segment[] = [];
|
|
69
|
+
const expression = splice.expression;
|
|
70
|
+
const end = expression.getEnd();
|
|
71
|
+
|
|
72
|
+
let cursor = expression.getStart(sourceFile);
|
|
73
|
+
for (const nested of splice.scripts) {
|
|
74
|
+
const start = nested.sourceNode.getStart(sourceFile);
|
|
75
|
+
segments.push(...renderVerbatim(sourceFile.text, cursor, start - cursor));
|
|
76
|
+
segments.push(...renderScript(ts, sourceFile, rewrittenFile, nested));
|
|
77
|
+
cursor = nested.sourceNode.getEnd();
|
|
78
|
+
}
|
|
79
|
+
segments.push(...renderVerbatim(sourceFile.text, cursor, end - cursor));
|
|
80
|
+
|
|
81
|
+
return segments;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function renderVerbatim(
|
|
85
|
+
sourceText: string,
|
|
86
|
+
start: number,
|
|
87
|
+
length: number,
|
|
88
|
+
): Segment[] {
|
|
89
|
+
if (length <= 0) {
|
|
90
|
+
return [];
|
|
91
|
+
}
|
|
92
|
+
return [[sourceText.slice(start, start + length), undefined, start, length]];
|
|
93
|
+
}
|