@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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gi-tcg/gts-transpiler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"repository": "https://github.com/piovium/gts.git",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -16,20 +16,22 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@jridgewell/sourcemap-codec": "^1.5.5",
|
|
20
19
|
"@sveltejs/acorn-typescript": "^1.0.8",
|
|
21
20
|
"acorn": "^8.15.0",
|
|
21
|
+
"dedent": "^1.7.2",
|
|
22
|
+
"espolar": "^0.5.1",
|
|
22
23
|
"esrap": "2.2.1",
|
|
23
24
|
"magic-string": "^0.30.21",
|
|
24
25
|
"path-browserify-esm": "^1.0.6",
|
|
25
26
|
"zimmerframe": "^1.1.4"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
29
|
+
"@types/estree": "^1.0.9",
|
|
28
30
|
"@types/node": "^24.3.0",
|
|
29
|
-
"@types/estree": "^1.0.8",
|
|
30
31
|
"@volar/language-core": "^2.4.27"
|
|
31
32
|
},
|
|
32
33
|
"scripts": {
|
|
33
|
-
"build": "tsdown --platform neutral"
|
|
34
|
+
"build": "tsdown --platform neutral",
|
|
35
|
+
"test": "vitest"
|
|
34
36
|
}
|
|
35
37
|
}
|
package/src/parse/gts_plugin.ts
CHANGED
|
@@ -251,7 +251,7 @@ export function gtsPlugin(options: GtsPluginOption = {}) {
|
|
|
251
251
|
this.isContextual("as"))
|
|
252
252
|
) {
|
|
253
253
|
// Allow omitting the attribute expression for language tooling
|
|
254
|
-
const dummy = this.
|
|
254
|
+
const dummy = this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc) as AST.Identifier;
|
|
255
255
|
dummy.name = DUMMY_PLACEHOLDER;
|
|
256
256
|
dummy.isDummy = true;
|
|
257
257
|
return this.finishNode(dummy, "Identifier");
|
|
@@ -297,7 +297,7 @@ export function gtsPlugin(options: GtsPluginOption = {}) {
|
|
|
297
297
|
this.type !== tokTypes.name
|
|
298
298
|
) {
|
|
299
299
|
// Allow omitting the identifier after ':' for language tooling
|
|
300
|
-
const dummy = this.
|
|
300
|
+
const dummy = this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc) as AST.Identifier;
|
|
301
301
|
dummy.name = DUMMY_PLACEHOLDER;
|
|
302
302
|
dummy.isDummy = true;
|
|
303
303
|
node.property = this.finishNode(dummy, "Identifier");
|
package/src/parse/index.ts
CHANGED
|
@@ -16,6 +16,7 @@ export function parse(input: string, options?: GtsPluginOption): Program {
|
|
|
16
16
|
ecmaVersion: "latest",
|
|
17
17
|
sourceType: "module",
|
|
18
18
|
locations: true,
|
|
19
|
+
ranges: true,
|
|
19
20
|
}) as Program;
|
|
20
21
|
} catch (e) {
|
|
21
22
|
if (e instanceof SyntaxError && "loc" in e) {
|
|
@@ -54,6 +55,7 @@ export function parseLoose(
|
|
|
54
55
|
ecmaVersion: "latest",
|
|
55
56
|
sourceType: "module",
|
|
56
57
|
locations: true,
|
|
58
|
+
ranges: true,
|
|
57
59
|
onComment,
|
|
58
60
|
}) as Program;
|
|
59
61
|
addComments(ast);
|
|
@@ -29,7 +29,7 @@ export function loosePlugin() {
|
|
|
29
29
|
});
|
|
30
30
|
|
|
31
31
|
createDummyIdentifier() {
|
|
32
|
-
const dummy = this.
|
|
32
|
+
const dummy = this.startNodeAt(this.lastTokEnd, this.lastTokEndLoc) as AST.Identifier;
|
|
33
33
|
dummy.name = DUMMY_PLACEHOLDER;
|
|
34
34
|
dummy.isDummy = true;
|
|
35
35
|
return this.finishNode(dummy, "Identifier");
|
|
@@ -2,15 +2,15 @@ import { tokTypes, type Parser } from "acorn";
|
|
|
2
2
|
import type { AST, Parse } from "../types.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* A plugin that records the location of the left parenthesis in CallExpression and
|
|
5
|
+
* A plugin that records the location of the left parenthesis in CallExpression and
|
|
6
6
|
* NewExpression.
|
|
7
|
-
*
|
|
8
|
-
* This is useful for Language tooling, that we can maps the '(' token from its location,
|
|
9
|
-
* to provide * a signature help for user when they press `(` after a function name.
|
|
10
7
|
*
|
|
11
|
-
*
|
|
8
|
+
* This is useful for Language tooling, that we can maps the '(' token from its location,
|
|
9
|
+
* to provide a signature help for user when they press `(` after a function name.
|
|
10
|
+
*
|
|
11
|
+
* The recorded location will be stored in `lParenLoc` property of the
|
|
12
12
|
* CallExpression/NewExpression node.
|
|
13
|
-
* @returns
|
|
13
|
+
* @returns the plugin function
|
|
14
14
|
*/
|
|
15
15
|
export function recordCallLParenPlugin() {
|
|
16
16
|
return function recordCallLParenPluginTransformer(
|
|
@@ -26,12 +26,9 @@ export function recordCallLParenPlugin() {
|
|
|
26
26
|
optionalChained?: boolean,
|
|
27
27
|
forInit?: boolean | "await",
|
|
28
28
|
): AST.Expression {
|
|
29
|
-
let recordedLParenLoc:
|
|
29
|
+
let recordedLParenLoc: [number, number] | null = null;
|
|
30
30
|
if (!noCalls && this.type === tokTypes.parenL) {
|
|
31
|
-
recordedLParenLoc =
|
|
32
|
-
start: this.startLoc,
|
|
33
|
-
end: this.endLoc,
|
|
34
|
-
};
|
|
31
|
+
recordedLParenLoc = [this.start, this.end];
|
|
35
32
|
}
|
|
36
33
|
const result = super.parseSubscript(
|
|
37
34
|
base,
|
|
@@ -43,18 +40,15 @@ export function recordCallLParenPlugin() {
|
|
|
43
40
|
forInit,
|
|
44
41
|
);
|
|
45
42
|
if (recordedLParenLoc && result.type === "CallExpression") {
|
|
46
|
-
result.
|
|
43
|
+
result.lParenRange = recordedLParenLoc;
|
|
47
44
|
}
|
|
48
45
|
return result;
|
|
49
46
|
}
|
|
50
47
|
|
|
51
|
-
private
|
|
48
|
+
private _capturedLParenRangeFromNew: [number, number] | null = null;
|
|
52
49
|
private readonly _patchedEat = (type: any) => {
|
|
53
50
|
if (type === tokTypes.parenL) {
|
|
54
|
-
this.
|
|
55
|
-
start: this.startLoc,
|
|
56
|
-
end: this.endLoc,
|
|
57
|
-
};
|
|
51
|
+
this._capturedLParenRangeFromNew = [this.start, this.end];
|
|
58
52
|
}
|
|
59
53
|
return this.eat(type);
|
|
60
54
|
};
|
|
@@ -74,9 +68,12 @@ export function recordCallLParenPlugin() {
|
|
|
74
68
|
|
|
75
69
|
override parseNew() {
|
|
76
70
|
const result = super.parseNew.apply(this.#proxiedThis);
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
if (
|
|
72
|
+
this._capturedLParenRangeFromNew &&
|
|
73
|
+
result.type === "NewExpression"
|
|
74
|
+
) {
|
|
75
|
+
result.lParenRange = this._capturedLParenRangeFromNew;
|
|
76
|
+
this._capturedLParenRangeFromNew = null;
|
|
80
77
|
}
|
|
81
78
|
return result;
|
|
82
79
|
}
|
package/src/transform/gts.ts
CHANGED
|
@@ -10,7 +10,6 @@ import type {
|
|
|
10
10
|
ModuleDeclaration,
|
|
11
11
|
Node,
|
|
12
12
|
ObjectExpression,
|
|
13
|
-
ObjectPattern,
|
|
14
13
|
Pattern,
|
|
15
14
|
Program,
|
|
16
15
|
Statement,
|
|
@@ -35,14 +34,12 @@ export interface TranspileState {
|
|
|
35
34
|
readonly fnArgId: Identifier;
|
|
36
35
|
readonly shortcutFunctionParameters: Pattern[];
|
|
37
36
|
readonly rootVmId: Identifier;
|
|
38
|
-
readonly queryFnId: Identifier;
|
|
39
37
|
readonly queryParameters: Pattern[];
|
|
38
|
+
readonly QueryLit: Literal;
|
|
39
|
+
readonly QueryAllLit: Literal;
|
|
40
40
|
|
|
41
41
|
readonly runtimeImportSource: string;
|
|
42
42
|
readonly providerImportSource: string;
|
|
43
|
-
readonly queryArg: ObjectPattern;
|
|
44
|
-
|
|
45
|
-
hasQueryExpressions: boolean;
|
|
46
43
|
|
|
47
44
|
externalizedBindings: ExternalizedBinding[];
|
|
48
45
|
/** Internal counters / state for emitting per-define nodes & bindings */
|
|
@@ -66,6 +63,7 @@ export const commonGtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
66
63
|
shorthand: false,
|
|
67
64
|
value: state.ActionLit,
|
|
68
65
|
loc: node.loc,
|
|
66
|
+
range: node.range,
|
|
69
67
|
},
|
|
70
68
|
{
|
|
71
69
|
type: "Property",
|
|
@@ -108,6 +106,7 @@ export const commonGtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
108
106
|
},
|
|
109
107
|
],
|
|
110
108
|
loc: node.loc,
|
|
109
|
+
range: node.range,
|
|
111
110
|
};
|
|
112
111
|
},
|
|
113
112
|
GTSShortcutFunctionExpression(
|
|
@@ -120,6 +119,7 @@ export const commonGtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
120
119
|
body: visit(node.body) as Expression | BlockStatement,
|
|
121
120
|
expression: node.expression,
|
|
122
121
|
loc: node.loc,
|
|
122
|
+
range: node.range,
|
|
123
123
|
};
|
|
124
124
|
},
|
|
125
125
|
GTSShortcutArgumentExpression(node, { state, visit }): MemberExpression {
|
|
@@ -130,15 +130,21 @@ export const commonGtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
130
130
|
optional: false,
|
|
131
131
|
property: visit(node.property) as Identifier,
|
|
132
132
|
loc: node.loc,
|
|
133
|
+
range: node.range,
|
|
133
134
|
};
|
|
134
135
|
},
|
|
135
136
|
GTSQueryExpression(node, { state, visit }) {
|
|
136
|
-
state.hasQueryExpressions = true;
|
|
137
137
|
return {
|
|
138
138
|
...node,
|
|
139
139
|
type: "CallExpression",
|
|
140
140
|
optional: false,
|
|
141
|
-
callee:
|
|
141
|
+
callee: {
|
|
142
|
+
type: "MemberExpression",
|
|
143
|
+
object: state.fnArgId,
|
|
144
|
+
property: node.star ? state.QueryAllLit : state.QueryLit,
|
|
145
|
+
computed: true,
|
|
146
|
+
optional: false,
|
|
147
|
+
},
|
|
142
148
|
arguments: [
|
|
143
149
|
{
|
|
144
150
|
type: "ArrowFunctionExpression",
|
|
@@ -146,47 +152,14 @@ export const commonGtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
146
152
|
params: state.queryParameters,
|
|
147
153
|
expression: true,
|
|
148
154
|
loc: node.argument.loc,
|
|
149
|
-
|
|
150
|
-
{
|
|
151
|
-
type: "ObjectExpression",
|
|
152
|
-
properties: [
|
|
153
|
-
{
|
|
154
|
-
type: "Property",
|
|
155
|
-
key: { type: "Identifier", name: "star" },
|
|
156
|
-
computed: false,
|
|
157
|
-
kind: "init",
|
|
158
|
-
method: false,
|
|
159
|
-
shorthand: false,
|
|
160
|
-
value: {
|
|
161
|
-
type: "Literal",
|
|
162
|
-
value: !!node.star,
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
type: "Property",
|
|
167
|
-
key: { type: "Identifier", name: "context" },
|
|
168
|
-
computed: false,
|
|
169
|
-
kind: "init",
|
|
170
|
-
method: false,
|
|
171
|
-
shorthand: false,
|
|
172
|
-
value: state.fnArgId,
|
|
173
|
-
}
|
|
174
|
-
],
|
|
155
|
+
range: node.argument.range,
|
|
175
156
|
},
|
|
176
157
|
],
|
|
177
|
-
// loc: node.loc,
|
|
178
158
|
};
|
|
179
159
|
},
|
|
180
160
|
};
|
|
181
161
|
|
|
182
162
|
const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
183
|
-
// _(node, { next }) {
|
|
184
|
-
// console.log(node.type, !!node.leadingComments)
|
|
185
|
-
// if (node.leadingComments) {
|
|
186
|
-
// console.log(node.leadingComments);
|
|
187
|
-
// }
|
|
188
|
-
// return next();
|
|
189
|
-
// },
|
|
190
163
|
Program(node, { state, visit }) {
|
|
191
164
|
const body: Program["body"] = [];
|
|
192
165
|
for (const stmt of node.body) {
|
|
@@ -197,22 +170,6 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
197
170
|
body.unshift(...state.bindingStatements);
|
|
198
171
|
state.bindingStatements = [];
|
|
199
172
|
|
|
200
|
-
if (state.hasQueryExpressions) {
|
|
201
|
-
body.unshift({
|
|
202
|
-
type: "ImportDeclaration",
|
|
203
|
-
specifiers: [
|
|
204
|
-
{
|
|
205
|
-
type: "ImportDefaultSpecifier",
|
|
206
|
-
local: state.queryFnId,
|
|
207
|
-
},
|
|
208
|
-
],
|
|
209
|
-
source: {
|
|
210
|
-
type: "Literal",
|
|
211
|
-
value: `${state.providerImportSource}/query`,
|
|
212
|
-
},
|
|
213
|
-
attributes: [],
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
173
|
body.unshift(
|
|
217
174
|
{
|
|
218
175
|
type: "ImportDeclaration",
|
|
@@ -279,6 +236,7 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
279
236
|
},
|
|
280
237
|
],
|
|
281
238
|
loc: node.loc,
|
|
239
|
+
range: node.range,
|
|
282
240
|
});
|
|
283
241
|
|
|
284
242
|
state.bindingStatements.push({
|
|
@@ -297,6 +255,7 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
297
255
|
},
|
|
298
256
|
],
|
|
299
257
|
loc: node.loc,
|
|
258
|
+
range: node.range,
|
|
300
259
|
});
|
|
301
260
|
|
|
302
261
|
for (let i = 0; i < newBindings.length; i++) {
|
|
@@ -338,6 +297,7 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
338
297
|
arguments: [state.rootVmId, nodeVarId],
|
|
339
298
|
},
|
|
340
299
|
loc: node.loc,
|
|
300
|
+
range: node.range,
|
|
341
301
|
};
|
|
342
302
|
},
|
|
343
303
|
GTSNamedAttributeDefinition(node, { visit, state }) {
|
|
@@ -363,6 +323,7 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
363
323
|
shorthand: false,
|
|
364
324
|
value: nameValue,
|
|
365
325
|
loc: node.loc,
|
|
326
|
+
range: node.range,
|
|
366
327
|
});
|
|
367
328
|
const body = { ...namedBody, properties };
|
|
368
329
|
if (node.bindingName) {
|
|
@@ -414,8 +375,10 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
414
375
|
body: positionals,
|
|
415
376
|
expression: true,
|
|
416
377
|
loc: positionals.loc,
|
|
378
|
+
range: positionals.range,
|
|
417
379
|
},
|
|
418
380
|
loc: positionals.loc,
|
|
381
|
+
range: positionals.range,
|
|
419
382
|
},
|
|
420
383
|
{
|
|
421
384
|
type: "Property",
|
|
@@ -426,9 +389,11 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
426
389
|
shorthand: false,
|
|
427
390
|
value: named,
|
|
428
391
|
loc: named.loc,
|
|
392
|
+
range: named.range,
|
|
429
393
|
},
|
|
430
394
|
],
|
|
431
395
|
loc: node.loc,
|
|
396
|
+
range: node.range,
|
|
432
397
|
};
|
|
433
398
|
return partialBody;
|
|
434
399
|
},
|
|
@@ -447,6 +412,7 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
447
412
|
}
|
|
448
413
|
}),
|
|
449
414
|
loc: node.loc,
|
|
415
|
+
range: node.range,
|
|
450
416
|
};
|
|
451
417
|
},
|
|
452
418
|
GTSNamedAttributeBlock(node, { visit }): ObjectExpression {
|
|
@@ -471,6 +437,7 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
|
|
|
471
437
|
},
|
|
472
438
|
],
|
|
473
439
|
loc: node.loc,
|
|
440
|
+
range: node.range,
|
|
474
441
|
};
|
|
475
442
|
},
|
|
476
443
|
...commonGtsVisitor,
|
|
@@ -541,26 +508,14 @@ export const initialTranspileState = (
|
|
|
541
508
|
fnArgId,
|
|
542
509
|
shortcutFunctionParameters,
|
|
543
510
|
rootVmId: { type: "Identifier", name: "__gts_rootVm" },
|
|
544
|
-
queryFnId: { type: "Identifier", name: "__gts_query" },
|
|
545
511
|
queryParameters,
|
|
512
|
+
QueryLit: { type: "Literal", value: "~query" },
|
|
513
|
+
QueryAllLit: { type: "Literal", value: "~queryAll" },
|
|
546
514
|
|
|
547
515
|
runtimeImportSource: option.runtimeImportSource ?? "@gi-tcg/gts-runtime",
|
|
548
516
|
providerImportSource: option.providerImportSource ?? "@gi-tcg/core/gts",
|
|
549
|
-
queryArg: {
|
|
550
|
-
type: "ObjectPattern",
|
|
551
|
-
properties: (option.queryBindings ?? []).map((name) => ({
|
|
552
|
-
type: "Property",
|
|
553
|
-
key: { type: "Identifier", name },
|
|
554
|
-
computed: false,
|
|
555
|
-
kind: "init",
|
|
556
|
-
method: false,
|
|
557
|
-
shorthand: true,
|
|
558
|
-
value: { type: "Identifier", name },
|
|
559
|
-
})),
|
|
560
|
-
},
|
|
561
517
|
|
|
562
518
|
externalizedBindings: [],
|
|
563
|
-
hasQueryExpressions: false,
|
|
564
519
|
defineIdCounter: 0,
|
|
565
520
|
|
|
566
521
|
bindingStatements: [],
|
package/src/transform/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Program } from "estree";
|
|
2
2
|
import { eraseTs } from "./erase_ts.ts";
|
|
3
|
-
import { print } from "esrap";
|
|
3
|
+
import { print, type Visitors } from "esrap";
|
|
4
4
|
import jsPrinter from "esrap/languages/ts";
|
|
5
5
|
import type { SourceMap } from "magic-string";
|
|
6
6
|
import { gtsToTs, type TranspileOption } from "./gts.ts";
|
|
@@ -18,11 +18,11 @@ export interface SourceInfo {
|
|
|
18
18
|
export function transform(
|
|
19
19
|
ast: Program,
|
|
20
20
|
option: TranspileOption = {},
|
|
21
|
-
sourceInfo: SourceInfo = {}
|
|
21
|
+
sourceInfo: SourceInfo = {},
|
|
22
22
|
): TranspileResult {
|
|
23
23
|
const ts = gtsToTs(ast, option);
|
|
24
24
|
const js = eraseTs(ts);
|
|
25
|
-
const { code, map } = print(js, jsPrinter(), {
|
|
25
|
+
const { code, map } = print(js, jsPrinter() as Visitors, {
|
|
26
26
|
indent: " ",
|
|
27
27
|
sourceMapContent: sourceInfo.content,
|
|
28
28
|
sourceMapSource: sourceInfo.filename,
|
|
@@ -1,33 +1,6 @@
|
|
|
1
|
-
import type { Node, SourceLocation } from "estree";
|
|
1
|
+
import type { Node, Program, SourceLocation } from "estree";
|
|
2
2
|
import { walk } from "zimmerframe";
|
|
3
3
|
|
|
4
|
-
function isLeafNode(node: any): boolean {
|
|
5
|
-
for (const key in node) {
|
|
6
|
-
const val = node[key];
|
|
7
|
-
|
|
8
|
-
// Ignore non-child properties (metadata)
|
|
9
|
-
if (key === "loc" || key === "start" || key === "end" || key === "range") {
|
|
10
|
-
continue;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
// Check if the property is a Node
|
|
14
|
-
// (An object with a 'type' property is generally an AST node)
|
|
15
|
-
if (val && typeof val === "object" && typeof val.type === "string") {
|
|
16
|
-
return false; // Found a valid child node
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Check if the property is an Array of Nodes (e.g., body: [...])
|
|
20
|
-
if (
|
|
21
|
-
Array.isArray(val) &&
|
|
22
|
-
val.length > 0 &&
|
|
23
|
-
typeof val[0].type === "string"
|
|
24
|
-
) {
|
|
25
|
-
return false; // Found an array of child nodes
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return true;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
4
|
export interface LeafToken {
|
|
32
5
|
loc: SourceLocation;
|
|
33
6
|
isDummy?: boolean;
|
|
@@ -53,15 +26,35 @@ export interface LeafToken {
|
|
|
53
26
|
generatedLength?: number;
|
|
54
27
|
}
|
|
55
28
|
|
|
56
|
-
export function collectLeafTokens(ast:
|
|
57
|
-
|
|
58
|
-
tokens:
|
|
29
|
+
export function collectLeafTokens(source: string, ast: Program): LeafToken[] {
|
|
30
|
+
interface CollectTokenState {
|
|
31
|
+
tokens: LeafToken[];
|
|
32
|
+
/** Whether the node is from source and purely TypeScript */
|
|
33
|
+
pureSource: boolean;
|
|
34
|
+
/** Whether the node is visited once (for detecting leaf node) */
|
|
35
|
+
visited: boolean;
|
|
36
|
+
}
|
|
37
|
+
const state: CollectTokenState = {
|
|
38
|
+
tokens: [],
|
|
39
|
+
pureSource: true,
|
|
40
|
+
visited: false,
|
|
59
41
|
};
|
|
60
42
|
walk(ast as Node, state, {
|
|
61
43
|
_(node, { state, next }) {
|
|
62
|
-
|
|
44
|
+
state.visited = true;
|
|
45
|
+
let currNodePureSource = !!node.loc && !node.type.startsWith("GTS");
|
|
46
|
+
const subState = { tokens: [], pureSource: true, visited: false };
|
|
47
|
+
next(subState);
|
|
48
|
+
currNodePureSource &&= subState.pureSource;
|
|
49
|
+
state.pureSource &&= currNodePureSource;
|
|
50
|
+
// record original source for purely branch node
|
|
51
|
+
if (subState.visited && node.range && currNodePureSource) {
|
|
52
|
+
const [start, end] = node.range;
|
|
53
|
+
node.pureSource = source.slice(start, end);
|
|
54
|
+
}
|
|
55
|
+
if (currNodePureSource) {
|
|
63
56
|
const token: LeafToken = {
|
|
64
|
-
loc: node.loc
|
|
57
|
+
loc: node.loc!,
|
|
65
58
|
};
|
|
66
59
|
if ("isDummy" in node && node.isDummy) {
|
|
67
60
|
token.isDummy = true;
|
|
@@ -70,8 +63,9 @@ export function collectLeafTokens(ast: any): LeafToken[] {
|
|
|
70
63
|
token.generatedLength = 1;
|
|
71
64
|
}
|
|
72
65
|
state.tokens.push(token);
|
|
66
|
+
} else {
|
|
67
|
+
state.tokens.push(...subState.tokens);
|
|
73
68
|
}
|
|
74
|
-
next();
|
|
75
69
|
},
|
|
76
70
|
NewExpression(node, { state, next }) {
|
|
77
71
|
const lParenLoc = node.lParenLoc;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the character offset after hashbang and file-scope leading comments.
|
|
3
|
+
* This is for simulating the behavior of TSServer insert auto-imports if no
|
|
4
|
+
* imports are present.
|
|
5
|
+
* @param source
|
|
6
|
+
* @returns
|
|
7
|
+
*/
|
|
8
|
+
export function getContentStartOffset(source: string): number {
|
|
9
|
+
let result = 0;
|
|
10
|
+
/** Current visiting character */
|
|
11
|
+
let pos = 0;
|
|
12
|
+
if (source.startsWith("#!")) {
|
|
13
|
+
const nl = source.indexOf("\n", pos);
|
|
14
|
+
if (nl === -1) {
|
|
15
|
+
return source.length;
|
|
16
|
+
}
|
|
17
|
+
pos = nl + 1;
|
|
18
|
+
}
|
|
19
|
+
const skipWhitespaces = () => {
|
|
20
|
+
let newlineCount = 0;
|
|
21
|
+
for (; pos < source.length; pos++) {
|
|
22
|
+
const ch = source[pos];
|
|
23
|
+
if (ch === " " || ch === "\t" || ch === "\r") {
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
if (ch === "\n") {
|
|
27
|
+
newlineCount++;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
break;
|
|
31
|
+
}
|
|
32
|
+
return newlineCount;
|
|
33
|
+
}
|
|
34
|
+
skipWhitespaces();
|
|
35
|
+
while (pos < source.length) {
|
|
36
|
+
let newlineCount = 0;
|
|
37
|
+
// Eat a comment
|
|
38
|
+
if (source[pos] === "/" && pos + 1 < source.length) {
|
|
39
|
+
if (source[pos + 1] === "/") {
|
|
40
|
+
const nl = source.indexOf("\n", pos);
|
|
41
|
+
pos = nl === -1 ? source.length : nl + 1;
|
|
42
|
+
newlineCount++;
|
|
43
|
+
} else if (source[pos + 1] === "*") {
|
|
44
|
+
pos += 2;
|
|
45
|
+
while (pos < source.length) {
|
|
46
|
+
if (
|
|
47
|
+
source[pos] === "*" &&
|
|
48
|
+
pos + 1 < source.length &&
|
|
49
|
+
source[pos + 1] === "/"
|
|
50
|
+
) {
|
|
51
|
+
pos += 2;
|
|
52
|
+
break;
|
|
53
|
+
}
|
|
54
|
+
pos++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
result = pos;
|
|
58
|
+
newlineCount += skipWhitespaces();
|
|
59
|
+
// If there are already 2 newlines in whitespace, the leading comments are ended
|
|
60
|
+
if (newlineCount >= 2) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
// Not a comment, stop here
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return result;
|
|
69
|
+
}
|