@gi-tcg/gts-transpiler 0.2.0 → 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.
- package/dist/index.d.ts +7 -1
- package/dist/index.js +424 -182
- package/package.json +3 -3
- package/src/config.ts +86 -2
- package/src/index.ts +10 -2
- package/src/parse/gts_plugin.ts +18 -11
- package/src/parse/index.ts +15 -5
- package/src/parse/loose_plugin.ts +2 -2
- package/src/parse/record_call_lparen_plugin.ts +85 -0
- package/src/transform/gts.ts +23 -45
- package/src/transform/volar/collect_tokens.ts +44 -18
- package/src/transform/volar/index.ts +16 -28
- package/src/transform/volar/mappings.ts +45 -26
- package/src/transform/volar/printer.ts +123 -0
- package/src/transform/volar/replacements.ts +89 -11
- package/src/transform/volar/walker.ts +52 -95
- package/src/types.ts +16 -4
|
@@ -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
|
-
|
|
20
|
-
|
|
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<
|
|
@@ -32,34 +34,20 @@ function gtsToTypings(
|
|
|
32
34
|
leafTokens: option.leafTokens,
|
|
33
35
|
idCounter: 0,
|
|
34
36
|
typingPendingStatements: [],
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
NamedDefinition: { type: "Identifier", name: "__gts_symbols_namedDef" },
|
|
41
|
-
},
|
|
40
|
+
MetaLit: { type: "Literal", value: "~meta" },
|
|
41
|
+
NamedDefinitionLit: { type: "Literal", value: "~namedDefinition" },
|
|
42
42
|
defineLeadingComments: [],
|
|
43
43
|
vmDefTypeIdStack: [],
|
|
44
44
|
metaTypeIdStack: [],
|
|
45
45
|
finalMetaTypeIdStack: [],
|
|
46
46
|
attrsOfCurrentVm: [],
|
|
47
|
-
|
|
47
|
+
extraMappings: option.extraMappings,
|
|
48
48
|
};
|
|
49
49
|
const newAst = walk(ast as AST.Node, state, gtsToTypingsWalker);
|
|
50
|
-
const
|
|
51
|
-
getLeadingComments: (node) => (node as AST.Node).leadingComments,
|
|
52
|
-
getTrailingComments: (node) => (node as AST.Node).trailingComments,
|
|
53
|
-
});
|
|
54
|
-
const prevIdentifier = printer.Identifier!;
|
|
55
|
-
printer.Identifier = function (node, context) {
|
|
56
|
-
if (node.isDummy) {
|
|
57
|
-
context.write("", node);
|
|
58
|
-
} else {
|
|
59
|
-
prevIdentifier(node, context);
|
|
60
|
-
}
|
|
61
|
-
};
|
|
62
|
-
const { code, map } = print(newAst, printer, {
|
|
50
|
+
const { code, map } = print(newAst, patchedPrinter, {
|
|
63
51
|
indent: " ",
|
|
64
52
|
});
|
|
65
53
|
return {
|
|
@@ -71,21 +59,21 @@ function gtsToTypings(
|
|
|
71
59
|
export function transformForVolar(
|
|
72
60
|
ast: Program,
|
|
73
61
|
option: TranspileOption,
|
|
74
|
-
sourceInfo: Required<SourceInfo
|
|
62
|
+
sourceInfo: Required<SourceInfo>,
|
|
75
63
|
): VolarMappingResult {
|
|
76
64
|
const tokens = collectLeafTokens(ast);
|
|
77
|
-
const
|
|
65
|
+
const extraMappings = new Map<string, string>();
|
|
78
66
|
const { code, sourceMap } = gtsToTypings(ast, {
|
|
79
67
|
...option,
|
|
80
68
|
leafTokens: tokens,
|
|
81
|
-
|
|
69
|
+
extraMappings,
|
|
82
70
|
});
|
|
83
71
|
const volarMappings = convertToVolarMappings(
|
|
84
72
|
code,
|
|
85
73
|
sourceInfo.content,
|
|
86
74
|
sourceMap,
|
|
87
75
|
tokens,
|
|
88
|
-
|
|
76
|
+
extraMappings,
|
|
89
77
|
);
|
|
90
78
|
return {
|
|
91
79
|
code,
|
|
@@ -192,19 +192,12 @@ export function getGeneratedPosition(
|
|
|
192
192
|
src_line: number,
|
|
193
193
|
src_column: number,
|
|
194
194
|
srcToGenMap: CodeToGeneratedMap,
|
|
195
|
-
): CodePosition
|
|
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
|
|
200
|
+
return positions || [];
|
|
208
201
|
}
|
|
209
202
|
// Helper to create a line-to-offset lookup table
|
|
210
203
|
function createLineOffsets(content: string): number[] {
|
|
@@ -245,7 +238,7 @@ export function convertToVolarMappings(
|
|
|
245
238
|
source: string,
|
|
246
239
|
sourceMap: SourceMap,
|
|
247
240
|
tokens: LeafToken[],
|
|
248
|
-
|
|
241
|
+
extraMappings: Map<string, string>,
|
|
249
242
|
): CodeMapping[] {
|
|
250
243
|
const sourceLineOffsets = createLineOffsets(source);
|
|
251
244
|
const generatedLineOffsets = createLineOffsets(generated);
|
|
@@ -263,13 +256,18 @@ export function convertToVolarMappings(
|
|
|
263
256
|
token.loc.start.column,
|
|
264
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
264
|
sourceLineOffsets,
|
|
270
265
|
);
|
|
266
|
+
|
|
271
267
|
let sourceLength = token.sourceLength ?? sourceEnd - sourceStart;
|
|
272
|
-
|
|
268
|
+
|
|
269
|
+
sourceLength += token.sourceLengthOffset ?? 0;
|
|
270
|
+
const [genLineCol] = getGeneratedPosition(
|
|
273
271
|
token.loc.start.line,
|
|
274
272
|
token.loc.start.column,
|
|
275
273
|
srcToGenMap,
|
|
@@ -283,19 +281,6 @@ export function convertToVolarMappings(
|
|
|
283
281
|
genLineCol.column,
|
|
284
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
|
-
|
|
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";
|
|
@@ -76,32 +80,106 @@ export function applyReplacements(
|
|
|
76
80
|
"\\b" + state.replacementTag.name + "`(.*?)`",
|
|
77
81
|
"gm",
|
|
78
82
|
);
|
|
79
|
-
const {
|
|
80
|
-
|
|
81
|
-
|
|
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();
|
|
82
98
|
// All replacement should be written in one line to avoid messing up source map
|
|
83
99
|
return code.replace(replacementRegex, (_, rawPayload) => {
|
|
84
100
|
const payload: ReplacementPayload = JSON.parse(rawPayload);
|
|
85
|
-
if (payload.type === "
|
|
86
|
-
return `
|
|
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
|
+
`;
|
|
87
113
|
} else if (payload.type === "enterVMFromAttr") {
|
|
88
|
-
return `
|
|
114
|
+
return oneLine`
|
|
115
|
+
type ${payload.defType} = ${payload.returnType} extends { namedDefinition: infer Def } ? Def : { ${Meta}: unknown };
|
|
116
|
+
type ${payload.metaType} = ${payload.defType}[${Meta}];
|
|
117
|
+
`;
|
|
89
118
|
} else if (payload.type === "exitVM") {
|
|
90
119
|
const lhs = `${payload.finalMetaType}_lhs`;
|
|
91
120
|
const requiredAttrsNs = `${payload.finalMetaType}_rans`;
|
|
92
121
|
const collectedAttrsExpr = `${payload.collectedAttrs.join(" | ")}`;
|
|
93
122
|
const needleString = `"${requiredAttrsNs}_NeedleString" as any as "required attributes are missing"`;
|
|
94
123
|
if (payload.errorLoc) {
|
|
95
|
-
state.
|
|
124
|
+
state.extraMappings.set(payload.errorLoc, needleString);
|
|
96
125
|
}
|
|
97
|
-
return `
|
|
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
|
+
`;
|
|
98
136
|
} else if (payload.type === "enterAttr") {
|
|
99
|
-
|
|
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
|
+
`;
|
|
100
167
|
} else if (payload.type === "createBindingTyping") {
|
|
101
168
|
const typingIdLhs = `${payload.typingId}_lhs`;
|
|
102
|
-
return `
|
|
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
|
+
`;
|
|
103
178
|
} else if (payload.type === "exitAttr") {
|
|
104
|
-
return `
|
|
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
|
+
`;
|
|
105
183
|
} else {
|
|
106
184
|
return "";
|
|
107
185
|
}
|