@gi-tcg/gts-transpiler 0.3.2 → 0.3.4
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 +1 -1
- package/dist/index.js +351 -458
- package/package.json +6 -4
- package/src/parse/gts_plugin.ts +2 -2
- package/src/parse/index.ts +2 -0
- package/src/parse/loose_plugin.ts +1 -1
- package/src/parse/record_call_lparen_plugin.ts +17 -20
- package/src/transform/gts.ts +26 -71
- package/src/transform/index.ts +3 -3
- package/src/transform/volar/collect_tokens.ts +28 -34
- package/src/transform/volar/content_start.ts +69 -0
- package/src/transform/volar/index.ts +70 -45
- package/src/transform/volar/mappings.ts +5 -352
- package/src/transform/volar/printer.ts +139 -117
- package/src/transform/volar/replacements.ts +75 -47
- package/src/transform/volar/walker.ts +123 -81
- package/src/types.ts +3 -9
|
@@ -1,123 +1,145 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type {
|
|
2
|
+
Identifier,
|
|
3
|
+
NewExpression,
|
|
4
|
+
Node,
|
|
5
|
+
SimpleCallExpression,
|
|
6
|
+
} from "estree";
|
|
7
|
+
import {
|
|
8
|
+
type PrintOptions,
|
|
9
|
+
type AST as EspolarAST,
|
|
10
|
+
defaultPrinters,
|
|
11
|
+
} from "espolar";
|
|
12
|
+
import type { CodeInformation } from "@volar/language-core";
|
|
13
|
+
import {
|
|
14
|
+
DEFAULT_VOLAR_MAPPING_DATA,
|
|
15
|
+
VERIFICATION_ONLY_MAPPING_DATA,
|
|
16
|
+
} from "./mappings.ts";
|
|
17
|
+
import type { TypingTranspileState } from "./walker.ts";
|
|
5
18
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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;
|
|
19
|
+
export function getPrintOptions(
|
|
20
|
+
source: string,
|
|
21
|
+
state: TypingTranspileState,
|
|
22
|
+
): PrintOptions<CodeInformation> {
|
|
23
|
+
return {
|
|
24
|
+
source,
|
|
25
|
+
isUntouched: (node) => {
|
|
26
|
+
if (node.type === "Identifier" && (node as Identifier).isDummy) {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return state.sourceNodes.has(node as Node);
|
|
30
|
+
},
|
|
31
|
+
getLeadingComments: (node) => (node as Node).leadingComments,
|
|
32
|
+
getTrailingComments: (node) => (node as Node).trailingComments,
|
|
33
|
+
getMappingData: () => DEFAULT_VOLAR_MAPPING_DATA,
|
|
34
|
+
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.
|
|
42
|
+
Identifier(node, context) {
|
|
43
|
+
const identifier = node as Identifier;
|
|
44
|
+
if (identifier.isDummy && identifier.range) {
|
|
45
|
+
const text = state.lastArgNodes.has(identifier) ? "," : "";
|
|
46
|
+
// extend the source mapping of dummy id to the before next token
|
|
47
|
+
let firstNonWhiteSpaceIndex = context.source
|
|
48
|
+
.slice(identifier.range[1])
|
|
49
|
+
.search(/\S/);
|
|
50
|
+
const rangeEnd =
|
|
51
|
+
firstNonWhiteSpaceIndex === -1
|
|
52
|
+
? context.source.length
|
|
53
|
+
: identifier.range[1] + firstNonWhiteSpaceIndex;
|
|
54
|
+
context.writeMapped(text, identifier.range[0], rangeEnd);
|
|
55
|
+
} else {
|
|
56
|
+
return defaultPrinters.Identifier(node, context);
|
|
81
57
|
}
|
|
82
|
-
|
|
83
|
-
|
|
58
|
+
},
|
|
59
|
+
Literal(node, context) {
|
|
60
|
+
const generatedStart = context.generatedOffset;
|
|
61
|
+
if (state.literalFromIdentifier.has(node) && node.range) {
|
|
62
|
+
const text = JSON.stringify(node.value);
|
|
63
|
+
context.write('"');
|
|
64
|
+
context.writeMapped(text.slice(1, -1), node.range[0], node.range[1]);
|
|
65
|
+
context.write('"');
|
|
66
|
+
} else {
|
|
67
|
+
defaultPrinters.Literal(node, context);
|
|
84
68
|
}
|
|
85
|
-
|
|
86
|
-
|
|
69
|
+
// For generated `import xxx from "yyy"`, add mappings from xxx and yyy
|
|
70
|
+
// to the top-of-file for diagnostics around missing / wrong imports. [[1]]
|
|
71
|
+
if (state.diagnosticsOnTopNodes.has(node)) {
|
|
72
|
+
const generatedEnd = context.generatedOffset;
|
|
73
|
+
context.createExtraMapping(
|
|
74
|
+
{ start: 0, end: 1 },
|
|
75
|
+
generatedStart,
|
|
76
|
+
generatedEnd,
|
|
77
|
+
VERIFICATION_ONLY_MAPPING_DATA,
|
|
78
|
+
);
|
|
87
79
|
}
|
|
80
|
+
},
|
|
81
|
+
// Same as [[1]]
|
|
82
|
+
ImportDefaultSpecifier(node, context) {
|
|
83
|
+
const generatedStart = context.generatedOffset;
|
|
84
|
+
defaultPrinters.ImportDefaultSpecifier(node, context);
|
|
85
|
+
if (state.diagnosticsOnTopNodes.has(node)) {
|
|
86
|
+
const generatedEnd = context.generatedOffset;
|
|
87
|
+
context.createExtraMapping(
|
|
88
|
+
{ start: 0, end: 1 },
|
|
89
|
+
generatedStart,
|
|
90
|
+
generatedEnd,
|
|
91
|
+
VERIFICATION_ONLY_MAPPING_DATA,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
// Same as [[1]]
|
|
96
|
+
ImportSpecifier(node, context) {
|
|
97
|
+
const generatedStart = context.generatedOffset;
|
|
98
|
+
defaultPrinters.ImportSpecifier(node, context);
|
|
99
|
+
if (state.diagnosticsOnTopNodes.has(node)) {
|
|
100
|
+
const generatedEnd = context.generatedOffset;
|
|
101
|
+
context.createExtraMapping(
|
|
102
|
+
{ start: 0, end: 1 },
|
|
103
|
+
generatedStart,
|
|
104
|
+
generatedEnd,
|
|
105
|
+
VERIFICATION_ONLY_MAPPING_DATA,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
// If the last import declaration is a generated one, because of we ensured that
|
|
110
|
+
// the generated import declarations are always unsorted, so TSServer will set the
|
|
111
|
+
// auto-insertion point next to this last import declaration.
|
|
112
|
+
// Add a mapping to that position (a written newline character) to the top-of-file,
|
|
113
|
+
// after skipping hashbang and leading comments.
|
|
114
|
+
// NOTE: since the insertion derived from here won't add additional newline *before*
|
|
115
|
+
// the inserted text, so here is a difference behavior from standard TSServer and our
|
|
116
|
+
// language server. We'd try our best.
|
|
117
|
+
ImportDeclaration(node, context) {
|
|
118
|
+
defaultPrinters.ImportDeclaration(node, context);
|
|
119
|
+
if (state.lastImportDeclarationIfGen === node) {
|
|
120
|
+
context.write("\n");
|
|
121
|
+
context.createExtraMapping(
|
|
122
|
+
{
|
|
123
|
+
start: state.contentStartOffset,
|
|
124
|
+
end: state.contentStartOffset + 1,
|
|
125
|
+
},
|
|
126
|
+
context.generatedOffset,
|
|
127
|
+
context.generatedOffset + 1,
|
|
128
|
+
DEFAULT_VOLAR_MAPPING_DATA,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
},
|
|
133
|
+
// Enable triggering signature completion
|
|
134
|
+
experimentalGetLeftParenSourceRange: (node) => {
|
|
135
|
+
const callLike = node as SimpleCallExpression | NewExpression;
|
|
136
|
+
if (callLike.lParenRange) {
|
|
137
|
+
return {
|
|
138
|
+
start: callLike.lParenRange[0],
|
|
139
|
+
end: callLike.lParenRange[1],
|
|
140
|
+
};
|
|
88
141
|
}
|
|
89
|
-
|
|
90
|
-
if (typeof value === "function") {
|
|
91
|
-
return value.bind(target);
|
|
92
|
-
}
|
|
93
|
-
return value;
|
|
142
|
+
return state.namedAttributeCalleeLParenRange.get(callLike.callee);
|
|
94
143
|
},
|
|
95
|
-
}
|
|
96
|
-
|
|
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;
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { ExpressionStatement } from "estree";
|
|
2
2
|
import type { TypingTranspileState } from "./walker.ts";
|
|
3
|
+
import dedent from "dedent";
|
|
4
|
+
import type { CodeMapping } from "@volar/language-core";
|
|
5
|
+
|
|
6
|
+
interface MatchInfo {
|
|
7
|
+
sourceEnd: number;
|
|
8
|
+
lengthOffset: number;
|
|
9
|
+
}
|
|
3
10
|
|
|
4
11
|
type ReplacementPayload =
|
|
5
12
|
| {
|
|
@@ -23,7 +30,7 @@ type ReplacementPayload =
|
|
|
23
30
|
defType: string;
|
|
24
31
|
collectedAttrs: string[];
|
|
25
32
|
finalMetaType: string;
|
|
26
|
-
|
|
33
|
+
errorRange?: [number, number];
|
|
27
34
|
}
|
|
28
35
|
| {
|
|
29
36
|
type: "enterAttr";
|
|
@@ -75,55 +82,54 @@ export const createReplacementHolder = (
|
|
|
75
82
|
export function applyReplacements(
|
|
76
83
|
state: TypingTranspileState,
|
|
77
84
|
code: string,
|
|
85
|
+
mappings: CodeMapping[],
|
|
78
86
|
): string {
|
|
79
87
|
const replacementRegex = new RegExp(
|
|
80
|
-
"\\b" + state.replacementTag.name + "`(.*?)`",
|
|
88
|
+
"\\b" + state.replacementTag.name + "`(.*?)(?<!\\\\)`",
|
|
81
89
|
"gm",
|
|
82
90
|
);
|
|
83
91
|
const { NamedDefinitionLit, MetaLit } = state;
|
|
84
92
|
const NamedDefinition = JSON.stringify(NamedDefinitionLit.value);
|
|
85
93
|
const Meta = JSON.stringify(MetaLit.value);
|
|
86
|
-
const
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
.
|
|
92
|
-
(
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
.trim();
|
|
98
|
-
// All replacement should be written in one line to avoid messing up source map
|
|
99
|
-
return code.replace(replacementRegex, (_, rawPayload) => {
|
|
100
|
-
const payload: ReplacementPayload = JSON.parse(rawPayload);
|
|
101
|
-
if (payload.type === "preface") {
|
|
102
|
-
return oneLine`
|
|
94
|
+
const matchInfos: MatchInfo[] = [];
|
|
95
|
+
|
|
96
|
+
const result = code.replace(
|
|
97
|
+
replacementRegex,
|
|
98
|
+
(match, rawPayload: string, offset: number) => {
|
|
99
|
+
const payload: ReplacementPayload = JSON.parse(
|
|
100
|
+
rawPayload.replace(/\\`/g, "`"),
|
|
101
|
+
);
|
|
102
|
+
let replacement: string;
|
|
103
|
+
if (payload.type === "preface") {
|
|
104
|
+
replacement = dedent`
|
|
103
105
|
namespace ${state.utilNsId.name} {
|
|
104
106
|
export type UniqueKeyProbSegment = "__gts_unique_prob_seg__";
|
|
105
107
|
export type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends ((k: infer I) => void) ? I : never;
|
|
106
108
|
}
|
|
107
109
|
`;
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
} else if (payload.type === "enterVMFromRoot") {
|
|
111
|
+
replacement = dedent`
|
|
110
112
|
type ${payload.defType} = (typeof ${payload.vm})[${NamedDefinition}];
|
|
111
113
|
type ${payload.metaType} = ${payload.defType}[${Meta}];
|
|
112
114
|
`;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
} else if (payload.type === "enterVMFromAttr") {
|
|
116
|
+
replacement = dedent`
|
|
115
117
|
type ${payload.defType} = ${payload.returnType} extends { namedDefinition: infer Def } ? Def : { ${Meta}: unknown };
|
|
116
118
|
type ${payload.metaType} = ${payload.defType}[${Meta}];
|
|
117
119
|
`;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
} else if (payload.type === "exitVM") {
|
|
121
|
+
const lhs = `${payload.finalMetaType}_lhs`;
|
|
122
|
+
const requiredAttrsNs = `${payload.finalMetaType}_rans`;
|
|
123
|
+
const collectedAttrsExpr = `${payload.collectedAttrs.join(" | ")}`;
|
|
124
|
+
const needleString = `"${requiredAttrsNs}_NeedleString" as any as "required attributes are missing"`;
|
|
125
|
+
if (payload.errorRange) {
|
|
126
|
+
state.extraMappings.push({
|
|
127
|
+
sourceOffset: payload.errorRange[0],
|
|
128
|
+
length: payload.errorRange[1] - payload.errorRange[0],
|
|
129
|
+
generatedNeedle: needleString,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
replacement = dedent`
|
|
127
133
|
type ${payload.finalMetaType} = ${payload.metaType};
|
|
128
134
|
let ${lhs}!: { ${Meta}: ${payload.metaType} } & Omit<${payload.defType}, ${Meta}>;
|
|
129
135
|
type ${lhs} = typeof ${lhs};
|
|
@@ -133,16 +139,17 @@ export function applyReplacements(
|
|
|
133
139
|
};
|
|
134
140
|
((_: ${requiredAttrsNs}.Expected extends ${requiredAttrsNs}.Collected ? string : ${requiredAttrsNs}.Expected) => 0)(${needleString});
|
|
135
141
|
`;
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
142
|
+
} else if (payload.type === "enterAttr") {
|
|
143
|
+
const uniqueKeyLhs = `${payload.lhs}_uniqueKey_lhs`;
|
|
144
|
+
const uniqueKey = `${payload.lhs}_uniqueKey`;
|
|
145
|
+
const uniqueKeyForThis = `${payload.lhs}_uniqueKeyFor_${payload.lhs}`;
|
|
146
|
+
const uniqueKeyHelperIntf = `${payload.defType}_uniqueKeyProbeHelper`;
|
|
147
|
+
const omittedKeys = `${payload.lhs}_omittedKeys`;
|
|
148
|
+
replacement = dedent`
|
|
143
149
|
type ${uniqueKeyLhs} = {
|
|
144
150
|
${Meta}: ${payload.metaType};
|
|
145
151
|
uniqueKey: ${payload.defType} extends { [${payload.attrName}]: { uniqueKey: infer UniqueKey } } ? UniqueKey : () => 0;
|
|
152
|
+
};
|
|
146
153
|
|
|
147
154
|
let ${uniqueKeyLhs}!: ${uniqueKeyLhs};
|
|
148
155
|
let ${uniqueKey} = ${uniqueKeyLhs}.uniqueKey();
|
|
@@ -164,9 +171,9 @@ export function applyReplacements(
|
|
|
164
171
|
);
|
|
165
172
|
let ${payload.lhs}!: { ${Meta}: ${payload.metaType} } & Omit<${payload.defType}, ${omittedKeys}>;
|
|
166
173
|
`;
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
174
|
+
} else if (payload.type === "createBindingTyping") {
|
|
175
|
+
const typingIdLhs = `${payload.typingId}_lhs`;
|
|
176
|
+
replacement = dedent`
|
|
170
177
|
type ${typingIdLhs} = {
|
|
171
178
|
${Meta}: ${payload.finalMetaType};
|
|
172
179
|
as: ${payload.defType} extends { [${payload.attrName}]: { as: infer As } } ? As : unknown;
|
|
@@ -175,13 +182,34 @@ export function applyReplacements(
|
|
|
175
182
|
let ${payload.typingId} = ${typingIdLhs}.as();
|
|
176
183
|
type ${payload.typingId} = typeof ${payload.typingId};
|
|
177
184
|
`;
|
|
178
|
-
|
|
179
|
-
|
|
185
|
+
} else if (payload.type === "exitAttr") {
|
|
186
|
+
replacement = dedent`
|
|
180
187
|
type ${payload.returnType} = typeof ${payload.returnType};
|
|
181
188
|
type ${payload.newMetaType} = ${payload.returnType} extends { rewriteMeta: infer NewMeta extends {} } ? NewMeta : ${payload.oldMetaType}
|
|
182
189
|
`;
|
|
183
|
-
|
|
184
|
-
|
|
190
|
+
} else {
|
|
191
|
+
replacement = "";
|
|
192
|
+
}
|
|
193
|
+
matchInfos.push({
|
|
194
|
+
sourceEnd: offset + match.length,
|
|
195
|
+
lengthOffset: replacement.length - match.length,
|
|
196
|
+
});
|
|
197
|
+
return replacement;
|
|
198
|
+
},
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
for (const mapping of mappings) {
|
|
202
|
+
for (let i = 0; i < mapping.generatedOffsets.length; i++) {
|
|
203
|
+
const orig = mapping.generatedOffsets[i];
|
|
204
|
+
let shift = 0;
|
|
205
|
+
for (const info of matchInfos) {
|
|
206
|
+
if (orig >= info.sourceEnd) {
|
|
207
|
+
shift += info.lengthOffset;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
mapping.generatedOffsets[i] = orig + shift;
|
|
185
211
|
}
|
|
186
|
-
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return result;
|
|
187
215
|
}
|