@eventra_dev/eventra-cli 0.1.29 → 0.1.31
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 +0 -1
- package/dist/engine/engine.js +7 -4
- package/dist/engine/functionAnalyzer.js +15 -50
- package/dist/engine/resolver.js +57 -138
- package/dist/engine/scanner.js +205 -54
- package/dist/utils/config.js +0 -1
- package/dist/utils/processFile.js +3 -3
- package/dist/utils/universal.js +91 -94
- package/package.json +1 -1
- package/src/engine/engine.ts +26 -7
- package/src/engine/functionAnalyzer.ts +14 -54
- package/src/engine/resolver.ts +83 -184
- package/src/engine/scanner.ts +263 -75
- package/src/types.ts +6 -1
- package/src/utils/config.ts +0 -1
- package/src/utils/processFile.ts +3 -3
- package/src/utils/universal.ts +108 -119
- package/tests/run.ts +0 -1
- package/dist/engine/trackingDetector.js +0 -68
- package/src/engine/trackingDetector.ts +0 -94
package/src/utils/universal.ts
CHANGED
|
@@ -1,100 +1,132 @@
|
|
|
1
1
|
import path from "path";
|
|
2
2
|
import { ParseResult } from "../types";
|
|
3
3
|
|
|
4
|
-
//
|
|
4
|
+
// SAFE COMMENT STRIP
|
|
5
5
|
function stripComments(input: string): string {
|
|
6
6
|
return input
|
|
7
7
|
.replace(/<!--[\s\S]*?-->/g, "")
|
|
8
|
-
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
9
|
-
.replace(/\/\/.*$/gm, "");
|
|
8
|
+
.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
10
9
|
}
|
|
11
10
|
|
|
12
|
-
//
|
|
13
|
-
function
|
|
14
|
-
|
|
11
|
+
// TRACK FILTER
|
|
12
|
+
function looksLikeTracking(code: string) {
|
|
13
|
+
return /\btrack\w*\s*\(/.test(code);
|
|
14
|
+
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
// SAFE {} PARSER
|
|
17
|
+
function extractBracedExpressions(input: string): string[] {
|
|
18
|
+
const result: string[] = [];
|
|
18
19
|
|
|
19
|
-
let
|
|
20
|
-
let
|
|
20
|
+
let depth = 0;
|
|
21
|
+
let start = -1;
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
const
|
|
23
|
+
for (let i = 0; i < input.length; i++) {
|
|
24
|
+
const char = input[i];
|
|
24
25
|
|
|
25
|
-
if (
|
|
26
|
-
if (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
if (char === "{") {
|
|
27
|
+
if (depth === 0) start = i + 1;
|
|
28
|
+
depth++;
|
|
29
|
+
} else if (char === "}") {
|
|
30
|
+
depth--;
|
|
31
|
+
if (depth === 0 && start !== -1) {
|
|
32
|
+
const expr = input.slice(start, i);
|
|
33
|
+
if (looksLikeTracking(expr)) {
|
|
34
|
+
result.push(expr);
|
|
35
|
+
}
|
|
36
|
+
start = -1;
|
|
37
|
+
}
|
|
31
38
|
}
|
|
39
|
+
}
|
|
32
40
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
i++;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
41
|
+
return result;
|
|
42
|
+
}
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
44
|
+
// HTML handlers
|
|
45
|
+
function extractHTMLHandlers(input: string): string[] {
|
|
46
|
+
const result: string[] = [];
|
|
42
47
|
|
|
43
|
-
|
|
44
|
-
if (input[j] === "{") depth++;
|
|
45
|
-
else if (input[j] === "}") depth--;
|
|
46
|
-
j++;
|
|
47
|
-
}
|
|
48
|
+
const regex = /\bon\w+\s*=\s*["']([^"']+)["']/g;
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
for (const m of input.matchAll(regex)) {
|
|
51
|
+
if (looksLikeTracking(m[1])) {
|
|
52
|
+
result.push(m[1]);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
51
55
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
55
58
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// VUE handlers
|
|
60
|
+
function extractVueHandlers(input: string): string[] {
|
|
61
|
+
const result: string[] = [];
|
|
62
|
+
|
|
63
|
+
const regex = /(?:@|v-on:)[\w:-]+\s*=\s*["']([^"']+)["']/g;
|
|
60
64
|
|
|
61
|
-
|
|
65
|
+
for (const m of input.matchAll(regex)) {
|
|
66
|
+
if (looksLikeTracking(m[1])) {
|
|
67
|
+
result.push(m[1]);
|
|
68
|
+
}
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
return result;
|
|
65
72
|
}
|
|
66
73
|
|
|
67
|
-
//
|
|
68
|
-
function
|
|
74
|
+
// SVELTE handlers
|
|
75
|
+
function extractSvelteHandlers(input: string): string[] {
|
|
69
76
|
const result: string[] = [];
|
|
70
77
|
|
|
71
|
-
const
|
|
72
|
-
/\bon\w+\s*=\s*["']([^"']+)["']/g
|
|
73
|
-
);
|
|
78
|
+
const regex = /\bon:[\w]+\s*=\s*{([^}]+)}/g;
|
|
74
79
|
|
|
75
|
-
for (const m of
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
result.push(code);
|
|
80
|
+
for (const m of input.matchAll(regex)) {
|
|
81
|
+
if (looksLikeTracking(m[1])) {
|
|
82
|
+
result.push(m[1]);
|
|
79
83
|
}
|
|
80
84
|
}
|
|
81
85
|
|
|
82
86
|
return result;
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
//
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
file: string
|
|
89
|
-
): { deps: string[]; calls: string[] } {
|
|
89
|
+
// SCRIPT EXTRACTION
|
|
90
|
+
function extractScripts(input: string, file: string) {
|
|
91
|
+
const scripts: string[] = [];
|
|
90
92
|
const deps: string[] = [];
|
|
91
|
-
const calls: string[] = [];
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
// src scripts
|
|
95
|
+
const srcRegex =
|
|
96
|
+
/<script\b[^>]*?\bsrc=["'](.+?)["'][^>]*>/g;
|
|
96
97
|
|
|
97
|
-
for (const m of
|
|
98
|
+
for (const m of input.matchAll(srcRegex)) {
|
|
99
|
+
const src = m[1];
|
|
100
|
+
|
|
101
|
+
const resolved = src.startsWith("/")
|
|
102
|
+
? path.resolve(process.cwd(), "." + src)
|
|
103
|
+
: path.resolve(path.dirname(file), src);
|
|
104
|
+
|
|
105
|
+
deps.push(resolved);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// inline scripts
|
|
109
|
+
const scriptRegex =
|
|
110
|
+
/<script\b[^>]*>([\s\S]*?)<\/script>/g;
|
|
111
|
+
|
|
112
|
+
for (const m of input.matchAll(scriptRegex)) {
|
|
113
|
+
if (m[1]?.trim()) {
|
|
114
|
+
scripts.push(m[1]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const noScripts = input.replace(scriptRegex, "");
|
|
119
|
+
|
|
120
|
+
return { scripts, deps, noScripts };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// DYNAMIC IMPORTS
|
|
124
|
+
function extractDynamicImports(input: string, file: string) {
|
|
125
|
+
const deps: string[] = [];
|
|
126
|
+
|
|
127
|
+
const regex = /import\(\s*["'](.+?)["']\s*\)/g;
|
|
128
|
+
|
|
129
|
+
for (const m of input.matchAll(regex)) {
|
|
98
130
|
const mod = m[1];
|
|
99
131
|
|
|
100
132
|
const resolved = mod.startsWith("/")
|
|
@@ -102,18 +134,12 @@ function extractDynamicImports(
|
|
|
102
134
|
: path.resolve(path.dirname(file), mod);
|
|
103
135
|
|
|
104
136
|
deps.push(resolved);
|
|
105
|
-
calls.push(`import("${mod}")`);
|
|
106
137
|
}
|
|
107
138
|
|
|
108
|
-
return
|
|
139
|
+
return deps;
|
|
109
140
|
}
|
|
110
141
|
|
|
111
|
-
//
|
|
112
|
-
function isProbablyTracking(code: string): boolean {
|
|
113
|
-
return /track|event|analytics/i.test(code);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// UNIVERSAL PARSER
|
|
142
|
+
// MAIN
|
|
117
143
|
export function parseUniversal(
|
|
118
144
|
content: string,
|
|
119
145
|
file: string
|
|
@@ -123,66 +149,29 @@ export function parseUniversal(
|
|
|
123
149
|
|
|
124
150
|
const clean = stripComments(content);
|
|
125
151
|
|
|
126
|
-
|
|
152
|
+
/* ---------- ASTRO FRONTMATTER ---------- */
|
|
127
153
|
const fm = clean.match(/^---([\s\S]*?)---/);
|
|
128
154
|
if (fm?.[1]) {
|
|
129
155
|
parts.push(fm[1]);
|
|
130
156
|
}
|
|
131
157
|
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
for (const s of scriptSrc) {
|
|
138
|
-
const src = s[1];
|
|
139
|
-
|
|
140
|
-
const resolved = src.startsWith("/")
|
|
141
|
-
? path.resolve(process.cwd(), "." + src)
|
|
142
|
-
: path.resolve(path.dirname(file), src);
|
|
143
|
-
|
|
144
|
-
deps.push(resolved);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// INLINE <script>
|
|
148
|
-
const scripts = clean.matchAll(
|
|
149
|
-
/<script\b([^>]*)>([\s\S]*?)<\/script>/g
|
|
150
|
-
);
|
|
158
|
+
/* ---------- SCRIPTS ---------- */
|
|
159
|
+
const { scripts, deps: scriptDeps, noScripts } =
|
|
160
|
+
extractScripts(clean, file);
|
|
151
161
|
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
parts.push(...scripts);
|
|
163
|
+
deps.push(...scriptDeps);
|
|
154
164
|
|
|
155
|
-
|
|
165
|
+
/* ---------- HANDLERS ---------- */
|
|
166
|
+
parts.push(...extractHTMLHandlers(noScripts));
|
|
167
|
+
parts.push(...extractVueHandlers(noScripts));
|
|
168
|
+
parts.push(...extractSvelteHandlers(noScripts));
|
|
156
169
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// REMOVE SCRIPTS → TEMPLATE PART
|
|
163
|
-
const noScripts = clean.replace(
|
|
164
|
-
/<script[\s\S]*?<\/script>/g,
|
|
165
|
-
""
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
// INLINE HANDLERS
|
|
169
|
-
parts.push(
|
|
170
|
-
...extractInlineHandlers(noScripts).filter(isProbablyTracking)
|
|
171
|
-
);
|
|
172
|
-
|
|
173
|
-
// DYNAMIC IMPORTS
|
|
174
|
-
const dyn = extractDynamicImports(clean, file);
|
|
175
|
-
deps.push(...dyn.deps);
|
|
176
|
-
parts.push(...dyn.calls.filter(isProbablyTracking));
|
|
170
|
+
/* ---------- JSX / TEMPLATE ---------- */
|
|
171
|
+
parts.push(...extractBracedExpressions(clean));
|
|
177
172
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
for (const e of extracted) {
|
|
182
|
-
if (isProbablyTracking(e)) {
|
|
183
|
-
parts.push(e + ";");
|
|
184
|
-
}
|
|
185
|
-
}
|
|
173
|
+
/* ---------- IMPORTS ---------- */
|
|
174
|
+
deps.push(...extractDynamicImports(clean, file));
|
|
186
175
|
|
|
187
176
|
return {
|
|
188
177
|
code: [...new Set(parts)].join("\n"),
|
package/tests/run.ts
CHANGED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.isTrackingCall = isTrackingCall;
|
|
7
|
-
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
-
function isStringLike(type) {
|
|
9
|
-
return ((type.flags & typescript_1.default.TypeFlags.StringLike) !== 0 ||
|
|
10
|
-
(type.flags & typescript_1.default.TypeFlags.StringLiteral) !== 0);
|
|
11
|
-
}
|
|
12
|
-
function hasTrackLikeSignature(type, checker) {
|
|
13
|
-
const sigs = type.getCallSignatures();
|
|
14
|
-
for (const sig of sigs) {
|
|
15
|
-
const params = sig.getParameters();
|
|
16
|
-
if (!params.length)
|
|
17
|
-
continue;
|
|
18
|
-
const t = checker.getTypeOfSymbolAtLocation(params[0], params[0].valueDeclaration);
|
|
19
|
-
if (isStringLike(t)) {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return false;
|
|
24
|
-
}
|
|
25
|
-
function isEventraLikeType(type, checker) {
|
|
26
|
-
const props = type.getProperties();
|
|
27
|
-
for (const prop of props) {
|
|
28
|
-
const decl = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
29
|
-
if (!decl)
|
|
30
|
-
continue;
|
|
31
|
-
const t = checker.getTypeOfSymbolAtLocation(prop, decl);
|
|
32
|
-
if (hasTrackLikeSignature(t, checker)) {
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
function isTrackingCall(node, checker) {
|
|
39
|
-
const expr = node.expression;
|
|
40
|
-
// fn(...)
|
|
41
|
-
if (typescript_1.default.isIdentifier(expr)) {
|
|
42
|
-
const type = checker.getTypeAtLocation(expr);
|
|
43
|
-
if (hasTrackLikeSignature(type, checker)) {
|
|
44
|
-
return true;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
// obj.method(...)
|
|
48
|
-
if (typescript_1.default.isPropertyAccessExpression(expr)) {
|
|
49
|
-
const objType = checker.getTypeAtLocation(expr.expression);
|
|
50
|
-
// объект похож на tracker
|
|
51
|
-
if (isEventraLikeType(objType, checker)) {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
// fallback
|
|
55
|
-
const methodType = checker.getTypeAtLocation(expr.name);
|
|
56
|
-
if (hasTrackLikeSignature(methodType, checker)) {
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// obj["track"](...)
|
|
61
|
-
if (typescript_1.default.isElementAccessExpression(expr)) {
|
|
62
|
-
const objType = checker.getTypeAtLocation(expr.expression);
|
|
63
|
-
if (isEventraLikeType(objType, checker)) {
|
|
64
|
-
return true;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return false;
|
|
68
|
-
}
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import ts from "typescript";
|
|
2
|
-
|
|
3
|
-
function isStringLike(type: ts.Type) {
|
|
4
|
-
return (
|
|
5
|
-
(type.flags & ts.TypeFlags.StringLike) !== 0 ||
|
|
6
|
-
(type.flags & ts.TypeFlags.StringLiteral) !== 0
|
|
7
|
-
);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function hasTrackLikeSignature(
|
|
11
|
-
type: ts.Type,
|
|
12
|
-
checker: ts.TypeChecker
|
|
13
|
-
): boolean {
|
|
14
|
-
const sigs = type.getCallSignatures();
|
|
15
|
-
|
|
16
|
-
for (const sig of sigs) {
|
|
17
|
-
const params = sig.getParameters();
|
|
18
|
-
if (!params.length) continue;
|
|
19
|
-
|
|
20
|
-
const t = checker.getTypeOfSymbolAtLocation(
|
|
21
|
-
params[0],
|
|
22
|
-
params[0].valueDeclaration!
|
|
23
|
-
);
|
|
24
|
-
|
|
25
|
-
if (isStringLike(t)) {
|
|
26
|
-
return true;
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isEventraLikeType(
|
|
34
|
-
type: ts.Type,
|
|
35
|
-
checker: ts.TypeChecker
|
|
36
|
-
): boolean {
|
|
37
|
-
const props = type.getProperties();
|
|
38
|
-
|
|
39
|
-
for (const prop of props) {
|
|
40
|
-
const decl = prop.valueDeclaration ?? prop.declarations?.[0];
|
|
41
|
-
if (!decl) continue;
|
|
42
|
-
|
|
43
|
-
const t = checker.getTypeOfSymbolAtLocation(prop, decl);
|
|
44
|
-
|
|
45
|
-
if (hasTrackLikeSignature(t, checker)) {
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function isTrackingCall(
|
|
54
|
-
node: ts.CallExpression,
|
|
55
|
-
checker: ts.TypeChecker
|
|
56
|
-
): boolean {
|
|
57
|
-
const expr = node.expression;
|
|
58
|
-
|
|
59
|
-
// fn(...)
|
|
60
|
-
if (ts.isIdentifier(expr)) {
|
|
61
|
-
const type = checker.getTypeAtLocation(expr);
|
|
62
|
-
if (hasTrackLikeSignature(type, checker)) {
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// obj.method(...)
|
|
68
|
-
if (ts.isPropertyAccessExpression(expr)) {
|
|
69
|
-
const objType = checker.getTypeAtLocation(expr.expression);
|
|
70
|
-
|
|
71
|
-
// объект похож на tracker
|
|
72
|
-
if (isEventraLikeType(objType, checker)) {
|
|
73
|
-
return true;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// fallback
|
|
77
|
-
const methodType = checker.getTypeAtLocation(expr.name);
|
|
78
|
-
|
|
79
|
-
if (hasTrackLikeSignature(methodType, checker)) {
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// obj["track"](...)
|
|
85
|
-
if (ts.isElementAccessExpression(expr)) {
|
|
86
|
-
const objType = checker.getTypeAtLocation(expr.expression);
|
|
87
|
-
|
|
88
|
-
if (isEventraLikeType(objType, checker)) {
|
|
89
|
-
return true;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return false;
|
|
94
|
-
}
|