@eventra_dev/eventra-cli 0.1.6 → 0.1.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.
- package/dist/commands/check.js +1 -1
- package/dist/commands/sync.js +36 -2
- package/dist/commands/watch.js +2 -2
- package/dist/engine/boundary.js +14 -0
- package/dist/engine/callResolver.js +61 -5
- package/dist/engine/engine.js +71 -20
- package/dist/engine/graph.js +39 -20
- package/dist/engine/languageService.js +10 -0
- package/dist/engine/resolver.js +10 -2
- package/dist/engine/scanner.js +147 -62
- package/package.json +1 -1
- package/src/commands/check.ts +1 -1
- package/src/commands/sync.ts +62 -2
- package/src/commands/watch.ts +2 -2
- package/src/engine/boundary.ts +12 -0
- package/src/engine/callResolver.ts +78 -8
- package/src/engine/engine.ts +94 -21
- package/src/engine/graph.ts +49 -24
- package/src/engine/languageService.ts +13 -0
- package/src/engine/resolver.ts +25 -4
- package/src/engine/scanner.ts +203 -72
package/src/engine/graph.ts
CHANGED
|
@@ -5,6 +5,7 @@ import fs from "fs";
|
|
|
5
5
|
export class DependencyGraph {
|
|
6
6
|
private reverse = new Map<string, Set<string>>();
|
|
7
7
|
private exports = new Map<string, Set<string>>();
|
|
8
|
+
private fileCache = new Map<string, string | null>();
|
|
8
9
|
|
|
9
10
|
constructor(
|
|
10
11
|
private baseUrl: string,
|
|
@@ -15,42 +16,43 @@ export class DependencyGraph {
|
|
|
15
16
|
return path.resolve(file);
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
// TS-like resolver
|
|
19
19
|
private resolveModule(module: string, from: string): string | null {
|
|
20
|
-
// relative
|
|
21
20
|
if (module.startsWith(".")) {
|
|
22
21
|
const abs = path.resolve(path.dirname(from), module);
|
|
23
22
|
return this.resolveFile(abs);
|
|
24
23
|
}
|
|
25
24
|
|
|
26
|
-
// paths mapping
|
|
27
25
|
for (const key in this.paths) {
|
|
28
|
-
const pattern = key.replace(
|
|
26
|
+
const pattern = key.replace(/\*/g, "(.*)");
|
|
29
27
|
const regex = new RegExp("^" + pattern + "$");
|
|
30
28
|
const match = module.match(regex);
|
|
31
29
|
|
|
32
30
|
if (!match) continue;
|
|
33
31
|
|
|
34
|
-
const wildcard = match
|
|
32
|
+
const wildcard = match.slice(1);
|
|
35
33
|
|
|
36
34
|
for (const target of this.paths[key]) {
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
let replaced = target;
|
|
36
|
+
|
|
37
|
+
wildcard.forEach(w => {
|
|
38
|
+
replaced = replaced.replace("*", w);
|
|
39
|
+
});
|
|
39
40
|
|
|
41
|
+
const abs = path.resolve(this.baseUrl, replaced);
|
|
40
42
|
const resolved = this.resolveFile(abs);
|
|
41
43
|
if (resolved) return resolved;
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
45
|
-
// baseUrl fallback
|
|
46
47
|
const abs = path.resolve(this.baseUrl, module);
|
|
47
|
-
|
|
48
|
-
if (resolved) return resolved;
|
|
49
|
-
|
|
50
|
-
return null;
|
|
48
|
+
return this.resolveFile(abs);
|
|
51
49
|
}
|
|
52
50
|
|
|
53
51
|
private resolveFile(base: string): string | null {
|
|
52
|
+
if (this.fileCache.has(base)) {
|
|
53
|
+
return this.fileCache.get(base)!;
|
|
54
|
+
}
|
|
55
|
+
|
|
54
56
|
const tryFiles = [
|
|
55
57
|
base,
|
|
56
58
|
base + ".ts",
|
|
@@ -64,10 +66,13 @@ export class DependencyGraph {
|
|
|
64
66
|
|
|
65
67
|
for (const f of tryFiles) {
|
|
66
68
|
if (fs.existsSync(f)) {
|
|
67
|
-
|
|
69
|
+
const normalized = this.normalize(f);
|
|
70
|
+
this.fileCache.set(base, normalized);
|
|
71
|
+
return normalized;
|
|
68
72
|
}
|
|
69
73
|
}
|
|
70
74
|
|
|
75
|
+
this.fileCache.set(base, null);
|
|
71
76
|
return null;
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -77,11 +82,10 @@ export class DependencyGraph {
|
|
|
77
82
|
this.exports.set(normalizedFile, new Set());
|
|
78
83
|
|
|
79
84
|
for (const stmt of source.statements) {
|
|
80
|
-
// IMPORT
|
|
81
85
|
if (ts.isImportDeclaration(stmt)) {
|
|
82
86
|
const module = stmt.moduleSpecifier.getText().replace(/['"]/g, "");
|
|
83
|
-
|
|
84
87
|
const resolved = this.resolveModule(module, normalizedFile);
|
|
88
|
+
|
|
85
89
|
if (!resolved) continue;
|
|
86
90
|
|
|
87
91
|
if (!this.reverse.has(resolved)) {
|
|
@@ -91,18 +95,13 @@ export class DependencyGraph {
|
|
|
91
95
|
this.reverse.get(resolved)!.add(normalizedFile);
|
|
92
96
|
}
|
|
93
97
|
|
|
94
|
-
// EXPORT *
|
|
95
98
|
if (ts.isExportDeclaration(stmt) && stmt.moduleSpecifier) {
|
|
96
99
|
const module = stmt.moduleSpecifier.getText().replace(/['"]/g, "");
|
|
97
|
-
|
|
98
100
|
const resolved = this.resolveModule(module, normalizedFile);
|
|
99
|
-
if (!resolved) continue;
|
|
100
101
|
|
|
101
|
-
if (!
|
|
102
|
-
this.exports.set(normalizedFile, new Set());
|
|
103
|
-
}
|
|
102
|
+
if (!resolved) continue;
|
|
104
103
|
|
|
105
|
-
this.exports.get(normalizedFile)
|
|
104
|
+
this.exports.get(normalizedFile)?.add(resolved);
|
|
106
105
|
|
|
107
106
|
if (!this.reverse.has(resolved)) {
|
|
108
107
|
this.reverse.set(resolved, new Set());
|
|
@@ -113,17 +112,43 @@ export class DependencyGraph {
|
|
|
113
112
|
}
|
|
114
113
|
}
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
getAllDependentsDeep(file: string): string[] {
|
|
116
|
+
const start = this.normalize(file);
|
|
117
|
+
const visited = new Set<string>();
|
|
118
|
+
const result = new Set<string>();
|
|
119
|
+
|
|
120
|
+
const walk = (f: string) => {
|
|
121
|
+
if (visited.has(f)) return;
|
|
122
|
+
visited.add(f);
|
|
123
|
+
|
|
124
|
+
const deps = this.reverse.get(f);
|
|
125
|
+
deps?.forEach(d => {
|
|
126
|
+
result.add(d);
|
|
127
|
+
walk(d);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
const ex = this.exports.get(f);
|
|
131
|
+
ex?.forEach(e => walk(e));
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
walk(start);
|
|
135
|
+
|
|
136
|
+
return [...result];
|
|
118
137
|
}
|
|
119
138
|
|
|
120
139
|
remove(file: string) {
|
|
121
140
|
const normalized = this.normalize(file);
|
|
122
141
|
|
|
142
|
+
this.reverse.delete(normalized);
|
|
123
143
|
this.exports.delete(normalized);
|
|
144
|
+
this.fileCache.delete(normalized);
|
|
124
145
|
|
|
125
146
|
for (const set of this.reverse.values()) {
|
|
126
147
|
set.delete(normalized);
|
|
127
148
|
}
|
|
149
|
+
|
|
150
|
+
for (const set of this.exports.values()) {
|
|
151
|
+
set.delete(normalized);
|
|
152
|
+
}
|
|
128
153
|
}
|
|
129
154
|
}
|
|
@@ -62,6 +62,10 @@ export class TSService {
|
|
|
62
62
|
this.files.get(file)?.version.toString() ?? "0",
|
|
63
63
|
|
|
64
64
|
getScriptSnapshot: (file) => {
|
|
65
|
+
if (!this.fileNames.has(file)) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
65
69
|
const f = this.files.get(file);
|
|
66
70
|
|
|
67
71
|
if (f) {
|
|
@@ -113,6 +117,15 @@ export class TSService {
|
|
|
113
117
|
return this.getProgram()?.getSourceFile(file);
|
|
114
118
|
}
|
|
115
119
|
|
|
120
|
+
getFileContent(file: string) {
|
|
121
|
+
return this.files.get(file)?.content;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
removeFile(file: string) {
|
|
125
|
+
this.files.delete(file);
|
|
126
|
+
this.fileNames.delete(file);
|
|
127
|
+
}
|
|
128
|
+
|
|
116
129
|
getModuleResolutionConfig() {
|
|
117
130
|
return {
|
|
118
131
|
baseUrl: this.baseUrl,
|
package/src/engine/resolver.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import {resolveExportedSymbol} from "./exportResolver";
|
|
2
|
+
import { resolveExportedSymbol } from "./exportResolver";
|
|
3
3
|
|
|
4
4
|
export type ResolveResult = {
|
|
5
5
|
values: string[];
|
|
@@ -21,7 +21,8 @@ export function resolveNodeValue(
|
|
|
21
21
|
return resolveNodeValue(
|
|
22
22
|
paramMap.get(node.text)!,
|
|
23
23
|
checker,
|
|
24
|
-
paramMap
|
|
24
|
+
paramMap,
|
|
25
|
+
seen
|
|
25
26
|
);
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -54,15 +55,35 @@ export function resolveNodeValue(
|
|
|
54
55
|
let symbol = checker.getSymbolAtLocation(node);
|
|
55
56
|
if (!symbol) return null;
|
|
56
57
|
|
|
58
|
+
// variable inline resolve FIRST
|
|
59
|
+
for (const decl of symbol.getDeclarations() ?? []) {
|
|
60
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
61
|
+
return resolveNodeValue(
|
|
62
|
+
decl.initializer,
|
|
63
|
+
checker,
|
|
64
|
+
paramMap,
|
|
65
|
+
seen
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const original = symbol;
|
|
71
|
+
|
|
57
72
|
symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
58
73
|
|
|
59
|
-
|
|
74
|
+
// avoid alias loop
|
|
75
|
+
if (symbol !== original && (symbol.flags & ts.SymbolFlags.Alias)) {
|
|
60
76
|
symbol = checker.getAliasedSymbol(symbol);
|
|
61
77
|
}
|
|
62
78
|
|
|
63
79
|
for (const decl of symbol.getDeclarations() ?? []) {
|
|
64
80
|
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
65
|
-
return resolveNodeValue(
|
|
81
|
+
return resolveNodeValue(
|
|
82
|
+
decl.initializer,
|
|
83
|
+
checker,
|
|
84
|
+
paramMap,
|
|
85
|
+
seen
|
|
86
|
+
);
|
|
66
87
|
}
|
|
67
88
|
|
|
68
89
|
if (ts.isEnumMember(decl) && decl.initializer) {
|
package/src/engine/scanner.ts
CHANGED
|
@@ -1,120 +1,247 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { resolveNodeValue } from "./resolver";
|
|
3
3
|
import { resolveFunctionFromCall } from "./callResolver";
|
|
4
|
+
import { resolveExportedSymbol } from "./exportResolver";
|
|
4
5
|
import { findTrackParamIndex } from "./functionAnalyzer";
|
|
6
|
+
import { EventraConfig } from "../types";
|
|
7
|
+
import { isExternalFile } from "./boundary";
|
|
8
|
+
|
|
9
|
+
const TRACKING_NAMES = new Set(["track", "event", "capture", "send"]);
|
|
5
10
|
|
|
6
11
|
function getCallName(expr: ts.Expression): string {
|
|
7
12
|
if (ts.isIdentifier(expr)) return expr.text;
|
|
8
|
-
|
|
9
|
-
if (ts.isPropertyAccessExpression(expr)) {
|
|
10
|
-
return expr.name.text;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
+
if (ts.isPropertyAccessExpression(expr)) return expr.name.text;
|
|
13
14
|
return "";
|
|
14
15
|
}
|
|
15
16
|
|
|
16
|
-
function
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
let best: { score: number; node: ts.Expression } | null = null;
|
|
17
|
+
function getJsxAttrName(name: ts.JsxAttributeName): string | null {
|
|
18
|
+
if (ts.isIdentifier(name)) return name.text;
|
|
19
|
+
if (ts.isJsxNamespacedName(name)) return name.name.text;
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
function isValidEvent(v: string) {
|
|
24
|
+
return (
|
|
25
|
+
v &&
|
|
26
|
+
v.length > 1 &&
|
|
27
|
+
v.length < 100 &&
|
|
28
|
+
!v.includes(" ") &&
|
|
29
|
+
/^[a-zA-Z0-9._:-]+$/.test(v)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
25
32
|
|
|
26
|
-
|
|
33
|
+
function isPropUsedInTracking(
|
|
34
|
+
fn: ts.FunctionLikeDeclaration,
|
|
35
|
+
prop: string
|
|
36
|
+
) {
|
|
37
|
+
let found = false;
|
|
27
38
|
|
|
28
|
-
|
|
39
|
+
function visit(node: ts.Node) {
|
|
40
|
+
if (ts.isCallExpression(node)) {
|
|
41
|
+
const name = getCallName(node.expression);
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
if (res.values.every(v => v.length < 50)) score += 2;
|
|
32
|
-
if (res.values.some(v => v.includes("*"))) score -= 2;
|
|
43
|
+
if (!TRACKING_NAMES.has(name)) return;
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
|
|
45
|
+
for (const arg of node.arguments) {
|
|
46
|
+
if (
|
|
47
|
+
(ts.isIdentifier(arg) && arg.text === prop) ||
|
|
48
|
+
(ts.isPropertyAccessExpression(arg) && arg.name.text === prop)
|
|
49
|
+
) {
|
|
50
|
+
found = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
36
53
|
}
|
|
54
|
+
|
|
55
|
+
ts.forEachChild(node, visit);
|
|
37
56
|
}
|
|
38
57
|
|
|
39
|
-
|
|
58
|
+
visit(fn);
|
|
59
|
+
return found;
|
|
40
60
|
}
|
|
41
61
|
|
|
62
|
+
export type ScanResult = {
|
|
63
|
+
events: Set<string>;
|
|
64
|
+
detectedFunctionWrappers: Set<string>;
|
|
65
|
+
detectedComponentWrappers: Map<string, string>;
|
|
66
|
+
};
|
|
67
|
+
|
|
42
68
|
export function scanSource(
|
|
43
69
|
source: ts.SourceFile,
|
|
44
70
|
checker: ts.TypeChecker,
|
|
71
|
+
config: EventraConfig,
|
|
45
72
|
visited = new Set<string>(),
|
|
46
73
|
depth = 0
|
|
47
|
-
):
|
|
48
|
-
if (depth > 5) return new Set();
|
|
49
|
-
|
|
74
|
+
): ScanResult {
|
|
50
75
|
const events = new Set<string>();
|
|
76
|
+
const detectedFunctionWrappers = new Set<string>();
|
|
77
|
+
const detectedComponentWrappers = new Map<string, string>();
|
|
78
|
+
|
|
79
|
+
if (depth > 5 || visited.has(source.fileName)) {
|
|
80
|
+
return { events, detectedFunctionWrappers, detectedComponentWrappers };
|
|
81
|
+
}
|
|
51
82
|
|
|
52
|
-
if (visited.has(source.fileName)) return events;
|
|
53
83
|
visited.add(source.fileName);
|
|
54
84
|
|
|
55
85
|
function visit(node: ts.Node) {
|
|
86
|
+
// CALL EXPRESSIONS
|
|
56
87
|
if (ts.isCallExpression(node)) {
|
|
57
88
|
const name = getCallName(node.expression);
|
|
58
89
|
|
|
59
|
-
|
|
60
|
-
|
|
90
|
+
const isKnownWrapper = config.functionWrappers.some(
|
|
91
|
+
w => w.name === name
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
const fn = resolveFunctionFromCall(node.expression, checker);
|
|
95
|
+
|
|
96
|
+
const isAutoWrapper =
|
|
97
|
+
fn && findTrackParamIndex(fn) !== null;
|
|
98
|
+
|
|
99
|
+
const isTracking =
|
|
100
|
+
isKnownWrapper ||
|
|
101
|
+
TRACKING_NAMES.has(name) ||
|
|
102
|
+
(isAutoWrapper && fn && !isExternalFile(fn.getSourceFile().fileName));
|
|
103
|
+
|
|
104
|
+
if (!isTracking) return;
|
|
105
|
+
|
|
106
|
+
if (!isKnownWrapper && isAutoWrapper && !TRACKING_NAMES.has(name)) {
|
|
107
|
+
detectedFunctionWrappers.add(name);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const paramMap = new Map<string, ts.Expression>();
|
|
111
|
+
|
|
112
|
+
if (fn && ts.isFunctionLike(fn)) {
|
|
113
|
+
fn.parameters.forEach((param, i) => {
|
|
114
|
+
const arg = node.arguments[i];
|
|
115
|
+
if (!arg) return;
|
|
116
|
+
|
|
117
|
+
if (ts.isIdentifier(param.name)) {
|
|
118
|
+
paramMap.set(param.name.text, arg);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
let handled = false;
|
|
124
|
+
|
|
125
|
+
if (fn) {
|
|
126
|
+
const idx = findTrackParamIndex(fn);
|
|
61
127
|
|
|
62
|
-
|
|
128
|
+
if (idx !== null && node.arguments[idx]) {
|
|
129
|
+
const res = resolveNodeValue(
|
|
130
|
+
node.arguments[idx],
|
|
131
|
+
checker,
|
|
132
|
+
paramMap
|
|
133
|
+
);
|
|
63
134
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
135
|
+
res?.values.forEach(v => {
|
|
136
|
+
if (isValidEvent(v)) events.add(v);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
handled = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
68
142
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
143
|
+
if (!handled) {
|
|
144
|
+
for (const arg of node.arguments) {
|
|
145
|
+
const res = resolveNodeValue(arg, checker, paramMap);
|
|
146
|
+
|
|
147
|
+
res?.values.forEach(v => {
|
|
148
|
+
if (isValidEvent(v)) events.add(v);
|
|
72
149
|
});
|
|
73
150
|
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (fn) {
|
|
154
|
+
const sf = fn.getSourceFile();
|
|
74
155
|
|
|
75
|
-
if (
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
156
|
+
if (!visited.has(sf.fileName)) {
|
|
157
|
+
const res = scanSource(
|
|
158
|
+
sf,
|
|
159
|
+
checker,
|
|
160
|
+
config,
|
|
161
|
+
visited,
|
|
162
|
+
depth + 1
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
res.events.forEach(e => events.add(e));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// JSX
|
|
171
|
+
if (
|
|
172
|
+
ts.isJsxSelfClosingElement(node) ||
|
|
173
|
+
ts.isJsxOpeningElement(node)
|
|
174
|
+
) {
|
|
175
|
+
const tagNode =
|
|
176
|
+
ts.isPropertyAccessExpression(node.tagName)
|
|
177
|
+
? node.tagName.name
|
|
178
|
+
: node.tagName;
|
|
179
|
+
|
|
180
|
+
let symbol = checker.getSymbolAtLocation(tagNode);
|
|
181
|
+
|
|
182
|
+
if (symbol) {
|
|
183
|
+
symbol = resolveExportedSymbol(symbol, checker) ?? symbol;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (symbol && (symbol.flags & ts.SymbolFlags.Alias)) {
|
|
187
|
+
symbol = checker.getAliasedSymbol(symbol);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (!symbol) return;
|
|
191
|
+
|
|
192
|
+
const tagName =
|
|
193
|
+
ts.isPropertyAccessExpression(node.tagName)
|
|
194
|
+
? node.tagName.name.getText()
|
|
195
|
+
: node.tagName.getText();
|
|
196
|
+
|
|
197
|
+
for (const decl of symbol.getDeclarations() ?? []) {
|
|
198
|
+
let fn: ts.FunctionLikeDeclaration | null = null;
|
|
199
|
+
|
|
200
|
+
if (ts.isFunctionDeclaration(decl)) fn = decl;
|
|
201
|
+
|
|
202
|
+
if (ts.isVariableDeclaration(decl) && decl.initializer) {
|
|
203
|
+
const init = decl.initializer;
|
|
204
|
+
if (ts.isArrowFunction(init) || ts.isFunctionExpression(init)) {
|
|
205
|
+
fn = init;
|
|
98
206
|
}
|
|
99
207
|
}
|
|
100
208
|
|
|
101
|
-
|
|
102
|
-
|
|
209
|
+
if (!fn) continue;
|
|
210
|
+
|
|
211
|
+
const param = fn.parameters[0];
|
|
212
|
+
if (!param || !ts.isObjectBindingPattern(param.name)) continue;
|
|
103
213
|
|
|
104
|
-
|
|
105
|
-
const
|
|
214
|
+
for (const el of param.name.elements) {
|
|
215
|
+
const prop = el.name.getText();
|
|
216
|
+
|
|
217
|
+
const isEvent =
|
|
218
|
+
isPropUsedInTracking(fn, prop) ||
|
|
219
|
+
config.wrappers.some(
|
|
220
|
+
w => w.name === tagName && w.prop === prop
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
if (!isEvent) continue;
|
|
224
|
+
|
|
225
|
+
const attr = node.attributes.properties.find(p => {
|
|
226
|
+
if (!ts.isJsxAttribute(p)) return false;
|
|
227
|
+
return getJsxAttrName(p.name) === prop;
|
|
228
|
+
}) as ts.JsxAttribute | undefined;
|
|
229
|
+
|
|
230
|
+
if (!attr || !attr.initializer) continue;
|
|
231
|
+
|
|
232
|
+
const expr = ts.isJsxExpression(attr.initializer)
|
|
233
|
+
? attr.initializer.expression
|
|
234
|
+
: attr.initializer;
|
|
235
|
+
|
|
236
|
+
if (!expr) continue;
|
|
237
|
+
|
|
238
|
+
const res = resolveNodeValue(expr, checker);
|
|
106
239
|
|
|
107
240
|
res?.values.forEach(v => {
|
|
108
|
-
if (
|
|
109
|
-
v &&
|
|
110
|
-
v.length > 1 &&
|
|
111
|
-
v.length < 100 &&
|
|
112
|
-
!v.includes(" ") &&
|
|
113
|
-
/^[a-zA-Z0-9._:-]+$/.test(v)
|
|
114
|
-
) {
|
|
115
|
-
events.add(v);
|
|
116
|
-
}
|
|
241
|
+
if (isValidEvent(v)) events.add(v);
|
|
117
242
|
});
|
|
243
|
+
|
|
244
|
+
detectedComponentWrappers.set(tagName, prop);
|
|
118
245
|
}
|
|
119
246
|
}
|
|
120
247
|
}
|
|
@@ -124,5 +251,9 @@ export function scanSource(
|
|
|
124
251
|
|
|
125
252
|
visit(source);
|
|
126
253
|
|
|
127
|
-
return
|
|
254
|
+
return {
|
|
255
|
+
events,
|
|
256
|
+
detectedFunctionWrappers,
|
|
257
|
+
detectedComponentWrappers,
|
|
258
|
+
};
|
|
128
259
|
}
|