@eventra_dev/eventra-cli 0.2.3 → 0.3.1
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 +154 -32
- package/dist/analysis/cache/evaluationCache.js +21 -0
- package/dist/analysis/cache/resolvedCallCache.js +21 -0
- package/dist/analysis/cache/resolvedExportCache.js +21 -0
- package/dist/analysis/cache/semanticDependencyGraph.js +25 -0
- package/dist/analysis/cache/symbolInvalidation.js +20 -0
- package/dist/analysis/extractor/extractor.js +47 -0
- package/dist/analysis/extractor/propagationExtractor.js +52 -0
- package/dist/analysis/pipeline/semanticPass.js +2 -0
- package/dist/analysis/resolver/callResolver.js +125 -0
- package/dist/analysis/resolver/exportResolver.js +75 -0
- package/dist/analysis/resolver/resolver.js +133 -0
- package/dist/analysis/scanner/analyzer/propagationAnalyzer.js +193 -0
- package/dist/analysis/scanner/analyzer/returnPropagationAnalyzer.js +105 -0
- package/dist/analysis/scanner/scanner.js +117 -0
- package/dist/analysis/scanner/sinkDetector.js +91 -0
- package/dist/analysis/shared/evaluationContext.js +9 -0
- package/dist/analysis/shared/propagation.js +3 -0
- package/dist/analysis/shared/returnPropagation.js +2 -0
- package/dist/analysis/shared/types.js +2 -0
- package/dist/analysis/shared/utils.js +62 -0
- package/dist/analysis/symbols/symbolUtils.js +68 -0
- package/dist/analysis/symbols/wrapperRegistry.js +55 -0
- package/dist/cli/check.js +73 -0
- package/dist/{commands → cli}/init.js +1 -1
- package/dist/{commands → cli}/send.js +1 -1
- package/dist/cli/sync.js +56 -0
- package/dist/{commands → cli}/watch.js +41 -25
- package/dist/compiler/compilerContext.js +204 -0
- package/dist/compiler/documentRegistry.js +78 -0
- package/dist/compiler/importGraph.js +83 -0
- package/dist/compiler/scheduler.js +29 -0
- package/dist/core/EventraEngine.js +234 -0
- package/dist/core/projectScanner.js +73 -0
- package/dist/{utils → filesystem}/getVirtualFile.js +1 -3
- package/dist/filesystem/processFile.js +38 -0
- package/dist/index.js +5 -5
- package/dist/shared/buildSyntheticTsx.js +20 -0
- package/dist/shared/templateExpressionExtractor.js +22 -0
- package/package.json +32 -9
- package/.github/workflows/release.yml +0 -43
- package/ARCHITECTURE.md +0 -88
- package/assets/eventra-icon-animated.svg +0 -19
- package/dist/commands/check.js +0 -78
- package/dist/commands/sync.js +0 -79
- package/dist/engine/boundary.js +0 -14
- package/dist/engine/callResolver.js +0 -58
- package/dist/engine/engine.js +0 -154
- package/dist/engine/exportResolver.js +0 -46
- package/dist/engine/functionAnalyzer.js +0 -71
- package/dist/engine/graph.js +0 -168
- package/dist/engine/languageService.js +0 -162
- package/dist/engine/resolver.js +0 -149
- package/dist/engine/scanner.js +0 -230
- package/dist/utils/processFile.js +0 -28
- package/dist/utils/universal.js +0 -190
- package/src/commands/check.ts +0 -91
- package/src/commands/init.ts +0 -174
- package/src/commands/send.ts +0 -213
- package/src/commands/sync.ts +0 -106
- package/src/commands/watch.ts +0 -144
- package/src/engine/boundary.ts +0 -12
- package/src/engine/callResolver.ts +0 -65
- package/src/engine/engine.ts +0 -227
- package/src/engine/exportResolver.ts +0 -56
- package/src/engine/functionAnalyzer.ts +0 -81
- package/src/engine/graph.ts +0 -193
- package/src/engine/hash.ts +0 -5
- package/src/engine/languageService.ts +0 -209
- package/src/engine/resolver.ts +0 -177
- package/src/engine/scanner.ts +0 -298
- package/src/index.ts +0 -49
- package/src/types.ts +0 -35
- package/src/utils/config.ts +0 -53
- package/src/utils/getVirtualFile.ts +0 -5
- package/src/utils/processFile.ts +0 -29
- package/src/utils/universal.ts +0 -249
- package/tests/fixtures/backend/express/app.ts +0 -233
- package/tests/fixtures/backend/nest/service.ts +0 -70
- package/tests/fixtures/backend/node/index.ts +0 -63
- package/tests/fixtures/frontend/next/page.tsx +0 -101
- package/tests/fixtures/frontend/react/App.tsx +0 -104
- package/tests/fixtures/frontend/vue/App.vue +0 -83
- package/tests/fixtures/wrappers/component/test.tsx +0 -62
- package/tests/fixtures/wrappers/function/test.ts +0 -60
- package/tests/run.ts +0 -106
- package/tsconfig.json +0 -14
- /package/dist/{utils → config}/config.js +0 -0
- /package/dist/{engine → shared}/hash.js +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/analysis/resolver/exportResolver.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.resolveExportedSymbol = resolveExportedSymbol;
|
|
8
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
9
|
+
function isConcreteDeclaration(node) {
|
|
10
|
+
return (typescript_1.default.isFunctionDeclaration(node)
|
|
11
|
+
|| typescript_1.default.isVariableDeclaration(node)
|
|
12
|
+
|| typescript_1.default.isClassDeclaration(node)
|
|
13
|
+
|| typescript_1.default.isEnumDeclaration(node)
|
|
14
|
+
|| typescript_1.default.isInterfaceDeclaration(node)
|
|
15
|
+
|| typescript_1.default.isTypeAliasDeclaration(node)
|
|
16
|
+
|| typescript_1.default.isMethodDeclaration(node)
|
|
17
|
+
|| typescript_1.default.isPropertyDeclaration(node));
|
|
18
|
+
}
|
|
19
|
+
function resolveExportedSymbol(symbol, checker, cache, visited) {
|
|
20
|
+
const seen = visited
|
|
21
|
+
?? new Set();
|
|
22
|
+
const cached = cache.get(symbol);
|
|
23
|
+
if (cached !== undefined) {
|
|
24
|
+
return cached;
|
|
25
|
+
}
|
|
26
|
+
//
|
|
27
|
+
// cycle guard
|
|
28
|
+
//
|
|
29
|
+
if (seen.has(symbol)) {
|
|
30
|
+
cache.set(symbol, null);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
seen.add(symbol);
|
|
34
|
+
//
|
|
35
|
+
// alias unwrap
|
|
36
|
+
//
|
|
37
|
+
if (symbol.flags
|
|
38
|
+
& typescript_1.default.SymbolFlags.Alias) {
|
|
39
|
+
const aliased = checker.getAliasedSymbol(symbol);
|
|
40
|
+
const resolved = resolveExportedSymbol(aliased, checker, cache, seen);
|
|
41
|
+
cache.set(symbol, resolved);
|
|
42
|
+
return resolved;
|
|
43
|
+
}
|
|
44
|
+
//
|
|
45
|
+
// export unwrap
|
|
46
|
+
//
|
|
47
|
+
const exportSymbol = checker.getExportSymbolOfSymbol(symbol);
|
|
48
|
+
if (exportSymbol
|
|
49
|
+
&& exportSymbol !== symbol) {
|
|
50
|
+
const resolved = resolveExportedSymbol(exportSymbol, checker, cache, seen);
|
|
51
|
+
cache.set(symbol, resolved);
|
|
52
|
+
return resolved;
|
|
53
|
+
}
|
|
54
|
+
//
|
|
55
|
+
// declaration existence
|
|
56
|
+
//
|
|
57
|
+
const declarations = symbol.getDeclarations()
|
|
58
|
+
?? [];
|
|
59
|
+
for (const declaration of declarations) {
|
|
60
|
+
if (isConcreteDeclaration(declaration)) {
|
|
61
|
+
cache.set(symbol, symbol);
|
|
62
|
+
return symbol;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//
|
|
66
|
+
// final validation
|
|
67
|
+
//
|
|
68
|
+
const valid = declarations.some(isConcreteDeclaration);
|
|
69
|
+
if (valid) {
|
|
70
|
+
cache.set(symbol, symbol);
|
|
71
|
+
return symbol;
|
|
72
|
+
}
|
|
73
|
+
cache.set(symbol, null);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/analysis/resolver/resolver.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.resolveNodeValue = resolveNodeValue;
|
|
8
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
9
|
+
const exportResolver_1 = require("./exportResolver");
|
|
10
|
+
const returnPropagationAnalyzer_1 = require("../scanner/analyzer/returnPropagationAnalyzer");
|
|
11
|
+
const callResolver_1 = require("./callResolver");
|
|
12
|
+
const resolvedCallCache_1 = require("../cache/resolvedCallCache");
|
|
13
|
+
function unique(values) {
|
|
14
|
+
return [
|
|
15
|
+
...new Set(values),
|
|
16
|
+
];
|
|
17
|
+
}
|
|
18
|
+
function empty() {
|
|
19
|
+
return {
|
|
20
|
+
values: [],
|
|
21
|
+
dynamic: true,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function concat(left, right) {
|
|
25
|
+
const result = [];
|
|
26
|
+
for (const l of left) {
|
|
27
|
+
for (const r of right) {
|
|
28
|
+
result.push(l + r);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return unique(result);
|
|
32
|
+
}
|
|
33
|
+
function resolveIdentifier(node, checker, context, visited, evaluationCache, exportCache) {
|
|
34
|
+
const boundSymbol = checker.getSymbolAtLocation(node);
|
|
35
|
+
if (boundSymbol
|
|
36
|
+
&& context.parameterBindings.has(boundSymbol)) {
|
|
37
|
+
const bound = context.parameterBindings.get(boundSymbol);
|
|
38
|
+
if (bound) {
|
|
39
|
+
return resolveNodeValue(bound, checker, context, visited, evaluationCache, undefined, exportCache);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
let symbol = checker.getSymbolAtLocation(node);
|
|
43
|
+
if (!symbol) {
|
|
44
|
+
return empty();
|
|
45
|
+
}
|
|
46
|
+
symbol =
|
|
47
|
+
(0, exportResolver_1.resolveExportedSymbol)(symbol, checker, exportCache)
|
|
48
|
+
?? symbol;
|
|
49
|
+
const cached = evaluationCache.get(symbol);
|
|
50
|
+
if (cached) {
|
|
51
|
+
return cached;
|
|
52
|
+
}
|
|
53
|
+
const resolving = empty();
|
|
54
|
+
evaluationCache.set(symbol, resolving);
|
|
55
|
+
const declarations = symbol.getDeclarations()
|
|
56
|
+
?? [];
|
|
57
|
+
for (const declaration of declarations) {
|
|
58
|
+
if (typescript_1.default.isVariableDeclaration(declaration)
|
|
59
|
+
&& declaration.initializer) {
|
|
60
|
+
const resolved = resolveNodeValue(declaration.initializer, checker, context, visited, evaluationCache, undefined, exportCache);
|
|
61
|
+
evaluationCache.set(symbol, resolved);
|
|
62
|
+
return resolved;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return resolving;
|
|
66
|
+
}
|
|
67
|
+
function resolveCallExpression(node, checker, context, visited, evaluationCache, resolvedCallCache, exportCache) {
|
|
68
|
+
const resolved = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker, resolvedCallCache
|
|
69
|
+
?? new resolvedCallCache_1.ResolvedCallCache(), exportCache);
|
|
70
|
+
if (!resolved) {
|
|
71
|
+
return empty();
|
|
72
|
+
}
|
|
73
|
+
const semantic = (0, returnPropagationAnalyzer_1.analyzeReturnPropagation)(resolved);
|
|
74
|
+
if (!semantic) {
|
|
75
|
+
return empty();
|
|
76
|
+
}
|
|
77
|
+
const values = [];
|
|
78
|
+
for (const propagation of semantic.propagations) {
|
|
79
|
+
const argument = node.arguments[propagation.parameterIndex];
|
|
80
|
+
if (!argument) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const nextContext = {
|
|
84
|
+
parameterBindings: new Map(context.parameterBindings),
|
|
85
|
+
};
|
|
86
|
+
const parameterSymbol = checker.getSymbolAtLocation(propagation.parameter.name);
|
|
87
|
+
if (parameterSymbol) {
|
|
88
|
+
nextContext.parameterBindings.set(parameterSymbol, argument);
|
|
89
|
+
}
|
|
90
|
+
const resolvedValue = resolveNodeValue(propagation.returnExpression, checker, nextContext, new Set(visited), evaluationCache, resolvedCallCache, exportCache);
|
|
91
|
+
values.push(...resolvedValue.values);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
values: unique(values),
|
|
95
|
+
dynamic: true,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function resolveNodeValue(node, checker, context, visited, evaluationCache, resolvedCallCache, exportCache) {
|
|
99
|
+
if (visited.has(node)) {
|
|
100
|
+
return empty();
|
|
101
|
+
}
|
|
102
|
+
visited.add(node);
|
|
103
|
+
try {
|
|
104
|
+
if (typescript_1.default.isStringLiteral(node)) {
|
|
105
|
+
return {
|
|
106
|
+
values: [
|
|
107
|
+
node.text,
|
|
108
|
+
],
|
|
109
|
+
dynamic: false,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (typescript_1.default.isIdentifier(node)) {
|
|
113
|
+
return resolveIdentifier(node, checker, context, visited, evaluationCache, exportCache);
|
|
114
|
+
}
|
|
115
|
+
if (typescript_1.default.isBinaryExpression(node)
|
|
116
|
+
&& node.operatorToken.kind
|
|
117
|
+
=== typescript_1.default.SyntaxKind.PlusToken) {
|
|
118
|
+
const left = resolveNodeValue(node.left, checker, context, visited, evaluationCache, resolvedCallCache, exportCache);
|
|
119
|
+
const right = resolveNodeValue(node.right, checker, context, visited, evaluationCache, resolvedCallCache, exportCache);
|
|
120
|
+
return {
|
|
121
|
+
values: concat(left.values, right.values),
|
|
122
|
+
dynamic: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
if (typescript_1.default.isCallExpression(node)) {
|
|
126
|
+
return resolveCallExpression(node, checker, context, visited, evaluationCache, resolvedCallCache, exportCache);
|
|
127
|
+
}
|
|
128
|
+
return empty();
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
visited.delete(node);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/analysis/scanner/propagationAnalyzer.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.analyzeWrapperPropagation = analyzeWrapperPropagation;
|
|
8
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
9
|
+
const symbolUtils_1 = require("../../symbols/symbolUtils");
|
|
10
|
+
function extractPropertyPath(expression) {
|
|
11
|
+
const result = [];
|
|
12
|
+
let current = expression;
|
|
13
|
+
while (typescript_1.default.isPropertyAccessExpression(current)) {
|
|
14
|
+
result.unshift(current.name.text);
|
|
15
|
+
current =
|
|
16
|
+
current.expression;
|
|
17
|
+
}
|
|
18
|
+
if (!typescript_1.default.isIdentifier(current)) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
}
|
|
23
|
+
function resolveObjectBinding(parameter, pattern, identifier, parentPath) {
|
|
24
|
+
for (const element of pattern.elements) {
|
|
25
|
+
const propertyName = element.propertyName
|
|
26
|
+
&& typescript_1.default.isIdentifier(element.propertyName)
|
|
27
|
+
? element.propertyName.text
|
|
28
|
+
: typescript_1.default.isIdentifier(element.name)
|
|
29
|
+
? element.name.text
|
|
30
|
+
: null;
|
|
31
|
+
if (!propertyName) {
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const nextPath = [
|
|
35
|
+
...parentPath,
|
|
36
|
+
propertyName,
|
|
37
|
+
];
|
|
38
|
+
//
|
|
39
|
+
// alias:
|
|
40
|
+
// { event: name }
|
|
41
|
+
//
|
|
42
|
+
if (typescript_1.default.isIdentifier(element.name)) {
|
|
43
|
+
if (element.name.text
|
|
44
|
+
=== identifier.text) {
|
|
45
|
+
return {
|
|
46
|
+
parameter,
|
|
47
|
+
propertyPath: nextPath,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
//
|
|
52
|
+
// nested:
|
|
53
|
+
// { meta: { event } }
|
|
54
|
+
//
|
|
55
|
+
if (typescript_1.default.isObjectBindingPattern(element.name)) {
|
|
56
|
+
const nested = resolveObjectBinding(parameter, element.name, identifier, nextPath);
|
|
57
|
+
if (nested) {
|
|
58
|
+
return nested;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
function resolveParameter(fn, identifier) {
|
|
65
|
+
for (const parameter of fn.parameters) {
|
|
66
|
+
//
|
|
67
|
+
// simple param
|
|
68
|
+
//
|
|
69
|
+
// function x(name)
|
|
70
|
+
//
|
|
71
|
+
if (typescript_1.default.isIdentifier(parameter.name)) {
|
|
72
|
+
if (parameter.name.text
|
|
73
|
+
=== identifier.text) {
|
|
74
|
+
return {
|
|
75
|
+
parameter,
|
|
76
|
+
propertyPath: [],
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
//
|
|
82
|
+
// destructuring
|
|
83
|
+
//
|
|
84
|
+
// function x({ event })
|
|
85
|
+
//
|
|
86
|
+
if (typescript_1.default.isObjectBindingPattern(parameter.name)) {
|
|
87
|
+
const resolved = resolveObjectBinding(parameter, parameter.name, identifier, []);
|
|
88
|
+
if (resolved) {
|
|
89
|
+
return resolved;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
function collectPropagations(fn, checker, sinks) {
|
|
96
|
+
const result = [];
|
|
97
|
+
for (const sink of sinks) {
|
|
98
|
+
for (const tracked of sink.trackedArguments) {
|
|
99
|
+
const argument = sink.call.arguments[tracked.index];
|
|
100
|
+
if (!argument) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
//
|
|
104
|
+
// direct parameter
|
|
105
|
+
//
|
|
106
|
+
// track(name)
|
|
107
|
+
// track(props.event)
|
|
108
|
+
//
|
|
109
|
+
if (typescript_1.default.isIdentifier(argument)
|
|
110
|
+
|| typescript_1.default.isPropertyAccessExpression(argument)) {
|
|
111
|
+
let identifier = null;
|
|
112
|
+
let propertyPath = [];
|
|
113
|
+
if (typescript_1.default.isIdentifier(argument)) {
|
|
114
|
+
identifier =
|
|
115
|
+
argument;
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
propertyPath =
|
|
119
|
+
extractPropertyPath(argument)
|
|
120
|
+
?? [];
|
|
121
|
+
let root = argument;
|
|
122
|
+
while (typescript_1.default.isPropertyAccessExpression(root)) {
|
|
123
|
+
root =
|
|
124
|
+
root.expression;
|
|
125
|
+
}
|
|
126
|
+
if (typescript_1.default.isIdentifier(root)) {
|
|
127
|
+
identifier =
|
|
128
|
+
root;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!identifier) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const parameter = resolveParameter(fn, identifier);
|
|
135
|
+
if (!parameter) {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
result.push({
|
|
139
|
+
sourceParameter: parameter.parameter,
|
|
140
|
+
sourceParameterIndex: fn.parameters.indexOf(parameter.parameter),
|
|
141
|
+
propertyPath: [
|
|
142
|
+
...parameter.propertyPath,
|
|
143
|
+
...propertyPath,
|
|
144
|
+
],
|
|
145
|
+
targetNode: argument,
|
|
146
|
+
});
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
//
|
|
150
|
+
// object literal
|
|
151
|
+
//
|
|
152
|
+
// track({ event: name })
|
|
153
|
+
//
|
|
154
|
+
if (typescript_1.default.isObjectLiteralExpression(argument)) {
|
|
155
|
+
for (const property of argument.properties) {
|
|
156
|
+
if (!typescript_1.default.isPropertyAssignment(property)) {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
const initializer = property.initializer;
|
|
160
|
+
if (!typescript_1.default.isIdentifier(initializer)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
const parameter = resolveParameter(fn, initializer);
|
|
164
|
+
if (!parameter) {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
result.push({
|
|
168
|
+
sourceParameter: parameter.parameter,
|
|
169
|
+
sourceParameterIndex: fn.parameters.indexOf(parameter.parameter),
|
|
170
|
+
propertyPath: parameter.propertyPath,
|
|
171
|
+
targetNode: initializer,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
function analyzeWrapperPropagation(fn, checker, sinks, wrapperRegistry, resolvedCallCache, exportCache) {
|
|
180
|
+
const symbol = (0, symbolUtils_1.getFunctionSymbol)(fn, checker);
|
|
181
|
+
if (!symbol) {
|
|
182
|
+
return null;
|
|
183
|
+
}
|
|
184
|
+
const propagations = collectPropagations(fn, checker, sinks);
|
|
185
|
+
if (propagations.length === 0) {
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
symbol,
|
|
190
|
+
declaration: fn,
|
|
191
|
+
propagations,
|
|
192
|
+
};
|
|
193
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/analysis/scanner/returnPropagationAnalyzer.ts
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.analyzeReturnPropagation = analyzeReturnPropagation;
|
|
8
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
9
|
+
function resolveParameter(fn, identifier) {
|
|
10
|
+
for (const parameter of fn.parameters) {
|
|
11
|
+
if (!typescript_1.default.isIdentifier(parameter.name)) {
|
|
12
|
+
continue;
|
|
13
|
+
}
|
|
14
|
+
if (parameter.name.text
|
|
15
|
+
=== identifier.text) {
|
|
16
|
+
return parameter;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
function analyzeReturnPropagation(fn) {
|
|
22
|
+
const propagations = [];
|
|
23
|
+
function visit(node) {
|
|
24
|
+
//
|
|
25
|
+
// return value
|
|
26
|
+
//
|
|
27
|
+
if (typescript_1.default.isReturnStatement(node)
|
|
28
|
+
&& node.expression) {
|
|
29
|
+
collectExpression(node.expression);
|
|
30
|
+
}
|
|
31
|
+
typescript_1.default.forEachChild(node, visit);
|
|
32
|
+
}
|
|
33
|
+
function collectExpression(expression) {
|
|
34
|
+
//
|
|
35
|
+
// direct return
|
|
36
|
+
// return event
|
|
37
|
+
//
|
|
38
|
+
if (typescript_1.default.isIdentifier(expression)) {
|
|
39
|
+
const parameter = resolveParameter(fn, expression);
|
|
40
|
+
if (!parameter) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
propagations.push({
|
|
44
|
+
parameter,
|
|
45
|
+
parameterIndex: fn.parameters.indexOf(parameter),
|
|
46
|
+
returnExpression: expression,
|
|
47
|
+
});
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
//
|
|
51
|
+
// object return
|
|
52
|
+
// return { event }
|
|
53
|
+
//
|
|
54
|
+
if (typescript_1.default.isObjectLiteralExpression(expression)) {
|
|
55
|
+
for (const property of expression.properties) {
|
|
56
|
+
if (!typescript_1.default.isPropertyAssignment(property)) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const initializer = property.initializer;
|
|
60
|
+
if (!typescript_1.default.isIdentifier(initializer)) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const parameter = resolveParameter(fn, initializer);
|
|
64
|
+
if (!parameter) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
propagations.push({
|
|
68
|
+
parameter,
|
|
69
|
+
parameterIndex: fn.parameters.indexOf(parameter),
|
|
70
|
+
returnExpression: initializer,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
//
|
|
76
|
+
// template returns
|
|
77
|
+
//
|
|
78
|
+
if (typescript_1.default.isTemplateExpression(expression)) {
|
|
79
|
+
for (const span of expression.templateSpans) {
|
|
80
|
+
if (!typescript_1.default.isIdentifier(span.expression)) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const parameter = resolveParameter(fn, span.expression);
|
|
84
|
+
if (!parameter) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
propagations.push({
|
|
88
|
+
parameter,
|
|
89
|
+
parameterIndex: fn.parameters.indexOf(parameter),
|
|
90
|
+
returnExpression: expression,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (fn.body) {
|
|
96
|
+
visit(fn.body);
|
|
97
|
+
}
|
|
98
|
+
if (propagations.length === 0) {
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
declaration: fn,
|
|
103
|
+
propagations,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
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.scanSource = scanSource;
|
|
7
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
8
|
+
const sinkDetector_1 = require("./sinkDetector");
|
|
9
|
+
const propagationAnalyzer_1 = require("./analyzer/propagationAnalyzer");
|
|
10
|
+
function scanSource(source, checker, _config, resolvedCallCache, resolvedExportCache, wrapperRegistry) {
|
|
11
|
+
const sinks = [];
|
|
12
|
+
const wrappers = [];
|
|
13
|
+
const trackCalls = [];
|
|
14
|
+
const functions = [];
|
|
15
|
+
function visit(node) {
|
|
16
|
+
//
|
|
17
|
+
// imports/exports are irrelevant
|
|
18
|
+
//
|
|
19
|
+
if (typescript_1.default.isImportDeclaration(node)
|
|
20
|
+
|| typescript_1.default.isExportDeclaration(node)) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
//
|
|
24
|
+
// function collection
|
|
25
|
+
//
|
|
26
|
+
if (typescript_1.default.isFunctionDeclaration(node)
|
|
27
|
+
|| typescript_1.default.isArrowFunction(node)
|
|
28
|
+
|| typescript_1.default.isFunctionExpression(node)
|
|
29
|
+
|| typescript_1.default.isMethodDeclaration(node)) {
|
|
30
|
+
functions.push(node);
|
|
31
|
+
}
|
|
32
|
+
//
|
|
33
|
+
// sink detection
|
|
34
|
+
//
|
|
35
|
+
if (typescript_1.default.isCallExpression(node)) {
|
|
36
|
+
const sink = (0, sinkDetector_1.detectTrackSink)(node);
|
|
37
|
+
if (sink) {
|
|
38
|
+
sinks.push(sink);
|
|
39
|
+
const trackedArguments = sink.trackedArguments
|
|
40
|
+
.map(tracked => {
|
|
41
|
+
const argument = node.arguments[tracked.index];
|
|
42
|
+
if (!argument) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
//
|
|
46
|
+
// track({
|
|
47
|
+
// event: name
|
|
48
|
+
// })
|
|
49
|
+
//
|
|
50
|
+
if (tracked.propertyPath
|
|
51
|
+
.length > 0
|
|
52
|
+
&& typescript_1.default.isObjectLiteralExpression(argument)) {
|
|
53
|
+
const property = argument.properties
|
|
54
|
+
.find(prop => {
|
|
55
|
+
return (typescript_1.default.isPropertyAssignment(prop)
|
|
56
|
+
&& prop.name.getText()
|
|
57
|
+
=== tracked
|
|
58
|
+
.propertyPath[0]);
|
|
59
|
+
});
|
|
60
|
+
if (property
|
|
61
|
+
&& typescript_1.default.isPropertyAssignment(property)) {
|
|
62
|
+
return property.initializer;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
//
|
|
66
|
+
// track(name)
|
|
67
|
+
//
|
|
68
|
+
return argument;
|
|
69
|
+
})
|
|
70
|
+
.filter((value) => {
|
|
71
|
+
return Boolean(value);
|
|
72
|
+
});
|
|
73
|
+
trackCalls.push({
|
|
74
|
+
node,
|
|
75
|
+
sourceFile: source,
|
|
76
|
+
trackedArguments,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
typescript_1.default.forEachChild(node, visit);
|
|
81
|
+
}
|
|
82
|
+
visit(source);
|
|
83
|
+
//
|
|
84
|
+
// generic semantic analysis
|
|
85
|
+
//
|
|
86
|
+
for (const fn of functions) {
|
|
87
|
+
if (!fn.body) {
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const localSinks = sinks.filter(sink => {
|
|
91
|
+
let current = sink.call;
|
|
92
|
+
while (current) {
|
|
93
|
+
if (current === fn) {
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
current =
|
|
97
|
+
current.parent;
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
});
|
|
101
|
+
//
|
|
102
|
+
// wrapper propagation
|
|
103
|
+
//
|
|
104
|
+
const wrapper = (0, propagationAnalyzer_1.analyzeWrapperPropagation)(fn, checker, localSinks, wrapperRegistry, resolvedCallCache, resolvedExportCache);
|
|
105
|
+
if (wrapper) {
|
|
106
|
+
wrappers.push(wrapper);
|
|
107
|
+
wrapperRegistry.set(wrapper, checker, resolvedExportCache);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
fileName: source.fileName,
|
|
112
|
+
sourceFile: source,
|
|
113
|
+
sinks,
|
|
114
|
+
trackCalls,
|
|
115
|
+
wrappers,
|
|
116
|
+
};
|
|
117
|
+
}
|