@eventra_dev/eventra-cli 0.0.18 → 0.0.19
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/README.md +74 -166
- package/dist/commands/check.js +80 -43
- package/dist/commands/sync.js +93 -53
- package/dist/commands/watch.js +97 -0
- package/dist/index.js +10 -6
- package/dist/utils/detect-aliases.js +39 -0
- package/dist/utils/detect-wrappers.js +84 -0
- package/dist/utils/extract.js +18 -98
- package/dist/utils/scanners/component-wrappers.js +23 -20
- package/dist/utils/scanners/function-wrappers.js +16 -18
- package/dist/utils/scanners/track.js +20 -8
- package/package.json +2 -1
- package/src/commands/check.ts +100 -75
- package/src/commands/sync.ts +170 -110
- package/src/commands/watch.ts +113 -0
- package/src/index.ts +11 -6
- package/src/types.ts +4 -0
- package/src/utils/detect-aliases.ts +46 -0
- package/src/utils/detect-wrappers.ts +119 -0
- package/src/utils/extract.ts +20 -133
- package/src/utils/scanners/component-wrappers.ts +27 -25
- package/src/utils/scanners/function-wrappers.ts +47 -90
- package/src/utils/scanners/track.ts +23 -8
package/src/commands/sync.ts
CHANGED
|
@@ -1,157 +1,217 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import fg from "fast-glob";
|
|
3
|
-
import {
|
|
3
|
+
import {Project} from "ts-morph";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
import inquirer from "inquirer";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
loadConfig,
|
|
9
|
-
saveConfig
|
|
10
|
-
} from "../utils/config";
|
|
7
|
+
import {loadConfig, saveConfig} from "../utils/config";
|
|
11
8
|
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { parseSvelte } from "../utils/parsers/svelte";
|
|
15
|
-
import { parseAstro } from "../utils/parsers/astro";
|
|
9
|
+
import {detectWrappers} from "../utils/detect-wrappers";
|
|
10
|
+
import {detectAliases} from "../utils/detect-aliases";
|
|
16
11
|
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
12
|
+
import {detectParser} from "../utils/parsers/router";
|
|
13
|
+
import {parseVue} from "../utils/parsers/vue";
|
|
14
|
+
import {parseSvelte} from "../utils/parsers/svelte";
|
|
15
|
+
import {parseAstro} from "../utils/parsers/astro";
|
|
20
16
|
|
|
21
|
-
import {
|
|
17
|
+
import {scanTrack} from "../utils/scanners/track";
|
|
18
|
+
import {scanFunctionWrappers} from "../utils/scanners/function-wrappers";
|
|
19
|
+
import {scanComponentWrappers} from "../utils/scanners/component-wrappers";
|
|
20
|
+
|
|
21
|
+
import {ExtractedEvent} from "../types";
|
|
22
22
|
|
|
23
23
|
export async function sync() {
|
|
24
24
|
const config = await loadConfig();
|
|
25
25
|
|
|
26
26
|
if (!config) {
|
|
27
|
-
console.log(
|
|
28
|
-
chalk.red("Run 'eventra init'")
|
|
29
|
-
);
|
|
27
|
+
console.log(chalk.red("Run 'eventra init'"));
|
|
30
28
|
return;
|
|
31
29
|
}
|
|
32
30
|
|
|
33
|
-
console.log(
|
|
34
|
-
chalk.blue("Scanning project...")
|
|
35
|
-
);
|
|
31
|
+
console.log(chalk.blue("Scanning project..."));
|
|
36
32
|
|
|
37
33
|
const project = new Project();
|
|
38
|
-
const events = new Set<string>();
|
|
39
34
|
|
|
35
|
+
const events = new Set<string>();
|
|
40
36
|
const aliases = config.aliases ?? {};
|
|
41
37
|
const asked = new Set<string>();
|
|
42
38
|
|
|
43
|
-
const files = await fg(
|
|
44
|
-
config.sync.
|
|
45
|
-
|
|
46
|
-
ignore: config.sync.exclude
|
|
47
|
-
}
|
|
48
|
-
);
|
|
39
|
+
const files = await fg(config.sync.include, {
|
|
40
|
+
ignore: config.sync.exclude
|
|
41
|
+
});
|
|
49
42
|
|
|
50
43
|
for (const file of files) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
file,
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
),
|
|
84
|
-
...scanComponentWrappers(
|
|
85
|
-
source,
|
|
86
|
-
config.wrappers ?? []
|
|
87
|
-
)
|
|
88
|
-
];
|
|
89
|
-
|
|
90
|
-
for (const event of found) {
|
|
91
|
-
const { value, dynamic } = event;
|
|
92
|
-
|
|
93
|
-
if (!value) continue;
|
|
94
|
-
|
|
95
|
-
// alias
|
|
96
|
-
if (aliases[value]) {
|
|
97
|
-
console.log(
|
|
98
|
-
chalk.gray(`alias: ${value} → ${aliases[value]}`)
|
|
99
|
-
);
|
|
100
|
-
events.add(aliases[value]);
|
|
101
|
-
continue;
|
|
44
|
+
try {
|
|
45
|
+
const parser = detectParser(file);
|
|
46
|
+
|
|
47
|
+
let content = await fs.readFile(file, "utf-8");
|
|
48
|
+
|
|
49
|
+
if (parser === "vue") content = parseVue(content);
|
|
50
|
+
if (parser === "svelte") content = parseSvelte(content);
|
|
51
|
+
if (parser === "astro") content = parseAstro(content);
|
|
52
|
+
|
|
53
|
+
const virtualFile =
|
|
54
|
+
parser === "ts" ? file : file + ".tsx";
|
|
55
|
+
|
|
56
|
+
const source = project.createSourceFile(
|
|
57
|
+
virtualFile,
|
|
58
|
+
content,
|
|
59
|
+
{overwrite: true}
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
// AUTO ALIASES
|
|
64
|
+
const detectedAliases = detectAliases(source);
|
|
65
|
+
|
|
66
|
+
for (const key in detectedAliases) {
|
|
67
|
+
if (!(key in aliases)) {
|
|
68
|
+
aliases[key] = detectedAliases[key];
|
|
69
|
+
|
|
70
|
+
console.log(
|
|
71
|
+
chalk.green(
|
|
72
|
+
`Detected alias: ${key} → ${detectedAliases[key]}`
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
}
|
|
102
76
|
}
|
|
103
77
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
78
|
+
|
|
79
|
+
// AUTO WRAPPERS
|
|
80
|
+
const detected = detectWrappers(source);
|
|
81
|
+
|
|
82
|
+
// function wrappers
|
|
83
|
+
for (const fn of detected.functions) {
|
|
84
|
+
if (
|
|
85
|
+
!config.functionWrappers?.some(
|
|
86
|
+
(w) => w.name === fn.name
|
|
87
|
+
)
|
|
88
|
+
) {
|
|
89
|
+
config.functionWrappers.push(fn);
|
|
90
|
+
|
|
91
|
+
console.log(
|
|
92
|
+
chalk.green(
|
|
93
|
+
`Detected function wrapper: ${fn.name}`
|
|
94
|
+
)
|
|
95
|
+
);
|
|
96
|
+
}
|
|
108
97
|
}
|
|
109
98
|
|
|
110
|
-
//
|
|
111
|
-
|
|
112
|
-
|
|
99
|
+
// component wrappers
|
|
100
|
+
for (const comp of detected.components) {
|
|
101
|
+
if (
|
|
102
|
+
!config.wrappers?.some(
|
|
103
|
+
(w) => w.name === comp.name
|
|
104
|
+
)
|
|
105
|
+
) {
|
|
106
|
+
config.wrappers.push(comp);
|
|
107
|
+
|
|
108
|
+
console.log(
|
|
109
|
+
chalk.green(
|
|
110
|
+
`Detected component wrapper: ${comp.name} (prop: ${comp.prop})`
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
}
|
|
113
114
|
}
|
|
114
115
|
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
116
|
+
// SCAN EVENTS
|
|
117
|
+
const found: ExtractedEvent[] = [
|
|
118
|
+
...scanTrack(source, config, aliases),
|
|
119
|
+
...scanFunctionWrappers(
|
|
120
|
+
source,
|
|
121
|
+
config.functionWrappers ?? [],
|
|
122
|
+
aliases
|
|
123
|
+
),
|
|
124
|
+
...scanComponentWrappers(
|
|
125
|
+
source,
|
|
126
|
+
config.wrappers ?? [],
|
|
127
|
+
aliases
|
|
128
|
+
)
|
|
129
|
+
];
|
|
130
|
+
|
|
131
|
+
for (const event of found) {
|
|
132
|
+
const {value, dynamic} = event;
|
|
133
|
+
|
|
134
|
+
if (!value) continue;
|
|
135
|
+
|
|
136
|
+
// skip explicit ignore
|
|
137
|
+
if (value in aliases && aliases[value] === "__skip__") {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// resolve alias
|
|
142
|
+
if (value in aliases) {
|
|
143
|
+
events.add(aliases[value]);
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// static
|
|
148
|
+
if (!dynamic) {
|
|
149
|
+
events.add(value);
|
|
150
|
+
continue;
|
|
127
151
|
}
|
|
128
|
-
]);
|
|
129
152
|
|
|
130
|
-
|
|
153
|
+
// already handled
|
|
154
|
+
if (asked.has(value)) continue;
|
|
131
155
|
|
|
132
|
-
|
|
156
|
+
// ASK ONLY IF NEEDED
|
|
157
|
+
console.log(
|
|
158
|
+
chalk.yellow("\nDynamic event detected:")
|
|
159
|
+
);
|
|
160
|
+
console.log(chalk.gray(value));
|
|
161
|
+
|
|
162
|
+
const {name} = await inquirer.prompt([
|
|
163
|
+
{
|
|
164
|
+
type: "input",
|
|
165
|
+
name: "name",
|
|
166
|
+
message:
|
|
167
|
+
"Enter event name (or type 'skip'):"
|
|
168
|
+
}
|
|
169
|
+
]);
|
|
170
|
+
|
|
171
|
+
const normalized = name?.trim();
|
|
172
|
+
asked.add(value);
|
|
173
|
+
|
|
174
|
+
if (!normalized || normalized === "skip") {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// save alias
|
|
179
|
+
aliases[value] = normalized;
|
|
180
|
+
|
|
181
|
+
console.log(
|
|
182
|
+
chalk.green(
|
|
183
|
+
`Saved alias: ${value} → ${normalized}`
|
|
184
|
+
)
|
|
185
|
+
);
|
|
133
186
|
|
|
134
|
-
|
|
135
|
-
continue;
|
|
187
|
+
events.add(normalized);
|
|
136
188
|
}
|
|
137
189
|
|
|
138
|
-
//
|
|
139
|
-
|
|
190
|
+
// free memory
|
|
191
|
+
project.removeSourceFile(source);
|
|
140
192
|
|
|
141
|
-
|
|
193
|
+
} catch {
|
|
194
|
+
console.log(
|
|
195
|
+
chalk.gray(`Skipped file: ${file}`)
|
|
196
|
+
);
|
|
142
197
|
}
|
|
143
198
|
}
|
|
199
|
+
// SAVE CONFIG
|
|
200
|
+
config.aliases = Object.fromEntries(
|
|
201
|
+
Object.entries(aliases).sort()
|
|
202
|
+
);
|
|
144
203
|
|
|
145
|
-
|
|
204
|
+
// MERGE
|
|
205
|
+
const merged = new Set([
|
|
206
|
+
...config.events,
|
|
207
|
+
...events
|
|
208
|
+
]);
|
|
146
209
|
|
|
147
|
-
|
|
148
|
-
config.events = list;
|
|
210
|
+
config.events = [...merged].sort();
|
|
149
211
|
|
|
150
212
|
await saveConfig(config);
|
|
151
213
|
|
|
152
214
|
console.log(
|
|
153
|
-
chalk.green(
|
|
154
|
-
`Found ${list.length} events`
|
|
155
|
-
)
|
|
215
|
+
chalk.green(`\nFound ${config.events.length} events`)
|
|
156
216
|
);
|
|
157
217
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import chokidar from "chokidar";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { Project } from "ts-morph";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
|
|
6
|
+
import { loadConfig, saveConfig } from "../utils/config";
|
|
7
|
+
|
|
8
|
+
import { detectParser } from "../utils/parsers/router";
|
|
9
|
+
import { parseVue } from "../utils/parsers/vue";
|
|
10
|
+
import { parseSvelte } from "../utils/parsers/svelte";
|
|
11
|
+
import { parseAstro } from "../utils/parsers/astro";
|
|
12
|
+
|
|
13
|
+
import { scanTrack } from "../utils/scanners/track";
|
|
14
|
+
import { scanFunctionWrappers } from "../utils/scanners/function-wrappers";
|
|
15
|
+
import { scanComponentWrappers } from "../utils/scanners/component-wrappers";
|
|
16
|
+
|
|
17
|
+
export async function watch() {
|
|
18
|
+
const config = await loadConfig();
|
|
19
|
+
|
|
20
|
+
if (!config) {
|
|
21
|
+
console.log(chalk.red("Run init first"));
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
console.log(chalk.blue("Watching...\n"));
|
|
26
|
+
|
|
27
|
+
const project = new Project();
|
|
28
|
+
|
|
29
|
+
const aliases = config.aliases ?? {};
|
|
30
|
+
|
|
31
|
+
// уже известные события
|
|
32
|
+
const seen = new Set(config.events);
|
|
33
|
+
|
|
34
|
+
const queue = new Set<string>();
|
|
35
|
+
let timer: NodeJS.Timeout;
|
|
36
|
+
|
|
37
|
+
const processQueue = async () => {
|
|
38
|
+
const files = [...queue];
|
|
39
|
+
queue.clear();
|
|
40
|
+
|
|
41
|
+
for (const file of files) {
|
|
42
|
+
await handle(file);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
config.events = [...new Set(config.events)].sort();
|
|
46
|
+
|
|
47
|
+
await saveConfig(config);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handle = async (file: string) => {
|
|
51
|
+
try {
|
|
52
|
+
let content = await fs.readFile(file, "utf-8");
|
|
53
|
+
|
|
54
|
+
const parser = detectParser(file);
|
|
55
|
+
|
|
56
|
+
if (parser === "vue") content = parseVue(content);
|
|
57
|
+
if (parser === "svelte") content = parseSvelte(content);
|
|
58
|
+
if (parser === "astro") content = parseAstro(content);
|
|
59
|
+
|
|
60
|
+
const source = project.createSourceFile(file, content, {
|
|
61
|
+
overwrite: true
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const found = [
|
|
65
|
+
...scanTrack(source, config, aliases),
|
|
66
|
+
...scanFunctionWrappers(source, config.functionWrappers ?? [], aliases),
|
|
67
|
+
...scanComponentWrappers(source, config.wrappers ?? [], aliases)
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const e of found) {
|
|
71
|
+
if (!e.value) continue;
|
|
72
|
+
|
|
73
|
+
if (aliases[e.value] === "__skip__") continue;
|
|
74
|
+
|
|
75
|
+
if (e.value in aliases) continue;
|
|
76
|
+
|
|
77
|
+
if (seen.has(e.value)) continue;
|
|
78
|
+
|
|
79
|
+
seen.add(e.value);
|
|
80
|
+
|
|
81
|
+
if (!e.dynamic) {
|
|
82
|
+
config.events.push(e.value);
|
|
83
|
+
console.log(chalk.green(`+ ${e.value}`));
|
|
84
|
+
} else {
|
|
85
|
+
console.log(chalk.yellow(`Dynamic: ${e.value}`));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
project.removeSourceFile(source);
|
|
90
|
+
|
|
91
|
+
} catch {
|
|
92
|
+
console.log(chalk.gray(`skip: ${file}`));
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const watcher = chokidar.watch(config.sync.include, {
|
|
97
|
+
ignored: [
|
|
98
|
+
...config.sync.exclude,
|
|
99
|
+
"**/*.log",
|
|
100
|
+
"**/*.tmp",
|
|
101
|
+
"**/*.swp"
|
|
102
|
+
],
|
|
103
|
+
ignoreInitial: true
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
watcher.on("change", (file) => {
|
|
107
|
+
queue.add(file);
|
|
108
|
+
|
|
109
|
+
clearTimeout(timer);
|
|
110
|
+
|
|
111
|
+
timer = setTimeout(processQueue, 200);
|
|
112
|
+
});
|
|
113
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -11,7 +11,6 @@ program
|
|
|
11
11
|
|
|
12
12
|
program
|
|
13
13
|
.command("init")
|
|
14
|
-
.description("Initialize Eventra")
|
|
15
14
|
.action(async () => {
|
|
16
15
|
const { init } = await import("./commands/init");
|
|
17
16
|
await init();
|
|
@@ -19,7 +18,6 @@ program
|
|
|
19
18
|
|
|
20
19
|
program
|
|
21
20
|
.command("sync")
|
|
22
|
-
.description("Sync events")
|
|
23
21
|
.action(async () => {
|
|
24
22
|
const { sync } = await import("./commands/sync");
|
|
25
23
|
await sync();
|
|
@@ -27,15 +25,22 @@ program
|
|
|
27
25
|
|
|
28
26
|
program
|
|
29
27
|
.command("check")
|
|
30
|
-
.
|
|
31
|
-
.action(async () => {
|
|
28
|
+
.option("--fix", "auto fix issues")
|
|
29
|
+
.action(async (opts) => {
|
|
32
30
|
const { check } = await import("./commands/check");
|
|
33
|
-
await check();
|
|
31
|
+
await check({ fix: opts.fix });
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
program
|
|
35
|
+
.command("watch")
|
|
36
|
+
.description("Watch files and detect events in real-time")
|
|
37
|
+
.action(async () => {
|
|
38
|
+
const { watch } = await import("./commands/watch");
|
|
39
|
+
await watch();
|
|
34
40
|
});
|
|
35
41
|
|
|
36
42
|
program
|
|
37
43
|
.command("send")
|
|
38
|
-
.description("Send events")
|
|
39
44
|
.action(async () => {
|
|
40
45
|
const { send } = await import("./commands/send");
|
|
41
46
|
await send();
|
package/src/types.ts
CHANGED
|
@@ -6,6 +6,8 @@ export type ComponentWrapper = {
|
|
|
6
6
|
export type FunctionWrapper = {
|
|
7
7
|
name: string;
|
|
8
8
|
event?: string;
|
|
9
|
+
file?: string;
|
|
10
|
+
line?: number;
|
|
9
11
|
};
|
|
10
12
|
|
|
11
13
|
export type EventraConfig = {
|
|
@@ -24,4 +26,6 @@ export type EventraConfig = {
|
|
|
24
26
|
export type ExtractedEvent = {
|
|
25
27
|
value: string;
|
|
26
28
|
dynamic: boolean;
|
|
29
|
+
file?: string;
|
|
30
|
+
line?: number;
|
|
27
31
|
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SourceFile,
|
|
3
|
+
SyntaxKind,
|
|
4
|
+
Node
|
|
5
|
+
} from "ts-morph";
|
|
6
|
+
|
|
7
|
+
export function detectAliases(source: SourceFile) {
|
|
8
|
+
const aliases: Record<string, string> = {};
|
|
9
|
+
|
|
10
|
+
const vars = source.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
|
|
11
|
+
|
|
12
|
+
for (const v of vars) {
|
|
13
|
+
const name = v.getName();
|
|
14
|
+
const init = v.getInitializer();
|
|
15
|
+
if (!init) continue;
|
|
16
|
+
|
|
17
|
+
// const A = "event"
|
|
18
|
+
if (Node.isStringLiteral(init)) {
|
|
19
|
+
aliases[name] = init.getLiteralText();
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// const A = `event`
|
|
24
|
+
if (init.getKind() === SyntaxKind.NoSubstitutionTemplateLiteral) {
|
|
25
|
+
aliases[name] = init.getText().replace(/`/g, "");
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// const EVENTS = { CLICK: "click" }
|
|
30
|
+
if (Node.isObjectLiteralExpression(init)) {
|
|
31
|
+
for (const p of init.getProperties()) {
|
|
32
|
+
if (!Node.isPropertyAssignment(p)) continue;
|
|
33
|
+
|
|
34
|
+
const key = p.getName();
|
|
35
|
+
const val = p.getInitializer();
|
|
36
|
+
if (!val) continue;
|
|
37
|
+
|
|
38
|
+
if (Node.isStringLiteral(val)) {
|
|
39
|
+
aliases[`${name}.${key}`] = val.getLiteralText();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return aliases;
|
|
46
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
SourceFile,
|
|
3
|
+
SyntaxKind,
|
|
4
|
+
Node,
|
|
5
|
+
CallExpression
|
|
6
|
+
} from "ts-morph";
|
|
7
|
+
|
|
8
|
+
export type FunctionWrapper = {
|
|
9
|
+
name: string;
|
|
10
|
+
event?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ComponentWrapper = {
|
|
14
|
+
name: string;
|
|
15
|
+
prop: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export function detectWrappers(source: SourceFile) {
|
|
19
|
+
return {
|
|
20
|
+
functions: detectFunctionWrappers(source),
|
|
21
|
+
components: detectComponentWrappers(source)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// FUNCTIONS
|
|
26
|
+
function detectFunctionWrappers(source: SourceFile): FunctionWrapper[] {
|
|
27
|
+
const result = new Map<string, FunctionWrapper>();
|
|
28
|
+
|
|
29
|
+
const functions = [
|
|
30
|
+
...source.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
|
|
31
|
+
...source.getDescendantsOfKind(SyntaxKind.ArrowFunction),
|
|
32
|
+
...source.getDescendantsOfKind(SyntaxKind.FunctionExpression)
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
for (const fn of functions) {
|
|
36
|
+
const body = fn.getBody();
|
|
37
|
+
if (!body) continue;
|
|
38
|
+
|
|
39
|
+
const calls = body.getDescendantsOfKind(SyntaxKind.CallExpression);
|
|
40
|
+
|
|
41
|
+
for (const call of calls) {
|
|
42
|
+
if (!isTrackCall(call)) continue;
|
|
43
|
+
|
|
44
|
+
const name = getFunctionName(fn);
|
|
45
|
+
if (!name) continue;
|
|
46
|
+
|
|
47
|
+
const arg = call.getArguments()[0];
|
|
48
|
+
|
|
49
|
+
let eventField: string | undefined;
|
|
50
|
+
|
|
51
|
+
if (arg && Node.isObjectLiteralExpression(arg)) {
|
|
52
|
+
const prop = arg.getProperties()[0];
|
|
53
|
+
if (prop && Node.isPropertyAssignment(prop)) {
|
|
54
|
+
eventField = prop.getName();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
result.set(name, { name, event: eventField });
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return [...result.values()];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isTrackCall(call: CallExpression) {
|
|
66
|
+
const expr = call.getExpression();
|
|
67
|
+
|
|
68
|
+
if (Node.isIdentifier(expr)) {
|
|
69
|
+
return expr.getText() === "track";
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (Node.isPropertyAccessExpression(expr)) {
|
|
73
|
+
return expr.getName() === "track";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getFunctionName(fn: Node): string | null {
|
|
80
|
+
if (Node.isFunctionDeclaration(fn)) {
|
|
81
|
+
return fn.getName() ?? null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const varDecl = fn.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
|
|
85
|
+
return varDecl?.getName() ?? null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// COMPONENTS
|
|
89
|
+
function detectComponentWrappers(source: SourceFile): ComponentWrapper[] {
|
|
90
|
+
const result = new Map<string, ComponentWrapper>();
|
|
91
|
+
|
|
92
|
+
const elements = [
|
|
93
|
+
...source.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),
|
|
94
|
+
...source.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement)
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
for (const el of elements) {
|
|
98
|
+
const name = el.getTagNameNode().getText();
|
|
99
|
+
|
|
100
|
+
for (const attr of el.getAttributes()) {
|
|
101
|
+
const a = attr.asKind(SyntaxKind.JsxAttribute);
|
|
102
|
+
if (!a) continue;
|
|
103
|
+
|
|
104
|
+
const prop = a.getNameNode().getText();
|
|
105
|
+
const val = a.getInitializer();
|
|
106
|
+
|
|
107
|
+
if (!val) continue;
|
|
108
|
+
|
|
109
|
+
if (
|
|
110
|
+
Node.isStringLiteral(val) ||
|
|
111
|
+
Node.isJsxExpression(val)
|
|
112
|
+
) {
|
|
113
|
+
result.set(name, { name, prop });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return [...result.values()];
|
|
119
|
+
}
|