@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.
Files changed (107) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -2
  3. package/dist/CodeInformation.d.ts +8 -0
  4. package/dist/CodeInformation.js +2 -0
  5. package/dist/CodeInformation.js.map +1 -0
  6. package/dist/SourceRange.d.ts +4 -0
  7. package/dist/SourceRange.js +2 -0
  8. package/dist/SourceRange.js.map +1 -0
  9. package/dist/buildMappings.d.ts +10 -0
  10. package/dist/buildMappings.js +21 -0
  11. package/dist/buildMappings.js.map +1 -0
  12. package/dist/compileModule.d.ts +25 -0
  13. package/dist/compileModule.js +78 -0
  14. package/dist/compileModule.js.map +1 -0
  15. package/dist/compileScript.d.ts +24 -0
  16. package/dist/compileScript.js +66 -0
  17. package/dist/compileScript.js.map +1 -0
  18. package/dist/diagnostics.d.ts +8 -0
  19. package/dist/diagnostics.js +2 -0
  20. package/dist/diagnostics.js.map +1 -0
  21. package/dist/emitScript.d.ts +52 -0
  22. package/dist/emitScript.js +201 -0
  23. package/dist/emitScript.js.map +1 -0
  24. package/dist/emitScripts.d.ts +10 -0
  25. package/dist/emitScripts.js +23 -0
  26. package/dist/emitScripts.js.map +1 -0
  27. package/dist/flattenScripts.d.ts +2 -0
  28. package/dist/flattenScripts.js +10 -0
  29. package/dist/flattenScripts.js.map +1 -0
  30. package/dist/hashText.d.ts +1 -0
  31. package/dist/hashText.js +18 -0
  32. package/dist/hashText.js.map +1 -0
  33. package/dist/hostElements.d.ts +3 -0
  34. package/dist/hostElements.js +28 -0
  35. package/dist/hostElements.js.map +1 -0
  36. package/dist/index.d.ts +16 -0
  37. package/dist/index.js +10 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/isComponentTag.d.ts +1 -0
  40. package/dist/isComponentTag.js +15 -0
  41. package/dist/isComponentTag.js.map +1 -0
  42. package/dist/nodeFactory.d.ts +3 -0
  43. package/dist/nodeFactory.js +5 -0
  44. package/dist/nodeFactory.js.map +1 -0
  45. package/dist/parseFile.d.ts +34 -0
  46. package/dist/parseFile.js +200 -0
  47. package/dist/parseFile.js.map +1 -0
  48. package/dist/printVirtualCode.d.ts +9 -0
  49. package/dist/printVirtualCode.js +52 -0
  50. package/dist/printVirtualCode.js.map +1 -0
  51. package/dist/resolveBindings.d.ts +76 -0
  52. package/dist/resolveBindings.js +240 -0
  53. package/dist/resolveBindings.js.map +1 -0
  54. package/dist/rewriteFile.d.ts +12 -0
  55. package/dist/rewriteFile.js +22 -0
  56. package/dist/rewriteFile.js.map +1 -0
  57. package/dist/rewriteScript.d.ts +27 -0
  58. package/dist/rewriteScript.js +268 -0
  59. package/dist/rewriteScript.js.map +1 -0
  60. package/dist/segmentsToString.d.ts +9 -0
  61. package/dist/segmentsToString.js +4 -0
  62. package/dist/segmentsToString.js.map +1 -0
  63. package/dist/tagRoot.d.ts +2 -0
  64. package/dist/tagRoot.js +10 -0
  65. package/dist/tagRoot.js.map +1 -0
  66. package/dist/transform.d.ts +17 -0
  67. package/dist/transform.js +61 -0
  68. package/dist/transform.js.map +1 -0
  69. package/dist/transpile.d.ts +27 -0
  70. package/dist/transpile.js +41 -0
  71. package/dist/transpile.js.map +1 -0
  72. package/dist/unmangle.d.ts +2 -0
  73. package/dist/unmangle.js +9 -0
  74. package/dist/unmangle.js.map +1 -0
  75. package/dist/virtualScript.d.ts +22 -0
  76. package/dist/virtualScript.js +305 -0
  77. package/dist/virtualScript.js.map +1 -0
  78. package/dist/virtualize.d.ts +8 -0
  79. package/dist/virtualize.js +10 -0
  80. package/dist/virtualize.js.map +1 -0
  81. package/package.json +58 -3
  82. package/src/CodeInformation.ts +14 -0
  83. package/src/SourceRange.ts +4 -0
  84. package/src/buildMappings.ts +31 -0
  85. package/src/compileModule.ts +132 -0
  86. package/src/compileScript.ts +97 -0
  87. package/src/diagnostics.ts +9 -0
  88. package/src/emitScript.ts +297 -0
  89. package/src/emitScripts.ts +39 -0
  90. package/src/flattenScripts.ts +13 -0
  91. package/src/hashText.ts +17 -0
  92. package/src/hostElements.ts +34 -0
  93. package/src/index.ts +20 -0
  94. package/src/isComponentTag.ts +15 -0
  95. package/src/nodeFactory.ts +18 -0
  96. package/src/parseFile.ts +331 -0
  97. package/src/printVirtualCode.ts +93 -0
  98. package/src/resolveBindings.ts +417 -0
  99. package/src/rewriteFile.ts +52 -0
  100. package/src/rewriteScript.ts +413 -0
  101. package/src/segmentsToString.ts +16 -0
  102. package/src/tagRoot.ts +14 -0
  103. package/src/transform.ts +96 -0
  104. package/src/transpile.ts +49 -0
  105. package/src/unmangle.ts +10 -0
  106. package/src/virtualScript.ts +355 -0
  107. 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
+ }