@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.
- package/dist/commands/sync.js +35 -10
- package/dist/utils/extract.js +64 -36
- package/dist/utils/scanners/component-wrappers.js +19 -14
- package/dist/utils/scanners/function-wrappers.js +35 -21
- package/dist/utils/scanners/track.js +11 -17
- package/package.json +1 -1
- package/src/commands/sync.ts +66 -30
- package/src/types.ts +6 -0
- package/src/utils/extract.ts +97 -56
- package/src/utils/scanners/component-wrappers.ts +35 -33
- package/src/utils/scanners/function-wrappers.ts +72 -32
- package/src/utils/scanners/track.ts +24 -38
package/dist/commands/sync.js
CHANGED
|
@@ -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
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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);
|
package/dist/utils/extract.js
CHANGED
|
@@ -1,44 +1,72 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.extractExpression = extractExpression;
|
|
4
4
|
const ts_morph_1 = require("ts-morph");
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
if (node.getKind() ===
|
|
13
|
+
// `event`
|
|
14
|
+
if (expr.getKind() ===
|
|
31
15
|
ts_morph_1.SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
32
|
-
return
|
|
33
|
-
|
|
34
|
-
|
|
16
|
+
return {
|
|
17
|
+
values: [
|
|
18
|
+
expr
|
|
19
|
+
.getText()
|
|
20
|
+
.replace(/`/g, "")
|
|
21
|
+
],
|
|
22
|
+
dynamic: false
|
|
23
|
+
};
|
|
35
24
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
// string
|
|
37
|
+
if (ts_morph_1.Node.isStringLiteral(init)) {
|
|
38
|
+
events.add({
|
|
39
|
+
value: init.getLiteralText(),
|
|
40
|
+
dynamic: false
|
|
41
|
+
});
|
|
39
42
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const expr = init
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
17
|
-
if (
|
|
18
|
-
|
|
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
|
-
//
|
|
50
|
+
// track("event")
|
|
51
51
|
if (!event) {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
//
|
|
60
|
+
// track({event})
|
|
64
61
|
if (event) {
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
|
|
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
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
package/src/commands/sync.ts
CHANGED
|
@@ -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
|
-
|
|
88
|
-
(
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
};
|
package/src/utils/extract.ts
CHANGED
|
@@ -1,74 +1,115 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CallExpression,
|
|
3
2
|
Node,
|
|
4
|
-
|
|
5
|
-
SyntaxKind,
|
|
3
|
+
SyntaxKind
|
|
6
4
|
} from "ts-morph";
|
|
7
5
|
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
52
|
-
SyntaxKind.NoSubstitutionTemplateLiteral
|
|
50
|
+
Node.isPropertyAccessExpression(expr)
|
|
53
51
|
) {
|
|
54
|
-
return
|
|
55
|
-
.getText()
|
|
56
|
-
|
|
52
|
+
return {
|
|
53
|
+
values: [expr.getText()],
|
|
54
|
+
dynamic: true
|
|
55
|
+
};
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
|
|
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
|
-
|
|
63
|
-
|
|
90
|
+
// template
|
|
91
|
+
if (
|
|
92
|
+
Node.isTemplateExpression(expr)
|
|
93
|
+
) {
|
|
94
|
+
return {
|
|
95
|
+
values: [expr.getText()],
|
|
96
|
+
dynamic: true
|
|
97
|
+
};
|
|
98
|
+
}
|
|
64
99
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
|
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 {
|
|
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 =
|
|
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
|
-
|
|
66
|
-
SyntaxKind.StringLiteral
|
|
73
|
+
Node.isStringLiteral(init)
|
|
67
74
|
) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
80
|
-
SyntaxKind.JsxExpression
|
|
84
|
+
Node.isJsxExpression(init)
|
|
81
85
|
) {
|
|
82
86
|
const expr =
|
|
83
|
-
init
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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 {
|
|
10
|
-
|
|
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 =
|
|
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
|
|
38
|
+
const results =
|
|
34
39
|
extractEventFromArgs(
|
|
35
40
|
call,
|
|
36
41
|
wrapper.event
|
|
37
42
|
);
|
|
38
43
|
|
|
39
|
-
if (
|
|
40
|
-
|
|
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
|
-
):
|
|
105
|
-
|
|
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
|
-
//
|
|
121
|
+
// track("event")
|
|
111
122
|
if (!event) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
//
|
|
138
|
+
// track({event})
|
|
128
139
|
if (event) {
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
`${i}.${event}`
|
|
140
|
+
const obj =
|
|
141
|
+
arg.asKind(
|
|
142
|
+
SyntaxKind.ObjectLiteralExpression
|
|
133
143
|
);
|
|
134
144
|
|
|
135
|
-
if (
|
|
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
|
|
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<
|
|
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
|
-
|
|
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
|
-
|
|
35
|
-
|
|
39
|
+
Node.isPropertyAccessExpression(
|
|
40
|
+
expr
|
|
41
|
+
)
|
|
36
42
|
) {
|
|
37
|
-
const prop =
|
|
38
|
-
expr.asKindOrThrow(
|
|
39
|
-
SyntaxKind.PropertyAccessExpression
|
|
40
|
-
);
|
|
41
|
-
|
|
42
43
|
isTrack =
|
|
43
|
-
|
|
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
|
-
|
|
54
|
-
arg
|
|
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
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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;
|