@gi-tcg/gts-transpiler 0.1.1 → 0.3.0

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.
@@ -2,7 +2,6 @@ import type { AST } from "../../types";
2
2
  import { walk } from "zimmerframe";
3
3
  import type { SourceInfo, TranspileResult } from "..";
4
4
  import { print } from "esrap";
5
- import tsPrinter from "esrap/languages/ts";
6
5
  import {
7
6
  initialTranspileState,
8
7
  type TranspileOption,
@@ -13,16 +12,19 @@ import { applyReplacements } from "./replacements";
13
12
  import type { Program } from "estree";
14
13
  import { convertToVolarMappings, type VolarMappingResult } from "./mappings";
15
14
  import { collectLeafTokens, type LeafToken } from "./collect_tokens";
15
+ import { patchedPrinter } from "./printer";
16
16
 
17
17
  interface TypingTranspileOption extends TranspileOption {
18
18
  leafTokens: LeafToken[];
19
- // "row:col" -> "replacement string"
20
- additionalMappings: Map<string, string>;
19
+ /**
20
+ * "row:col" -> "replacement string"
21
+ */
22
+ extraMappings: Map<string, string>;
21
23
  }
22
24
 
23
25
  function gtsToTypings(
24
26
  ast: AST.Program,
25
- option: TypingTranspileOption
27
+ option: TypingTranspileOption,
26
28
  ): TranspileResult {
27
29
  const state: TypingTranspileState = {
28
30
  ...(initialTranspileState(option) as Pick<
@@ -31,34 +33,21 @@ function gtsToTypings(
31
33
  >),
32
34
  leafTokens: option.leafTokens,
33
35
  idCounter: 0,
34
- pendingStatements: [],
35
- prefaceInserted: false,
36
- rootVmId: { type: "Identifier", name: "__root_vm" },
36
+ typingPendingStatements: [],
37
+ rootVmId: { type: "Identifier", name: "__gts_root_vm" },
38
+ utilNsId: { type: "Identifier", name: "__gts_util" },
37
39
  replacementTag: { type: "Identifier", name: "__gts_replacement_tag" },
38
- symbolsId: {
39
- Meta: { type: "Identifier", name: "__gts_symbols_meta" },
40
- NamedDefinition: { type: "Identifier", name: "__gts_symbols_namedDef" },
41
- },
40
+ MetaLit: { type: "Literal", value: "~meta" },
41
+ NamedDefinitionLit: { type: "Literal", value: "~namedDefinition" },
42
+ defineLeadingComments: [],
42
43
  vmDefTypeIdStack: [],
43
44
  metaTypeIdStack: [],
44
45
  finalMetaTypeIdStack: [],
45
46
  attrsOfCurrentVm: [],
46
- additionalMappings: option.additionalMappings,
47
+ extraMappings: option.extraMappings,
47
48
  };
48
49
  const newAst = walk(ast as AST.Node, state, gtsToTypingsWalker);
49
- const printer = tsPrinter({
50
- getLeadingComments: (node) => (node as AST.Node).leadingComments,
51
- getTrailingComments: (node) => (node as AST.Node).trailingComments,
52
- });
53
- const prevIdentifier = printer.Identifier!;
54
- printer.Identifier = function (node, context) {
55
- if (node.isDummy) {
56
- context.write("", node);
57
- } else {
58
- prevIdentifier(node, context);
59
- }
60
- };
61
- const { code, map } = print(newAst, printer, {
50
+ const { code, map } = print(newAst, patchedPrinter, {
62
51
  indent: " ",
63
52
  });
64
53
  return {
@@ -70,21 +59,21 @@ function gtsToTypings(
70
59
  export function transformForVolar(
71
60
  ast: Program,
72
61
  option: TranspileOption,
73
- sourceInfo: Required<SourceInfo>
62
+ sourceInfo: Required<SourceInfo>,
74
63
  ): VolarMappingResult {
75
64
  const tokens = collectLeafTokens(ast);
76
- const additionalMappings = new Map<string, string>();
65
+ const extraMappings = new Map<string, string>();
77
66
  const { code, sourceMap } = gtsToTypings(ast, {
78
67
  ...option,
79
68
  leafTokens: tokens,
80
- additionalMappings,
69
+ extraMappings,
81
70
  });
82
71
  const volarMappings = convertToVolarMappings(
83
72
  code,
84
73
  sourceInfo.content,
85
74
  sourceMap,
86
75
  tokens,
87
- additionalMappings
76
+ extraMappings,
88
77
  );
89
78
  return {
90
79
  code,
@@ -39,8 +39,8 @@ type CodeToGeneratedMap = Map<string, CodePosition[]>;
39
39
  * @returns */
40
40
  export const offset_to_line_col = (
41
41
  offset: number,
42
- line_offsets: LineOffsets
43
- ) => {
42
+ line_offsets: LineOffsets,
43
+ ): { line: number; column: number } => {
44
44
  // Binary search
45
45
  let left = 0;
46
46
  let right = line_offsets.length - 1;
@@ -71,13 +71,13 @@ export const offset_to_line_col = (
71
71
  * @param source_map - The source map object from esrap (v3 format)
72
72
  * @param line_offsets - Pre-computed line offsets array
73
73
  * @param generated_code - The final generated code (after post-processing)
74
- * @returns Tuple of [source-to-generated map, generated-to-source map]
74
+ * @returns source-to-generated map
75
75
  */
76
76
  export function buildSrcToGenMap(
77
77
  source_map: SourceMap,
78
78
  line_offsets: LineOffsets,
79
- generated_code: string
80
- ) {
79
+ generated_code: string,
80
+ ): CodeToGeneratedMap {
81
81
  const map: CodeToGeneratedMap = new Map();
82
82
 
83
83
  // Decode the VLQ-encoded mappings string
@@ -191,20 +191,13 @@ export function buildSrcToGenMap(
191
191
  export function getGeneratedPosition(
192
192
  src_line: number,
193
193
  src_column: number,
194
- srcToGenMap: CodeToGeneratedMap
195
- ) {
194
+ srcToGenMap: CodeToGeneratedMap,
195
+ ): CodePosition[] {
196
196
  const key = `${src_line}:${src_column}`;
197
197
  const positions = srcToGenMap.get(key);
198
198
 
199
- if (!positions || positions.length === 0) {
200
- // No mapping found in source map - this shouldn't happen since all tokens should have mappings
201
- // throw new Error(
202
- // `No source map entry for position "${src_line}:${src_column}"`
203
- // );
204
- }
205
-
206
199
  // If multiple generated positions map to same source, return the first
207
- return positions?.[0];
200
+ return positions || [];
208
201
  }
209
202
  // Helper to create a line-to-offset lookup table
210
203
  function createLineOffsets(content: string): number[] {
@@ -230,7 +223,7 @@ function createLineOffsets(content: string): number[] {
230
223
  function locToOffset(
231
224
  line: number,
232
225
  column: number,
233
- line_offsets: number[]
226
+ line_offsets: number[],
234
227
  ): number {
235
228
  if (line < 1 || line > line_offsets.length) {
236
229
  // throw new Error(
@@ -245,14 +238,14 @@ export function convertToVolarMappings(
245
238
  source: string,
246
239
  sourceMap: SourceMap,
247
240
  tokens: LeafToken[],
248
- additionalMappings: Map<string, string>
241
+ extraMappings: Map<string, string>,
249
242
  ): CodeMapping[] {
250
243
  const sourceLineOffsets = createLineOffsets(source);
251
244
  const generatedLineOffsets = createLineOffsets(generated);
252
245
  const srcToGenMap = buildSrcToGenMap(
253
246
  sourceMap,
254
247
  generatedLineOffsets,
255
- generated
248
+ generated,
256
249
  );
257
250
 
258
251
  const mappings: CodeMapping[] = [];
@@ -261,18 +254,23 @@ export function convertToVolarMappings(
261
254
  let sourceStart = locToOffset(
262
255
  token.loc.start.line,
263
256
  token.loc.start.column,
264
- sourceLineOffsets
257
+ sourceLineOffsets,
265
258
  );
259
+ sourceStart += token.sourceStartOffset ?? 0;
260
+
266
261
  const sourceEnd = locToOffset(
267
262
  token.loc.end.line,
268
263
  token.loc.end.column,
269
- sourceLineOffsets
264
+ sourceLineOffsets,
270
265
  );
266
+
271
267
  let sourceLength = token.sourceLength ?? sourceEnd - sourceStart;
272
- const genLineCol = getGeneratedPosition(
268
+
269
+ sourceLength += token.sourceLengthOffset ?? 0;
270
+ const [genLineCol] = getGeneratedPosition(
273
271
  token.loc.start.line,
274
272
  token.loc.start.column,
275
- srcToGenMap
273
+ srcToGenMap,
276
274
  );
277
275
  if (!genLineCol) {
278
276
  // No mapping found for this token - skip it
@@ -281,21 +279,8 @@ export function convertToVolarMappings(
281
279
  let genStart = locToOffset(
282
280
  genLineCol.line,
283
281
  genLineCol.column,
284
- generatedLineOffsets
282
+ generatedLineOffsets,
285
283
  );
286
- if (token.locationAdjustment) {
287
- // maps verification back to the start of source
288
- mappings.push({
289
- sourceOffsets: [sourceStart],
290
- generatedOffsets: [genStart],
291
- lengths: [0],
292
- generatedLengths: [token.locationAdjustment.generatedLength],
293
- data: {
294
- verification: true,
295
- },
296
- });
297
- genStart += token.locationAdjustment.startOffset;
298
- }
299
284
  if (token.isDummy) {
300
285
  // A dummy token might be generated for a missing property / argument.
301
286
  // Notice that when facing this scenario, the parser tries to 'defer' and step through
@@ -308,9 +293,25 @@ export function convertToVolarMappings(
308
293
  sourceLength++;
309
294
  }
310
295
  }
311
-
312
296
  const generatedLength = token.generatedLength ?? sourceLength;
313
297
 
298
+ if (
299
+ typeof token.generatedStartOffset === "number" &&
300
+ token.generatedStartOffset > 0
301
+ ) {
302
+ // maps verification back to the start of source
303
+ mappings.push({
304
+ sourceOffsets: [sourceStart],
305
+ generatedOffsets: [genStart],
306
+ lengths: [0],
307
+ generatedLengths: [generatedLength],
308
+ data: {
309
+ verification: true,
310
+ },
311
+ });
312
+ genStart += token.generatedStartOffset;
313
+ }
314
+
314
315
  mappings.push({
315
316
  sourceOffsets: [sourceStart],
316
317
  generatedOffsets: [genStart],
@@ -320,7 +321,25 @@ export function convertToVolarMappings(
320
321
  });
321
322
  }
322
323
 
323
- for (const [loc, codeSnippet] of additionalMappings) {
324
+ // Handle extra mappings that purely generated and wants a diagnostic
325
+ // that appears on the top of file.
326
+ // We'd add these mappings at walker that they will have a `loc` to `1:0`
327
+ // so find them by looking up a source position of `1:0` will work
328
+ const locMapsToTop = getGeneratedPosition(1, 0, srcToGenMap);
329
+ for (const loc of locMapsToTop) {
330
+ const offset = locToOffset(loc.line, loc.column, generatedLineOffsets);
331
+ mappings.push({
332
+ sourceOffsets: [0],
333
+ generatedOffsets: [offset],
334
+ lengths: [0],
335
+ generatedLengths: [0],
336
+ data: {
337
+ verification: true,
338
+ },
339
+ });
340
+ }
341
+
342
+ for (const [loc, codeSnippet] of extraMappings) {
324
343
  const generatedStart = generated.indexOf(codeSnippet);
325
344
  if (generatedStart === -1) {
326
345
  continue;
@@ -0,0 +1,123 @@
1
+ import tsPrinter from "esrap/languages/ts";
2
+ import type { AST } from "../../types";
3
+ import type { Context, Visitors } from "esrap";
4
+ import type { Node, SimpleCallExpression } from "estree";
5
+
6
+ const printer = tsPrinter({
7
+ getLeadingComments: (node) => (node as AST.Node).leadingComments,
8
+ getTrailingComments: (node) => (node as AST.Node).trailingComments,
9
+ });
10
+
11
+ // Make the print of dummy identifier print nothing.
12
+ // Exception: if GTS attribute list's last argument is dummy, e.g.
13
+ // foo bar, ;
14
+ // ^~ here
15
+ // Then the printed JS will be `foo(bar, )` which WILL NOT be syntax error in ES6.
16
+ // So we mark the lastArg manually and print an additional comma
17
+ // for this dummy identifier, i.e. `foo(bar,,)` and TypeScript will recognize the error.
18
+
19
+ const prevIdentifier = printer.Identifier!;
20
+ printer.Identifier = function (node, context) {
21
+ if (node.isDummy) {
22
+ const text = Reflect.get(node, "lastArg") ? "," : "";
23
+ context.write(text, node);
24
+ } else {
25
+ prevIdentifier(node, context);
26
+ }
27
+ };
28
+
29
+ const prevCallNewExpression = printer.CallExpression!;
30
+ const newCallNewExpression: typeof prevCallNewExpression = function (
31
+ node: SimpleCallExpression,
32
+ context,
33
+ ) {
34
+ const lastArg = node.arguments.at(-1);
35
+ if (lastArg) {
36
+ Reflect.set(lastArg, "lastArg", true);
37
+ }
38
+ if (!node.lParenLoc) {
39
+ return prevCallNewExpression(node, context);
40
+ }
41
+ let hasDeferredWrite = false;
42
+ let interceptionDone = false;
43
+ const lParenFakeNode = {
44
+ loc: node.lParenLoc!,
45
+ } as AST.Node;
46
+
47
+ // Map the print of `(` with the fake lParen node.
48
+ // The print of `(` can be happened in two area:
49
+ // 1. The wrapped parenthesis towards callee, which follows a `context.visit` to the callee;
50
+ // 2. The argument list (what we should map), which follows a `context.append` call
51
+ // so we defer the write of `(` to the next call of `context.visit|append`.
52
+ // If it is `context.append`, then make `context.write("(")` happens with our fake node
53
+ // and mapping will be added to the final code-mapping. Otherwise, keep original write.
54
+
55
+ const patchedWrite = (text: string, node?: AST.Node) => {
56
+ if (text === "(") {
57
+ hasDeferredWrite = true;
58
+ } else {
59
+ context.write(text, node);
60
+ }
61
+ };
62
+ const patchedVisit = (node: AST.Node) => {
63
+ if (hasDeferredWrite) {
64
+ context.write("(");
65
+ }
66
+ return context.visit(node);
67
+ };
68
+ const patchedAppend = (subcontext: Context) => {
69
+ if (hasDeferredWrite) {
70
+ context.write("(", lParenFakeNode);
71
+ // console.log("Inserted fake [LPAREN] for node at ", lParenFakeNode.loc);
72
+ interceptionDone = true;
73
+ }
74
+ return context.append(subcontext);
75
+ };
76
+ const proxiedContext = new Proxy(context, {
77
+ get(target, prop) {
78
+ if (!interceptionDone) {
79
+ if (prop === "write") {
80
+ return patchedWrite;
81
+ }
82
+ if (prop === "visit") {
83
+ return patchedVisit;
84
+ }
85
+ if (prop === "append") {
86
+ return patchedAppend;
87
+ }
88
+ }
89
+ const value = Reflect.get(target, prop);
90
+ if (typeof value === "function") {
91
+ return value.bind(target);
92
+ }
93
+ return value;
94
+ },
95
+ });
96
+ return prevCallNewExpression(node, proxiedContext);
97
+ };
98
+ printer.CallExpression = newCallNewExpression;
99
+ printer.NewExpression = newCallNewExpression;
100
+
101
+ // Handle node with `diagnosticsOnTop`. These nodes and their children will be
102
+ // printed with an extra `loc` that point to the beginning of the source file,
103
+ // so the source-mapping will include them with a 0:1 -> generated position entry.
104
+
105
+ const prevWildcard = printer._!;
106
+ printer._ = (node: Node, context, visit) => {
107
+ const contextProto = Object.getPrototypeOf(context);
108
+ const prevContextWrite: typeof context.write = contextProto.write;
109
+ if ("diagnosticsOnTop" in node && node.diagnosticsOnTop) {
110
+ contextProto.write = function (text: string, node?: AST.Node) {
111
+ node ??= {} as AST.Node;
112
+ node.loc ??= {
113
+ start: { line: 1, column: 0 },
114
+ end: { line: 1, column: 0 },
115
+ };
116
+ return prevContextWrite.call(this, text, node);
117
+ };
118
+ }
119
+ prevWildcard(node, context, visit);
120
+ contextProto.write = prevContextWrite;
121
+ };
122
+
123
+ export const patchedPrinter: Visitors<AST.Node> = printer;
@@ -2,6 +2,9 @@ import type { ExpressionStatement } from "estree";
2
2
  import type { TypingTranspileState } from "./walker";
3
3
 
4
4
  type ReplacementPayload =
5
+ | {
6
+ type: "preface";
7
+ }
5
8
  | {
6
9
  type: "enterVMFromRoot";
7
10
  vm: string;
@@ -27,6 +30,7 @@ type ReplacementPayload =
27
30
  defType: string;
28
31
  metaType: string;
29
32
  lhs: string;
33
+ attrName: string;
30
34
  }
31
35
  | {
32
36
  type: "createBindingTyping";
@@ -45,7 +49,7 @@ type ReplacementPayload =
45
49
 
46
50
  export const createReplacementHolder = (
47
51
  state: TypingTranspileState,
48
- value: ReplacementPayload
52
+ value: ReplacementPayload,
49
53
  ): ExpressionStatement => {
50
54
  const rawValue = JSON.stringify(value);
51
55
  return {
@@ -68,37 +72,114 @@ export const createReplacementHolder = (
68
72
  };
69
73
  };
70
74
 
71
- export function applyReplacements(state: TypingTranspileState, code: string) {
75
+ export function applyReplacements(
76
+ state: TypingTranspileState,
77
+ code: string,
78
+ ): string {
72
79
  const replacementRegex = new RegExp(
73
80
  "\\b" + state.replacementTag.name + "`(.*?)`",
74
- "gm"
81
+ "gm",
75
82
  );
76
- const {
77
- symbolsId: { NamedDefinition, Meta },
78
- } = state;
83
+ const { NamedDefinitionLit, MetaLit } = state;
84
+ const NamedDefinition = JSON.stringify(NamedDefinitionLit.value);
85
+ const Meta = JSON.stringify(MetaLit.value);
86
+ const oneLine = (
87
+ strings: TemplateStringsArray,
88
+ ...values: Array<string | number | boolean>
89
+ ): string =>
90
+ strings
91
+ .reduce(
92
+ (result, chunk, index) =>
93
+ result + chunk + (index < values.length ? values[index] : ""),
94
+ "",
95
+ )
96
+ .replace(/\n[ \t]*/g, " ")
97
+ .trim();
79
98
  // All replacement should be written in one line to avoid messing up source map
80
99
  return code.replace(replacementRegex, (_, rawPayload) => {
81
100
  const payload: ReplacementPayload = JSON.parse(rawPayload);
82
- if (payload.type === "enterVMFromRoot") {
83
- return `type ${payload.defType} = (typeof ${payload.vm})[${NamedDefinition.name}]; type ${payload.metaType} = ${payload.defType}[${Meta.name}];`;
101
+ if (payload.type === "preface") {
102
+ return oneLine`
103
+ namespace ${state.utilNsId.name} {
104
+ export type UniqueKeyProbSegment = "__gts_unique_prob_seg__";
105
+ export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
106
+ }
107
+ `;
108
+ } else if (payload.type === "enterVMFromRoot") {
109
+ return oneLine`
110
+ type ${payload.defType} = (typeof ${payload.vm})[${NamedDefinition}];
111
+ type ${payload.metaType} = ${payload.defType}[${Meta}];
112
+ `;
84
113
  } else if (payload.type === "enterVMFromAttr") {
85
- return `type ${payload.defType} = ${payload.returnType} extends { namedDefinition: infer Def } ? Def : { [${Meta.name}]: unknown }; type ${payload.metaType} = ${payload.defType}[${Meta.name}];`;
114
+ return oneLine`
115
+ type ${payload.defType} = ${payload.returnType} extends { namedDefinition: infer Def } ? Def : { ${Meta}: unknown };
116
+ type ${payload.metaType} = ${payload.defType}[${Meta}];
117
+ `;
86
118
  } else if (payload.type === "exitVM") {
87
119
  const lhs = `${payload.finalMetaType}_lhs`;
88
120
  const requiredAttrsNs = `${payload.finalMetaType}_rans`;
89
121
  const collectedAttrsExpr = `${payload.collectedAttrs.join(" | ")}`;
90
- const needleString = `null! as ${requiredAttrsNs}.RequiredAttributes`;
122
+ const needleString = `"${requiredAttrsNs}_NeedleString" as any as "required attributes are missing"`;
91
123
  if (payload.errorLoc) {
92
- state.additionalMappings.set(payload.errorLoc, needleString);
124
+ state.extraMappings.set(payload.errorLoc, needleString);
93
125
  }
94
- return `type ${payload.finalMetaType} = ${payload.metaType}; const ${lhs}: { [${Meta.name}]: ${payload.metaType} } & Omit<${payload.defType}, ${Meta.name}> = 0 as any; type ${lhs} = typeof ${lhs}; namespace ${requiredAttrsNs} { export type CollectedAttributes = ${collectedAttrsExpr}; export type RequiredAttributes = { [K in keyof ${payload.defType}]: ${lhs}[K] extends { required(this: ${lhs}): true } ? K : never }[keyof ${payload.defType}]; }; ((_: ${requiredAttrsNs}.CollectedAttributes) => 0)(${needleString});`;
126
+ return oneLine`
127
+ type ${payload.finalMetaType} = ${payload.metaType};
128
+ let ${lhs}!: { ${Meta}: ${payload.metaType} } & Omit<${payload.defType}, ${Meta}>;
129
+ type ${lhs} = typeof ${lhs};
130
+ namespace ${requiredAttrsNs} {
131
+ export type Collected = ${collectedAttrsExpr};
132
+ export type Expected = { [K in keyof ${payload.defType}]: ${lhs}[K] extends { required(this: ${lhs}): true } ? K : never }[keyof ${payload.defType}];
133
+ };
134
+ ((_: ${requiredAttrsNs}.Expected extends ${requiredAttrsNs}.Collected ? string : ${requiredAttrsNs}.Expected) => 0)(${needleString});
135
+ `;
95
136
  } else if (payload.type === "enterAttr") {
96
- return `const ${payload.lhs}: { [${Meta.name}]: ${payload.metaType} } & Omit<${payload.defType}, ${Meta.name}> = 0 as any;`;
137
+ const uniqueKeyLhs = `${payload.lhs}_uniqueKey_lhs`;
138
+ const uniqueKey = `${payload.lhs}_uniqueKey`;
139
+ const uniqueKeyForThis = `${payload.lhs}_uniqueKeyFor_${payload.lhs}`;
140
+ const uniqueKeyHelperIntf = `${payload.defType}_uniqueKeyProbeHelper`;
141
+ const omittedKeys = `${payload.lhs}_omittedKeys`;
142
+ return oneLine`
143
+ type ${uniqueKeyLhs} = {
144
+ ${Meta}: ${payload.metaType};
145
+ uniqueKey: ${payload.defType} extends { [${payload.attrName}]: { uniqueKey: infer UniqueKey } } ? UniqueKey : () => 0;
146
+
147
+ let ${uniqueKeyLhs}!: ${uniqueKeyLhs};
148
+ let ${uniqueKey} = ${uniqueKeyLhs}.uniqueKey();
149
+ type ${uniqueKey} = typeof ${uniqueKey};
150
+ let ${uniqueKeyForThis}!: \`\${${uniqueKey}}\${${state.utilNsId.name}.UniqueKeyProbSegment}${payload.lhs}\`;
151
+ interface ${uniqueKeyHelperIntf} {
152
+ [${uniqueKeyForThis}]: 1;
153
+ }
154
+ type ${omittedKeys} = ${Meta} | (
155
+ ${uniqueKey} extends 0
156
+ ? never /* no unique requirement */
157
+ : string extends keyof ${uniqueKeyHelperIntf}
158
+ ? keyof ${payload.defType} /* too loose, disable all */
159
+ : ${state.utilNsId.name}.UnionToIntersection<
160
+ keyof ${uniqueKeyHelperIntf} & \`\${${uniqueKey}}\${${state.utilNsId.name}.UniqueKeyProbSegment}\${string}\`
161
+ > extends never
162
+ ? ${payload.attrName} /* have duplicate, disable this */
163
+ : never
164
+ );
165
+ let ${payload.lhs}!: { ${Meta}: ${payload.metaType} } & Omit<${payload.defType}, ${omittedKeys}>;
166
+ `;
97
167
  } else if (payload.type === "createBindingTyping") {
98
168
  const typingIdLhs = `${payload.typingId}_lhs`;
99
- return `type ${typingIdLhs} = { [${Meta.name}]: ${payload.finalMetaType}; as: ${payload.defType}[${payload.attrName}] extends { as: infer As } ? As : unknown }; let ${typingIdLhs}!: ${typingIdLhs}; let ${payload.typingId} = ${typingIdLhs}.as(); type ${payload.typingId} = typeof ${payload.typingId};`;
169
+ return oneLine`
170
+ type ${typingIdLhs} = {
171
+ ${Meta}: ${payload.finalMetaType};
172
+ as: ${payload.defType} extends { [${payload.attrName}]: { as: infer As } } ? As : unknown;
173
+ };
174
+ let ${typingIdLhs}!: ${typingIdLhs};
175
+ let ${payload.typingId} = ${typingIdLhs}.as();
176
+ type ${payload.typingId} = typeof ${payload.typingId};
177
+ `;
100
178
  } else if (payload.type === "exitAttr") {
101
- return `type ${payload.returnType} = typeof ${payload.returnType}; type ${payload.newMetaType} = ${payload.returnType} extends { rewriteMeta: infer NewMeta extends {} } ? NewMeta : ${payload.oldMetaType}`;
179
+ return oneLine`
180
+ type ${payload.returnType} = typeof ${payload.returnType};
181
+ type ${payload.newMetaType} = ${payload.returnType} extends { rewriteMeta: infer NewMeta extends {} } ? NewMeta : ${payload.oldMetaType}
182
+ `;
102
183
  } else {
103
184
  return "";
104
185
  }