@gi-tcg/gts-transpiler 0.3.2 → 0.3.4

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.
@@ -1,21 +1,19 @@
1
1
  import type { Visitors } from "zimmerframe";
2
2
  import type {
3
- ArrayExpression,
4
3
  ArrowFunctionExpression,
5
- BlockStatement,
6
4
  Comment,
7
- Declaration,
8
5
  EmptyStatement,
9
6
  ExportNamedDeclaration,
10
7
  Expression,
11
8
  ExpressionStatement,
12
9
  GTSDefineStatement,
13
10
  Identifier,
11
+ ImportDeclaration,
14
12
  Literal,
15
13
  MemberExpression,
16
14
  Node,
17
15
  Program,
18
- SourceLocation,
16
+ SimpleLiteral,
19
17
  Statement,
20
18
  VariableDeclaration,
21
19
  VariableDeclarator,
@@ -25,8 +23,8 @@ import {
25
23
  type ExternalizedBinding,
26
24
  type TranspileState,
27
25
  } from "../gts.ts";
28
- import type { LeafToken } from "./collect_tokens.ts";
29
26
  import { createReplacementHolder } from "./replacements.ts";
27
+ import type { SourceRange } from "espolar";
30
28
 
31
29
  interface ExternalizedTypedBinding extends ExternalizedBinding {
32
30
  typingId: Identifier;
@@ -34,7 +32,6 @@ interface ExternalizedTypedBinding extends ExternalizedBinding {
34
32
  }
35
33
 
36
34
  export interface TypingTranspileState extends TranspileState {
37
- leafTokens: LeafToken[];
38
35
  externalizedBindings: ExternalizedTypedBinding[];
39
36
  idCounter: number;
40
37
  rootVmId: Identifier;
@@ -55,23 +52,40 @@ export interface TypingTranspileState extends TranspileState {
55
52
  /** Pending statements to be inserted to the top-level */
56
53
  typingPendingStatements: Statement[];
57
54
  replacementTag: Identifier;
58
- extraMappings: Map<string, string>;
55
+ /** untouched source nodes */
56
+ sourceNodes: WeakSet<Node>;
57
+ /**
58
+ * The callee of typing source of GTS' attribute names. Map the character
59
+ * after the name (typically whitespace) as the lParen of CallExpression
60
+ */
61
+ namedAttributeCalleeLParenRange: WeakMap<Node, SourceRange>;
62
+ /**
63
+ * String literal nodes that are derived from identifiers.
64
+ * - sourceStart += 1
65
+ * - sourceLength -= 2
66
+ */
67
+ literalFromIdentifier: WeakSet<Literal>;
68
+ /** Nodes that are last arguments of a CallExpression */
69
+ lastArgNodes: WeakSet<Node>;
70
+ /**
71
+ * The last import declaration, record if it is a generated one.
72
+ * This declaration marks the TS auto-import insertion point.
73
+ */
74
+ lastImportDeclarationIfGen: ImportDeclaration | null;
75
+ /** Nodes that have a diagnostic mappings to the top of the file */
76
+ diagnosticsOnTopNodes: WeakSet<Node>;
77
+ /** Extra mappings, the generated range will be found by the needle after replacement */
78
+ extraMappings: {
79
+ sourceOffset: number;
80
+ length: number;
81
+ generatedNeedle: string;
82
+ }[];
83
+ /** Character offset after hashbang and file-scope leading comments */
84
+ contentStartOffset: number;
59
85
  }
60
86
 
61
87
  const EMPTY: EmptyStatement = { type: "EmptyStatement" };
62
88
 
63
- const ANY = {
64
- type: "TSAnyKeyword",
65
- };
66
-
67
- // definite not supported by esrap yet, so we init the binding with an `as any` cast
68
- // https://github.com/sveltejs/esrap/issues/95
69
- const ANY_INIT = {
70
- type: "TSAsExpression",
71
- expression: { type: "Literal", value: 0 },
72
- typeAnnotation: ANY,
73
- } as {} as Expression;
74
-
75
89
  const enterVMFromRoot = (state: TypingTranspileState) => {
76
90
  let defTypeId: Identifier = {
77
91
  type: "Identifier",
@@ -128,7 +142,7 @@ const enterVMFromAttr = (
128
142
  state.attrsOfCurrentVm.push([]);
129
143
  };
130
144
 
131
- const exitVM = (state: TypingTranspileState, errorLoc?: string) => {
145
+ const exitVM = (state: TypingTranspileState, errorRange?: [number, number]) => {
132
146
  const currentDefTypeId = state.vmDefTypeIdStack.pop()!;
133
147
  const currentMetaId = state.metaTypeIdStack.pop()!;
134
148
  const finalMetaId = state.finalMetaTypeIdStack.pop()!;
@@ -140,7 +154,7 @@ const exitVM = (state: TypingTranspileState, errorLoc?: string) => {
140
154
  defType: currentDefTypeId.name,
141
155
  finalMetaType: finalMetaId.name,
142
156
  collectedAttrs: collectedAttrNames,
143
- errorLoc,
157
+ errorRange,
144
158
  }),
145
159
  );
146
160
  };
@@ -153,7 +167,7 @@ const enterAttr = (
153
167
  const metaTypeId = state.metaTypeIdStack.at(-1);
154
168
  if (!defTypeId || !metaTypeId) {
155
169
  // TODO error handling?
156
- return { lhsId: { type: "Identifier", name: "__invalid_attr_obj" } };
170
+ return { lhsId: { type: "Identifier", name: "__gts_invalid_attr_obj" } };
157
171
  }
158
172
  state.attrsOfCurrentVm.at(-1)!.push(attrName);
159
173
  const lhsId: Identifier = {
@@ -239,7 +253,7 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
239
253
  {
240
254
  type: "VariableDeclarator",
241
255
  id: extBinding.bindingName,
242
- init: ANY_INIT,
256
+ // @ts-expect-error TS property not provided in ESTree
243
257
  typeAnnotation: {
244
258
  type: "TSTypeAnnotation",
245
259
  typeAnnotation: {
@@ -247,7 +261,14 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
247
261
  typeName: extBinding.typingId,
248
262
  },
249
263
  },
250
- } as VariableDeclarator,
264
+ init: {
265
+ type: "TSNonNullExpression",
266
+ expression: {
267
+ type: "Literal",
268
+ value: null,
269
+ } satisfies Expression,
270
+ } as any,
271
+ },
251
272
  ],
252
273
  };
253
274
  if (extBinding.export) {
@@ -264,39 +285,66 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
264
285
  body.unshift(varDecl);
265
286
  }
266
287
  }
267
- if (state.hasQueryExpressions) {
268
- body.unshift({
288
+ const importDecls: ImportDeclaration[] = [];
289
+ importDecls.push(
290
+ {
269
291
  type: "ImportDeclaration",
270
- diagnosticsOnTop: true,
271
292
  specifiers: [
272
293
  {
273
294
  type: "ImportDefaultSpecifier",
274
- local: state.queryFnId,
295
+ local: state.rootVmId,
275
296
  },
276
297
  ],
277
298
  source: {
278
299
  type: "Literal",
279
- value: `${state.providerImportSource}/query`,
300
+ value: `${state.providerImportSource}/vm`,
280
301
  },
281
302
  attributes: [],
282
- });
283
- }
284
- body.unshift(
303
+ },
285
304
  {
286
305
  type: "ImportDeclaration",
287
- diagnosticsOnTop: true,
288
306
  specifiers: [
289
307
  {
290
- type: "ImportDefaultSpecifier",
291
- local: state.rootVmId,
308
+ type: "ImportSpecifier",
309
+ imported: { type: "Identifier", name: "createDefine" },
310
+ local: state.createDefineFnId,
311
+ },
312
+ {
313
+ type: "ImportSpecifier",
314
+ imported: { type: "Identifier", name: "createBinding" },
315
+ local: state.createBindingFnId,
292
316
  },
293
317
  ],
294
- source: {
295
- type: "Literal",
296
- value: `${state.providerImportSource}/vm`,
297
- },
318
+ source: { type: "Literal", value: state.runtimeImportSource },
298
319
  attributes: [],
299
320
  },
321
+ );
322
+ for (const importDecl of importDecls) {
323
+ state.diagnosticsOnTopNodes.add(importDecl.source);
324
+ for (const specifier of importDecl.specifiers) {
325
+ state.diagnosticsOnTopNodes.add(specifier);
326
+ }
327
+ }
328
+ const lastImportDecl = importDecls.pop() ?? {
329
+ type: "ImportDeclaration",
330
+ specifiers: [],
331
+ source: { type: "Literal", value: "" },
332
+ attributes: [],
333
+ } satisfies ImportDeclaration;
334
+ body.unshift(
335
+ ...importDecls,
336
+ // Add an unrelated statement between system generated imports to make them unsorted,
337
+ // so that TSServer will always insert auto-imports after the last ImportDeclaration.
338
+ // `lastImportDecl` will be marked as insertion point in printer if no user's
339
+ // ImportDeclaration is provided
340
+ {
341
+ type: "ExpressionStatement",
342
+ expression: {
343
+ type: "Literal",
344
+ value: 0,
345
+ }
346
+ },
347
+ lastImportDecl,
300
348
  createReplacementHolder(state, {
301
349
  type: "preface",
302
350
  }),
@@ -317,30 +365,20 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
317
365
  const attrName = JSON.stringify(
318
366
  name.type === "Literal" ? String(name.value) : name.name,
319
367
  );
320
- const attributeNameToken = state.leafTokens.find((t) => t.loc === name.loc);
321
- if (attributeNameToken) {
322
- attributeNameToken.sourceLengthOffset = 1;
323
- }
324
368
  const { lhsId } = enterAttr(state, attrName);
325
- state.extraMappings.set(
326
- `${name.loc?.start.line}:${name.loc?.start.column}`,
327
- `${lhsId.name}${name.type === "Literal" ? `[` : `.`}`,
328
- );
329
369
  const positionals = body.positionalAttributes.attributes.map(
330
370
  (attr): Expression => {
331
371
  if (attr.type === "Identifier" && /^[a-z_]/.test(attr.name)) {
332
- const token = state.leafTokens.find((t) => t.loc === attr.loc);
333
- if (token) {
334
- token.generatedStartOffset = 1;
335
- token.generatedLength = attr.name.length + 2; // quotation mark
336
- }
337
- return {
372
+ const lit: SimpleLiteral = {
338
373
  type: "Literal",
339
374
  value: attr.name,
340
375
  loc: attr.loc,
376
+ range: attr.range,
341
377
  };
378
+ state.literalFromIdentifier.add(lit);
379
+ return lit;
342
380
  } else {
343
- return visit(attr) as Expression;
381
+ return { ...(visit(attr) as Expression) };
344
382
  }
345
383
  },
346
384
  );
@@ -348,6 +386,27 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
348
386
  type: "Identifier",
349
387
  name: `__gts_attrRet_${state.idCounter++}`,
350
388
  };
389
+ const callee: MemberExpression = {
390
+ type: "MemberExpression",
391
+ object: lhsId,
392
+ property: name,
393
+ computed: name.type === "Literal",
394
+ optional: false,
395
+ };
396
+ if (name.range) {
397
+ // If the `obj.name` is not callable, the error squiggle begins from `obj` not `name`
398
+ // Add a diagnostic-only mapping from the `name` to a call starting from `obj.`
399
+ // This should be unique since each attribute decl generates a new obj name
400
+ state.extraMappings.push({
401
+ sourceOffset: name.range[0],
402
+ length: name.range[1] - name.range[0],
403
+ generatedNeedle: `${lhsId.name}${name.type === "Literal" ? `[` : `.`}`,
404
+ });
405
+ state.namedAttributeCalleeLParenRange.set(callee, {
406
+ start: name.range[1],
407
+ end: name.range[1] + 1,
408
+ });
409
+ }
351
410
  state.typingPendingStatements.push({
352
411
  type: "VariableDeclaration",
353
412
  kind: "const",
@@ -358,13 +417,7 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
358
417
  init: {
359
418
  type: "CallExpression",
360
419
  optional: false,
361
- callee: {
362
- type: "MemberExpression",
363
- object: lhsId,
364
- property: name,
365
- computed: name.type === "Literal",
366
- optional: false,
367
- },
420
+ callee,
368
421
  arguments: positionals,
369
422
  },
370
423
  },
@@ -373,16 +426,13 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
373
426
  if (body.namedAttributes) {
374
427
  enterVMFromAttr(state, returnValue);
375
428
  visit(body.namedAttributes);
376
- exitVM(
377
- state,
378
- `${body.namedAttributes.loc?.start.line}:${body.namedAttributes.loc?.start.column}`,
379
- );
429
+ exitVM(state, body.namedAttributes.range);
380
430
  }
381
431
  if (bindingName) {
382
432
  const export_ = node.bindingAccessModifier !== "private";
383
433
  const typingId: Identifier = {
384
434
  type: "Identifier",
385
- name: `gts_binding_type_${state.externalizedBindings.length}`,
435
+ name: `__gts_binding_type_${state.externalizedBindings.length}`,
386
436
  };
387
437
  genBindingTyping(state, {
388
438
  attrName,
@@ -415,11 +465,13 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
415
465
  const attrName = JSON.stringify(state.ActionLit.value);
416
466
  const { lhsId } = enterAttr(state, attrName);
417
467
  const actionNotExistsReplacementStr = `${lhsId.name}[${attrName}]`;
418
- const actionNotExistsErrorLoc = `${node.directAction.loc?.start.line}:${node.directAction.loc?.start.column}`;
419
- state.extraMappings.set(
420
- actionNotExistsErrorLoc,
421
- actionNotExistsReplacementStr,
422
- );
468
+ if (node.directAction.range) {
469
+ state.extraMappings.push({
470
+ sourceOffset: node.directAction.range[0],
471
+ length: node.directAction.range[1] - node.directAction.range[0],
472
+ generatedNeedle: actionNotExistsReplacementStr,
473
+ });
474
+ }
423
475
  const fn: ArrowFunctionExpression = {
424
476
  type: "ArrowFunctionExpression",
425
477
  params: state.shortcutFunctionParameters,
@@ -459,14 +511,4 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
459
511
  return EMPTY;
460
512
  },
461
513
  ...(commonGtsVisitor as Visitors<Node, TypingTranspileState>),
462
- GTSShortcutArgumentExpression(node, { state, visit }): MemberExpression {
463
- const lhs = { ...state.fnArgId };
464
- return {
465
- type: "MemberExpression",
466
- object: lhs,
467
- computed: false,
468
- optional: false,
469
- property: visit(node.property) as Identifier,
470
- };
471
- },
472
514
  };
package/src/types.ts CHANGED
@@ -6,7 +6,6 @@ import type * as AST from "estree";
6
6
  type ForInit = boolean | "await";
7
7
 
8
8
  declare module "estree" {
9
-
10
9
  interface Identifier {
11
10
  isDummy?: boolean;
12
11
  }
@@ -24,17 +23,12 @@ declare module "estree" {
24
23
  GTSShortcutFunctionExpression: GTSShortcutFunctionExpression;
25
24
  GTSQueryExpression: GTSQueryExpression;
26
25
  }
27
-
28
- interface ImportDeclaration {
29
- /** Emit inner diagnostics to the top-of-file */
30
- diagnosticsOnTop?: boolean;
31
- }
32
-
26
+
33
27
  interface SimpleCallExpression {
34
- lParenLoc?: SourceLocation;
28
+ lParenRange?: [number, number];
35
29
  }
36
30
  interface NewExpression {
37
- lParenLoc?: SourceLocation;
31
+ lParenRange?: [number, number];
38
32
  }
39
33
 
40
34
  interface GTSDefineStatement extends BaseStatement {