@eventra_dev/eventra-cli 0.0.7 → 0.0.9

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,40 @@ 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
+ const { value, dynamic } = event;
53
+ // alias exists
54
+ if (aliases[value]) {
55
+ events.add(aliases[value]);
56
+ continue;
57
+ }
58
+ // dynamic event
59
+ if (dynamic) {
60
+ console.log(chalk_1.default.yellow("\nDynamic event detected:"));
61
+ console.log(chalk_1.default.gray(value));
62
+ const { name } = await inquirer_1.default.prompt([
63
+ {
64
+ type: "input",
65
+ name: "name",
66
+ message: "Enter event name:",
67
+ validate: (v) => v
68
+ ? true
69
+ : "Required"
70
+ }
71
+ ]);
72
+ aliases[value] = name;
73
+ events.add(name);
74
+ continue;
75
+ }
76
+ events.add(value);
77
+ }
54
78
  }
79
+ config.aliases = aliases;
55
80
  const list = [...events].sort();
56
81
  config.events = list;
57
82
  await (0, config_1.saveConfig)(config);
@@ -1,44 +1,72 @@
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);
5
+ function extractExpression(expr) {
6
+ // "event"
7
+ if (ts_morph_1.Node.isStringLiteral(expr)) {
8
+ return {
9
+ values: [expr.getLiteralText()],
10
+ dynamic: false
11
+ };
26
12
  }
27
- if (ts_morph_1.Node.isStringLiteral(node)) {
28
- return node.getLiteralText();
29
- }
30
- if (node.getKind() ===
13
+ // `event`
14
+ if (expr.getKind() ===
31
15
  ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
32
- return node
33
- .getText()
34
- .replace(/`/g, "");
16
+ return {
17
+ values: [
18
+ expr
19
+ .getText()
20
+ .replace(/`/g, "")
21
+ ],
22
+ dynamic: false
23
+ };
35
24
  }
36
- return null;
37
- }
38
- function unwrap(node) {
39
- let current = node;
40
- while (ts_morph_1.Node.isParenthesizedExpression(current)) {
41
- current = current.getExpression();
25
+ // identifier
26
+ if (ts_morph_1.Node.isIdentifier(expr)) {
27
+ return {
28
+ values: [expr.getText()],
29
+ dynamic: true
30
+ };
31
+ }
32
+ // property access (ROUTES.ACCOUNT)
33
+ if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
34
+ return {
35
+ values: [expr.getText()],
36
+ dynamic: true
37
+ };
42
38
  }
43
- return current;
39
+ // ternary
40
+ if (ts_morph_1.Node.isConditionalExpression(expr)) {
41
+ const values = [];
42
+ const whenTrue = extractExpression(expr.getWhenTrue());
43
+ const whenFalse = extractExpression(expr.getWhenFalse());
44
+ if (whenTrue)
45
+ values.push(...whenTrue.values);
46
+ if (whenFalse)
47
+ values.push(...whenFalse.values);
48
+ return {
49
+ values,
50
+ dynamic: true
51
+ };
52
+ }
53
+ // template
54
+ if (ts_morph_1.Node.isTemplateExpression(expr)) {
55
+ return {
56
+ values: [expr.getText()],
57
+ dynamic: true
58
+ };
59
+ }
60
+ // call
61
+ if (ts_morph_1.Node.isCallExpression(expr)) {
62
+ return {
63
+ values: [
64
+ expr
65
+ .getExpression()
66
+ .getText()
67
+ ],
68
+ dynamic: true
69
+ };
70
+ }
71
+ return null;
44
72
  }
@@ -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,25 @@ 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
+ // string
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
+ // jsx expression
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
+ continue;
51
+ result.values.forEach((value) => events.add({
52
+ value,
53
+ dynamic: result.dynamic
54
+ }));
50
55
  }
51
56
  }
52
57
  }
@@ -13,20 +13,19 @@ 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 results = extractEventFromArgs(call, wrapper.event);
17
+ if (!results)
18
+ continue;
19
+ results.forEach((r) => events.add(r));
19
20
  }
20
21
  }
21
22
  return events;
22
23
  }
23
24
  function getFunctionName(call) {
24
25
  const expression = call.getExpression();
25
- // trackFeature()
26
26
  if (ts_morph_1.Node.isIdentifier(expression)) {
27
27
  return expression.getText();
28
28
  }
29
- // analytics.trackFeature()
30
29
  if (ts_morph_1.Node.isPropertyAccessExpression(expression)) {
31
30
  return getDeepName(expression);
32
31
  }
@@ -45,27 +44,42 @@ function getDeepName(node) {
45
44
  }
46
45
  function extractEventFromArgs(call, event) {
47
46
  const args = call.getArguments();
47
+ const events = [];
48
48
  for (let i = 0; i < args.length; i++) {
49
49
  const arg = args[i];
50
- // string case
50
+ // track("event")
51
51
  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
- }
52
+ const result = (0, extract_1.extractExpression)(arg);
53
+ if (!result)
54
+ continue;
55
+ result.values.forEach((value) => events.push({
56
+ value,
57
+ dynamic: result.dynamic
58
+ }));
62
59
  }
63
- // object case
60
+ // track({event})
64
61
  if (event) {
65
- const result = (0, extract_1.extractEvent)(call, `${i}.${event}`);
66
- if (result)
67
- return result;
62
+ const obj = arg.asKind(ts_morph_1.SyntaxKind.ObjectLiteralExpression);
63
+ if (!obj)
64
+ continue;
65
+ const prop = obj.getProperty(event);
66
+ if (!prop)
67
+ continue;
68
+ if (ts_morph_1.Node.isPropertyAssignment(prop)) {
69
+ const init = prop.getInitializer();
70
+ if (!init)
71
+ continue;
72
+ const result = (0, extract_1.extractExpression)(init);
73
+ if (!result)
74
+ continue;
75
+ result.values.forEach((value) => events.push({
76
+ value,
77
+ dynamic: result.dynamic
78
+ }));
79
+ }
68
80
  }
69
81
  }
70
- return null;
82
+ return events.length
83
+ ? events
84
+ : null;
71
85
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.scanTrack = scanTrack;
4
4
  const ts_morph_1 = require("ts-morph");
5
+ const extract_1 = require("../extract");
5
6
  function scanTrack(source) {
6
7
  const events = new Set();
7
8
  const calls = source.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression);
@@ -9,34 +10,27 @@ function scanTrack(source) {
9
10
  const expr = call.getExpression();
10
11
  let isTrack = false;
11
12
  // track()
12
- if (expr.getKind() ===
13
- ts_morph_1.SyntaxKind.Identifier) {
13
+ if (ts_morph_1.Node.isIdentifier(expr)) {
14
14
  isTrack =
15
15
  expr.getText() === "track";
16
16
  }
17
17
  // analytics.track()
18
- if (expr.getKind() ===
19
- ts_morph_1.SyntaxKind.PropertyAccessExpression) {
20
- const prop = expr.asKindOrThrow(ts_morph_1.SyntaxKind.PropertyAccessExpression);
18
+ if (ts_morph_1.Node.isPropertyAccessExpression(expr)) {
21
19
  isTrack =
22
- prop.getName() === "track";
20
+ expr.getName() === "track";
23
21
  }
24
22
  if (!isTrack)
25
23
  continue;
26
24
  const arg = call.getArguments()[0];
27
25
  if (!arg)
28
26
  continue;
29
- if (arg.getKind() ===
30
- ts_morph_1.SyntaxKind.StringLiteral) {
31
- const value = arg.asKindOrThrow(ts_morph_1.SyntaxKind.StringLiteral);
32
- events.add(value.getLiteralText());
33
- }
34
- // template literal
35
- if (arg.getKind() ===
36
- ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
37
- const value = arg.asKindOrThrow(ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral);
38
- events.add(value.getLiteralText());
39
- }
27
+ const result = (0, extract_1.extractExpression)(arg);
28
+ if (!result)
29
+ continue;
30
+ result.values.forEach((value) => events.add({
31
+ value,
32
+ dynamic: result.dynamic
33
+ }));
40
34
  }
41
35
  return events;
42
36
  }
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.9",
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,
@@ -17,6 +18,8 @@ import { scanTrack } from "../utils/scanners/track";
17
18
  import { scanFunctionWrappers } from "../utils/scanners/function-wrappers";
18
19
  import { scanComponentWrappers } from "../utils/scanners/component-wrappers";
19
20
 
21
+ import { ExtractedEvent } from "../types";
22
+
20
23
  export async function sync() {
21
24
  const config = await loadConfig();
22
25
 
@@ -34,6 +37,9 @@ export async function sync() {
34
37
  const project = new Project();
35
38
  const events = new Set<string>();
36
39
 
40
+ const aliases =
41
+ config.aliases ?? {};
42
+
37
43
  const files = await fg(
38
44
  config.sync.include,
39
45
  {
@@ -41,18 +47,6 @@ export async function sync() {
41
47
  }
42
48
  );
43
49
 
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
50
  for (const file of files) {
57
51
  const parser =
58
52
  detectParser(file);
@@ -84,26 +78,68 @@ export async function sync() {
84
78
  { overwrite: true }
85
79
  );
86
80
 
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
- );
81
+ const found: ExtractedEvent[] = [
82
+ ...scanTrack(source),
83
+ ...scanFunctionWrappers(
84
+ source,
85
+ config.functionWrappers ?? []
86
+ ),
87
+ ...scanComponentWrappers(
88
+ source,
89
+ config.wrappers ?? []
90
+ )
91
+ ];
92
+
93
+ for (const event of found) {
94
+ const { value, dynamic } = event;
95
+
96
+ // alias exists
97
+ if (aliases[value]) {
98
+ events.add(
99
+ aliases[value]
100
+ );
101
+ continue;
102
+ }
103
+
104
+ // dynamic event
105
+ if (dynamic) {
106
+ console.log(
107
+ chalk.yellow(
108
+ "\nDynamic event detected:"
109
+ )
110
+ );
111
+
112
+ console.log(
113
+ chalk.gray(value)
114
+ );
115
+
116
+ const { name } =
117
+ await inquirer.prompt([
118
+ {
119
+ type: "input",
120
+ name: "name",
121
+ message:
122
+ "Enter event name:",
123
+ validate: (v) =>
124
+ v
125
+ ? true
126
+ : "Required"
127
+ }
128
+ ]);
129
+
130
+ aliases[value] = name;
131
+
132
+ events.add(name);
133
+
134
+ continue;
135
+ }
136
+
137
+ events.add(value);
138
+ }
105
139
  }
106
140
 
141
+ config.aliases = aliases;
142
+
107
143
  const list =
108
144
  [...events].sort();
109
145
 
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,115 @@
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(".");
13
-
14
- let node: Node | undefined =
15
- call.getArguments()[Number(parts[0])];
16
-
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();
40
-
41
- if (!initializer) return null;
6
+ export type ExtractResult = {
7
+ values: string[];
8
+ dynamic: boolean;
9
+ };
10
+
11
+ export function extractExpression(
12
+ expr: Node
13
+ ): ExtractResult | null {
14
+
15
+ // "event"
16
+ if (Node.isStringLiteral(expr)) {
17
+ return {
18
+ values: [expr.getLiteralText()],
19
+ dynamic: false
20
+ };
21
+ }
42
22
 
43
- node = unwrap(initializer);
23
+ // `event`
24
+ if (
25
+ expr.getKind() ===
26
+ SyntaxKind.NoSubstitutionTemplateLiteral
27
+ ) {
28
+ return {
29
+ values: [
30
+ expr
31
+ .getText()
32
+ .replace(/`/g, "")
33
+ ],
34
+ dynamic: false
35
+ };
44
36
  }
45
37
 
46
- if (Node.isStringLiteral(node)) {
47
- return node.getLiteralText();
38
+ // identifier
39
+ if (
40
+ Node.isIdentifier(expr)
41
+ ) {
42
+ return {
43
+ values: [expr.getText()],
44
+ dynamic: true
45
+ };
48
46
  }
49
47
 
48
+ // property access (ROUTES.ACCOUNT)
50
49
  if (
51
- node.getKind() ===
52
- SyntaxKind.NoSubstitutionTemplateLiteral
50
+ Node.isPropertyAccessExpression(expr)
53
51
  ) {
54
- return node
55
- .getText()
56
- .replace(/`/g, "");
52
+ return {
53
+ values: [expr.getText()],
54
+ dynamic: true
55
+ };
57
56
  }
58
57
 
59
- return null;
60
- }
58
+ // ternary
59
+ if (
60
+ Node.isConditionalExpression(expr)
61
+ ) {
62
+ const values: string[] = [];
63
+
64
+ const whenTrue =
65
+ extractExpression(
66
+ expr.getWhenTrue()
67
+ );
68
+
69
+ const whenFalse =
70
+ extractExpression(
71
+ expr.getWhenFalse()
72
+ );
73
+
74
+ if (whenTrue)
75
+ values.push(
76
+ ...whenTrue.values
77
+ );
78
+
79
+ if (whenFalse)
80
+ values.push(
81
+ ...whenFalse.values
82
+ );
83
+
84
+ return {
85
+ values,
86
+ dynamic: true
87
+ };
88
+ }
61
89
 
62
- function unwrap(node: Node): Node {
63
- let current = node;
90
+ // template
91
+ if (
92
+ Node.isTemplateExpression(expr)
93
+ ) {
94
+ return {
95
+ values: [expr.getText()],
96
+ dynamic: true
97
+ };
98
+ }
64
99
 
65
- while (
66
- Node.isParenthesizedExpression(
67
- current
68
- )
69
- ) {
70
- current = current.getExpression();
100
+ // call
101
+ if (
102
+ Node.isCallExpression(expr)
103
+ ) {
104
+ return {
105
+ values: [
106
+ expr
107
+ .getExpression()
108
+ .getText()
109
+ ],
110
+ dynamic: true
111
+ };
71
112
  }
72
113
 
73
- return current;
114
+ return null;
74
115
  }
@@ -1,15 +1,22 @@
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 {
8
+ ComponentWrapper,
9
+ ExtractedEvent
10
+ } from "../../types";
11
+
12
+ import { extractExpression } from "../extract";
7
13
 
8
14
  export function scanComponentWrappers(
9
15
  source: SourceFile,
10
16
  wrappers: ComponentWrapper[]
11
17
  ) {
12
- const events = new Set<string>();
18
+ const events =
19
+ new Set<ExtractedEvent>();
13
20
 
14
21
  const elements = [
15
22
  ...source.getDescendantsOfKind(
@@ -61,44 +68,39 @@ export function scanComponentWrappers(
61
68
 
62
69
  if (!init) continue;
63
70
 
71
+ // string
64
72
  if (
65
- init.getKind() ===
66
- SyntaxKind.StringLiteral
73
+ Node.isStringLiteral(init)
67
74
  ) {
68
- const value =
69
- init.asKindOrThrow(
70
- SyntaxKind.StringLiteral
71
- );
72
-
73
- events.add(
74
- value.getLiteralText()
75
- );
75
+ events.add({
76
+ value:
77
+ init.getLiteralText(),
78
+ dynamic: false
79
+ });
76
80
  }
77
81
 
82
+ // jsx expression
78
83
  if (
79
- init.getKind() ===
80
- SyntaxKind.JsxExpression
84
+ Node.isJsxExpression(init)
81
85
  ) {
82
86
  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
- }
87
+ init.getExpression();
88
+
89
+ if (!expr) continue;
90
+
91
+ const result =
92
+ extractExpression(expr);
93
+
94
+ if (!result) continue;
95
+
96
+ result.values.forEach(
97
+ (value) =>
98
+ events.add({
99
+ value,
100
+ dynamic:
101
+ result.dynamic
102
+ })
103
+ );
102
104
  }
103
105
  }
104
106
  }
@@ -6,14 +6,19 @@ import {
6
6
  PropertyAccessExpression,
7
7
  } from "ts-morph";
8
8
 
9
- import { extractEvent } from "../extract";
10
- import { FunctionWrapper } from "../../types";
9
+ import {
10
+ FunctionWrapper,
11
+ ExtractedEvent
12
+ } from "../../types";
13
+
14
+ import { extractExpression } from "../extract";
11
15
 
12
16
  export function scanFunctionWrappers(
13
17
  source: SourceFile,
14
18
  wrappers: FunctionWrapper[]
15
19
  ) {
16
- const events = new Set<string>();
20
+ const events =
21
+ new Set<ExtractedEvent>();
17
22
 
18
23
  const calls =
19
24
  source.getDescendantsOfKind(
@@ -30,14 +35,17 @@ export function scanFunctionWrappers(
30
35
  if (wrapper.name !== name)
31
36
  continue;
32
37
 
33
- const event =
38
+ const results =
34
39
  extractEventFromArgs(
35
40
  call,
36
41
  wrapper.event
37
42
  );
38
43
 
39
- if (event)
40
- events.add(event);
44
+ if (!results) continue;
45
+
46
+ results.forEach(
47
+ (r) => events.add(r)
48
+ );
41
49
  }
42
50
  }
43
51
 
@@ -47,10 +55,10 @@ export function scanFunctionWrappers(
47
55
  function getFunctionName(
48
56
  call: CallExpression
49
57
  ): string | null {
58
+
50
59
  const expression =
51
60
  call.getExpression();
52
61
 
53
- // trackFeature()
54
62
  if (
55
63
  Node.isIdentifier(
56
64
  expression
@@ -59,7 +67,6 @@ function getFunctionName(
59
67
  return expression.getText();
60
68
  }
61
69
 
62
- // analytics.trackFeature()
63
70
  if (
64
71
  Node.isPropertyAccessExpression(
65
72
  expression
@@ -76,6 +83,7 @@ function getFunctionName(
76
83
  function getDeepName(
77
84
  node: PropertyAccessExpression
78
85
  ): string {
86
+
79
87
  let current:
80
88
  | Node
81
89
  | undefined = node;
@@ -97,44 +105,76 @@ function getDeepName(
97
105
  return name;
98
106
  }
99
107
 
100
-
101
108
  function extractEventFromArgs(
102
109
  call: CallExpression,
103
110
  event?: string
104
- ): string | null {
105
- const args = call.getArguments();
111
+ ): ExtractedEvent[] | null {
112
+
113
+ const args =
114
+ call.getArguments();
115
+
116
+ const events: ExtractedEvent[] = [];
106
117
 
107
118
  for (let i = 0; i < args.length; i++) {
108
119
  const arg = args[i];
109
120
 
110
- // string case
121
+ // track("event")
111
122
  if (!event) {
112
- if (Node.isStringLiteral(arg)) {
113
- return arg.getLiteralText();
114
- }
115
-
116
- // template literal
117
- if (
118
- arg.getKind() ===
119
- SyntaxKind.NoSubstitutionTemplateLiteral
120
- ) {
121
- return arg
122
- .getText()
123
- .replace(/`/g, "");
124
- }
123
+ const result =
124
+ extractExpression(arg);
125
+
126
+ if (!result) continue;
127
+
128
+ result.values.forEach(
129
+ (value) =>
130
+ events.push({
131
+ value,
132
+ dynamic:
133
+ result.dynamic
134
+ })
135
+ );
125
136
  }
126
137
 
127
- // object case
138
+ // track({event})
128
139
  if (event) {
129
- const result =
130
- extractEvent(
131
- call,
132
- `${i}.${event}`
140
+ const obj =
141
+ arg.asKind(
142
+ SyntaxKind.ObjectLiteralExpression
133
143
  );
134
144
 
135
- if (result) return result;
145
+ if (!obj) continue;
146
+
147
+ const prop =
148
+ obj.getProperty(event);
149
+
150
+ if (!prop) continue;
151
+
152
+ if (
153
+ Node.isPropertyAssignment(prop)
154
+ ) {
155
+ const init =
156
+ prop.getInitializer();
157
+
158
+ if (!init) continue;
159
+
160
+ const result =
161
+ extractExpression(init);
162
+
163
+ if (!result) continue;
164
+
165
+ result.values.forEach(
166
+ (value) =>
167
+ events.push({
168
+ value,
169
+ dynamic:
170
+ result.dynamic
171
+ })
172
+ );
173
+ }
136
174
  }
137
175
  }
138
176
 
139
- return null;
177
+ return events.length
178
+ ? events
179
+ : null;
140
180
  }
@@ -1,13 +1,19 @@
1
1
  import {
2
2
  SourceFile,
3
- SyntaxKind
3
+ SyntaxKind,
4
+ Node,
5
+ CallExpression,
6
+ PropertyAccessExpression
4
7
  } from "ts-morph";
5
8
 
9
+ import { ExtractedEvent } from "../../types";
10
+ import { extractExpression } from "../extract";
11
+
6
12
  export function scanTrack(
7
13
  source: SourceFile
8
14
  ) {
9
15
  const events =
10
- new Set<string>();
16
+ new Set<ExtractedEvent>();
11
17
 
12
18
  const calls =
13
19
  source.getDescendantsOfKind(
@@ -22,8 +28,7 @@ export function scanTrack(
22
28
 
23
29
  // track()
24
30
  if (
25
- expr.getKind() ===
26
- SyntaxKind.Identifier
31
+ Node.isIdentifier(expr)
27
32
  ) {
28
33
  isTrack =
29
34
  expr.getText() === "track";
@@ -31,16 +36,12 @@ export function scanTrack(
31
36
 
32
37
  // analytics.track()
33
38
  if (
34
- expr.getKind() ===
35
- SyntaxKind.PropertyAccessExpression
39
+ Node.isPropertyAccessExpression(
40
+ expr
41
+ )
36
42
  ) {
37
- const prop =
38
- expr.asKindOrThrow(
39
- SyntaxKind.PropertyAccessExpression
40
- );
41
-
42
43
  isTrack =
43
- prop.getName() === "track";
44
+ expr.getName() === "track";
44
45
  }
45
46
 
46
47
  if (!isTrack) continue;
@@ -50,34 +51,19 @@ export function scanTrack(
50
51
 
51
52
  if (!arg) continue;
52
53
 
53
- if (
54
- arg.getKind() ===
55
- SyntaxKind.StringLiteral
56
- ) {
57
- const value =
58
- arg.asKindOrThrow(
59
- SyntaxKind.StringLiteral
60
- );
61
-
62
- events.add(
63
- value.getLiteralText()
64
- );
65
- }
54
+ const result =
55
+ extractExpression(arg);
66
56
 
67
- // template literal
68
- if (
69
- arg.getKind() ===
70
- SyntaxKind.NoSubstitutionTemplateLiteral
71
- ) {
72
- const value =
73
- arg.asKindOrThrow(
74
- SyntaxKind.NoSubstitutionTemplateLiteral
75
- );
57
+ if (!result) continue;
76
58
 
77
- events.add(
78
- value.getLiteralText()
79
- );
80
- }
59
+ result.values.forEach(
60
+ (value) =>
61
+ events.add({
62
+ value,
63
+ dynamic:
64
+ result.dynamic
65
+ })
66
+ );
81
67
  }
82
68
 
83
69
  return events;