@eventra_dev/eventra-cli 1.0.4 → 1.0.5
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/analysis/extractor/extractor.js +7 -1
- package/dist/analysis/extractor/propagationExtractor.js +7 -3
- package/dist/analysis/scanner/scanner.js +2 -1
- package/dist/analysis/shared/dynamicOccurrence.js +22 -0
- package/dist/cli/check.js +2 -1
- package/dist/cli/sync.js +2 -1
- package/dist/core/EventraEngine.js +8 -0
- package/dist/core/projectScanner.js +1 -0
- package/dist/plugin/builtins/consoleDynamicEventReporter.js +24 -0
- package/dist/plugin/loadPlugins.js +2 -0
- package/dist/plugin/registry.js +34 -0
- package/package.json +1 -1
|
@@ -9,8 +9,10 @@ const resolver_1 = require("../resolver/resolver");
|
|
|
9
9
|
const evaluationContext_1 = require("../shared/evaluationContext");
|
|
10
10
|
const propagationExtractor_1 = require("./propagationExtractor");
|
|
11
11
|
const eventValidation_1 = require("../shared/eventValidation");
|
|
12
|
+
const dynamicOccurrence_1 = require("../shared/dynamicOccurrence");
|
|
12
13
|
function extractEvents(index, checker, _config, evaluationCache, exportCache, resolvedCallCache, returnPropagationCache, wrapperRegistry) {
|
|
13
14
|
const events = new Set();
|
|
15
|
+
const dynamicOccurrences = [];
|
|
14
16
|
// direct track sinks
|
|
15
17
|
for (const call of index.trackCalls) {
|
|
16
18
|
for (const argument of call.trackedArguments) {
|
|
@@ -21,12 +23,15 @@ function extractEvents(index, checker, _config, evaluationCache, exportCache, re
|
|
|
21
23
|
events.add(normalized);
|
|
22
24
|
}
|
|
23
25
|
}
|
|
26
|
+
if (resolved.dynamic) {
|
|
27
|
+
(0, dynamicOccurrence_1.recordDynamicOccurrence)(call.node, resolved.values, dynamicOccurrences);
|
|
28
|
+
}
|
|
24
29
|
}
|
|
25
30
|
}
|
|
26
31
|
const propagationVisited = new Set();
|
|
27
32
|
function visit(node) {
|
|
28
33
|
if (typescript_1.default.isCallExpression(node)) {
|
|
29
|
-
(0, propagationExtractor_1.extractPropagationEvents)(node, checker, wrapperRegistry, resolvedCallCache, returnPropagationCache, evaluationCache, exportCache, events, propagationVisited);
|
|
34
|
+
(0, propagationExtractor_1.extractPropagationEvents)(node, checker, wrapperRegistry, resolvedCallCache, returnPropagationCache, evaluationCache, exportCache, events, propagationVisited, dynamicOccurrences);
|
|
30
35
|
}
|
|
31
36
|
typescript_1.default.forEachChild(node, visit);
|
|
32
37
|
}
|
|
@@ -34,5 +39,6 @@ function extractEvents(index, checker, _config, evaluationCache, exportCache, re
|
|
|
34
39
|
return {
|
|
35
40
|
events,
|
|
36
41
|
detectedFunctionWrappers: new Set(index.wrappers.map(wrapper => wrapper.symbol.getName())),
|
|
42
|
+
dynamicOccurrences,
|
|
37
43
|
};
|
|
38
44
|
}
|
|
@@ -9,6 +9,7 @@ const callResolver_1 = require("../resolver/callResolver");
|
|
|
9
9
|
const resolver_1 = require("../resolver/resolver");
|
|
10
10
|
const evaluationContext_1 = require("../shared/evaluationContext");
|
|
11
11
|
const eventValidation_1 = require("../shared/eventValidation");
|
|
12
|
+
const dynamicOccurrence_1 = require("../shared/dynamicOccurrence");
|
|
12
13
|
function resolveObjectPath(expression, path) {
|
|
13
14
|
let current = expression;
|
|
14
15
|
for (const segment of path) {
|
|
@@ -34,7 +35,7 @@ function resolveObjectPath(expression, path) {
|
|
|
34
35
|
}
|
|
35
36
|
return current;
|
|
36
37
|
}
|
|
37
|
-
function extractPropagationEvents(call, checker, wrapperRegistry, resolvedCallCache, returnPropagationCache, evaluationCache, exportCache, events, visited) {
|
|
38
|
+
function extractPropagationEvents(call, checker, wrapperRegistry, resolvedCallCache, returnPropagationCache, evaluationCache, exportCache, events, visited, dynamicOccurrences) {
|
|
38
39
|
const resolved = (0, callResolver_1.resolveFunctionFromCall)(call.expression, checker, resolvedCallCache, exportCache);
|
|
39
40
|
if (!resolved) {
|
|
40
41
|
return;
|
|
@@ -53,14 +54,14 @@ function extractPropagationEvents(call, checker, wrapperRegistry, resolvedCallCa
|
|
|
53
54
|
visited.add(signature);
|
|
54
55
|
try {
|
|
55
56
|
for (const propagation of semantic.propagations) {
|
|
56
|
-
extractPropagation(propagation, call, checker, evaluationCache, resolvedCallCache, returnPropagationCache, exportCache, events);
|
|
57
|
+
extractPropagation(propagation, call, checker, evaluationCache, resolvedCallCache, returnPropagationCache, exportCache, events, dynamicOccurrences);
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
finally {
|
|
60
61
|
visited.delete(signature);
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
function extractPropagation(propagation, call, checker, evaluationCache, resolvedCallCache, returnPropagationCache, exportCache, events) {
|
|
64
|
+
function extractPropagation(propagation, call, checker, evaluationCache, resolvedCallCache, returnPropagationCache, exportCache, events, dynamicOccurrences) {
|
|
64
65
|
let argument = call.arguments[propagation.sourceParameterIndex];
|
|
65
66
|
if (!argument) {
|
|
66
67
|
return;
|
|
@@ -89,4 +90,7 @@ function extractPropagation(propagation, call, checker, evaluationCache, resolve
|
|
|
89
90
|
events.add(normalized);
|
|
90
91
|
}
|
|
91
92
|
}
|
|
93
|
+
if (resolved.dynamic) {
|
|
94
|
+
(0, dynamicOccurrence_1.recordDynamicOccurrence)(call, resolved.values, dynamicOccurrences);
|
|
95
|
+
}
|
|
92
96
|
}
|
|
@@ -72,7 +72,8 @@ function scanSource(source, checker, wrapperRegistry, plugins) {
|
|
|
72
72
|
}
|
|
73
73
|
return false;
|
|
74
74
|
});
|
|
75
|
-
const wrapper = (
|
|
75
|
+
const wrapper = plugins.detectWrapper({ fn, checker, sinks: localSinks }) ??
|
|
76
|
+
(0, propagationAnalyzer_1.analyzeWrapperPropagation)(fn, checker, localSinks);
|
|
76
77
|
if (!wrapper) {
|
|
77
78
|
continue;
|
|
78
79
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// src/analysis/shared/dynamicOccurrence.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.recordDynamicOccurrence = recordDynamicOccurrence;
|
|
8
|
+
const typescript_1 = __importDefault(require("typescript"));
|
|
9
|
+
const CALL_TEXT_MAX_LENGTH = 120;
|
|
10
|
+
function recordDynamicOccurrence(call, resolvedValues, out) {
|
|
11
|
+
const sourceFile = call.getSourceFile();
|
|
12
|
+
const { line, character } = typescript_1.default.getLineAndCharacterOfPosition(sourceFile, call.getStart(sourceFile));
|
|
13
|
+
const callText = call.getText(sourceFile);
|
|
14
|
+
out.push({
|
|
15
|
+
fileName: sourceFile.fileName,
|
|
16
|
+
line: line + 1,
|
|
17
|
+
character,
|
|
18
|
+
calleeText: call.expression.getText(sourceFile),
|
|
19
|
+
callText: callText.length > CALL_TEXT_MAX_LENGTH ? `${callText.slice(0, CALL_TEXT_MAX_LENGTH)}…` : callText,
|
|
20
|
+
resolvedValues,
|
|
21
|
+
});
|
|
22
|
+
}
|
package/dist/cli/check.js
CHANGED
|
@@ -21,7 +21,8 @@ async function check({ fix = false }) {
|
|
|
21
21
|
process.exit(1);
|
|
22
22
|
}
|
|
23
23
|
console.log(chalk_1.default.blue("Checking events and function wrappers..."));
|
|
24
|
-
const { engine } = await (0, projectScanner_1.scanProject)(config);
|
|
24
|
+
const { engine, plugins } = await (0, projectScanner_1.scanProject)(config);
|
|
25
|
+
await plugins.runDynamicEventReporters(engine.getAllDynamicOccurrences());
|
|
25
26
|
const foundEvents = new Set(engine.getAllEvents());
|
|
26
27
|
const knownEvents = new Set(config.events ?? []);
|
|
27
28
|
const eventDiff = diffSets(foundEvents, knownEvents);
|
package/dist/cli/sync.js
CHANGED
|
@@ -15,9 +15,10 @@ async function sync() {
|
|
|
15
15
|
return;
|
|
16
16
|
}
|
|
17
17
|
console.log(chalk_1.default.blue("Scanning..."));
|
|
18
|
-
const { engine } = await (0, projectScanner_1.scanProject)(config);
|
|
18
|
+
const { engine, plugins } = await (0, projectScanner_1.scanProject)(config);
|
|
19
19
|
await (0, scanResults_1.persistScanResults)(config, engine);
|
|
20
20
|
const events = engine.getAllEvents();
|
|
21
21
|
const wrappers = engine.getAllFunctionWrappers();
|
|
22
22
|
console.log(chalk_1.default.green(`Found ${events.length} events, ${wrappers.length} function wrappers`));
|
|
23
|
+
await plugins.runDynamicEventReporters(engine.getAllDynamicOccurrences());
|
|
23
24
|
}
|
|
@@ -20,6 +20,7 @@ const loadPlugins_1 = require("../plugin/loadPlugins");
|
|
|
20
20
|
const EMPTY_RESULT = () => ({
|
|
21
21
|
events: new Set(),
|
|
22
22
|
detectedFunctionWrappers: new Set(),
|
|
23
|
+
dynamicOccurrences: [],
|
|
23
24
|
});
|
|
24
25
|
class EventraEngine {
|
|
25
26
|
compiler;
|
|
@@ -253,5 +254,12 @@ class EventraEngine {
|
|
|
253
254
|
getScanResult(fileName) {
|
|
254
255
|
return this.fileResults.get(this.normalize(fileName)) ?? EMPTY_RESULT();
|
|
255
256
|
}
|
|
257
|
+
getAllDynamicOccurrences() {
|
|
258
|
+
const occurrences = [];
|
|
259
|
+
for (const result of this.fileResults.values()) {
|
|
260
|
+
occurrences.push(...result.dynamicOccurrences);
|
|
261
|
+
}
|
|
262
|
+
return occurrences.sort((a, b) => a.fileName.localeCompare(b.fileName) || a.line - b.line);
|
|
263
|
+
}
|
|
256
264
|
}
|
|
257
265
|
exports.EventraEngine = EventraEngine;
|
|
@@ -0,0 +1,24 @@
|
|
|
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.registerConsoleDynamicEventReporterPlugin = registerConsoleDynamicEventReporterPlugin;
|
|
7
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const consoleDynamicEventReporter = {
|
|
9
|
+
name: "console",
|
|
10
|
+
report({ occurrences }) {
|
|
11
|
+
if (occurrences.length === 0) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
console.log(chalk_1.default.blue("\nDynamic event names:"));
|
|
15
|
+
for (const occ of occurrences) {
|
|
16
|
+
const values = occ.resolvedValues.length > 0 ? ` (resolved: ${occ.resolvedValues.join(", ")})` : "";
|
|
17
|
+
console.log(chalk_1.default.yellow(`~ ${occ.fileName}:${occ.line} ${occ.calleeText}(...)${values}`));
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
/** Default reporter: prints unresolved/dynamic event-name call sites to the console (always enabled). */
|
|
22
|
+
function registerConsoleDynamicEventReporterPlugin(registry) {
|
|
23
|
+
registry.registerDynamicEventReporter(consoleDynamicEventReporter);
|
|
24
|
+
}
|
|
@@ -38,6 +38,7 @@ exports.createBuiltinPluginRegistry = createBuiltinPluginRegistry;
|
|
|
38
38
|
exports.createPluginRegistry = createPluginRegistry;
|
|
39
39
|
const external_1 = require("./adapters/external");
|
|
40
40
|
const eventra_sdk_1 = require("./builtins/eventra-sdk");
|
|
41
|
+
const consoleDynamicEventReporter_1 = require("./builtins/consoleDynamicEventReporter");
|
|
41
42
|
const registry_1 = require("./registry");
|
|
42
43
|
/**
|
|
43
44
|
* Only packages published under the `@eventra_dev` npm scope can ever be
|
|
@@ -89,6 +90,7 @@ async function loadExternalPlugin(registry, spec) {
|
|
|
89
90
|
function createBuiltinPluginRegistry() {
|
|
90
91
|
const registry = new registry_1.PluginRegistry();
|
|
91
92
|
(0, eventra_sdk_1.registerEventraSdkPlugin)(registry);
|
|
93
|
+
(0, consoleDynamicEventReporter_1.registerConsoleDynamicEventReporterPlugin)(registry);
|
|
92
94
|
return registry;
|
|
93
95
|
}
|
|
94
96
|
/** Built-ins + plugins listed in eventra.json `plugins` array. */
|
package/dist/plugin/registry.js
CHANGED
|
@@ -8,6 +8,8 @@ const path_1 = __importDefault(require("path"));
|
|
|
8
8
|
class PluginRegistry {
|
|
9
9
|
preprocessors = [];
|
|
10
10
|
sinkDetectors = [];
|
|
11
|
+
wrapperDetectors = [];
|
|
12
|
+
dynamicEventReporters = [];
|
|
11
13
|
includePatterns = [];
|
|
12
14
|
sourceToVirtuals = new Map();
|
|
13
15
|
registerFilePreprocessor(preprocessor) {
|
|
@@ -22,6 +24,18 @@ class PluginRegistry {
|
|
|
22
24
|
}
|
|
23
25
|
this.sinkDetectors.push(detector);
|
|
24
26
|
}
|
|
27
|
+
registerWrapperDetector(detector) {
|
|
28
|
+
if (this.wrapperDetectors.some((d) => d.name === detector.name)) {
|
|
29
|
+
throw new Error(`Wrapper detector already registered: ${detector.name}`);
|
|
30
|
+
}
|
|
31
|
+
this.wrapperDetectors.push(detector);
|
|
32
|
+
}
|
|
33
|
+
registerDynamicEventReporter(reporter) {
|
|
34
|
+
if (this.dynamicEventReporters.some((r) => r.name === reporter.name)) {
|
|
35
|
+
throw new Error(`Dynamic event reporter already registered: ${reporter.name}`);
|
|
36
|
+
}
|
|
37
|
+
this.dynamicEventReporters.push(reporter);
|
|
38
|
+
}
|
|
25
39
|
registerIncludePattern(pattern) {
|
|
26
40
|
const trimmed = pattern.trim();
|
|
27
41
|
if (!trimmed)
|
|
@@ -36,6 +50,12 @@ class PluginRegistry {
|
|
|
36
50
|
getSinkDetectors() {
|
|
37
51
|
return this.sinkDetectors;
|
|
38
52
|
}
|
|
53
|
+
getWrapperDetectors() {
|
|
54
|
+
return this.wrapperDetectors;
|
|
55
|
+
}
|
|
56
|
+
getDynamicEventReporters() {
|
|
57
|
+
return this.dynamicEventReporters;
|
|
58
|
+
}
|
|
39
59
|
getFilePreprocessors() {
|
|
40
60
|
return this.preprocessors;
|
|
41
61
|
}
|
|
@@ -71,6 +91,20 @@ class PluginRegistry {
|
|
|
71
91
|
}
|
|
72
92
|
return null;
|
|
73
93
|
}
|
|
94
|
+
detectWrapper(context) {
|
|
95
|
+
for (const detector of this.wrapperDetectors) {
|
|
96
|
+
const wrapper = detector.detect(context);
|
|
97
|
+
if (wrapper) {
|
|
98
|
+
return wrapper;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
async runDynamicEventReporters(occurrences) {
|
|
104
|
+
for (const reporter of this.dynamicEventReporters) {
|
|
105
|
+
await reporter.report({ occurrences });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
74
108
|
normalizePath(fileName) {
|
|
75
109
|
return path_1.default.resolve(fileName).replace(/\\/g, "/");
|
|
76
110
|
}
|