@gi-tcg/gts-transpiler 0.5.0 → 0.6.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/dist/index.js CHANGED
@@ -1198,11 +1198,20 @@ function applyReplacements(state, code, mappings) {
1198
1198
  let ${payload.typingId} = ${typingIdLhs}.as();
1199
1199
  type ${payload.typingId} = typeof ${payload.typingId};
1200
1200
  `;
1201
- } else if (payload.type === "exitAttr") replacement = dedent`
1201
+ } else if (payload.type === "exitAttr") {
1202
+ const rewrittenMeta = `${payload.oldMetaType}_rewritten`;
1203
+ const mergeFn = `${payload.oldMetaType}_mergeFn`;
1204
+ const mergeFnRet = `${payload.oldMetaType}_mergeFnRet`;
1205
+ replacement = dedent`
1202
1206
  type ${payload.returnType} = typeof ${payload.returnType};
1203
- type ${payload.newMetaType} = ${payload.returnType} extends { rewriteMeta: infer NewMeta extends {} } ? NewMeta : ${payload.oldMetaType}
1207
+ type ${rewrittenMeta} = ${payload.returnType} extends { rewriteMeta: infer NewMeta extends {} } ? NewMeta : ${payload.oldMetaType};
1208
+ let ${mergeFn}!: ${payload.defType} extends {
1209
+ [${payload.attrName}]: { mergeMeta: infer M }
1210
+ } ? M : null;
1211
+ let ${mergeFnRet} = ${mergeFn}?.(null! as ${rewrittenMeta}, null! as ${payload.innerMetaType});
1212
+ type ${payload.newMetaType} = [typeof ${mergeFn}] extends [null] ? ${rewrittenMeta} : typeof ${mergeFnRet};
1204
1213
  `;
1205
- else replacement = "";
1214
+ } else replacement = "";
1206
1215
  matchInfos.push({
1207
1216
  sourceEnd: offset + match.length,
1208
1217
  lengthOffset: replacement.length - match.length
@@ -1282,6 +1291,7 @@ const exitVM = (state, errorRange) => {
1282
1291
  collectedAttrs: collectedAttrNames,
1283
1292
  errorRange
1284
1293
  }));
1294
+ return { finalMetaId };
1285
1295
  };
1286
1296
  const enterAttr = (state, attrName) => {
1287
1297
  const defTypeId = state.vmDefTypeIdStack.at(-1);
@@ -1317,7 +1327,7 @@ const genBindingTyping = (state, info) => {
1317
1327
  typingId: info.typingId.name
1318
1328
  }));
1319
1329
  };
1320
- const exitAttr = (state, returningId) => {
1330
+ const exitAttr = (state, attrName, innerVMFinalMetaId, returningId) => {
1321
1331
  const currentDefId = state.vmDefTypeIdStack.at(-1);
1322
1332
  if (!currentDefId) return;
1323
1333
  const newMetaTypeId = {
@@ -1328,6 +1338,8 @@ const exitAttr = (state, returningId) => {
1328
1338
  state.typingPendingStatements.push(createReplacementHolder(state, {
1329
1339
  type: "exitAttr",
1330
1340
  defType: currentDefId.name,
1341
+ attrName,
1342
+ innerMetaType: innerVMFinalMetaId.name,
1331
1343
  oldMetaType: oldMetaTypeId.name,
1332
1344
  newMetaType: newMetaTypeId.name,
1333
1345
  returnType: returningId.name
@@ -1508,10 +1520,11 @@ const gtsToTypingsWalker = {
1508
1520
  }]
1509
1521
  });
1510
1522
  enterVMFromAttr(state, returnValue);
1523
+ let exitVMResult;
1511
1524
  if (body.namedAttributes) {
1512
1525
  visit(body.namedAttributes);
1513
- exitVM(state, body.namedAttributes.range);
1514
- } else exitVM(state, name.range);
1526
+ exitVMResult = exitVM(state, body.namedAttributes.range);
1527
+ } else exitVMResult = exitVM(state, name.range);
1515
1528
  if (bindingName) {
1516
1529
  const export_ = node.bindingAccessModifier !== "private";
1517
1530
  const typingId = {
@@ -1538,7 +1551,7 @@ const gtsToTypingsWalker = {
1538
1551
  leadingComments: state.defineLeadingComments
1539
1552
  });
1540
1553
  }
1541
- exitAttr(state, returnValue);
1554
+ exitAttr(state, attrName, exitVMResult.finalMetaId, returnValue);
1542
1555
  return EMPTY;
1543
1556
  },
1544
1557
  GTSNamedAttributeBlock(node, { state, visit }) {
@@ -1620,7 +1633,6 @@ const DEFAULT_VOLAR_MAPPING_DATA = {
1620
1633
  completion: true,
1621
1634
  format: true,
1622
1635
  navigation: true,
1623
- semantic: true,
1624
1636
  structure: true,
1625
1637
  verification: true
1626
1638
  };
@@ -1633,7 +1645,7 @@ const LITERAL_FROM_ID_MAPPING_DATA = {
1633
1645
  literalFromId: true
1634
1646
  };
1635
1647
  const DIRECT_ACTION_STUB_MAPPING_DATA = {
1636
- ...DEFAULT_VOLAR_MAPPING_DATA,
1648
+ semantic: true,
1637
1649
  directActionStub: true
1638
1650
  };
1639
1651
  const VERIFICATION_ONLY_MAPPING_DATA = { verification: true };
@@ -1653,13 +1665,22 @@ function getPrintOptions(source, state) {
1653
1665
  getLeadingComments: (node) => node.leadingComments,
1654
1666
  getTrailingComments: (node) => node.trailingComments,
1655
1667
  getMappingData: () => DEFAULT_VOLAR_MAPPING_DATA,
1656
- beforeWriteNode: ({ range, isUntouched, context }) => {
1657
- if (isUntouched || !range) return;
1668
+ beforeWriteNode: ({ range, node, isUntouched, context }) => {
1669
+ if (isUntouched || !range || node.type === "ImportDeclaration") return;
1658
1670
  context.writeMapped("", range.start, range.start, VERIFICATION_ONLY_MAPPING_DATA);
1659
1671
  },
1660
- afterWriteNode: ({ range, isUntouched, context }) => {
1661
- if (isUntouched || !range) return;
1662
- context.writeMapped("", range.end, range.end, VERIFICATION_ONLY_MAPPING_DATA);
1672
+ afterWriteNode: ({ range, node, isUntouched, context }) => {
1673
+ const [lastImportDecl, lastImportGenerated] = state.lastImportDeclaration ?? [];
1674
+ if (node.type === "ImportDeclaration" && lastImportDecl === node) {
1675
+ context.write("\n");
1676
+ let sourceOffsetBaseBase = lastImportGenerated ? state.contentStartOffset : node.range?.[1] ?? 0;
1677
+ const sourceOffset = sourceOffsetBaseBase + Math.max(0, context.source.slice(sourceOffsetBaseBase).search(/\n/));
1678
+ context.createExtraMapping({
1679
+ start: sourceOffset + 1,
1680
+ end: sourceOffset + 1
1681
+ }, context.generatedOffset, context.generatedOffset, DEFAULT_VOLAR_MAPPING_DATA);
1682
+ } else if (isUntouched || !range) return;
1683
+ else context.writeMapped("", range.end, range.end, VERIFICATION_ONLY_MAPPING_DATA);
1663
1684
  },
1664
1685
  printers: {
1665
1686
  Identifier(node, context) {
@@ -1713,16 +1734,6 @@ function getPrintOptions(source, state) {
1713
1734
  }, generatedStart, generatedEnd, VERIFICATION_ONLY_MAPPING_DATA);
1714
1735
  }
1715
1736
  },
1716
- ImportDeclaration(node, context) {
1717
- defaultPrinters.ImportDeclaration(node, context);
1718
- if (state.lastImportDeclarationIfGen === node) {
1719
- context.write("\n");
1720
- context.createExtraMapping({
1721
- start: state.contentStartOffset,
1722
- end: state.contentStartOffset + 1
1723
- }, context.generatedOffset, context.generatedOffset + 1, DEFAULT_VOLAR_MAPPING_DATA);
1724
- }
1725
- },
1726
1737
  GTSAttributeNameHintStatement(node, context) {
1727
1738
  context.writeNode(node.object);
1728
1739
  context.write(".");
@@ -1834,7 +1845,7 @@ function transformForVolar(ast, option, sourceInfo) {
1834
1845
  directActionStubRange: /* @__PURE__ */ new WeakMap(),
1835
1846
  literalFromIdentifier: /* @__PURE__ */ new WeakSet(),
1836
1847
  lastArgNodes: /* @__PURE__ */ new WeakSet(),
1837
- lastImportDeclarationIfGen: null,
1848
+ lastImportDeclaration: null,
1838
1849
  diagnosticsOnTopNodes: /* @__PURE__ */ new WeakSet(),
1839
1850
  extraMappings: [],
1840
1851
  contentStartOffset: getContentStartOffset(sourceInfo.content)
@@ -1850,9 +1861,16 @@ function transformForVolar(ast, option, sourceInfo) {
1850
1861
  if (lastArg) state.lastArgNodes.add(lastArg);
1851
1862
  },
1852
1863
  ImportDeclaration(node, { state }) {
1853
- if (!state.sourceNodes.has(node)) state.lastImportDeclarationIfGen = node;
1864
+ state.lastImportDeclaration = [node, !state.sourceNodes.has(node)];
1854
1865
  }
1855
1866
  });
1867
+ walk(newAst, state, { Program(node, { state }) {
1868
+ const lastImportDeclarationNode = state.lastImportDeclaration?.[0];
1869
+ if (lastImportDeclarationNode) {
1870
+ const index = node.body.indexOf(lastImportDeclarationNode);
1871
+ if (index >= 0) node.body.splice(index + 1, 0, { type: "EmptyStatement" });
1872
+ }
1873
+ } });
1856
1874
  let { code, mappings } = print$1(newAst, getPrintOptions(sourceInfo.content, state));
1857
1875
  code = applyReplacements(state, code, mappings);
1858
1876
  for (const extraMapping of state.extraMappings) {
@@ -1865,12 +1883,6 @@ function transformForVolar(ast, option, sourceInfo) {
1865
1883
  data: VERIFICATION_ONLY_MAPPING_DATA
1866
1884
  });
1867
1885
  }
1868
- walk(newAst, null, { ImportDeclaration(node) {
1869
- if (!node.range) return;
1870
- const endOffset = node.range[1];
1871
- const mappingEndsWithThisImport = mappings.find((m) => m.sourceOffsets[0] + m.lengths[0] === endOffset);
1872
- if (mappingEndsWithThisImport?.lengths[0]) mappingEndsWithThisImport.lengths[0] += 1;
1873
- } });
1874
1886
  return {
1875
1887
  code,
1876
1888
  mappings
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gi-tcg/gts-transpiler",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/piovium/gts.git"
@@ -46,7 +46,7 @@ export function transformForVolar(
46
46
  directActionStubRange: new WeakMap(),
47
47
  literalFromIdentifier: new WeakSet(),
48
48
  lastArgNodes: new WeakSet(),
49
- lastImportDeclarationIfGen: null,
49
+ lastImportDeclaration: null,
50
50
  diagnosticsOnTopNodes: new WeakSet(),
51
51
  extraMappings: [],
52
52
  contentStartOffset: getContentStartOffset(sourceInfo.content),
@@ -61,7 +61,7 @@ export function transformForVolar(
61
61
  },
62
62
  });
63
63
  const newAst = walk(ast as AST.Node, state, gtsToTypingsWalker);
64
- // mark lastArgNodes
64
+ // mark lastArgNodes and lastImportDeclaration
65
65
  walk(newAst as AST.Node, state, {
66
66
  CallExpression(node, { state }) {
67
67
  const lastArg = node.arguments.at(-1);
@@ -70,11 +70,22 @@ export function transformForVolar(
70
70
  }
71
71
  },
72
72
  ImportDeclaration(node, { state }) {
73
- if (!state.sourceNodes.has(node)) {
74
- state.lastImportDeclarationIfGen = node;
73
+ state.lastImportDeclaration = [node, !state.sourceNodes.has(node)];
74
+ },
75
+ });
76
+ walk(newAst, state, {
77
+ Program(node, { state }) {
78
+ const lastImportDeclarationNode = state.lastImportDeclaration?.[0];
79
+ // break continuous gap between last import declaration and its next node
80
+ if (lastImportDeclarationNode) {
81
+ const index = node.body.indexOf(lastImportDeclarationNode);
82
+ if (index >= 0) {
83
+ node.body.splice(index + 1, 0, { type: "EmptyStatement" });
84
+ }
75
85
  }
76
86
  },
77
87
  });
88
+
78
89
  const printOptions = getPrintOptions(sourceInfo.content, state);
79
90
  let { code, mappings } = print(newAst, printOptions);
80
91
  code = applyReplacements(state, code, mappings);
@@ -88,20 +99,6 @@ export function transformForVolar(
88
99
  data: VERIFICATION_ONLY_MAPPING_DATA,
89
100
  });
90
101
  }
91
- walk(newAst, null, {
92
- ImportDeclaration(node) {
93
- if (!node.range) {
94
- return;
95
- }
96
- const endOffset = node.range[1];
97
- const mappingEndsWithThisImport = mappings.find(
98
- (m) => m.sourceOffsets[0] + m.lengths[0] === endOffset,
99
- );
100
- if (mappingEndsWithThisImport?.lengths[0]) {
101
- mappingEndsWithThisImport.lengths[0] += 1; // include the newline after the import
102
- }
103
- },
104
- });
105
102
  return {
106
103
  code,
107
104
  mappings,
@@ -17,7 +17,6 @@ export const DEFAULT_VOLAR_MAPPING_DATA: CodeInformation = {
17
17
  completion: true,
18
18
  format: true,
19
19
  navigation: true,
20
- semantic: true,
21
20
  structure: true,
22
21
  verification: true,
23
22
  };
@@ -30,7 +29,7 @@ export const LITERAL_FROM_ID_MAPPING_DATA: CodeInformation = {
30
29
  literalFromId: true,
31
30
  }
32
31
  export const DIRECT_ACTION_STUB_MAPPING_DATA: CodeInformation = {
33
- ...DEFAULT_VOLAR_MAPPING_DATA,
32
+ semantic: true,
34
33
  directActionStub: true,
35
34
  }
36
35
  export const VERIFICATION_ONLY_MAPPING_DATA: CodeInformation = {
@@ -20,7 +20,10 @@ import {
20
20
  LITERAL_FROM_ID_MAPPING_DATA,
21
21
  VERIFICATION_ONLY_MAPPING_DATA,
22
22
  } from "./mappings.ts";
23
- import type { TypingTranspileState, GTSAttributeNameHintStatement } from "./walker.ts";
23
+ import type {
24
+ TypingTranspileState,
25
+ GTSAttributeNameHintStatement,
26
+ } from "./walker.ts";
24
27
 
25
28
  export function getPrintOptions(
26
29
  source: string,
@@ -37,23 +40,61 @@ export function getPrintOptions(
37
40
  }
38
41
  return state.sourceNodes.has(node as Node);
39
42
  },
43
+
40
44
  printCommentsOnUntouchedNodes: true,
41
45
  getLeadingComments: (node) => (node as Node).leadingComments,
42
46
  getTrailingComments: (node) => (node as Node).trailingComments,
43
47
  getMappingData: () => DEFAULT_VOLAR_MAPPING_DATA,
44
48
  // Add a 0-length mapping before and after each touched node with verification only,
45
49
  // so that error squiggles start/ends at those positions can be reflected.
46
- beforeWriteNode: ({ range, isUntouched, context }) => {
47
- if (isUntouched || !range) {
50
+ beforeWriteNode: ({ range, node, isUntouched, context }) => {
51
+ if (isUntouched || !range || node.type === "ImportDeclaration") {
52
+ // Don't do that for ImportDeclaration -- it mess up auto-import insertion point
48
53
  return;
49
54
  }
50
- context.writeMapped("", range.start, range.start, VERIFICATION_ONLY_MAPPING_DATA);
55
+ context.writeMapped(
56
+ "",
57
+ range.start,
58
+ range.start,
59
+ VERIFICATION_ONLY_MAPPING_DATA,
60
+ );
51
61
  },
52
- afterWriteNode: ({ range, isUntouched, context }) => {
53
- if (isUntouched || !range) {
62
+ afterWriteNode: ({ range, node, isUntouched, context }) => {
63
+ const [lastImportDecl, lastImportGenerated] =
64
+ state.lastImportDeclaration ?? [];
65
+ if (node.type === "ImportDeclaration" && lastImportDecl === node) {
66
+ // If the last import declaration is a generated one, because of we ensured that
67
+ // the generated import declarations are always unsorted, so TSServer will set the
68
+ // auto-insertion point next to this last import declaration.
69
+ // Add a mapping to that position (a written newline character) to the top-of-file,
70
+ // after skipping hashbang and leading comments.
71
+ context.write("\n");
72
+ let sourceOffsetBaseBase = lastImportGenerated
73
+ ? state.contentStartOffset
74
+ : (node.range?.[1] ?? 0);
75
+ const sourceOffset =
76
+ sourceOffsetBaseBase +
77
+ Math.max(0, context.source.slice(sourceOffsetBaseBase).search(/\n/));
78
+ // These +1s below is magic and I don't know why, but it works.
79
+ context.createExtraMapping(
80
+ {
81
+ start: sourceOffset + 1,
82
+ end: sourceOffset + 1,
83
+ },
84
+ context.generatedOffset,
85
+ context.generatedOffset,
86
+ DEFAULT_VOLAR_MAPPING_DATA,
87
+ );
88
+ } else if (isUntouched || !range) {
54
89
  return;
90
+ } else {
91
+ context.writeMapped(
92
+ "",
93
+ range.end,
94
+ range.end,
95
+ VERIFICATION_ONLY_MAPPING_DATA,
96
+ );
55
97
  }
56
- context.writeMapped("", range.end, range.end, VERIFICATION_ONLY_MAPPING_DATA);
57
98
  },
58
99
  printers: {
59
100
  // 1) Make the print of dummy identifier print nothing.
@@ -115,7 +156,9 @@ export function getPrintOptions(
115
156
  node.range[1],
116
157
  ATTRIBUTE_NAME_MAPPING_DATA,
117
158
  );
118
- } else if ((directActionStubRange = state.directActionStubRange.get(node))) {
159
+ } else if (
160
+ (directActionStubRange = state.directActionStubRange.get(node))
161
+ ) {
119
162
  // For direct action stubs, add mappings from this expression statement line to the
120
163
  // start of original direct action start position. Add `directActionStub` data
121
164
  // for recognizing them in language service plugin.
@@ -124,7 +167,7 @@ export function getPrintOptions(
124
167
  directActionStubRange.start,
125
168
  directActionStubRange.start + 1,
126
169
  DIRECT_ACTION_STUB_MAPPING_DATA,
127
- )
170
+ );
128
171
  } else {
129
172
  defaultPrinters.Literal(node, context);
130
173
  }
@@ -168,37 +211,17 @@ export function getPrintOptions(
168
211
  );
169
212
  }
170
213
  },
171
- // If the last import declaration is a generated one, because of we ensured that
172
- // the generated import declarations are always unsorted, so TSServer will set the
173
- // auto-insertion point next to this last import declaration.
174
- // Add a mapping to that position (a written newline character) to the top-of-file,
175
- // after skipping hashbang and leading comments.
176
- // NOTE: since the insertion derived from here won't add additional newline *before*
177
- // the inserted text, so here is a difference behavior from standard TSServer and our
178
- // language server. We'd try our best.
179
- ImportDeclaration(node, context) {
180
- defaultPrinters.ImportDeclaration(node, context);
181
- if (state.lastImportDeclarationIfGen === node) {
182
- context.write("\n");
183
- context.createExtraMapping(
184
- {
185
- start: state.contentStartOffset,
186
- end: state.contentStartOffset + 1,
187
- },
188
- context.generatedOffset,
189
- context.generatedOffset + 1,
190
- DEFAULT_VOLAR_MAPPING_DATA,
191
- );
192
- }
193
- },
194
214
  // @ts-expect-error This is a custom node type that don't have typing.
195
215
  // @see `GTSAttributeNameHintStatement`
196
- GTSAttributeNameHintStatement(node: GTSAttributeNameHintStatement, context: PrinterContext<CodeInformation>) {
216
+ GTSAttributeNameHintStatement(
217
+ node: GTSAttributeNameHintStatement,
218
+ context: PrinterContext<CodeInformation>,
219
+ ) {
197
220
  context.writeNode(node.object as EspolarAST.Node);
198
221
  context.write(".");
199
222
  context.writeSource(node.whiteSpaceStart, node.whiteSpaceEnd);
200
223
  context.write(";");
201
- }
224
+ },
202
225
  },
203
226
  // Enable triggering signature completion
204
227
  experimentalGetLeftParenSourceRange: (node) => {
@@ -51,6 +51,8 @@ type ReplacementPayload =
51
51
  type: "exitAttr";
52
52
  returnType: string;
53
53
  defType: string;
54
+ attrName: string;
55
+ innerMetaType: string;
54
56
  oldMetaType: string;
55
57
  newMetaType: string;
56
58
  };
@@ -194,9 +196,17 @@ export function applyReplacements(
194
196
  type ${payload.typingId} = typeof ${payload.typingId};
195
197
  `;
196
198
  } else if (payload.type === "exitAttr") {
199
+ const rewrittenMeta = `${payload.oldMetaType}_rewritten`;
200
+ const mergeFn = `${payload.oldMetaType}_mergeFn`;
201
+ const mergeFnRet = `${payload.oldMetaType}_mergeFnRet`;
197
202
  replacement = dedent`
198
203
  type ${payload.returnType} = typeof ${payload.returnType};
199
- type ${payload.newMetaType} = ${payload.returnType} extends { rewriteMeta: infer NewMeta extends {} } ? NewMeta : ${payload.oldMetaType}
204
+ type ${rewrittenMeta} = ${payload.returnType} extends { rewriteMeta: infer NewMeta extends {} } ? NewMeta : ${payload.oldMetaType};
205
+ let ${mergeFn}!: ${payload.defType} extends {
206
+ [${payload.attrName}]: { mergeMeta: infer M }
207
+ } ? M : null;
208
+ let ${mergeFnRet} = ${mergeFn}?.(null! as ${rewrittenMeta}, null! as ${payload.innerMetaType});
209
+ type ${payload.newMetaType} = [typeof ${mergeFn}] extends [null] ? ${rewrittenMeta} : typeof ${mergeFnRet};
200
210
  `;
201
211
  } else {
202
212
  replacement = "";
@@ -76,10 +76,10 @@ export interface TypingTranspileState extends TranspileState {
76
76
  /** Nodes that are last arguments of a CallExpression */
77
77
  lastArgNodes: WeakSet<Node>;
78
78
  /**
79
- * The last import declaration, record if it is a generated one.
80
- * This declaration marks the TS auto-import insertion point.
79
+ * The last import declaration and whether it is a generated one.
80
+ * This declaration marks the TS auto-import insertion point if it's generated.
81
81
  */
82
- lastImportDeclarationIfGen: ImportDeclaration | null;
82
+ lastImportDeclaration: [node: ImportDeclaration, generated: boolean] | null;
83
83
  /** Nodes that have a diagnostic mappings to the top of the file */
84
84
  diagnosticsOnTopNodes: WeakSet<Node>;
85
85
  /** Extra mappings, the generated range will be found by the needle after replacement */
@@ -168,7 +168,14 @@ const enterVMFromAttr = (
168
168
  state.attrsOfCurrentVm.push([]);
169
169
  };
170
170
 
171
- const exitVM = (state: TypingTranspileState, errorRange?: [number, number]) => {
171
+ interface ExitVMResult {
172
+ finalMetaId: Identifier;
173
+ }
174
+
175
+ const exitVM = (
176
+ state: TypingTranspileState,
177
+ errorRange?: [number, number],
178
+ ): ExitVMResult => {
172
179
  const currentDefTypeId = state.vmDefTypeIdStack.pop()!;
173
180
  const currentMetaId = state.metaTypeIdStack.pop()!;
174
181
  const finalMetaId = state.finalMetaTypeIdStack.pop()!;
@@ -183,17 +190,24 @@ const exitVM = (state: TypingTranspileState, errorRange?: [number, number]) => {
183
190
  errorRange,
184
191
  }),
185
192
  );
193
+ return { finalMetaId };
186
194
  };
187
195
 
196
+ interface EnterAttrResult {
197
+ lhsId: Identifier;
198
+ }
199
+
188
200
  const enterAttr = (
189
201
  state: TypingTranspileState,
190
202
  attrName: string,
191
- ): { lhsId: Identifier } => {
203
+ ): EnterAttrResult => {
192
204
  const defTypeId = state.vmDefTypeIdStack.at(-1);
193
205
  const metaTypeId = state.metaTypeIdStack.at(-1);
194
206
  if (!defTypeId || !metaTypeId) {
195
207
  // FIXME error handling?
196
- return { lhsId: { type: "Identifier", name: "__gts_invalid_attr_obj" } };
208
+ return {
209
+ lhsId: { type: "Identifier", name: "__gts_invalid_attr_obj" },
210
+ };
197
211
  }
198
212
  state.attrsOfCurrentVm.at(-1)!.push(attrName);
199
213
  const lhsId: Identifier = {
@@ -210,7 +224,7 @@ const enterAttr = (
210
224
  hintOnly: attrName === ATTR_HINT_ATTR_NAME,
211
225
  }),
212
226
  );
213
- return { lhsId: lhsId };
227
+ return { lhsId };
214
228
  };
215
229
 
216
230
  const genBindingTyping = (
@@ -236,7 +250,12 @@ const genBindingTyping = (
236
250
  );
237
251
  };
238
252
 
239
- const exitAttr = (state: TypingTranspileState, returningId: Identifier) => {
253
+ const exitAttr = (
254
+ state: TypingTranspileState,
255
+ attrName: string,
256
+ innerVMFinalMetaId: Identifier,
257
+ returningId: Identifier,
258
+ ) => {
240
259
  const currentDefId = state.vmDefTypeIdStack.at(-1);
241
260
  if (!currentDefId) {
242
261
  return;
@@ -250,6 +269,8 @@ const exitAttr = (state: TypingTranspileState, returningId: Identifier) => {
250
269
  createReplacementHolder(state, {
251
270
  type: "exitAttr",
252
271
  defType: currentDefId.name,
272
+ attrName,
273
+ innerMetaType: innerVMFinalMetaId.name,
253
274
  oldMetaType: oldMetaTypeId.name,
254
275
  newMetaType: newMetaTypeId.name,
255
276
  returnType: returningId.name,
@@ -473,11 +494,12 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
473
494
  ],
474
495
  });
475
496
  enterVMFromAttr(state, returnValue);
497
+ let exitVMResult: ExitVMResult;
476
498
  if (body.namedAttributes) {
477
499
  visit(body.namedAttributes);
478
- exitVM(state, body.namedAttributes.range);
500
+ exitVMResult = exitVM(state, body.namedAttributes.range);
479
501
  } else {
480
- exitVM(state, name.range);
502
+ exitVMResult = exitVM(state, name.range);
481
503
  }
482
504
  if (bindingName) {
483
505
  const export_ = node.bindingAccessModifier !== "private";
@@ -505,7 +527,7 @@ export const gtsToTypingsWalker: Visitors<Node, TypingTranspileState> = {
505
527
  leadingComments: state.defineLeadingComments,
506
528
  });
507
529
  }
508
- exitAttr(state, returnValue);
530
+ exitAttr(state, attrName, exitVMResult.finalMetaId, returnValue);
509
531
  return EMPTY;
510
532
  },
511
533
  GTSNamedAttributeBlock(node, { state, visit }) {