@gi-tcg/gts-transpiler 0.4.3 → 0.4.5

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.js CHANGED
@@ -713,17 +713,12 @@ const eraseTs = (ast) => {
713
713
  };
714
714
  //#endregion
715
715
  //#region src/transform/constants.ts
716
- const DEFAULT_SHORTCUT_FUNCTION_PRELUDES = [
717
- "cryo",
718
- "hydro",
719
- "pyro",
720
- "electro",
721
- "anemo",
722
- "geo",
723
- "dendro",
724
- "omni"
716
+ const DEFAULT_SHORTCUT_FUNCTION_PRELUDES = ["$"];
717
+ const DEFAULT_QUERY_BINDINGS = [
718
+ "my",
719
+ "opp",
720
+ "macros"
725
721
  ];
726
- const DEFAULT_QUERY_BINDINGS = ["my", "opp"];
727
722
  //#endregion
728
723
  //#region src/transform/gts.ts
729
724
  const commonGtsVisitor = {
@@ -1245,10 +1240,11 @@ function applyReplacements(state, code, mappings) {
1245
1240
  const lhs = `${payload.finalMetaType}_lhs`;
1246
1241
  const requiredAttrsNs = `${payload.finalMetaType}_rans`;
1247
1242
  const collectedAttrsExpr = `${payload.collectedAttrs.join(" | ") || "never"}`;
1248
- const needleString = `"${requiredAttrsNs}_NeedleString" as any as "required attributes are missing"`;
1243
+ const length = payload.errorRange ? payload.errorRange[1] - payload.errorRange[0] : 0;
1244
+ const needleString = `"${requiredAttrsNs}_NeedleString${"0".repeat(length)}" as string as ${requiredAttrsNs}.DiagMsg`;
1249
1245
  if (payload.errorRange) state.extraMappings.push({
1250
1246
  sourceOffset: payload.errorRange[0],
1251
- length: payload.errorRange[1] - payload.errorRange[0],
1247
+ length,
1252
1248
  generatedNeedle: needleString
1253
1249
  });
1254
1250
  replacement = dedent`
@@ -1257,7 +1253,13 @@ function applyReplacements(state, code, mappings) {
1257
1253
  type ${lhs} = typeof ${lhs};
1258
1254
  namespace ${requiredAttrsNs} {
1259
1255
  export type Collected = ${collectedAttrsExpr};
1260
- export type Expected = { [K in keyof ${payload.defType}]: ${lhs}[K] extends { required(this: ${lhs}): true } ? K : never }[keyof ${payload.defType}];
1256
+ export type Expected = {
1257
+ [K in keyof ${payload.defType}]: ${lhs}[K] extends { required(this: ${lhs}): true } ? K : never;
1258
+ }[keyof ${payload.defType}];
1259
+ type DiagObj = {
1260
+ [K in Expected]: K extends Collected ? never : \`'\${K}' is a required attribute but not provided\`;
1261
+ }
1262
+ export type DiagMsg = DiagObj[Expected];
1261
1263
  };
1262
1264
  ((_: ${requiredAttrsNs}.Expected extends ${requiredAttrsNs}.Collected ? string : ${requiredAttrsNs}.Expected) => 0)(${needleString});
1263
1265
  `;
@@ -1291,7 +1293,7 @@ function applyReplacements(state, code, mappings) {
1291
1293
  ? ${payload.attrName} /* have duplicate, disable this */
1292
1294
  : never
1293
1295
  );
1294
- let ${payload.lhs}!: { ${Meta}: ${payload.metaType} } & Omit<${payload.defType}, ${omittedKeys}>;
1296
+ let ${payload.lhs}!: ${payload.hintOnly ? `{}` : `{ ${Meta}: ${payload.metaType} }`} & Omit<${payload.defType}, ${omittedKeys}>;
1295
1297
  `;
1296
1298
  } else if (payload.type === "createBindingTyping") {
1297
1299
  const typingIdLhs = `${payload.typingId}_lhs`;
@@ -1326,6 +1328,7 @@ function applyReplacements(state, code, mappings) {
1326
1328
  //#endregion
1327
1329
  //#region src/transform/volar/walker.ts
1328
1330
  const EMPTY = { type: "EmptyStatement" };
1331
+ const ATTR_HINT_ATTR_NAME = JSON.stringify("~attrNameHint");
1329
1332
  const enterVMFromRoot = (state) => {
1330
1333
  let defTypeId = {
1331
1334
  type: "Identifier",
@@ -1405,7 +1408,8 @@ const enterAttr = (state, attrName) => {
1405
1408
  defType: defTypeId.name,
1406
1409
  metaType: metaTypeId.name,
1407
1410
  lhs: lhsId.name,
1408
- attrName
1411
+ attrName,
1412
+ hintOnly: attrName === ATTR_HINT_ATTR_NAME
1409
1413
  }));
1410
1414
  return { lhsId };
1411
1415
  };
@@ -1437,6 +1441,19 @@ const exitAttr = (state, returningId) => {
1437
1441
  returnType: returningId.name
1438
1442
  }));
1439
1443
  };
1444
+ const insertHintStatement = (state, whiteSpaceStart, whiteSpaceEnd) => {
1445
+ const { lhsId } = enterAttr(state, ATTR_HINT_ATTR_NAME);
1446
+ state.typingPendingStatements.push({
1447
+ type: "GTSAttributeNameHintStatement",
1448
+ object: lhsId,
1449
+ whiteSpaceStart,
1450
+ whiteSpaceEnd
1451
+ });
1452
+ exitAttr(state, {
1453
+ type: "Identifier",
1454
+ name: `__gts_attrRet_hint_${state.idCounter++}`
1455
+ });
1456
+ };
1440
1457
  const gtsToTypingsWalker = {
1441
1458
  Program(node, { state, visit }) {
1442
1459
  const body = [];
@@ -1601,11 +1618,11 @@ const gtsToTypingsWalker = {
1601
1618
  }
1602
1619
  }]
1603
1620
  });
1621
+ enterVMFromAttr(state, returnValue);
1604
1622
  if (body.namedAttributes) {
1605
- enterVMFromAttr(state, returnValue);
1606
1623
  visit(body.namedAttributes);
1607
1624
  exitVM(state, body.namedAttributes.range);
1608
- }
1625
+ } else exitVM(state, name.range);
1609
1626
  if (bindingName) {
1610
1627
  const export_ = node.bindingAccessModifier !== "private";
1611
1628
  const typingId = {
@@ -1636,7 +1653,17 @@ const gtsToTypingsWalker = {
1636
1653
  return EMPTY;
1637
1654
  },
1638
1655
  GTSNamedAttributeBlock(node, { state, visit }) {
1639
- for (const attr of node.attributes) visit(attr);
1656
+ const attributeListEnd = node.directAction?.range?.[0] ?? node.range?.[1] ?? -1;
1657
+ const attributeListStart = node.attributes[0]?.range?.[0] ?? attributeListEnd;
1658
+ if (node.range && attributeListStart > node.range[0] + 1) insertHintStatement(state, node.range[0] + 1, attributeListStart - 1);
1659
+ for (let i = 0; i < node.attributes.length; i++) {
1660
+ const attribute = node.attributes[i];
1661
+ visit(attribute);
1662
+ let nextTokenStart;
1663
+ if (i < node.attributes.length - 1) nextTokenStart = node.attributes[i + 1].range?.[0] ?? -1;
1664
+ else nextTokenStart = attributeListEnd;
1665
+ if (attribute.range && nextTokenStart > attribute.range[1]) insertHintStatement(state, attribute.range[1], nextTokenStart - 1);
1666
+ }
1640
1667
  if (node.directAction) {
1641
1668
  const stubStatement = {
1642
1669
  type: "ExpressionStatement",
@@ -1693,6 +1720,7 @@ const gtsToTypingsWalker = {
1693
1720
  }
1694
1721
  }]
1695
1722
  });
1723
+ exitAttr(state, returnValue);
1696
1724
  }
1697
1725
  return EMPTY;
1698
1726
  },
@@ -1733,6 +1761,7 @@ function getPrintOptions(source, state) {
1733
1761
  if (state.attributeNameNodes.has(node)) return false;
1734
1762
  return state.sourceNodes.has(node);
1735
1763
  },
1764
+ printCommentsOnUntouchedNodes: true,
1736
1765
  getLeadingComments: (node) => node.leadingComments,
1737
1766
  getTrailingComments: (node) => node.trailingComments,
1738
1767
  getMappingData: () => DEFAULT_VOLAR_MAPPING_DATA,
@@ -1802,6 +1831,12 @@ function getPrintOptions(source, state) {
1802
1831
  end: state.contentStartOffset + 1
1803
1832
  }, context.generatedOffset, context.generatedOffset + 1, DEFAULT_VOLAR_MAPPING_DATA);
1804
1833
  }
1834
+ },
1835
+ GTSAttributeNameHintStatement(node, context) {
1836
+ context.writeNode(node.object);
1837
+ context.write(".");
1838
+ context.writeSource(node.whiteSpaceStart, node.whiteSpaceEnd);
1839
+ context.write(";");
1805
1840
  }
1806
1841
  },
1807
1842
  experimentalGetLeftParenSourceRange: (node) => {
@@ -1968,18 +2003,12 @@ function transform(ast, option = {}, sourceInfo = {}) {
1968
2003
  const DEFAULT_GTS_CONFIG = {
1969
2004
  runtimeImportSource: "@gi-tcg/gts-runtime",
1970
2005
  providerImportSource: "@gi-tcg/core/gts",
1971
- shortcutFunctionPreludes: [
1972
- "cryo",
1973
- "hydro",
1974
- "pyro",
1975
- "electro",
1976
- "anemo",
1977
- "geo",
1978
- "dendro",
1979
- "omni",
1980
- "$"
1981
- ],
1982
- queryBindings: ["my", "opp"]
2006
+ shortcutFunctionPreludes: ["$"],
2007
+ queryBindings: [
2008
+ "my",
2009
+ "opp",
2010
+ "macros"
2011
+ ]
1983
2012
  };
1984
2013
  function* resolveGtsConfigImpl(filePath, inlineConfig = {}, options) {
1985
2014
  const pathModule = options.pathModule || browserPath;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gi-tcg/gts-transpiler",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
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.6.0",
25
+ "espolar": "^0.6.1",
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
@@ -33,18 +33,8 @@ export interface ResolveGtsConfigAsyncOptions extends ResolveGtsConfigBaseOption
33
33
  const DEFAULT_GTS_CONFIG: Required<GtsConfig> = {
34
34
  runtimeImportSource: "@gi-tcg/gts-runtime",
35
35
  providerImportSource: "@gi-tcg/core/gts",
36
- shortcutFunctionPreludes: [
37
- "cryo",
38
- "hydro",
39
- "pyro",
40
- "electro",
41
- "anemo",
42
- "geo",
43
- "dendro",
44
- "omni",
45
- "$",
46
- ],
47
- queryBindings: ["my", "opp"],
36
+ shortcutFunctionPreludes: ["$"],
37
+ queryBindings: ["my", "opp", "macros"],
48
38
  };
49
39
 
50
40
  function* resolveGtsConfigImpl(
@@ -1,11 +1,2 @@
1
- export const DEFAULT_SHORTCUT_FUNCTION_PRELUDES: string[] = [
2
- "cryo",
3
- "hydro",
4
- "pyro",
5
- "electro",
6
- "anemo",
7
- "geo",
8
- "dendro",
9
- "omni",
10
- ];
11
- export const DEFAULT_QUERY_BINDINGS: string[] = ["my", "opp"];
1
+ export const DEFAULT_SHORTCUT_FUNCTION_PRELUDES: string[] = ["$"];
2
+ export const DEFAULT_QUERY_BINDINGS: string[] = ["my", "opp", "macros"];
@@ -10,6 +10,7 @@ import {
10
10
  type AST as EspolarAST,
11
11
  defaultPrinters,
12
12
  type SourceRange,
13
+ type PrinterContext,
13
14
  } from "espolar";
14
15
  import type { CodeInformation } from "@volar/language-core";
15
16
  import {
@@ -19,7 +20,7 @@ import {
19
20
  LITERAL_FROM_ID_MAPPING_DATA,
20
21
  VERIFICATION_ONLY_MAPPING_DATA,
21
22
  } from "./mappings.ts";
22
- import type { TypingTranspileState } from "./walker.ts";
23
+ import type { TypingTranspileState, GTSAttributeNameHintStatement } from "./walker.ts";
23
24
 
24
25
  export function getPrintOptions(
25
26
  source: string,
@@ -36,6 +37,7 @@ export function getPrintOptions(
36
37
  }
37
38
  return state.sourceNodes.has(node as Node);
38
39
  },
40
+ printCommentsOnUntouchedNodes: true,
39
41
  getLeadingComments: (node) => (node as Node).leadingComments,
40
42
  getTrailingComments: (node) => (node as Node).trailingComments,
41
43
  getMappingData: () => DEFAULT_VOLAR_MAPPING_DATA,
@@ -186,6 +188,14 @@ export function getPrintOptions(
186
188
  );
187
189
  }
188
190
  },
191
+ // @ts-expect-error This is a custom node type that don't have typing.
192
+ // @see `GTSAttributeNameHintStatement`
193
+ GTSAttributeNameHintStatement(node: GTSAttributeNameHintStatement, context: PrinterContext<CodeInformation>) {
194
+ context.writeNode(node.object as EspolarAST.Node);
195
+ context.write(".");
196
+ context.writeSource(node.whiteSpaceStart, node.whiteSpaceEnd);
197
+ context.write(";");
198
+ }
189
199
  },
190
200
  // Enable triggering signature completion
191
201
  experimentalGetLeftParenSourceRange: (node) => {
@@ -38,6 +38,7 @@ type ReplacementPayload =
38
38
  metaType: string;
39
39
  lhs: string;
40
40
  attrName: string;
41
+ hintOnly: boolean;
41
42
  }
42
43
  | {
43
44
  type: "createBindingTyping";
@@ -121,11 +122,15 @@ export function applyReplacements(
121
122
  const lhs = `${payload.finalMetaType}_lhs`;
122
123
  const requiredAttrsNs = `${payload.finalMetaType}_rans`;
123
124
  const collectedAttrsExpr = `${payload.collectedAttrs.join(" | ") || "never"}`;
124
- const needleString = `"${requiredAttrsNs}_NeedleString" as any as "required attributes are missing"`;
125
+ const length = payload.errorRange
126
+ ? payload.errorRange[1] - payload.errorRange[0]
127
+ : 0;
128
+ // Ensure that generated needle string is longer than error range so that error squiggle can cover all
129
+ const needleString = `"${requiredAttrsNs}_NeedleString${"0".repeat(length)}" as string as ${requiredAttrsNs}.DiagMsg`;
125
130
  if (payload.errorRange) {
126
131
  state.extraMappings.push({
127
132
  sourceOffset: payload.errorRange[0],
128
- length: payload.errorRange[1] - payload.errorRange[0],
133
+ length,
129
134
  generatedNeedle: needleString,
130
135
  });
131
136
  }
@@ -135,7 +140,13 @@ export function applyReplacements(
135
140
  type ${lhs} = typeof ${lhs};
136
141
  namespace ${requiredAttrsNs} {
137
142
  export type Collected = ${collectedAttrsExpr};
138
- export type Expected = { [K in keyof ${payload.defType}]: ${lhs}[K] extends { required(this: ${lhs}): true } ? K : never }[keyof ${payload.defType}];
143
+ export type Expected = {
144
+ [K in keyof ${payload.defType}]: ${lhs}[K] extends { required(this: ${lhs}): true } ? K : never;
145
+ }[keyof ${payload.defType}];
146
+ type DiagObj = {
147
+ [K in Expected]: K extends Collected ? never : \`'\${K}' is a required attribute but not provided\`;
148
+ }
149
+ export type DiagMsg = DiagObj[Expected];
139
150
  };
140
151
  ((_: ${requiredAttrsNs}.Expected extends ${requiredAttrsNs}.Collected ? string : ${requiredAttrsNs}.Expected) => 0)(${needleString});
141
152
  `;
@@ -169,7 +180,7 @@ export function applyReplacements(
169
180
  ? ${payload.attrName} /* have duplicate, disable this */
170
181
  : never
171
182
  );
172
- let ${payload.lhs}!: { ${Meta}: ${payload.metaType} } & Omit<${payload.defType}, ${omittedKeys}>;
183
+ let ${payload.lhs}!: ${payload.hintOnly ? `{}` : `{ ${Meta}: ${payload.metaType} }`} & Omit<${payload.defType}, ${omittedKeys}>;
173
184
  `;
174
185
  } else if (payload.type === "createBindingTyping") {
175
186
  const typingIdLhs = `${payload.typingId}_lhs`;
@@ -92,7 +92,25 @@ export interface TypingTranspileState extends TranspileState {
92
92
  contentStartOffset: number;
93
93
  }
94
94
 
95
+ /**
96
+ * Map whitespaces inside named attribute blocks to include a "NameHintStatement", which printed
97
+ * as following:
98
+ * ```ts
99
+ * __gts_attr_obj. ;
100
+ * // ^~~~~~ these whitespaces are mapped from source
101
+ * ```
102
+ * so that when user press Ctrl+Space inside whitespace characters, they can get hint of available
103
+ * attribute names inside this block.
104
+ */
105
+ export interface GTSAttributeNameHintStatement {
106
+ type: "GTSAttributeNameHintStatement";
107
+ object: Identifier;
108
+ whiteSpaceStart: number;
109
+ whiteSpaceEnd: number;
110
+ }
111
+
95
112
  const EMPTY: EmptyStatement = { type: "EmptyStatement" };
113
+ const ATTR_HINT_ATTR_NAME = JSON.stringify("~attrNameHint");
96
114
 
97
115
  const enterVMFromRoot = (state: TypingTranspileState) => {
98
116
  let defTypeId: Identifier = {
@@ -174,7 +192,7 @@ const enterAttr = (
174
192
  const defTypeId = state.vmDefTypeIdStack.at(-1);
175
193
  const metaTypeId = state.metaTypeIdStack.at(-1);
176
194
  if (!defTypeId || !metaTypeId) {
177
- // TODO error handling?
195
+ // FIXME error handling?
178
196
  return { lhsId: { type: "Identifier", name: "__gts_invalid_attr_obj" } };
179
197
  }
180
198
  state.attrsOfCurrentVm.at(-1)!.push(attrName);
@@ -189,6 +207,7 @@ const enterAttr = (
189
207
  metaType: metaTypeId.name,
190
208
  lhs: lhsId.name,
191
209
  attrName,
210
+ hintOnly: attrName === ATTR_HINT_ATTR_NAME,
192
211
  }),
193
212
  );
194
213
  return { lhsId: lhsId };
@@ -238,6 +257,25 @@ const exitAttr = (state: TypingTranspileState, returningId: Identifier) => {
238
257
  );
239
258
  };
240
259
 
260
+ const insertHintStatement = (
261
+ state: TypingTranspileState,
262
+ whiteSpaceStart: number,
263
+ whiteSpaceEnd: number,
264
+ ) => {
265
+ const { lhsId } = enterAttr(state, ATTR_HINT_ATTR_NAME);
266
+ state.typingPendingStatements.push({
267
+ type: "GTSAttributeNameHintStatement",
268
+ object: lhsId,
269
+ whiteSpaceStart,
270
+ whiteSpaceEnd,
271
+ } satisfies GTSAttributeNameHintStatement as any);
272
+ const returnValue: Identifier = {
273
+ type: "Identifier",
274
+ name: `__gts_attrRet_hint_${state.idCounter++}`,
275
+ };
276
+ exitAttr(state, returnValue);
277
+ };
278
+
241
279
  export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
242
280
  Program(node, { state, visit }) {
243
281
  const body: Program["body"] = [];
@@ -434,10 +472,12 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
434
472
  },
435
473
  ],
436
474
  });
475
+ enterVMFromAttr(state, returnValue);
437
476
  if (body.namedAttributes) {
438
- enterVMFromAttr(state, returnValue);
439
477
  visit(body.namedAttributes);
440
478
  exitVM(state, body.namedAttributes.range);
479
+ } else {
480
+ exitVM(state, name.range);
441
481
  }
442
482
  if (bindingName) {
443
483
  const export_ = node.bindingAccessModifier !== "private";
@@ -469,8 +509,35 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
469
509
  return EMPTY;
470
510
  },
471
511
  GTSNamedAttributeBlock(node, { state, visit }) {
472
- for (const attr of node.attributes) {
473
- visit(attr);
512
+ // Insert hint statement around each attribute name:
513
+ // ```
514
+ // define foo {
515
+ // // (1) hidden hint
516
+ // bar 1; // (2a) hidden hint
517
+ // baz 2; // (2b) hidden hint
518
+ // }
519
+ // ```
520
+ const attributeListEnd =
521
+ node.directAction?.range?.[0] ?? node.range?.[1] ?? -1;
522
+ const attributeListStart =
523
+ node.attributes[0]?.range?.[0] ?? attributeListEnd;
524
+ // (1)
525
+ if (node.range && attributeListStart > node.range[0] + 1) {
526
+ insertHintStatement(state, node.range[0] + 1, attributeListStart - 1);
527
+ }
528
+ for (let i = 0; i < node.attributes.length; i++) {
529
+ const attribute = node.attributes[i];
530
+ visit(attribute);
531
+ let nextTokenStart: number;
532
+ if (i < node.attributes.length - 1) {
533
+ nextTokenStart = node.attributes[i + 1].range?.[0] ?? -1;
534
+ } else {
535
+ nextTokenStart = attributeListEnd;
536
+ }
537
+ // (2)
538
+ if (attribute.range && nextTokenStart > attribute.range[1]) {
539
+ insertHintStatement(state, attribute.range[1], nextTokenStart - 1);
540
+ }
474
541
  }
475
542
  if (node.directAction) {
476
543
  const stubStatement: ExpressionStatement = {
@@ -527,6 +594,7 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
527
594
  },
528
595
  ],
529
596
  });
597
+ exitAttr(state, returnValue);
530
598
  }
531
599
  return EMPTY;
532
600
  },