@gi-tcg/gts-transpiler 0.3.6 → 0.3.8

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/index.d.ts CHANGED
@@ -87,6 +87,13 @@ interface CodeInformation {
87
87
  }
88
88
  //#endregion
89
89
  //#region src/transform/volar/mappings.d.ts
90
+ declare module "@volar/language-core" {
91
+ interface CodeInformation {
92
+ gtsAttribute?: boolean;
93
+ literalFromId?: boolean;
94
+ directActionStub?: boolean;
95
+ }
96
+ }
90
97
  interface VolarMappingResult {
91
98
  code: string;
92
99
  mappings: CodeMapping[];
package/dist/index.js CHANGED
@@ -1549,6 +1549,7 @@ const gtsToTypingsWalker = {
1549
1549
  },
1550
1550
  GTSNamedAttributeDefinition(node, { visit, state }) {
1551
1551
  const { name, body, bindingName } = node;
1552
+ state.attributeNameNodes.add(name);
1552
1553
  const attrName = JSON.stringify(name.type === "Literal" ? String(name.value) : name.name);
1553
1554
  const { lhsId } = enterAttr(state, attrName);
1554
1555
  const positionals = body.positionalAttributes.attributes.map((attr) => {
@@ -1636,14 +1637,28 @@ const gtsToTypingsWalker = {
1636
1637
  GTSNamedAttributeBlock(node, { state, visit }) {
1637
1638
  for (const attr of node.attributes) visit(attr);
1638
1639
  if (node.directAction) {
1640
+ const stubStatement = {
1641
+ type: "ExpressionStatement",
1642
+ expression: {
1643
+ type: "Literal",
1644
+ value: 0
1645
+ }
1646
+ };
1647
+ state.typingPendingStatements.push(stubStatement);
1639
1648
  const attrName = JSON.stringify(state.ActionLit.value);
1640
1649
  const { lhsId } = enterAttr(state, attrName);
1641
1650
  const actionNotExistsReplacementStr = `${lhsId.name}[${attrName}]`;
1642
- if (node.directAction.range) state.extraMappings.push({
1643
- sourceOffset: node.directAction.range[0],
1644
- length: node.directAction.range[1] - node.directAction.range[0],
1645
- generatedNeedle: actionNotExistsReplacementStr
1646
- });
1651
+ if (node.directAction.range) {
1652
+ state.extraMappings.push({
1653
+ sourceOffset: node.directAction.range[0],
1654
+ length: node.directAction.range[1] - node.directAction.range[0],
1655
+ generatedNeedle: actionNotExistsReplacementStr
1656
+ });
1657
+ state.directActionStubRange.set(stubStatement.expression, {
1658
+ start: node.directAction.range[0],
1659
+ end: node.directAction.range[1]
1660
+ });
1661
+ }
1647
1662
  const fn = {
1648
1663
  type: "ArrowFunctionExpression",
1649
1664
  params: state.shortcutFunctionParameters,
@@ -1692,6 +1707,18 @@ const DEFAULT_VOLAR_MAPPING_DATA = {
1692
1707
  structure: true,
1693
1708
  verification: true
1694
1709
  };
1710
+ const ATTRIBUTE_NAME_MAPPING_DATA = {
1711
+ ...DEFAULT_VOLAR_MAPPING_DATA,
1712
+ gtsAttribute: true
1713
+ };
1714
+ const LITERAL_FROM_ID_MAPPING_DATA = {
1715
+ ...DEFAULT_VOLAR_MAPPING_DATA,
1716
+ literalFromId: true
1717
+ };
1718
+ const DIRECT_ACTION_STUB_MAPPING_DATA = {
1719
+ ...DEFAULT_VOLAR_MAPPING_DATA,
1720
+ directActionStub: true
1721
+ };
1695
1722
  const VERIFICATION_ONLY_MAPPING_DATA = { verification: true };
1696
1723
  Object.freeze(DEFAULT_VOLAR_MAPPING_DATA);
1697
1724
  Object.freeze(VERIFICATION_ONLY_MAPPING_DATA);
@@ -1702,6 +1729,7 @@ function getPrintOptions(source, state) {
1702
1729
  source,
1703
1730
  isUntouched: (node) => {
1704
1731
  if (node.type === "Identifier" && node.isDummy) return false;
1732
+ if (state.attributeNameNodes.has(node)) return false;
1705
1733
  return state.sourceNodes.has(node);
1706
1734
  },
1707
1735
  getLeadingComments: (node) => node.leadingComments,
@@ -1715,16 +1743,20 @@ function getPrintOptions(source, state) {
1715
1743
  let firstNonWhiteSpaceIndex = context.source.slice(identifier.range[1]).search(/\S/);
1716
1744
  const rangeEnd = firstNonWhiteSpaceIndex === -1 ? context.source.length : identifier.range[1] + firstNonWhiteSpaceIndex;
1717
1745
  context.writeMapped(text, identifier.range[0], rangeEnd);
1718
- } else return defaultPrinters.Identifier(node, context);
1746
+ } else if (identifier.range && state.attributeNameNodes.has(identifier)) context.writeMapped(identifier.name, identifier.range[0], identifier.range[1], ATTRIBUTE_NAME_MAPPING_DATA);
1747
+ else defaultPrinters.Identifier(node, context);
1719
1748
  },
1720
1749
  Literal(node, context) {
1721
1750
  const generatedStart = context.generatedOffset;
1751
+ let directActionStubRange;
1722
1752
  if (state.literalFromIdentifier.has(node) && node.range) {
1723
1753
  const text = JSON.stringify(node.value);
1724
1754
  context.write("\"");
1725
- context.writeMapped(text.slice(1, -1), node.range[0], node.range[1]);
1755
+ context.writeMapped(text.slice(1, -1), node.range[0], node.range[1], LITERAL_FROM_ID_MAPPING_DATA);
1726
1756
  context.write("\"");
1727
- } else defaultPrinters.Literal(node, context);
1757
+ } else if (state.attributeNameNodes.has(node) && node.range) context.writeMapped(node.raw ?? JSON.stringify(node.value), node.range[0], node.range[1], ATTRIBUTE_NAME_MAPPING_DATA);
1758
+ else if (directActionStubRange = state.directActionStubRange.get(node)) context.writeMapped(node.raw ?? JSON.stringify(node.value), directActionStubRange.start, directActionStubRange.start + 1, DIRECT_ACTION_STUB_MAPPING_DATA);
1759
+ else defaultPrinters.Literal(node, context);
1728
1760
  if (state.diagnosticsOnTopNodes.has(node)) {
1729
1761
  const generatedEnd = context.generatedOffset;
1730
1762
  context.createExtraMapping({
@@ -1865,7 +1897,9 @@ function transformForVolar(ast, option, sourceInfo) {
1865
1897
  finalMetaTypeIdStack: [],
1866
1898
  attrsOfCurrentVm: [],
1867
1899
  sourceNodes: /* @__PURE__ */ new WeakSet(),
1900
+ attributeNameNodes: /* @__PURE__ */ new WeakSet(),
1868
1901
  namedAttributeCalleeLParenRange: /* @__PURE__ */ new WeakMap(),
1902
+ directActionStubRange: /* @__PURE__ */ new WeakMap(),
1869
1903
  literalFromIdentifier: /* @__PURE__ */ new WeakSet(),
1870
1904
  lastArgNodes: /* @__PURE__ */ new WeakSet(),
1871
1905
  lastImportDeclarationIfGen: null,
@@ -1954,7 +1988,8 @@ async function resolveGtsConfig(filePath, inlineConfig, options) {
1954
1988
  const generator = resolveGtsConfigImpl(filePath, inlineConfig, options);
1955
1989
  let result = generator.next();
1956
1990
  while (!result.done) {
1957
- const content = await result.value;
1991
+ const toRead = result.value;
1992
+ const content = await Promise.resolve(toRead).catch(() => "");
1958
1993
  result = generator.next(content);
1959
1994
  }
1960
1995
  return result.value;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gi-tcg/gts-transpiler",
3
- "version": "0.3.6",
3
+ "version": "0.3.8",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/piovium/gts.git"
@@ -22,7 +22,7 @@
22
22
  "@sveltejs/acorn-typescript": "^1.0.8",
23
23
  "acorn": "^8.15.0",
24
24
  "dedent": "^1.7.2",
25
- "espolar": "^0.5.2",
25
+ "espolar": "^0.6.0",
26
26
  "esrap": "2.2.1",
27
27
  "magic-string": "^0.30.21",
28
28
  "path-browserify-esm": "^1.0.6",
package/src/config.ts CHANGED
@@ -66,7 +66,9 @@ export async function resolveGtsConfig(
66
66
  let result = generator.next();
67
67
  while (!result.done) {
68
68
  const toRead = result.value;
69
- const content = await toRead;
69
+ // return an invalid JSON if readFile fails, so that the generator
70
+ // can catch the error and try next one
71
+ const content = await Promise.resolve(toRead).catch(() => "");
70
72
  result = generator.next(content);
71
73
  }
72
74
  return result.value;
@@ -41,7 +41,9 @@ export function transformForVolar(
41
41
  attrsOfCurrentVm: [],
42
42
 
43
43
  sourceNodes: new WeakSet(),
44
+ attributeNameNodes: new WeakSet(),
44
45
  namedAttributeCalleeLParenRange: new WeakMap(),
46
+ directActionStubRange: new WeakMap(),
45
47
  literalFromIdentifier: new WeakSet(),
46
48
  lastArgNodes: new WeakSet(),
47
49
  lastImportDeclarationIfGen: null,
@@ -1,5 +1,13 @@
1
1
  import type { CodeInformation, CodeMapping } from "@volar/language-core";
2
2
 
3
+ declare module "@volar/language-core" {
4
+ export interface CodeInformation {
5
+ gtsAttribute?: boolean;
6
+ literalFromId?: boolean;
7
+ directActionStub?: boolean;
8
+ }
9
+ }
10
+
3
11
  export interface VolarMappingResult {
4
12
  code: string;
5
13
  mappings: CodeMapping[];
@@ -13,6 +21,18 @@ export const DEFAULT_VOLAR_MAPPING_DATA: CodeInformation = {
13
21
  structure: true,
14
22
  verification: true,
15
23
  };
24
+ export const ATTRIBUTE_NAME_MAPPING_DATA: CodeInformation = {
25
+ ...DEFAULT_VOLAR_MAPPING_DATA,
26
+ gtsAttribute: true,
27
+ }
28
+ export const LITERAL_FROM_ID_MAPPING_DATA: CodeInformation = {
29
+ ...DEFAULT_VOLAR_MAPPING_DATA,
30
+ literalFromId: true,
31
+ }
32
+ export const DIRECT_ACTION_STUB_MAPPING_DATA: CodeInformation = {
33
+ ...DEFAULT_VOLAR_MAPPING_DATA,
34
+ directActionStub: true,
35
+ }
16
36
  export const VERIFICATION_ONLY_MAPPING_DATA: CodeInformation = {
17
37
  verification: true,
18
38
  };
@@ -1,5 +1,6 @@
1
1
  import type {
2
2
  Identifier,
3
+ Literal,
3
4
  NewExpression,
4
5
  Node,
5
6
  SimpleCallExpression,
@@ -8,10 +9,14 @@ import {
8
9
  type PrintOptions,
9
10
  type AST as EspolarAST,
10
11
  defaultPrinters,
12
+ type SourceRange,
11
13
  } from "espolar";
12
14
  import type { CodeInformation } from "@volar/language-core";
13
15
  import {
16
+ ATTRIBUTE_NAME_MAPPING_DATA,
14
17
  DEFAULT_VOLAR_MAPPING_DATA,
18
+ DIRECT_ACTION_STUB_MAPPING_DATA,
19
+ LITERAL_FROM_ID_MAPPING_DATA,
15
20
  VERIFICATION_ONLY_MAPPING_DATA,
16
21
  } from "./mappings.ts";
17
22
  import type { TypingTranspileState } from "./walker.ts";
@@ -26,19 +31,25 @@ export function getPrintOptions(
26
31
  if (node.type === "Identifier" && (node as Identifier).isDummy) {
27
32
  return false;
28
33
  }
34
+ if (state.attributeNameNodes.has(node as Identifier | Literal)) {
35
+ return false;
36
+ }
29
37
  return state.sourceNodes.has(node as Node);
30
38
  },
31
39
  getLeadingComments: (node) => (node as Node).leadingComments,
32
40
  getTrailingComments: (node) => (node as Node).trailingComments,
33
41
  getMappingData: () => DEFAULT_VOLAR_MAPPING_DATA,
34
42
  printers: {
35
- // Make the print of dummy identifier print nothing.
36
- // Exception: if GTS attribute list's last argument is dummy, e.g.
37
- // foo bar, ;
38
- // ^~ here
39
- // Then the printed JS will be `foo(bar, )` which WILL NOT be syntax error in ES6.
40
- // So we mark the lastArg manually and print an additional comma
41
- // for this dummy identifier, i.e. `foo(bar,,)` and TypeScript will recognize the error.
43
+ // 1) Make the print of dummy identifier print nothing.
44
+ // Exception: if GTS attribute list's last argument is dummy, e.g.
45
+ // foo bar, ;
46
+ // ^~ here
47
+ // Then the printed JS will be `foo(bar, )` which WILL NOT be syntax error in ES6.
48
+ // So we mark the lastArg manually and print an additional comma
49
+ // for this dummy identifier, i.e. `foo(bar,,)` and TypeScript will recognize the error.
50
+ // 2) Add mapping data "gtsAttribute" to GTS attribute name identifiers and literals.
51
+ // This will be recognized as "*.gtsAttribute" semantic token in language service plugin
52
+ // and remapped to "emphasis" in the language client that rendered as italic.
42
53
  Identifier(node, context) {
43
54
  const identifier = node as Identifier;
44
55
  if (identifier.isDummy && identifier.range) {
@@ -52,17 +63,52 @@ export function getPrintOptions(
52
63
  ? context.source.length
53
64
  : identifier.range[1] + firstNonWhiteSpaceIndex;
54
65
  context.writeMapped(text, identifier.range[0], rangeEnd);
66
+ } else if (
67
+ identifier.range &&
68
+ state.attributeNameNodes.has(identifier)
69
+ ) {
70
+ context.writeMapped(
71
+ identifier.name,
72
+ identifier.range[0],
73
+ identifier.range[1],
74
+ ATTRIBUTE_NAME_MAPPING_DATA,
75
+ );
55
76
  } else {
56
- return defaultPrinters.Identifier(node, context);
77
+ defaultPrinters.Identifier(node, context);
57
78
  }
58
79
  },
59
80
  Literal(node, context) {
60
81
  const generatedStart = context.generatedOffset;
82
+ let directActionStubRange: SourceRange | undefined;
61
83
  if (state.literalFromIdentifier.has(node) && node.range) {
84
+ // For string literals generated from identifiers, add mappings from only the content of literal
85
+ // to the identifier, so the highlight, auto-complete, etc. will work correctly.
62
86
  const text = JSON.stringify(node.value);
63
87
  context.write('"');
64
- context.writeMapped(text.slice(1, -1), node.range[0], node.range[1]);
88
+ context.writeMapped(
89
+ text.slice(1, -1),
90
+ node.range[0],
91
+ node.range[1],
92
+ LITERAL_FROM_ID_MAPPING_DATA,
93
+ );
65
94
  context.write('"');
95
+ } else if (state.attributeNameNodes.has(node) && node.range) {
96
+ context.writeMapped(
97
+ node.raw ?? JSON.stringify((node as EspolarAST.Literal).value),
98
+ node.range[0],
99
+ node.range[1],
100
+ ATTRIBUTE_NAME_MAPPING_DATA,
101
+ );
102
+ } else if ((directActionStubRange = state.directActionStubRange.get(node))) {
103
+ // For direct action stubs, add mappings from this expression statement line to the
104
+ // start of original direct action start position. Add `directActionStub` data
105
+ // for recognizing them in language service plugin.
106
+ context.writeMapped(
107
+ node.raw ?? JSON.stringify((node as EspolarAST.Literal).value),
108
+ directActionStubRange.start,
109
+ directActionStubRange.start + 1,
110
+ DIRECT_ACTION_STUB_MAPPING_DATA,
111
+ )
66
112
  } else {
67
113
  defaultPrinters.Literal(node, context);
68
114
  }
@@ -54,11 +54,19 @@ export interface TypingTranspileState extends TranspileState {
54
54
  replacementTag: Identifier;
55
55
  /** untouched source nodes */
56
56
  sourceNodes: WeakSet<Node>;
57
+ /** GTS' attribute name nodes */
58
+ attributeNameNodes: WeakSet<Identifier | Literal>;
57
59
  /**
58
- * The callee of typing source of GTS' attribute names. Map the character
60
+ * For each callee of typing source of GTS' attribute names, map the character
59
61
  * after the name (typically whitespace) as the lParen of CallExpression
60
62
  */
61
63
  namedAttributeCalleeLParenRange: WeakMap<Node, SourceRange>;
64
+ /**
65
+ * For each GTSDirectFunction, map a generated stub statement to the range of
66
+ * GTSDirectFunction itself. This extra mapping is used to insert CodeLens that
67
+ * mark the start position of direct action body.
68
+ */
69
+ directActionStubRange: WeakMap<Node, SourceRange>;
62
70
  /**
63
71
  * String literal nodes that are derived from identifiers.
64
72
  * - sourceStart += 1
@@ -325,12 +333,14 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
325
333
  state.diagnosticsOnTopNodes.add(specifier);
326
334
  }
327
335
  }
328
- const lastImportDecl = importDecls.pop() ?? {
329
- type: "ImportDeclaration",
330
- specifiers: [],
331
- source: { type: "Literal", value: "" },
332
- attributes: [],
333
- } satisfies ImportDeclaration;
336
+ const lastImportDecl =
337
+ importDecls.pop() ??
338
+ ({
339
+ type: "ImportDeclaration",
340
+ specifiers: [],
341
+ source: { type: "Literal", value: "" },
342
+ attributes: [],
343
+ } satisfies ImportDeclaration);
334
344
  body.unshift(
335
345
  ...importDecls,
336
346
  // Add an unrelated statement between system generated imports to make them unsorted,
@@ -342,7 +352,7 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
342
352
  expression: {
343
353
  type: "Literal",
344
354
  value: 0,
345
- }
355
+ },
346
356
  },
347
357
  lastImportDecl,
348
358
  createReplacementHolder(state, {
@@ -362,6 +372,7 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
362
372
  },
363
373
  GTSNamedAttributeDefinition(node, { visit, state }) {
364
374
  const { name, body, bindingName } = node;
375
+ state.attributeNameNodes.add(name);
365
376
  const attrName = JSON.stringify(
366
377
  name.type === "Literal" ? String(name.value) : name.name,
367
378
  );
@@ -462,6 +473,11 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
462
473
  visit(attr);
463
474
  }
464
475
  if (node.directAction) {
476
+ const stubStatement: ExpressionStatement = {
477
+ type: "ExpressionStatement",
478
+ expression: { type: "Literal", value: 0 },
479
+ };
480
+ state.typingPendingStatements.push(stubStatement);
465
481
  const attrName = JSON.stringify(state.ActionLit.value);
466
482
  const { lhsId } = enterAttr(state, attrName);
467
483
  const actionNotExistsReplacementStr = `${lhsId.name}[${attrName}]`;
@@ -471,6 +487,10 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
471
487
  length: node.directAction.range[1] - node.directAction.range[0],
472
488
  generatedNeedle: actionNotExistsReplacementStr,
473
489
  });
490
+ state.directActionStubRange.set(stubStatement.expression, {
491
+ start: node.directAction.range[0],
492
+ end: node.directAction.range[1],
493
+ });
474
494
  }
475
495
  const fn: ArrowFunctionExpression = {
476
496
  type: "ArrowFunctionExpression",
@@ -1,90 +0,0 @@
1
- import type { Node, Program, SourceLocation } from "estree";
2
- import { walk } from "zimmerframe";
3
-
4
- export interface LeafToken {
5
- loc: SourceLocation;
6
- isDummy?: boolean;
7
- /**
8
- * Override source length (instead of loc.end - loc.start)
9
- */
10
- sourceLength?: number;
11
- /**
12
- * Adjust the start position of source code
13
- */
14
- sourceStartOffset?: number;
15
- /**
16
- * Adjust the start position of generated code
17
- */
18
- generatedStartOffset?: number;
19
- /**
20
- * Make the source length longer
21
- */
22
- sourceLengthOffset?: number;
23
- /**
24
- * The original length of generated code, used for mapping diagnostics
25
- */
26
- generatedLength?: number;
27
- }
28
-
29
- export function collectLeafTokens(source: string, ast: Program): LeafToken[] {
30
- interface CollectTokenState {
31
- tokens: LeafToken[];
32
- /** Whether the node is from source and purely TypeScript */
33
- pureSource: boolean;
34
- /** Whether the node is visited once (for detecting leaf node) */
35
- visited: boolean;
36
- }
37
- const state: CollectTokenState = {
38
- tokens: [],
39
- pureSource: true,
40
- visited: false,
41
- };
42
- walk(ast as Node, state, {
43
- _(node, { state, next }) {
44
- state.visited = true;
45
- let currNodePureSource = !!node.loc && !node.type.startsWith("GTS");
46
- const subState = { tokens: [], pureSource: true, visited: false };
47
- next(subState);
48
- currNodePureSource &&= subState.pureSource;
49
- state.pureSource &&= currNodePureSource;
50
- // record original source for purely branch node
51
- if (subState.visited && node.range && currNodePureSource) {
52
- const [start, end] = node.range;
53
- node.pureSource = source.slice(start, end);
54
- }
55
- if (currNodePureSource) {
56
- const token: LeafToken = {
57
- loc: node.loc!,
58
- };
59
- if ("isDummy" in node && node.isDummy) {
60
- token.isDummy = true;
61
- token.sourceLength = 0;
62
- // add 1 for squiggle on next character
63
- token.generatedLength = 1;
64
- }
65
- state.tokens.push(token);
66
- } else {
67
- state.tokens.push(...subState.tokens);
68
- }
69
- },
70
- NewExpression(node, { state, next }) {
71
- const lParenLoc = node.lParenLoc;
72
- if (lParenLoc) {
73
- state.tokens.push({
74
- loc: lParenLoc,
75
- });
76
- }
77
- next();
78
- },
79
- CallExpression(node, { state, next }) {
80
- const lParenLoc = node.lParenLoc;
81
- if (lParenLoc) {
82
- state.tokens.push({
83
- loc: lParenLoc,
84
- });
85
- }
86
- next();
87
- },
88
- });
89
- return state.tokens;
90
- }