@eventra_dev/eventra-cli 0.0.7 → 0.0.8

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.
@@ -8,6 +8,7 @@ const chalk_1 = __importDefault(require("chalk"));
8
8
  const fast_glob_1 = __importDefault(require("fast-glob"));
9
9
  const ts_morph_1 = require("ts-morph");
10
10
  const promises_1 = __importDefault(require("fs/promises"));
11
+ const inquirer_1 = __importDefault(require("inquirer"));
11
12
  const config_1 = require("../utils/config");
12
13
  const router_1 = require("../utils/parsers/router");
13
14
  const vue_1 = require("../utils/parsers/vue");
@@ -25,16 +26,10 @@ async function sync() {
25
26
  console.log(chalk_1.default.blue("Scanning project..."));
26
27
  const project = new ts_morph_1.Project();
27
28
  const events = new Set();
29
+ const aliases = config.aliases ?? {};
28
30
  const files = await (0, fast_glob_1.default)(config.sync.include, {
29
31
  ignore: config.sync.exclude
30
32
  });
31
- const functionWrappers = (config.functionWrappers ?? []).map((w) => ({
32
- name: w.name,
33
- path: w.event
34
- ? `0.${w.event}`
35
- : "0"
36
- }));
37
- const componentWrappers = config.wrappers ?? [];
38
33
  for (const file of files) {
39
34
  const parser = (0, router_1.detectParser)(file);
40
35
  let content = await promises_1.default.readFile(file, "utf-8");
@@ -48,10 +43,47 @@ async function sync() {
48
43
  ? file
49
44
  : file + ".tsx";
50
45
  const source = project.createSourceFile(virtualFile, content, { overwrite: true });
51
- (0, track_1.scanTrack)(source).forEach((e) => events.add(e));
52
- (0, function_wrappers_1.scanFunctionWrappers)(source, functionWrappers).forEach((e) => events.add(e));
53
- (0, component_wrappers_1.scanComponentWrappers)(source, componentWrappers).forEach((e) => events.add(e));
46
+ const found = [
47
+ ...(0, track_1.scanTrack)(source),
48
+ ...(0, function_wrappers_1.scanFunctionWrappers)(source, config.functionWrappers ?? []),
49
+ ...(0, component_wrappers_1.scanComponentWrappers)(source, config.wrappers ?? [])
50
+ ];
51
+ for (const event of found) {
52
+ if (!event)
53
+ continue;
54
+ const value = typeof event === "string"
55
+ ? event
56
+ : event.value;
57
+ const dynamic = typeof event === "string"
58
+ ? false
59
+ : event.dynamic;
60
+ // alias exists
61
+ if (aliases[value]) {
62
+ events.add(aliases[value]);
63
+ continue;
64
+ }
65
+ // dynamic event
66
+ if (dynamic) {
67
+ console.log(chalk_1.default.yellow("\nDynamic event detected:"));
68
+ console.log(chalk_1.default.gray(value));
69
+ const { name } = await inquirer_1.default.prompt([
70
+ {
71
+ type: "input",
72
+ name: "name",
73
+ message: "Enter event name:",
74
+ validate: (v) => v
75
+ ? true
76
+ : "Required"
77
+ }
78
+ ]);
79
+ aliases[value] = name;
80
+ events.add(name);
81
+ continue;
82
+ }
83
+ events.add(value);
84
+ }
54
85
  }
86
+ config.aliases = aliases;
55
87
  const list = [...events].sort();
56
88
  config.events = list;
57
89
  await (0, config_1.saveConfig)(config);
@@ -1,44 +1,68 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.extractEvent = extractEvent;
3
+ exports.extractExpression = extractExpression;
4
4
  const ts_morph_1 = require("ts-morph");
5
- function extractEvent(call, path) {
6
- const parts = path.split(".");
7
- let node = call.getArguments()[Number(parts[0])];
8
- if (!node)
9
- return null;
10
- node = unwrap(node);
11
- for (let i = 1; i < parts.length; i++) {
12
- if (!ts_morph_1.Node.isObjectLiteralExpression(node)) {
13
- return null;
14
- }
15
- const obj = node;
16
- const prop = obj.getProperty(parts[i]);
17
- if (!prop)
18
- return null;
19
- if (!ts_morph_1.Node.isPropertyAssignment(prop)) {
20
- return null;
21
- }
22
- const initializer = prop.getInitializer();
23
- if (!initializer)
24
- return null;
25
- node = unwrap(initializer);
26
- }
27
- if (ts_morph_1.Node.isStringLiteral(node)) {
28
- return node.getLiteralText();
29
- }
30
- if (node.getKind() ===
5
+ function extractExpression(expr) {
6
+ // "signup"
7
+ if (ts_morph_1.Node.isStringLiteral(expr)) {
8
+ return {
9
+ value: expr.getLiteralText(),
10
+ dynamic: false
11
+ };
12
+ }
13
+ // `signup`
14
+ if (expr.getKind() ===
31
15
  ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
32
- return node
33
- .getText()
34
- .replace(/`/g, "");
16
+ return {
17
+ value: expr
18
+ .getText()
19
+ .replace(/`/g, ""),
20
+ dynamic: false
21
+ };
35
22
  }
36
- return null;
37
- }
38
- function unwrap(node) {
39
- let current = node;
40
- while (ts_morph_1.Node.isParenthesizedExpression(current)) {
41
- current = current.getExpression();
23
+ // template `${event}`
24
+ if (ts_morph_1.Node.isTemplateExpression(expr)) {
25
+ return {
26
+ value: expr.getText(),
27
+ dynamic: true
28
+ };
29
+ }
30
+ // ROUTES.APP
31
+ if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
32
+ return {
33
+ value: expr.getText(),
34
+ dynamic: true
35
+ };
36
+ }
37
+ // EVENT
38
+ if (ts_morph_1.Node.isIdentifier(expr)) {
39
+ return {
40
+ value: expr.getText(),
41
+ dynamic: true
42
+ };
42
43
  }
43
- return current;
44
+ // getEvent()
45
+ if (ts_morph_1.Node.isCallExpression(expr)) {
46
+ return {
47
+ value: expr
48
+ .getExpression()
49
+ .getText(),
50
+ dynamic: true
51
+ };
52
+ }
53
+ // condition ? "a" : "b"
54
+ if (ts_morph_1.Node.isConditionalExpression(expr)) {
55
+ return {
56
+ value: expr.getText(),
57
+ dynamic: true
58
+ };
59
+ }
60
+ // array[index]
61
+ if (ts_morph_1.Node.isElementAccessExpression(expr)) {
62
+ return {
63
+ value: expr.getText(),
64
+ dynamic: true
65
+ };
66
+ }
67
+ return null;
44
68
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.scanComponentWrappers = scanComponentWrappers;
4
4
  const ts_morph_1 = require("ts-morph");
5
+ const extract_1 = require("../extract");
5
6
  function scanComponentWrappers(source, wrappers) {
6
7
  const events = new Set();
7
8
  const elements = [
@@ -32,21 +33,21 @@ function scanComponentWrappers(source, wrappers) {
32
33
  const init = attrNode.getInitializer();
33
34
  if (!init)
34
35
  continue;
35
- if (init.getKind() ===
36
- ts_morph_1.SyntaxKind.StringLiteral) {
37
- const value = init.asKindOrThrow(ts_morph_1.SyntaxKind.StringLiteral);
38
- events.add(value.getLiteralText());
36
+ // event="signup"
37
+ if (ts_morph_1.Node.isStringLiteral(init)) {
38
+ events.add({
39
+ value: init.getLiteralText(),
40
+ dynamic: false
41
+ });
39
42
  }
40
- if (init.getKind() ===
41
- ts_morph_1.SyntaxKind.JsxExpression) {
42
- const expr = init
43
- .asKindOrThrow(ts_morph_1.SyntaxKind.JsxExpression)
44
- .getExpression();
45
- if (expr?.getKind() ===
46
- ts_morph_1.SyntaxKind.StringLiteral) {
47
- const value = expr.asKindOrThrow(ts_morph_1.SyntaxKind.StringLiteral);
48
- events.add(value.getLiteralText());
49
- }
43
+ // event={...}
44
+ if (ts_morph_1.Node.isJsxExpression(init)) {
45
+ const expr = init.getExpression();
46
+ if (!expr)
47
+ continue;
48
+ const result = (0, extract_1.extractExpression)(expr);
49
+ if (result)
50
+ events.add(result);
50
51
  }
51
52
  }
52
53
  }
@@ -13,20 +13,18 @@ function scanFunctionWrappers(source, wrappers) {
13
13
  for (const wrapper of wrappers) {
14
14
  if (wrapper.name !== name)
15
15
  continue;
16
- const event = extractEventFromArgs(call, wrapper.event);
17
- if (event)
18
- events.add(event);
16
+ const result = extractEventFromArgs(call, wrapper.event);
17
+ if (result)
18
+ events.add(result);
19
19
  }
20
20
  }
21
21
  return events;
22
22
  }
23
23
  function getFunctionName(call) {
24
24
  const expression = call.getExpression();
25
- // trackFeature()
26
25
  if (ts_morph_1.Node.isIdentifier(expression)) {
27
26
  return expression.getText();
28
27
  }
29
- // analytics.trackFeature()
30
28
  if (ts_morph_1.Node.isPropertyAccessExpression(expression)) {
31
29
  return getDeepName(expression);
32
30
  }
@@ -47,25 +45,27 @@ function extractEventFromArgs(call, event) {
47
45
  const args = call.getArguments();
48
46
  for (let i = 0; i < args.length; i++) {
49
47
  const arg = args[i];
50
- // string case
48
+ // track("event")
51
49
  if (!event) {
52
- if (ts_morph_1.Node.isStringLiteral(arg)) {
53
- return arg.getLiteralText();
54
- }
55
- // template literal
56
- if (arg.getKind() ===
57
- ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
58
- return arg
59
- .getText()
60
- .replace(/`/g, "");
61
- }
62
- }
63
- // object case
64
- if (event) {
65
- const result = (0, extract_1.extractEvent)(call, `${i}.${event}`);
50
+ const result = (0, extract_1.extractExpression)(arg);
66
51
  if (result)
67
52
  return result;
68
53
  }
54
+ // track({ event: "..." })
55
+ if (event) {
56
+ const obj = arg.asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
57
+ if (!obj)
58
+ continue;
59
+ const prop = obj.getProperty(event);
60
+ if (!prop)
61
+ continue;
62
+ if (ts_morph_1.Node.isPropertyAssignment(prop)) {
63
+ const init = prop.getInitializer();
64
+ if (!init)
65
+ continue;
66
+ return (0, extract_1.extractExpression)(init);
67
+ }
68
+ }
69
69
  }
70
70
  return null;
71
71
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -2,6 +2,7 @@ import chalk from "chalk";
2
2
  import fg from "fast-glob";
3
3
  import { Project } from "ts-morph";
4
4
  import fs from "fs/promises";
5
+ import inquirer from "inquirer";
5
6
 
6
7
  import {
7
8
  loadConfig,
@@ -34,6 +35,9 @@ export async function sync() {
34
35
  const project = new Project();
35
36
  const events = new Set<string>();
36
37
 
38
+ const aliases =
39
+ config.aliases ?? {};
40
+
37
41
  const files = await fg(
38
42
  config.sync.include,
39
43
  {
@@ -41,18 +45,6 @@ export async function sync() {
41
45
  }
42
46
  );
43
47
 
44
- const functionWrappers =
45
- (config.functionWrappers ?? []).map(
46
- (w) => ({
47
- name: w.name,
48
- path: w.event
49
- ? `0.${w.event}`
50
- : "0"
51
- })
52
- );
53
- const componentWrappers =
54
- config.wrappers ?? [];
55
-
56
48
  for (const file of files) {
57
49
  const parser =
58
50
  detectParser(file);
@@ -84,26 +76,78 @@ export async function sync() {
84
76
  { overwrite: true }
85
77
  );
86
78
 
87
- scanTrack(source).forEach(
88
- (e) => events.add(e)
89
- );
90
-
91
-
92
- scanFunctionWrappers(
93
- source,
94
- functionWrappers
95
- ).forEach((e) =>
96
- events.add(e)
97
- );
98
-
99
- scanComponentWrappers(
100
- source,
101
- componentWrappers
102
- ).forEach((e) =>
103
- events.add(e)
104
- );
79
+ const found = [
80
+ ...scanTrack(source),
81
+ ...scanFunctionWrappers(
82
+ source,
83
+ config.functionWrappers ?? []
84
+ ),
85
+ ...scanComponentWrappers(
86
+ source,
87
+ config.wrappers ?? []
88
+ )
89
+ ];
90
+
91
+ for (const event of found) {
92
+ if (!event) continue;
93
+
94
+ const value =
95
+ typeof event === "string"
96
+ ? event
97
+ : event.value;
98
+
99
+ const dynamic =
100
+ typeof event === "string"
101
+ ? false
102
+ : event.dynamic;
103
+
104
+ // alias exists
105
+ if (aliases[value]) {
106
+ events.add(
107
+ aliases[value]
108
+ );
109
+ continue;
110
+ }
111
+
112
+ // dynamic event
113
+ if (dynamic) {
114
+ console.log(
115
+ chalk.yellow(
116
+ "\nDynamic event detected:"
117
+ )
118
+ );
119
+
120
+ console.log(
121
+ chalk.gray(value)
122
+ );
123
+
124
+ const { name } =
125
+ await inquirer.prompt([
126
+ {
127
+ type: "input",
128
+ name: "name",
129
+ message:
130
+ "Enter event name:",
131
+ validate: (v) =>
132
+ v
133
+ ? true
134
+ : "Required"
135
+ }
136
+ ]);
137
+
138
+ aliases[value] = name;
139
+
140
+ events.add(name);
141
+
142
+ continue;
143
+ }
144
+
145
+ events.add(value);
146
+ }
105
147
  }
106
148
 
149
+ config.aliases = aliases;
150
+
107
151
  const list =
108
152
  [...events].sort();
109
153
 
package/src/types.ts CHANGED
@@ -13,8 +13,14 @@ export type EventraConfig = {
13
13
  events: string[];
14
14
  wrappers: ComponentWrapper[];
15
15
  functionWrappers: FunctionWrapper[];
16
+ aliases?: Record<string, string>;
16
17
  sync: {
17
18
  include: string[];
18
19
  exclude: string[];
19
20
  };
20
21
  };
22
+
23
+ export type ExtractedEvent = {
24
+ value: string;
25
+ dynamic: boolean;
26
+ };
@@ -1,74 +1,97 @@
1
1
  import {
2
- CallExpression,
3
2
  Node,
4
- ObjectLiteralExpression,
5
- SyntaxKind,
3
+ SyntaxKind
6
4
  } from "ts-morph";
7
5
 
8
- export function extractEvent(
9
- call: CallExpression,
10
- path: string
11
- ): string | null {
12
- const parts = path.split(".");
6
+ import { ExtractedEvent } from "../types";
13
7
 
14
- let node: Node | undefined =
15
- call.getArguments()[Number(parts[0])];
8
+ export function extractExpression(
9
+ expr: Node
10
+ ): ExtractedEvent | null {
16
11
 
17
- if (!node) return null;
18
-
19
- node = unwrap(node);
20
-
21
- for (let i = 1; i < parts.length; i++) {
22
- if (!Node.isObjectLiteralExpression(node)) {
23
- return null;
24
- }
25
-
26
- const obj: ObjectLiteralExpression =
27
- node;
28
-
29
- const prop =
30
- obj.getProperty(parts[i]);
31
-
32
- if (!prop) return null;
33
-
34
- if (!Node.isPropertyAssignment(prop)) {
35
- return null;
36
- }
37
-
38
- const initializer =
39
- prop.getInitializer();
12
+ // "signup"
13
+ if (Node.isStringLiteral(expr)) {
14
+ return {
15
+ value: expr.getLiteralText(),
16
+ dynamic: false
17
+ };
18
+ }
40
19
 
41
- if (!initializer) return null;
20
+ // `signup`
21
+ if (
22
+ expr.getKind() ===
23
+ SyntaxKind.NoSubstitutionTemplateLiteral
24
+ ) {
25
+ return {
26
+ value: expr
27
+ .getText()
28
+ .replace(/`/g, ""),
29
+ dynamic: false
30
+ };
31
+ }
42
32
 
43
- node = unwrap(initializer);
33
+ // template `${event}`
34
+ if (
35
+ Node.isTemplateExpression(expr)
36
+ ) {
37
+ return {
38
+ value: expr.getText(),
39
+ dynamic: true
40
+ };
44
41
  }
45
42
 
46
- if (Node.isStringLiteral(node)) {
47
- return node.getLiteralText();
43
+ // ROUTES.APP
44
+ if (
45
+ Node.isPropertyAccessExpression(expr)
46
+ ) {
47
+ return {
48
+ value: expr.getText(),
49
+ dynamic: true
50
+ };
48
51
  }
49
52
 
53
+ // EVENT
50
54
  if (
51
- node.getKind() ===
52
- SyntaxKind.NoSubstitutionTemplateLiteral
55
+ Node.isIdentifier(expr)
53
56
  ) {
54
- return node
55
- .getText()
56
- .replace(/`/g, "");
57
+ return {
58
+ value: expr.getText(),
59
+ dynamic: true
60
+ };
57
61
  }
58
62
 
59
- return null;
60
- }
63
+ // getEvent()
64
+ if (
65
+ Node.isCallExpression(expr)
66
+ ) {
67
+ return {
68
+ value:
69
+ expr
70
+ .getExpression()
71
+ .getText(),
72
+ dynamic: true
73
+ };
74
+ }
61
75
 
62
- function unwrap(node: Node): Node {
63
- let current = node;
76
+ // condition ? "a" : "b"
77
+ if (
78
+ Node.isConditionalExpression(expr)
79
+ ) {
80
+ return {
81
+ value: expr.getText(),
82
+ dynamic: true
83
+ };
84
+ }
64
85
 
65
- while (
66
- Node.isParenthesizedExpression(
67
- current
68
- )
69
- ) {
70
- current = current.getExpression();
86
+ // array[index]
87
+ if (
88
+ Node.isElementAccessExpression(expr)
89
+ ) {
90
+ return {
91
+ value: expr.getText(),
92
+ dynamic: true
93
+ };
71
94
  }
72
95
 
73
- return current;
96
+ return null;
74
97
  }
@@ -1,15 +1,18 @@
1
1
  import {
2
2
  SourceFile,
3
3
  SyntaxKind,
4
+ Node
4
5
  } from "ts-morph";
5
6
 
6
- import { ComponentWrapper } from "../../types";
7
+ import { ComponentWrapper, ExtractedEvent } from "../../types";
8
+ import { extractExpression } from "../extract";
7
9
 
8
10
  export function scanComponentWrappers(
9
11
  source: SourceFile,
10
12
  wrappers: ComponentWrapper[]
11
13
  ) {
12
- const events = new Set<string>();
14
+ const events =
15
+ new Set<ExtractedEvent>();
13
16
 
14
17
  const elements = [
15
18
  ...source.getDescendantsOfKind(
@@ -61,44 +64,31 @@ export function scanComponentWrappers(
61
64
 
62
65
  if (!init) continue;
63
66
 
67
+ // event="signup"
64
68
  if (
65
- init.getKind() ===
66
- SyntaxKind.StringLiteral
69
+ Node.isStringLiteral(init)
67
70
  ) {
68
- const value =
69
- init.asKindOrThrow(
70
- SyntaxKind.StringLiteral
71
- );
72
-
73
- events.add(
74
- value.getLiteralText()
75
- );
71
+ events.add({
72
+ value:
73
+ init.getLiteralText(),
74
+ dynamic: false
75
+ });
76
76
  }
77
77
 
78
+ // event={...}
78
79
  if (
79
- init.getKind() ===
80
- SyntaxKind.JsxExpression
80
+ Node.isJsxExpression(init)
81
81
  ) {
82
82
  const expr =
83
- init
84
- .asKindOrThrow(
85
- SyntaxKind.JsxExpression
86
- )
87
- .getExpression();
88
-
89
- if (
90
- expr?.getKind() ===
91
- SyntaxKind.StringLiteral
92
- ) {
93
- const value =
94
- expr.asKindOrThrow(
95
- SyntaxKind.StringLiteral
96
- );
97
-
98
- events.add(
99
- value.getLiteralText()
100
- );
101
- }
83
+ init.getExpression();
84
+
85
+ if (!expr) continue;
86
+
87
+ const result =
88
+ extractExpression(expr);
89
+
90
+ if (result)
91
+ events.add(result);
102
92
  }
103
93
  }
104
94
  }
@@ -6,14 +6,15 @@ import {
6
6
  PropertyAccessExpression,
7
7
  } from "ts-morph";
8
8
 
9
- import { extractEvent } from "../extract";
10
- import { FunctionWrapper } from "../../types";
9
+ import { extractExpression } from "../extract";
10
+ import { FunctionWrapper, ExtractedEvent } from "../../types";
11
11
 
12
12
  export function scanFunctionWrappers(
13
13
  source: SourceFile,
14
14
  wrappers: FunctionWrapper[]
15
15
  ) {
16
- const events = new Set<string>();
16
+ const events =
17
+ new Set<ExtractedEvent>();
17
18
 
18
19
  const calls =
19
20
  source.getDescendantsOfKind(
@@ -30,14 +31,14 @@ export function scanFunctionWrappers(
30
31
  if (wrapper.name !== name)
31
32
  continue;
32
33
 
33
- const event =
34
+ const result =
34
35
  extractEventFromArgs(
35
36
  call,
36
37
  wrapper.event
37
38
  );
38
39
 
39
- if (event)
40
- events.add(event);
40
+ if (result)
41
+ events.add(result);
41
42
  }
42
43
  }
43
44
 
@@ -50,16 +51,12 @@ function getFunctionName(
50
51
  const expression =
51
52
  call.getExpression();
52
53
 
53
- // trackFeature()
54
54
  if (
55
- Node.isIdentifier(
56
- expression
57
- )
55
+ Node.isIdentifier(expression)
58
56
  ) {
59
57
  return expression.getText();
60
58
  }
61
59
 
62
- // analytics.trackFeature()
63
60
  if (
64
61
  Node.isPropertyAccessExpression(
65
62
  expression
@@ -97,42 +94,52 @@ function getDeepName(
97
94
  return name;
98
95
  }
99
96
 
100
-
101
97
  function extractEventFromArgs(
102
98
  call: CallExpression,
103
99
  event?: string
104
- ): string | null {
105
- const args = call.getArguments();
100
+ ): ExtractedEvent | null {
101
+
102
+ const args =
103
+ call.getArguments();
106
104
 
107
105
  for (let i = 0; i < args.length; i++) {
108
106
  const arg = args[i];
109
107
 
110
- // string case
108
+ // track("event")
111
109
  if (!event) {
112
- if (Node.isStringLiteral(arg)) {
113
- return arg.getLiteralText();
114
- }
110
+ const result =
111
+ extractExpression(arg);
115
112
 
116
- // template literal
117
- if (
118
- arg.getKind() ===
119
- SyntaxKind.NoSubstitutionTemplateLiteral
120
- ) {
121
- return arg
122
- .getText()
123
- .replace(/`/g, "");
124
- }
113
+ if (result)
114
+ return result;
125
115
  }
126
116
 
127
- // object case
117
+ // track({ event: "..." })
128
118
  if (event) {
129
- const result =
130
- extractEvent(
131
- call,
132
- `${i}.${event}`
119
+ const obj =
120
+ arg.asKind(
121
+ SyntaxKind.ObjectLiteralExpression
133
122
  );
134
123
 
135
- if (result) return result;
124
+ if (!obj) continue;
125
+
126
+ const prop =
127
+ obj.getProperty(event);
128
+
129
+ if (!prop) continue;
130
+
131
+ if (
132
+ Node.isPropertyAssignment(prop)
133
+ ) {
134
+ const init =
135
+ prop.getInitializer();
136
+
137
+ if (!init) continue;
138
+
139
+ return extractExpression(
140
+ init
141
+ );
142
+ }
136
143
  }
137
144
  }
138
145