@flisk/analyze-tracking 0.7.2 → 0.7.4
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 +4 -0
- package/bin/cli.js +30 -2
- package/package.json +12 -8
- package/src/analyze/go/astTraversal.js +121 -0
- package/src/analyze/go/constants.js +20 -0
- package/src/analyze/go/eventDeduplicator.js +47 -0
- package/src/analyze/go/eventExtractor.js +156 -0
- package/src/analyze/go/goAstParser/constants.js +39 -0
- package/src/analyze/go/goAstParser/expressionParser.js +281 -0
- package/src/analyze/go/goAstParser/index.js +52 -0
- package/src/analyze/go/goAstParser/statementParser.js +387 -0
- package/src/analyze/go/goAstParser/tokenizer.js +196 -0
- package/src/analyze/go/goAstParser/typeParser.js +202 -0
- package/src/analyze/go/goAstParser/utils.js +99 -0
- package/src/analyze/go/index.js +55 -0
- package/src/analyze/go/propertyExtractor.js +670 -0
- package/src/analyze/go/trackingDetector.js +71 -0
- package/src/analyze/go/trackingExtractor.js +54 -0
- package/src/analyze/go/typeContext.js +88 -0
- package/src/analyze/go/utils.js +215 -0
- package/src/analyze/index.js +11 -6
- package/src/analyze/javascript/constants.js +115 -0
- package/src/analyze/javascript/detectors/analytics-source.js +119 -0
- package/src/analyze/javascript/detectors/index.js +10 -0
- package/src/analyze/javascript/extractors/event-extractor.js +179 -0
- package/src/analyze/javascript/extractors/index.js +13 -0
- package/src/analyze/javascript/extractors/property-extractor.js +172 -0
- package/src/analyze/javascript/index.js +38 -0
- package/src/analyze/javascript/parser.js +126 -0
- package/src/analyze/javascript/utils/function-finder.js +123 -0
- package/src/analyze/python/index.js +111 -0
- package/src/analyze/python/pythonTrackingAnalyzer.py +814 -0
- package/src/analyze/ruby/detectors.js +46 -0
- package/src/analyze/ruby/extractors.js +258 -0
- package/src/analyze/ruby/index.js +51 -0
- package/src/analyze/ruby/traversal.js +123 -0
- package/src/analyze/ruby/types.js +30 -0
- package/src/analyze/ruby/visitor.js +66 -0
- package/src/analyze/typescript/constants.js +109 -0
- package/src/analyze/typescript/detectors/analytics-source.js +125 -0
- package/src/analyze/typescript/detectors/index.js +10 -0
- package/src/analyze/typescript/extractors/event-extractor.js +269 -0
- package/src/analyze/typescript/extractors/index.js +14 -0
- package/src/analyze/typescript/extractors/property-extractor.js +427 -0
- package/src/analyze/typescript/index.js +48 -0
- package/src/analyze/typescript/parser.js +131 -0
- package/src/analyze/typescript/utils/function-finder.js +139 -0
- package/src/analyze/typescript/utils/type-resolver.js +208 -0
- package/src/generateDescriptions/index.js +81 -0
- package/src/generateDescriptions/llmUtils.js +33 -0
- package/src/generateDescriptions/promptUtils.js +62 -0
- package/src/generateDescriptions/schemaUtils.js +61 -0
- package/src/index.js +13 -4
- package/src/{fileProcessor.js → utils/fileProcessor.js} +5 -0
- package/src/{repoDetails.js → utils/repoDetails.js} +5 -0
- package/src/utils/yamlGenerator.js +47 -0
- package/src/analyze/analyzeGoFile.js +0 -1164
- package/src/analyze/analyzeJsFile.js +0 -87
- package/src/analyze/analyzePythonFile.js +0 -42
- package/src/analyze/analyzeRubyFile.js +0 -419
- package/src/analyze/analyzeTsFile.js +0 -192
- package/src/analyze/go2json.js +0 -1069
- package/src/analyze/helpers.js +0 -656
- package/src/analyze/pythonTrackingAnalyzer.py +0 -541
- package/src/generateDescriptions.js +0 -196
- package/src/yamlGenerator.js +0 -23
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Analytics tracking detection for Go function calls
|
|
3
|
+
* @module analyze/go/trackingDetector
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { ANALYTICS_SOURCES } = require('./constants');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detect the analytics source from a call or struct literal node
|
|
10
|
+
* @param {Object} callNode - AST node representing a function call or struct literal
|
|
11
|
+
* @param {string|null} customFunction - Name of custom tracking function to detect
|
|
12
|
+
* @returns {string|null} Analytics source name (e.g., 'segment', 'amplitude') or null if not recognized
|
|
13
|
+
*/
|
|
14
|
+
function detectSource(callNode, customFunction) {
|
|
15
|
+
// Check for struct literals (Segment/Rudderstack/PostHog/Amplitude)
|
|
16
|
+
if (callNode.tag === 'structlit') {
|
|
17
|
+
if (callNode.struct) {
|
|
18
|
+
if (callNode.struct.tag === 'access') {
|
|
19
|
+
const structType = callNode.struct.member;
|
|
20
|
+
const namespace = callNode.struct.struct?.value;
|
|
21
|
+
|
|
22
|
+
// Check for specific struct types with their namespaces
|
|
23
|
+
if (structType === 'Track' && namespace === 'analytics') return ANALYTICS_SOURCES.SEGMENT;
|
|
24
|
+
if (structType === 'Capture' && namespace === 'posthog') return ANALYTICS_SOURCES.POSTHOG;
|
|
25
|
+
if (structType === 'Event' && namespace === 'amplitude') return ANALYTICS_SOURCES.AMPLITUDE;
|
|
26
|
+
|
|
27
|
+
// Fallback for struct types without namespace check (backward compatibility)
|
|
28
|
+
if (structType === 'Track') return ANALYTICS_SOURCES.SEGMENT;
|
|
29
|
+
if (structType === 'Capture') return ANALYTICS_SOURCES.POSTHOG;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// For function calls, check if func property exists
|
|
36
|
+
if (!callNode.func) return null;
|
|
37
|
+
|
|
38
|
+
// Check for method calls (e.g., client.Track, mp.Track)
|
|
39
|
+
if (callNode.func.tag === 'access') {
|
|
40
|
+
const objName = callNode.func.struct?.tag === 'ident' ? callNode.func.struct.value : null;
|
|
41
|
+
const methodName = callNode.func.member;
|
|
42
|
+
|
|
43
|
+
if (!objName || !methodName) return null;
|
|
44
|
+
|
|
45
|
+
// Check various analytics providers
|
|
46
|
+
switch (true) {
|
|
47
|
+
// Mixpanel: mp.Track(ctx, []*mixpanel.Event{...})
|
|
48
|
+
case objName === 'mp' && methodName === 'Track':
|
|
49
|
+
return ANALYTICS_SOURCES.MIXPANEL;
|
|
50
|
+
|
|
51
|
+
// Amplitude: client.Track(amplitude.Event{...})
|
|
52
|
+
case objName === 'client' && methodName === 'Track':
|
|
53
|
+
return ANALYTICS_SOURCES.AMPLITUDE;
|
|
54
|
+
|
|
55
|
+
// Snowplow: tracker.TrackStructEvent(...)
|
|
56
|
+
case objName === 'tracker' && methodName === 'TrackStructEvent':
|
|
57
|
+
return ANALYTICS_SOURCES.SNOWPLOW;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check for custom function calls
|
|
62
|
+
if (customFunction && callNode.func.tag === 'ident' && callNode.func.value === customFunction) {
|
|
63
|
+
return ANALYTICS_SOURCES.CUSTOM;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = {
|
|
70
|
+
detectSource
|
|
71
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Tracking event extraction from Go function calls
|
|
3
|
+
* @module analyze/go/trackingExtractor
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { ANALYTICS_SOURCES } = require('./constants');
|
|
7
|
+
const { detectSource } = require('./trackingDetector');
|
|
8
|
+
const { extractEventName } = require('./eventExtractor');
|
|
9
|
+
const { extractProperties } = require('./propertyExtractor');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extract tracking event information from a node
|
|
13
|
+
* @param {Object} callNode - AST node representing a function call or struct literal
|
|
14
|
+
* @param {string} filePath - Path to the file being analyzed
|
|
15
|
+
* @param {string} functionName - Name of the function containing this tracking call
|
|
16
|
+
* @param {string|null} customFunction - Name of custom tracking function to detect
|
|
17
|
+
* @param {Object} typeContext - Type information context for variable resolution
|
|
18
|
+
* @param {string} currentFunction - Current function context for type lookups
|
|
19
|
+
* @returns {Object|null} Tracking event object with eventName, source, properties, etc., or null if not a tracking call
|
|
20
|
+
*/
|
|
21
|
+
function extractTrackingEvent(callNode, filePath, functionName, customFunction, typeContext, currentFunction) {
|
|
22
|
+
const source = detectSource(callNode, customFunction);
|
|
23
|
+
if (!source) return null;
|
|
24
|
+
|
|
25
|
+
const eventName = extractEventName(callNode, source);
|
|
26
|
+
if (!eventName) return null;
|
|
27
|
+
|
|
28
|
+
const properties = extractProperties(callNode, source, typeContext, currentFunction);
|
|
29
|
+
|
|
30
|
+
// Get line number based on source type
|
|
31
|
+
let line = 0;
|
|
32
|
+
if (source === ANALYTICS_SOURCES.SEGMENT || source === ANALYTICS_SOURCES.POSTHOG) {
|
|
33
|
+
// For Segment and PostHog, we need to get the line number from the struct.struct object
|
|
34
|
+
if (callNode.tag === 'structlit' && callNode.struct && callNode.struct.struct) {
|
|
35
|
+
line = callNode.struct.struct.line || 0;
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
// For other sources, use the line number from the AST node
|
|
39
|
+
line = callNode.line || 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return {
|
|
43
|
+
eventName,
|
|
44
|
+
source,
|
|
45
|
+
properties,
|
|
46
|
+
filePath,
|
|
47
|
+
line,
|
|
48
|
+
functionName
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = {
|
|
53
|
+
extractTrackingEvent
|
|
54
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Type context builder for Go AST analysis
|
|
3
|
+
* @module analyze/go/typeContext
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Build a context of type information from the AST
|
|
8
|
+
* @param {Array<Object>} ast - Abstract Syntax Tree of the Go file
|
|
9
|
+
* @returns {Object} Type context object with 'functions' and 'globals' properties
|
|
10
|
+
* @returns {Object} typeContext.functions - Map of function names to their parameter and local variable types
|
|
11
|
+
* @returns {Object} typeContext.globals - Map of global variable names to their types and values
|
|
12
|
+
*/
|
|
13
|
+
function buildTypeContext(ast) {
|
|
14
|
+
const context = {
|
|
15
|
+
functions: {},
|
|
16
|
+
globals: {}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
for (const node of ast) {
|
|
20
|
+
if (node.tag === 'func') {
|
|
21
|
+
// Store function parameter types
|
|
22
|
+
context.functions[node.name] = {
|
|
23
|
+
params: {},
|
|
24
|
+
locals: {}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
if (node.args) {
|
|
28
|
+
for (const arg of node.args) {
|
|
29
|
+
if (arg.name && arg.type) {
|
|
30
|
+
context.functions[node.name].params[arg.name] = { type: arg.type };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Scan function body for local variable declarations
|
|
36
|
+
if (node.body) {
|
|
37
|
+
scanForDeclarations(node.body, context.functions[node.name].locals);
|
|
38
|
+
}
|
|
39
|
+
} else if (node.tag === 'declare') {
|
|
40
|
+
// Global variable declarations
|
|
41
|
+
if (node.names && node.names.length > 0 && node.type) {
|
|
42
|
+
for (const name of node.names) {
|
|
43
|
+
context.globals[name] = { type: node.type, value: node.value };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return context;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Scan statements for variable declarations
|
|
54
|
+
* @param {Array<Object>} body - Array of AST statement nodes to scan
|
|
55
|
+
* @param {Object} locals - Object to store local variable declarations (modified in place)
|
|
56
|
+
*/
|
|
57
|
+
function scanForDeclarations(body, locals) {
|
|
58
|
+
for (const stmt of body) {
|
|
59
|
+
if (stmt.tag === 'declare') {
|
|
60
|
+
if (stmt.names && stmt.type) {
|
|
61
|
+
for (const name of stmt.names) {
|
|
62
|
+
locals[name] = { type: stmt.type, value: stmt.value };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} else if (stmt.tag === 'if' && stmt.body) {
|
|
66
|
+
scanForDeclarations(stmt.body, locals);
|
|
67
|
+
} else if (stmt.tag === 'elseif' && stmt.body) {
|
|
68
|
+
scanForDeclarations(stmt.body, locals);
|
|
69
|
+
} else if (stmt.tag === 'else' && stmt.body) {
|
|
70
|
+
scanForDeclarations(stmt.body, locals);
|
|
71
|
+
} else if (stmt.tag === 'for' && stmt.body) {
|
|
72
|
+
scanForDeclarations(stmt.body, locals);
|
|
73
|
+
} else if (stmt.tag === 'foreach' && stmt.body) {
|
|
74
|
+
scanForDeclarations(stmt.body, locals);
|
|
75
|
+
} else if (stmt.tag === 'switch' && stmt.cases) {
|
|
76
|
+
for (const caseNode of stmt.cases) {
|
|
77
|
+
if (caseNode.body) {
|
|
78
|
+
scanForDeclarations(caseNode.body, locals);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = {
|
|
86
|
+
buildTypeContext,
|
|
87
|
+
scanForDeclarations
|
|
88
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utility functions for Go AST analysis and type extraction
|
|
3
|
+
* @module analyze/go/utils
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Extract string value from various node types
|
|
8
|
+
* @param {Object} node - AST node that may contain a string value
|
|
9
|
+
* @returns {string|null} Extracted string value without quotes, or null if not found
|
|
10
|
+
*/
|
|
11
|
+
function extractStringValue(node) {
|
|
12
|
+
if (!node) return null;
|
|
13
|
+
|
|
14
|
+
// Handle direct string literals
|
|
15
|
+
if (node.tag === 'string') {
|
|
16
|
+
// Remove quotes from the value
|
|
17
|
+
return node.value.slice(1, -1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Handle expressions that might contain a string
|
|
21
|
+
if (node.tag === 'expr' && node.body && node.body.length > 0) {
|
|
22
|
+
// Look for string literals in the expression body
|
|
23
|
+
for (const item of node.body) {
|
|
24
|
+
if (item.tag === 'string') {
|
|
25
|
+
return item.value.slice(1, -1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Find a struct literal in an expression
|
|
35
|
+
* @param {Object} expr - AST expression node to search
|
|
36
|
+
* @returns {Object|null} Struct literal AST node or null if not found
|
|
37
|
+
*/
|
|
38
|
+
function findStructLiteral(expr) {
|
|
39
|
+
if (!expr) return null;
|
|
40
|
+
|
|
41
|
+
if (expr.tag === 'structlit') {
|
|
42
|
+
return expr;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (expr.tag === 'expr' && expr.body) {
|
|
46
|
+
for (const item of expr.body) {
|
|
47
|
+
if (item.tag === 'structlit') {
|
|
48
|
+
return item;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Find a field in a struct by name
|
|
58
|
+
* @param {Object} structlit - Struct literal AST node
|
|
59
|
+
* @param {string} fieldName - Name of the field to find
|
|
60
|
+
* @returns {Object|null} Field AST node or null if not found
|
|
61
|
+
*/
|
|
62
|
+
function findStructField(structlit, fieldName) {
|
|
63
|
+
if (!structlit.fields) return null;
|
|
64
|
+
|
|
65
|
+
for (const field of structlit.fields) {
|
|
66
|
+
const name = extractFieldName(field);
|
|
67
|
+
if (name === fieldName) {
|
|
68
|
+
return field;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Extract field name from a struct field
|
|
77
|
+
* @param {Object} field - Struct field AST node
|
|
78
|
+
* @returns {string|null} Field name or null if not found
|
|
79
|
+
*/
|
|
80
|
+
function extractFieldName(field) {
|
|
81
|
+
if (field.name) {
|
|
82
|
+
return field.name;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (field.value && field.value.tag === 'expr' && field.value.body) {
|
|
86
|
+
// Look for pattern: fieldName: value
|
|
87
|
+
const body = field.value.body;
|
|
88
|
+
if (body.length >= 3 &&
|
|
89
|
+
body[0].tag === 'ident' &&
|
|
90
|
+
body[1].tag === 'op' &&
|
|
91
|
+
body[1].value === ':') {
|
|
92
|
+
return body[0].value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Map Go types to schema types
|
|
101
|
+
* @param {Object} goType - Go type AST node
|
|
102
|
+
* @returns {Object} Schema type object with 'type' property and optionally 'items' or 'properties'
|
|
103
|
+
*/
|
|
104
|
+
function mapGoTypeToSchemaType(goType) {
|
|
105
|
+
if (!goType) return { type: 'any' };
|
|
106
|
+
|
|
107
|
+
// Handle case where goType might be an object with a type property
|
|
108
|
+
if (goType.type) {
|
|
109
|
+
goType = goType.type;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Handle simple types
|
|
113
|
+
if (goType.tag === 'string') return { type: 'string' };
|
|
114
|
+
if (goType.tag === 'bool') return { type: 'boolean' };
|
|
115
|
+
if (goType.tag === 'int' || goType.tag === 'int8' || goType.tag === 'int16' ||
|
|
116
|
+
goType.tag === 'int32' || goType.tag === 'int64' || goType.tag === 'uint' ||
|
|
117
|
+
goType.tag === 'uint8' || goType.tag === 'uint16' || goType.tag === 'uint32' ||
|
|
118
|
+
goType.tag === 'uint64' || goType.tag === 'float32' || goType.tag === 'float64' ||
|
|
119
|
+
goType.tag === 'byte' || goType.tag === 'rune') {
|
|
120
|
+
return { type: 'number' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Handle array types
|
|
124
|
+
if (goType.tag === 'array') {
|
|
125
|
+
const itemType = mapGoTypeToSchemaType(goType.item);
|
|
126
|
+
return {
|
|
127
|
+
type: 'array',
|
|
128
|
+
items: itemType
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle slice types (arrays without fixed size)
|
|
133
|
+
if (goType.tag === 'array' && !goType.size) {
|
|
134
|
+
const itemType = mapGoTypeToSchemaType(goType.item);
|
|
135
|
+
return {
|
|
136
|
+
type: 'array',
|
|
137
|
+
items: itemType
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Handle map types
|
|
142
|
+
if (goType.tag === 'map') {
|
|
143
|
+
return {
|
|
144
|
+
type: 'object',
|
|
145
|
+
properties: {}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Handle pointer types by dereferencing
|
|
150
|
+
if (goType.tag === 'ptr') {
|
|
151
|
+
return mapGoTypeToSchemaType(goType.item);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Default to any for complex or unknown types
|
|
155
|
+
return { type: 'any' };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Extract Snowplow values from sphelp.NewString/NewFloat64
|
|
160
|
+
* @param {Object} expr - Expression containing Snowplow helper function call
|
|
161
|
+
* @returns {string|number|null} Extracted value or null if not found
|
|
162
|
+
*/
|
|
163
|
+
function extractSnowplowValue(expr) {
|
|
164
|
+
if (!expr) return null;
|
|
165
|
+
|
|
166
|
+
// Direct value
|
|
167
|
+
if (expr.tag === 'string') {
|
|
168
|
+
return expr.value.slice(1, -1);
|
|
169
|
+
}
|
|
170
|
+
if (expr.tag === 'number') {
|
|
171
|
+
return parseFloat(expr.value);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Look for sphelp.NewString("value") or sphelp.NewFloat64(value)
|
|
175
|
+
if (expr.tag === 'expr' && expr.body) {
|
|
176
|
+
for (const item of expr.body) {
|
|
177
|
+
if (item.tag === 'call' && item.func && item.func.tag === 'access') {
|
|
178
|
+
if (item.func.member === 'NewString' && item.args && item.args.length > 0) {
|
|
179
|
+
return extractStringValue(item.args[0]);
|
|
180
|
+
}
|
|
181
|
+
if (item.func.member === 'NewFloat64' && item.args && item.args.length > 0) {
|
|
182
|
+
const numExpr = item.args[0];
|
|
183
|
+
if (numExpr.tag === 'number') {
|
|
184
|
+
return parseFloat(numExpr.value);
|
|
185
|
+
}
|
|
186
|
+
if (numExpr.tag === 'expr' && numExpr.body && numExpr.body[0] && numExpr.body[0].tag === 'number') {
|
|
187
|
+
return parseFloat(numExpr.body[0].value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Resolves a variable to its value by looking it up in the type context
|
|
199
|
+
* @param {string} varName - The variable name to resolve
|
|
200
|
+
* @param {Object} typeContext - The type context containing variable types
|
|
201
|
+
* @param {string} currentFunction - The current function scope
|
|
202
|
+
* @returns {Object|null} The resolved value or null
|
|
203
|
+
*/
|
|
204
|
+
function resolveVariable(varName, typeContext, currentFunction) {
|
|
205
|
+
// ... existing code ...
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = {
|
|
209
|
+
extractStringValue,
|
|
210
|
+
findStructLiteral,
|
|
211
|
+
findStructField,
|
|
212
|
+
extractFieldName,
|
|
213
|
+
mapGoTypeToSchemaType,
|
|
214
|
+
extractSnowplowValue
|
|
215
|
+
};
|
package/src/analyze/index.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Directory analyzer for detecting analytics tracking across multiple programming languages
|
|
3
|
+
* @module analyze-tracking/analyze
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
const path = require('path');
|
|
2
7
|
const ts = require('typescript');
|
|
3
|
-
const { getAllFiles } = require('../fileProcessor');
|
|
4
|
-
const { analyzeJsFile } = require('./
|
|
5
|
-
const { analyzeTsFile } = require('./
|
|
6
|
-
const { analyzePythonFile } = require('./
|
|
7
|
-
const { analyzeRubyFile } = require('./
|
|
8
|
-
const { analyzeGoFile } = require('./
|
|
8
|
+
const { getAllFiles } = require('../utils/fileProcessor');
|
|
9
|
+
const { analyzeJsFile } = require('./javascript');
|
|
10
|
+
const { analyzeTsFile } = require('./typescript');
|
|
11
|
+
const { analyzePythonFile } = require('./python');
|
|
12
|
+
const { analyzeRubyFile } = require('./ruby');
|
|
13
|
+
const { analyzeGoFile } = require('./go');
|
|
9
14
|
|
|
10
15
|
async function analyzeDirectory(dirPath, customFunction) {
|
|
11
16
|
const allEvents = {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Constants and configurations for analytics tracking providers
|
|
3
|
+
* @module analyze/javascript/constants
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Analytics provider configurations
|
|
8
|
+
* @typedef {Object} ProviderConfig
|
|
9
|
+
* @property {string} name - Provider display name
|
|
10
|
+
* @property {string} objectName - Object name in JavaScript
|
|
11
|
+
* @property {string} methodName - Method name for tracking
|
|
12
|
+
* @property {string} type - Type of detection (member|function)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Supported analytics providers and their detection patterns
|
|
17
|
+
* @type {Object.<string, ProviderConfig>}
|
|
18
|
+
*/
|
|
19
|
+
const ANALYTICS_PROVIDERS = {
|
|
20
|
+
SEGMENT: {
|
|
21
|
+
name: 'segment',
|
|
22
|
+
objectName: 'analytics',
|
|
23
|
+
methodName: 'track',
|
|
24
|
+
type: 'member'
|
|
25
|
+
},
|
|
26
|
+
MIXPANEL: {
|
|
27
|
+
name: 'mixpanel',
|
|
28
|
+
objectName: 'mixpanel',
|
|
29
|
+
methodName: 'track',
|
|
30
|
+
type: 'member'
|
|
31
|
+
},
|
|
32
|
+
AMPLITUDE: {
|
|
33
|
+
name: 'amplitude',
|
|
34
|
+
objectName: 'amplitude',
|
|
35
|
+
methodName: 'track',
|
|
36
|
+
type: 'member'
|
|
37
|
+
},
|
|
38
|
+
RUDDERSTACK: {
|
|
39
|
+
name: 'rudderstack',
|
|
40
|
+
objectName: 'rudderanalytics',
|
|
41
|
+
methodName: 'track',
|
|
42
|
+
type: 'member'
|
|
43
|
+
},
|
|
44
|
+
MPARTICLE: {
|
|
45
|
+
name: 'mparticle',
|
|
46
|
+
objectNames: ['mParticle', 'mparticle'],
|
|
47
|
+
methodName: 'logEvent',
|
|
48
|
+
type: 'member'
|
|
49
|
+
},
|
|
50
|
+
POSTHOG: {
|
|
51
|
+
name: 'posthog',
|
|
52
|
+
objectName: 'posthog',
|
|
53
|
+
methodName: 'capture',
|
|
54
|
+
type: 'member'
|
|
55
|
+
},
|
|
56
|
+
PENDO: {
|
|
57
|
+
name: 'pendo',
|
|
58
|
+
objectName: 'pendo',
|
|
59
|
+
methodName: 'track',
|
|
60
|
+
type: 'member'
|
|
61
|
+
},
|
|
62
|
+
HEAP: {
|
|
63
|
+
name: 'heap',
|
|
64
|
+
objectName: 'heap',
|
|
65
|
+
methodName: 'track',
|
|
66
|
+
type: 'member'
|
|
67
|
+
},
|
|
68
|
+
SNOWPLOW: {
|
|
69
|
+
name: 'snowplow',
|
|
70
|
+
objectName: 'tracker',
|
|
71
|
+
methodName: 'track',
|
|
72
|
+
type: 'member'
|
|
73
|
+
},
|
|
74
|
+
GOOGLE_ANALYTICS: {
|
|
75
|
+
name: 'googleanalytics',
|
|
76
|
+
functionName: 'gtag',
|
|
77
|
+
type: 'function'
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Parser options for Acorn
|
|
83
|
+
* @type {Object}
|
|
84
|
+
*/
|
|
85
|
+
const PARSER_OPTIONS = {
|
|
86
|
+
ecmaVersion: 'latest',
|
|
87
|
+
sourceType: 'module',
|
|
88
|
+
locations: true
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* AST node types
|
|
93
|
+
* @enum {string}
|
|
94
|
+
*/
|
|
95
|
+
const NODE_TYPES = {
|
|
96
|
+
CALL_EXPRESSION: 'CallExpression',
|
|
97
|
+
MEMBER_EXPRESSION: 'MemberExpression',
|
|
98
|
+
IDENTIFIER: 'Identifier',
|
|
99
|
+
OBJECT_EXPRESSION: 'ObjectExpression',
|
|
100
|
+
ARRAY_EXPRESSION: 'ArrayExpression',
|
|
101
|
+
LITERAL: 'Literal',
|
|
102
|
+
ARROW_FUNCTION: 'ArrowFunctionExpression',
|
|
103
|
+
FUNCTION_EXPRESSION: 'FunctionExpression',
|
|
104
|
+
FUNCTION_DECLARATION: 'FunctionDeclaration',
|
|
105
|
+
METHOD_DEFINITION: 'MethodDefinition',
|
|
106
|
+
VARIABLE_DECLARATOR: 'VariableDeclarator',
|
|
107
|
+
EXPORT_NAMED: 'ExportNamedDeclaration',
|
|
108
|
+
PROPERTY: 'Property'
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
module.exports = {
|
|
112
|
+
ANALYTICS_PROVIDERS,
|
|
113
|
+
PARSER_OPTIONS,
|
|
114
|
+
NODE_TYPES
|
|
115
|
+
};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Analytics source detection module
|
|
3
|
+
* @module analyze/javascript/detectors/analytics-source
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { ANALYTICS_PROVIDERS, NODE_TYPES } = require('../constants');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Detects the analytics provider from a CallExpression node
|
|
10
|
+
* @param {Object} node - AST CallExpression node
|
|
11
|
+
* @param {string} [customFunction] - Custom function name to detect
|
|
12
|
+
* @returns {string} The detected analytics source or 'unknown'
|
|
13
|
+
*/
|
|
14
|
+
function detectAnalyticsSource(node, customFunction) {
|
|
15
|
+
if (!node.callee) {
|
|
16
|
+
return 'unknown';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Check for custom function first
|
|
20
|
+
if (customFunction && isCustomFunction(node, customFunction)) {
|
|
21
|
+
return 'custom';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Check for function-based providers (e.g., gtag)
|
|
25
|
+
const functionSource = detectFunctionBasedProvider(node);
|
|
26
|
+
if (functionSource !== 'unknown') {
|
|
27
|
+
return functionSource;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Check for member-based providers (e.g., analytics.track)
|
|
31
|
+
const memberSource = detectMemberBasedProvider(node);
|
|
32
|
+
if (memberSource !== 'unknown') {
|
|
33
|
+
return memberSource;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return 'unknown';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Checks if the node is a custom function call
|
|
41
|
+
* @param {Object} node - AST CallExpression node
|
|
42
|
+
* @param {string} customFunction - Custom function name
|
|
43
|
+
* @returns {boolean}
|
|
44
|
+
*/
|
|
45
|
+
function isCustomFunction(node, customFunction) {
|
|
46
|
+
return node.callee.type === NODE_TYPES.IDENTIFIER &&
|
|
47
|
+
node.callee.name === customFunction;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Detects function-based analytics providers
|
|
52
|
+
* @param {Object} node - AST CallExpression node
|
|
53
|
+
* @returns {string} Provider name or 'unknown'
|
|
54
|
+
*/
|
|
55
|
+
function detectFunctionBasedProvider(node) {
|
|
56
|
+
if (node.callee.type !== NODE_TYPES.IDENTIFIER) {
|
|
57
|
+
return 'unknown';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const functionName = node.callee.name;
|
|
61
|
+
|
|
62
|
+
for (const provider of Object.values(ANALYTICS_PROVIDERS)) {
|
|
63
|
+
if (provider.type === 'function' && provider.functionName === functionName) {
|
|
64
|
+
return provider.name;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return 'unknown';
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Detects member expression-based analytics providers
|
|
73
|
+
* @param {Object} node - AST CallExpression node
|
|
74
|
+
* @returns {string} Provider name or 'unknown'
|
|
75
|
+
*/
|
|
76
|
+
function detectMemberBasedProvider(node) {
|
|
77
|
+
if (node.callee.type !== NODE_TYPES.MEMBER_EXPRESSION) {
|
|
78
|
+
return 'unknown';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const objectName = node.callee.object.name;
|
|
82
|
+
const methodName = node.callee.property.name;
|
|
83
|
+
|
|
84
|
+
if (!objectName || !methodName) {
|
|
85
|
+
return 'unknown';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
for (const provider of Object.values(ANALYTICS_PROVIDERS)) {
|
|
89
|
+
if (provider.type === 'member' && matchesMemberProvider(provider, objectName, methodName)) {
|
|
90
|
+
return provider.name;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return 'unknown';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Checks if object and method names match a provider configuration
|
|
99
|
+
* @param {Object} provider - Provider configuration
|
|
100
|
+
* @param {string} objectName - Object name from AST
|
|
101
|
+
* @param {string} methodName - Method name from AST
|
|
102
|
+
* @returns {boolean}
|
|
103
|
+
*/
|
|
104
|
+
function matchesMemberProvider(provider, objectName, methodName) {
|
|
105
|
+
if (provider.methodName !== methodName) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Handle providers with multiple possible object names (e.g., mParticle/mparticle)
|
|
110
|
+
if (provider.objectNames) {
|
|
111
|
+
return provider.objectNames.includes(objectName);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return provider.objectName === objectName;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
detectAnalyticsSource
|
|
119
|
+
};
|