@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gi-tcg/gts-transpiler",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "repository": "https://github.com/piovium/gts.git",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -19,8 +19,8 @@
19
19
  "dependencies": {
20
20
  "@jridgewell/sourcemap-codec": "^1.5.5",
21
21
  "@sveltejs/acorn-typescript": "^1.0.8",
22
- "acorn": "^8.15.0",
23
- "esrap": "^2.2.1",
22
+ "acorn": "8.15.0",
23
+ "esrap": "2.2.1",
24
24
  "magic-string": "^0.30.21",
25
25
  "zimmerframe": "^1.1.4"
26
26
  },
package/src/config.ts CHANGED
@@ -1,6 +1,90 @@
1
- import path from "node:path";
2
1
  import type { TranspileOption } from "./transform/gts";
3
2
 
3
+ export interface PathPolyfill {
4
+ isAbsolute(filePath: string): boolean;
5
+ dirname(filePath: string): string;
6
+ resolve(...segments: string[]): string;
7
+ }
8
+
9
+ export const path: PathPolyfill = globalThis.require
10
+ ? (globalThis.require("node:path") as typeof import("node:path"))
11
+ : {
12
+ isAbsolute(filePath) {
13
+ return filePath.startsWith("/");
14
+ },
15
+ dirname(filePath) {
16
+ if (!filePath) {
17
+ return ".";
18
+ }
19
+ const normalized = normalizeSlashes(filePath);
20
+ if (normalized === "/") {
21
+ return "/";
22
+ }
23
+ const trimmed =
24
+ normalized.length > 1 && normalized.endsWith("/")
25
+ ? normalized.slice(0, -1)
26
+ : normalized;
27
+ const idx = trimmed.lastIndexOf("/");
28
+ if (idx < 0) {
29
+ return ".";
30
+ }
31
+ if (idx === 0) {
32
+ return "/";
33
+ }
34
+ return trimmed.slice(0, idx);
35
+ },
36
+ resolve(...segments) {
37
+ if (segments.length === 0) {
38
+ return ".";
39
+ }
40
+ let resolved = "";
41
+ for (let i = segments.length - 1; i >= 0; i--) {
42
+ const segment = segments[i];
43
+ if (!segment) {
44
+ continue;
45
+ }
46
+ if (resolved) {
47
+ resolved = `${segment}/${resolved}`;
48
+ } else {
49
+ resolved = segment;
50
+ }
51
+ if (path.isAbsolute(segment)) {
52
+ break;
53
+ }
54
+ }
55
+ return normalizeResolvedPath(resolved || ".");
56
+ },
57
+ };
58
+
59
+ function normalizeSlashes(filePath: string): string {
60
+ return filePath.replace(/\\+/g, "/");
61
+ }
62
+
63
+ function normalizeResolvedPath(filePath: string): string {
64
+ const normalized = normalizeSlashes(filePath);
65
+ const isAbsolute = normalized.startsWith("/");
66
+ const parts = normalized.split("/");
67
+ const stack: string[] = [];
68
+ for (const part of parts) {
69
+ if (!part || part === ".") {
70
+ continue;
71
+ }
72
+ if (part === "..") {
73
+ if (stack.length > 0 && stack[stack.length - 1] !== "..") {
74
+ stack.pop();
75
+ } else if (!isAbsolute) {
76
+ stack.push("..");
77
+ }
78
+ continue;
79
+ }
80
+ stack.push(part);
81
+ }
82
+ if (isAbsolute) {
83
+ return `/${stack.join("/")}` || "/";
84
+ }
85
+ return stack.join("/") || ".";
86
+ }
87
+
4
88
  export interface GtsConfig extends TranspileOption {}
5
89
 
6
90
  export interface PackageJson {
@@ -106,7 +190,7 @@ function* findNearestPackageConfig(
106
190
  ): Generator<string | Promise<string>, GtsConfig, string> {
107
191
  let currentDir = startDir;
108
192
  while (true) {
109
- const pkgPath = path.join(currentDir, "package.json");
193
+ const pkgPath = path.resolve(currentDir, "package.json");
110
194
  const config = yield* readPackageConfig(readFileFn, pkgPath);
111
195
  if (config) {
112
196
  return config;
package/src/index.ts CHANGED
@@ -25,7 +25,9 @@ export function transpileForVolar(
25
25
  filename: string,
26
26
  option: TranspileOption,
27
27
  ): VolarMappingResult {
28
- const ast = parseLoose(source);
28
+ const ast = parseLoose(source, {
29
+ recordCallLParens: true,
30
+ });
29
31
  return transformForVolar(ast, option, {
30
32
  content: source,
31
33
  filename,
@@ -33,4 +35,10 @@ export function transpileForVolar(
33
35
  }
34
36
 
35
37
  export type { TranspileOption, TranspileResult, VolarMappingResult };
36
- export { resolveGtsConfig, resolveGtsConfigSync, type GtsConfig } from "./config";
38
+ export {
39
+ resolveGtsConfig,
40
+ resolveGtsConfigSync,
41
+ type GtsConfig,
42
+ path,
43
+ type PathPolyfill,
44
+ } from "./config";
@@ -75,7 +75,7 @@ export interface GtsPluginOption {
75
75
 
76
76
  export function gtsPlugin(options: GtsPluginOption = {}) {
77
77
  return function gtsPluginTransformer(
78
- Parser: typeof ParserClass
78
+ Parser: typeof ParserClass,
79
79
  ): typeof ParserClass {
80
80
  const skipWhiteSpace = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g;
81
81
  const lineBreak = /\r\n?|\n|\u2028|\u2029/;
@@ -95,7 +95,7 @@ export function gtsPlugin(options: GtsPluginOption = {}) {
95
95
  override parseStatement(
96
96
  context?: string | null,
97
97
  topLevel?: boolean,
98
- exports?: AST.ExportSpecifier
98
+ exports?: AST.ExportSpecifier,
99
99
  ) {
100
100
  if (topLevel && this.gts_isDefineStatement()) {
101
101
  const node = this.startNode() as AST.GTSDefineStatement;
@@ -119,13 +119,20 @@ export function gtsPlugin(options: GtsPluginOption = {}) {
119
119
  gts_parseNamedAttributeDefinition() {
120
120
  const node = this.startNode() as AST.GTSNamedAttributeDefinition;
121
121
  // AttributeName
122
- let name: any;
122
+ const start = this.start;
123
+ let name: AST.Identifier | AST.SimpleLiteral;
123
124
  if (this.type.label === "string") {
124
- name = this.parseExprAtom();
125
+ name = this.parseExprAtom() as AST.SimpleLiteral;
126
+ if (typeof name.value === "string" && name.value.startsWith("~")) {
127
+ this.raise(
128
+ start,
129
+ `Attribute name starts with '~' is reserved for internal use.`,
130
+ );
131
+ }
125
132
  } else if (this.type.label === "name") {
126
133
  name = this.parseIdent();
127
134
  } else {
128
- this.raise(this.start, "Expected attribute name");
135
+ this.raise(start, "Expected attribute name");
129
136
  }
130
137
  node.name = name;
131
138
  // AttributeBody
@@ -215,7 +222,7 @@ export function gtsPlugin(options: GtsPluginOption = {}) {
215
222
  if (stmt.type === "GTSDefineStatement") {
216
223
  this.raise(
217
224
  startPos,
218
- "DefineStatement is not allowed in direct function."
225
+ "DefineStatement is not allowed in direct function.",
219
226
  );
220
227
  }
221
228
  node.body.push(stmt);
@@ -274,13 +281,13 @@ export function gtsPlugin(options: GtsPluginOption = {}) {
274
281
  override parseExprAtom(
275
282
  refDestructuringErrors?: Parse.DestructuringErrors,
276
283
  forInit?: boolean | "await",
277
- forNew?: boolean
284
+ forNew?: boolean,
278
285
  ): AST.Expression {
279
286
  if (this.type === tokTypes.colon) {
280
287
  if (!this.isShortcutContext) {
281
288
  this.raise(
282
289
  this.start,
283
- "ShortcutArgumentExpression ':' must be inside ShortcutFunction or DirectShortcutFunction."
290
+ "ShortcutArgumentExpression ':' must be inside ShortcutFunction or DirectShortcutFunction.",
284
291
  );
285
292
  }
286
293
  const node = this.startNode() as AST.GTSShortcutArgumentExpression;
@@ -307,7 +314,7 @@ export function gtsPlugin(options: GtsPluginOption = {}) {
307
314
  refDestructuringErrors?: Parse.DestructuringErrors | null,
308
315
  sawUnary?: boolean,
309
316
  incDec?: boolean,
310
- forInit?: boolean | "await"
317
+ forInit?: boolean | "await",
311
318
  ): AST.Expression {
312
319
  if (this.isContextual("query")) {
313
320
  const expr = this.gts_parseQueryExpression();
@@ -320,12 +327,12 @@ export function gtsPlugin(options: GtsPluginOption = {}) {
320
327
  refDestructuringErrors,
321
328
  sawUnary,
322
329
  incDec,
323
- forInit
330
+ forInit,
324
331
  );
325
332
  }
326
333
 
327
334
  gts_parseQueryExpression(
328
- forInit?: boolean | "await"
335
+ forInit?: boolean | "await",
329
336
  ): AST.GTSQueryExpression {
330
337
  const node = this.startNode() as AST.GTSQueryExpression;
331
338
  this.next(); // consume 'query'
@@ -5,6 +5,7 @@ import { gtsPlugin, type GtsPluginOption } from "./gts_plugin.js";
5
5
  import { loosePlugin } from "./loose_plugin.js";
6
6
  import { getCommentHandlers } from "./comment.js";
7
7
  import { GtsTranspilerError } from "../error.js";
8
+ import { recordCallLParenPlugin } from "./record_call_lparen_plugin.js";
8
9
 
9
10
  const TsParser = Parser.extend(tsPlugin());
10
11
 
@@ -29,15 +30,24 @@ export function parse(input: string, options?: GtsPluginOption): Program {
29
30
  }
30
31
  }
31
32
 
32
- export function parseLoose(input: string, options?: GtsPluginOption): Program {
33
+ export interface ParseLooseOptions extends GtsPluginOption {
34
+ recordCallLParens?: boolean;
35
+ }
36
+
37
+ export function parseLoose(
38
+ input: string,
39
+ options?: ParseLooseOptions,
40
+ ): Program {
33
41
  try {
34
42
  const GtsParser = TsParser.extend(
35
43
  loosePlugin(),
44
+ ...(options?.recordCallLParens ? [recordCallLParenPlugin()] : []),
36
45
  gtsPlugin({
37
- ...options,
38
- allowEmptyShortcutMember: true,
39
- allowEmptyPositionalAttribute: true,
40
- })
46
+ allowEmptyShortcutMember:
47
+ options?.allowEmptyPositionalAttribute || true,
48
+ allowEmptyPositionalAttribute:
49
+ options?.allowEmptyPositionalAttribute || true,
50
+ }),
41
51
  );
42
52
  const { onComment, addComments } = getCommentHandlers(input, []);
43
53
  const ast = GtsParser.parse(input, {
@@ -15,7 +15,7 @@ export function loosePlugin() {
15
15
  return super.parseIdent(liberal);
16
16
  }
17
17
  };
18
- private _proxiedThis = new Proxy(this, {
18
+ readonly #proxiedThis = new Proxy(this, {
19
19
  get: (target, prop) => {
20
20
  if (prop === "parseIdent") {
21
21
  return this._patchedParseIdent;
@@ -45,7 +45,7 @@ export function loosePlugin() {
45
45
  forInit?: boolean | "await",
46
46
  ): AST.Expression {
47
47
  return super.parseSubscript.call(
48
- this._proxiedThis,
48
+ this.#proxiedThis,
49
49
  base,
50
50
  startPos,
51
51
  startLoc,
@@ -0,0 +1,85 @@
1
+ import { tokTypes, type Parser } from "acorn";
2
+ import type { AST, Parse } from "../types.js";
3
+
4
+ /**
5
+ * A plugin that records the location of the left parenthesis in CallExpression and
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
+ *
11
+ * The recorded location will be stored in `lParenLoc` property of the
12
+ * CallExpression/NewExpression node.
13
+ * @returns
14
+ */
15
+ export function recordCallLParenPlugin() {
16
+ return function recordCallLParenPluginTransformer(
17
+ parser: typeof Parser,
18
+ ): typeof Parser {
19
+ return class RecordCallLParenParser extends (parser as typeof Parse.Parser) {
20
+ override parseSubscript(
21
+ base: AST.Expression,
22
+ startPos: number,
23
+ startLoc: AST.Position,
24
+ noCalls?: boolean,
25
+ maybeAsyncArrow?: boolean,
26
+ optionalChained?: boolean,
27
+ forInit?: boolean | "await",
28
+ ): AST.Expression {
29
+ let recordedLParenLoc: AST.SourceLocation | null = null;
30
+ if (!noCalls && this.type === tokTypes.parenL) {
31
+ recordedLParenLoc = {
32
+ start: this.startLoc,
33
+ end: this.endLoc,
34
+ };
35
+ }
36
+ const result = super.parseSubscript(
37
+ base,
38
+ startPos,
39
+ startLoc,
40
+ noCalls,
41
+ maybeAsyncArrow,
42
+ optionalChained,
43
+ forInit,
44
+ );
45
+ if (recordedLParenLoc && result.type === "CallExpression") {
46
+ result.lParenLoc = recordedLParenLoc;
47
+ }
48
+ return result;
49
+ }
50
+
51
+ private _capturedLParenLocFromNew: AST.SourceLocation | null = null;
52
+ private readonly _patchedEat = (type: any) => {
53
+ if (type === tokTypes.parenL) {
54
+ this._capturedLParenLocFromNew = {
55
+ start: this.startLoc,
56
+ end: this.endLoc,
57
+ };
58
+ }
59
+ return this.eat(type);
60
+ };
61
+
62
+ readonly #proxiedThis = new Proxy(this, {
63
+ get: (target, prop) => {
64
+ if (prop === "eat") {
65
+ return this._patchedEat;
66
+ }
67
+ const value = Reflect.get(target, prop);
68
+ if (typeof value === "function") {
69
+ return value.bind(target);
70
+ }
71
+ return value;
72
+ },
73
+ });
74
+
75
+ override parseNew() {
76
+ const result = super.parseNew.apply(this.#proxiedThis);
77
+ if (this._capturedLParenLocFromNew && result.type === "NewExpression") {
78
+ result.lParenLoc = this._capturedLParenLocFromNew;
79
+ this._capturedLParenLocFromNew = null;
80
+ }
81
+ return result;
82
+ }
83
+ };
84
+ };
85
+ }
@@ -30,8 +30,8 @@ export interface ExternalizedBinding {
30
30
  export interface TranspileState {
31
31
  readonly createDefineFnId: Identifier;
32
32
  readonly createBindingFnId: Identifier;
33
- readonly ActionId: Identifier;
34
- readonly preludeSymbolId: Identifier;
33
+ readonly ActionLit: Literal;
34
+ readonly PreludeLit: Literal;
35
35
  readonly fnArgId: Identifier;
36
36
  readonly shortcutFunctionParameters: Pattern[];
37
37
  readonly rootVmId: Identifier;
@@ -48,8 +48,8 @@ export interface TranspileState {
48
48
  /** Internal counters / state for emitting per-define nodes & bindings */
49
49
  defineIdCounter: number;
50
50
 
51
- /** Buffered statements to be inserted after visiting a define statement */
52
- pendingStatements: (Statement | ModuleDeclaration)[];
51
+ /** Binding statements to be inserted before all define statement */
52
+ bindingStatements: (Statement | ModuleDeclaration)[];
53
53
  }
54
54
 
55
55
  export const commonGtsVisitor: Visitors<Node, TranspileState> = {
@@ -64,7 +64,7 @@ export const commonGtsVisitor: Visitors<Node, TranspileState> = {
64
64
  kind: "init",
65
65
  method: false,
66
66
  shorthand: false,
67
- value: state.ActionId,
67
+ value: state.ActionLit,
68
68
  loc: node.loc,
69
69
  },
70
70
  {
@@ -182,16 +182,12 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
182
182
  const body: Program["body"] = [];
183
183
  for (const stmt of node.body) {
184
184
  const visited = visit(stmt) as Statement;
185
- // `GTSDefineStatement` is expanded into multiple statements via buffer
186
- if (visited.type !== "EmptyStatement") {
187
- body.push(visited);
188
- }
189
- if (state.pendingStatements.length > 0) {
190
- body.push(...(state.pendingStatements));
191
- state.pendingStatements = [];
192
- }
185
+ body.push(visited);
193
186
  }
194
187
 
188
+ body.unshift(...state.bindingStatements);
189
+ state.bindingStatements = [];
190
+
195
191
  if (state.hasQueryExpressions) {
196
192
  body.unshift({
197
193
  type: "ImportDeclaration",
@@ -222,16 +218,6 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
222
218
  imported: { type: "Identifier", name: "createBinding" },
223
219
  local: state.createBindingFnId,
224
220
  },
225
- {
226
- type: "ImportSpecifier",
227
- imported: { type: "Identifier", name: "Action" },
228
- local: state.ActionId,
229
- },
230
- {
231
- type: "ImportSpecifier",
232
- imported: { type: "Identifier", name: "Prelude" },
233
- local: state.preludeSymbolId,
234
- },
235
221
  ],
236
222
  source: { type: "Literal", value: state.runtimeImportSource },
237
223
  attributes: [],
@@ -272,9 +258,8 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
272
258
 
273
259
  const newBindings = state.externalizedBindings;
274
260
  state.externalizedBindings = [];
275
- const statements: (Statement | ModuleDeclaration)[] = [];
276
261
 
277
- statements.push({
262
+ state.bindingStatements.push({
278
263
  type: "VariableDeclaration",
279
264
  kind: "const",
280
265
  declarations: [
@@ -287,7 +272,7 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
287
272
  loc: node.loc,
288
273
  });
289
274
 
290
- statements.push({
275
+ state.bindingStatements.push({
291
276
  type: "VariableDeclaration",
292
277
  kind: "const",
293
278
  declarations: [
@@ -325,33 +310,26 @@ const gtsVisitor: Visitors<Node, TranspileState> = {
325
310
  ],
326
311
  };
327
312
  if (binding.export) {
328
- statements.push({
313
+ state.bindingStatements.push({
329
314
  type: "ExportNamedDeclaration",
330
315
  declaration: decl,
331
316
  specifiers: [],
332
317
  attributes: [],
333
318
  });
334
319
  } else {
335
- statements.push(decl);
320
+ state.bindingStatements.push(decl);
336
321
  }
337
322
  }
338
-
339
- statements.push({
323
+ return {
340
324
  type: "ExpressionStatement",
341
325
  expression: {
342
326
  type: "CallExpression",
343
327
  optional: false,
344
- callee: {
345
- ...state.createDefineFnId,
346
- loc: node.loc,
347
- },
328
+ callee: state.createDefineFnId,
348
329
  arguments: [state.rootVmId, nodeVarId],
349
330
  },
350
331
  loc: node.loc,
351
- });
352
-
353
- state.pendingStatements.push(...statements);
354
- return { type: "EmptyStatement" };
332
+ };
355
333
  },
356
334
  GTSNamedAttributeDefinition(node, { visit, state }) {
357
335
  const namedBody = visit(node.body) as ObjectExpression;
@@ -503,9 +481,9 @@ export const initialTranspileState = (
503
481
  option.shortcutFunctionPreludes ?? DEFAULT_SHORTCUT_FUNCTION_PRELUDES;
504
482
  const queryBindings = option.queryBindings ?? DEFAULT_QUERY_BINDINGS;
505
483
  const fnArgId: Identifier = { type: "Identifier", name: "__gts_fnArg" };
506
- const preludeSymbolId: Identifier = {
507
- type: "Identifier",
508
- name: "__gts_Prelude",
484
+ const PreludeLit: Literal = {
485
+ type: "Literal",
486
+ value: "~prelude",
509
487
  };
510
488
  const shortcutFunctionParameters: Pattern[] = [
511
489
  fnArgId,
@@ -526,7 +504,7 @@ export const initialTranspileState = (
526
504
  right: {
527
505
  type: "MemberExpression",
528
506
  object: fnArgId,
529
- property: preludeSymbolId,
507
+ property: PreludeLit,
530
508
  computed: true,
531
509
  optional: false,
532
510
  },
@@ -549,8 +527,8 @@ export const initialTranspileState = (
549
527
  return {
550
528
  createDefineFnId: { type: "Identifier", name: "__gts_createDefine" },
551
529
  createBindingFnId: { type: "Identifier", name: "__gts_createBinding" },
552
- ActionId: { type: "Identifier", name: "__gts_Action" },
553
- preludeSymbolId,
530
+ ActionLit: { type: "Literal", value: "~action" },
531
+ PreludeLit,
554
532
  fnArgId,
555
533
  shortcutFunctionParameters,
556
534
  rootVmId: { type: "Identifier", name: "__gts_rootVm" },
@@ -576,7 +554,7 @@ export const initialTranspileState = (
576
554
  hasQueryExpressions: false,
577
555
  defineIdCounter: 0,
578
556
 
579
- pendingStatements: [],
557
+ bindingStatements: [],
580
558
  };
581
559
  };
582
560
 
@@ -1,4 +1,4 @@
1
- import type { SourceLocation } from "estree";
1
+ import type { Node, SourceLocation } from "estree";
2
2
  import { walk } from "zimmerframe";
3
3
 
4
4
  function isLeafNode(node: any): boolean {
@@ -28,43 +28,69 @@ function isLeafNode(node: any): boolean {
28
28
  return true;
29
29
  }
30
30
 
31
- export interface LocationAdjustment {
31
+ export interface LeafToken {
32
+ loc: SourceLocation;
33
+ isDummy?: boolean;
34
+ /**
35
+ * Override source length (instead of loc.end - loc.start)
36
+ */
37
+ sourceLength?: number;
38
+ /**
39
+ * Adjust the start position of source code
40
+ */
41
+ sourceStartOffset?: number;
32
42
  /**
33
43
  * Adjust the start position of generated code
34
44
  */
35
- startOffset: number;
45
+ generatedStartOffset?: number;
46
+ /**
47
+ * Make the source length longer
48
+ */
49
+ sourceLengthOffset?: number;
36
50
  /**
37
51
  * The original length of generated code, used for mapping diagnostics
38
52
  */
39
- generatedLength: number;
40
- }
41
-
42
- export interface LeafToken {
43
- loc: SourceLocation;
44
- isDummy?: boolean;
45
- sourceLength?: number;
46
53
  generatedLength?: number;
47
- locationAdjustment?: LocationAdjustment;
48
54
  }
49
55
 
50
56
  export function collectLeafTokens(ast: any): LeafToken[] {
51
- const tokens: LeafToken[] = [];
52
- walk(ast, tokens, {
57
+ const state = {
58
+ tokens: [] as LeafToken[],
59
+ };
60
+ walk(ast as Node, state, {
53
61
  _(node, { state, next }) {
54
62
  if (isLeafNode(node) && node.loc) {
55
63
  const token: LeafToken = {
56
- loc: node.loc
64
+ loc: node.loc,
57
65
  };
58
- if (node.isDummy) {
66
+ if ("isDummy" in node && node.isDummy) {
59
67
  token.isDummy = true;
60
68
  token.sourceLength = 0;
61
- // include next character for diagnostic
69
+ // add 1 for squiggle on next character
62
70
  token.generatedLength = 1;
63
71
  }
64
- state.push(token);
72
+ state.tokens.push(token);
73
+ }
74
+ next();
75
+ },
76
+ NewExpression(node, { state, next }) {
77
+ const lParenLoc = node.lParenLoc;
78
+ if (lParenLoc) {
79
+ state.tokens.push({
80
+ loc: lParenLoc,
81
+ });
82
+ }
83
+ next();
84
+ },
85
+ CallExpression(node, { state, next }) {
86
+ const lParenLoc = node.lParenLoc;
87
+ if (lParenLoc) {
88
+ state.tokens.push({
89
+ loc: lParenLoc,
90
+ });
65
91
  }
66
92
  next();
67
93
  },
68
94
  });
69
- return tokens;
95
+ return state.tokens;
70
96
  }