Skip to content

Commit dae7859

Browse files
committed
multiple custom functions spec
1 parent afdc5ae commit dae7859

File tree

14 files changed

+134
-70
lines changed

14 files changed

+134
-70
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ yourCustomTrackFunctionName(userId, EVENT_NAME, PROPERTIES)
5757

5858
If your function follows the standard format `yourCustomTrackFunctionName(EVENT_NAME, PROPERTIES)`, you can simply pass in `yourCustomTrackFunctionName` to `--customFunction` as a shorthand.
5959

60+
You can also pass in multiple custom function signatures by passing in the `--customFunction` option multiple times or by passing in a space-separated list of function signatures.
61+
62+
```sh
63+
npx @flisk/analyze-tracking /path/to/project --customFunction "yourFunc1" --customFunction "yourFunc2(userId, EVENT_NAME, PROPERTIES)"
64+
npx @flisk/analyze-tracking /path/to/project -c "yourFunc1" "yourFunc2(userId, EVENT_NAME, PROPERTIES)"
65+
```
66+
6067

6168
## What's Generated?
6269
A clear YAML schema that shows where your events are tracked, their properties, and more.

bin/cli.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const optionDefinitions = [
4343
name: 'customFunction',
4444
alias: 'c',
4545
type: String,
46+
multiple: true,
4647
},
4748
{
4849
name: 'repositoryUrl',

src/analyze/go/index.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ const { extractGoAST } = require('./goAstParser');
88
const { buildTypeContext } = require('./typeContext');
99
const { deduplicateEvents } = require('./eventDeduplicator');
1010
const { extractEventsFromBody } = require('./astTraversal');
11-
const { processGoFile } = require('./utils');
12-
const { parseCustomFunctionSignature } = require('../utils/customFunctionParser');
1311

1412
/**
1513
* Analyze a Go file and extract tracking events
@@ -18,9 +16,10 @@ const { parseCustomFunctionSignature } = require('../utils/customFunctionParser'
1816
* @returns {Promise<Array>} Array of tracking events found in the file
1917
* @throws {Error} If the file cannot be read or parsed
2018
*/
21-
async function analyzeGoFile(filePath, customFunctionSignature) {
19+
async function analyzeGoFile(filePath, customFunctionSignatures = null) {
2220
try {
23-
const customConfig = customFunctionSignature ? parseCustomFunctionSignature(customFunctionSignature) : null;
21+
// temporary: only support one custom function signature for now, will add support for multiple in the future
22+
const customConfig = !!customFunctionSignatures?.length ? customFunctionSignatures[0] : null;
2423

2524
// Read the Go file
2625
const source = fs.readFileSync(filePath, 'utf8');

src/analyze/index.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,19 @@
55

66
const path = require('path');
77
const ts = require('typescript');
8+
const { parseCustomFunctionSignature } = require('./utils/customFunctionParser');
89
const { getAllFiles } = require('../utils/fileProcessor');
910
const { analyzeJsFile } = require('./javascript');
1011
const { analyzeTsFile } = require('./typescript');
1112
const { analyzePythonFile } = require('./python');
1213
const { analyzeRubyFile } = require('./ruby');
1314
const { analyzeGoFile } = require('./go');
1415

15-
async function analyzeDirectory(dirPath, customFunction) {
16+
async function analyzeDirectory(dirPath, customFunctions) {
1617
const allEvents = {};
1718

19+
const customFunctionSignatures = (customFunctions && customFunctions?.length > 0) ? customFunctions.map(parseCustomFunctionSignature) : null;
20+
1821
const files = getAllFiles(dirPath);
1922
const tsFiles = files.filter(file => /\.(tsx?)$/.test(file));
2023
const tsProgram = ts.createProgram(tsFiles, {
@@ -32,15 +35,15 @@ async function analyzeDirectory(dirPath, customFunction) {
3235
const isGoFile = /\.(go)$/.test(file);
3336

3437
if (isJsFile) {
35-
events = analyzeJsFile(file, customFunction);
38+
events = analyzeJsFile(file, customFunctionSignatures);
3639
} else if (isTsFile) {
37-
events = analyzeTsFile(file, tsProgram, customFunction);
40+
events = analyzeTsFile(file, tsProgram, customFunctionSignatures);
3841
} else if (isPythonFile) {
39-
events = await analyzePythonFile(file, customFunction);
42+
events = await analyzePythonFile(file, customFunctionSignatures);
4043
} else if (isRubyFile) {
41-
events = await analyzeRubyFile(file, customFunction);
44+
events = await analyzeRubyFile(file, customFunctionSignatures);
4245
} else if (isGoFile) {
43-
events = await analyzeGoFile(file, customFunction);
46+
events = await analyzeGoFile(file, customFunctionSignatures);
4447
} else {
4548
continue;
4649
}

src/analyze/javascript/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,18 @@
44
*/
55

66
const { parseFile, findTrackingEvents, FileReadError, ParseError } = require('./parser');
7-
const { parseCustomFunctionSignature } = require('../utils/customFunctionParser');
87

98
/**
109
* Analyzes a JavaScript file for analytics tracking calls
1110
* @param {string} filePath - Path to the JavaScript file to analyze
1211
* @param {string} [customFunction] - Optional custom function name to detect
1312
* @returns {Array<Object>} Array of tracking events found in the file
1413
*/
15-
function analyzeJsFile(filePath, customFunctionSignature) {
14+
function analyzeJsFile(filePath, customFunctionSignatures = null) {
1615
const events = [];
17-
const customConfig = customFunctionSignature ? parseCustomFunctionSignature(customFunctionSignature) : null;
16+
17+
// temporary: only support one custom function signature for now, will add support for multiple in the future
18+
const customConfig = !!customFunctionSignatures?.length ? customFunctionSignatures[0] : null;
1819

1920
try {
2021
// Parse the file into an AST

src/analyze/python/index.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
const fs = require('fs');
77
const path = require('path');
8-
const { parseCustomFunctionSignature } = require('../utils/customFunctionParser');
98

109
// Singleton instance of Pyodide
1110
let pyodide = null;
@@ -53,8 +52,9 @@ async function initPyodide() {
5352
* // With custom tracking function
5453
* const events = await analyzePythonFile('./app.py', 'track_event');
5554
*/
56-
async function analyzePythonFile(filePath, customFunctionSignature = null) {
57-
const customConfig = customFunctionSignature ? parseCustomFunctionSignature(customFunctionSignature) : null;
55+
async function analyzePythonFile(filePath, customFunctionSignatures = null) {
56+
// temporary: only support one custom function signature for now, will add support for multiple in the future
57+
const customConfig = !!customFunctionSignatures?.length ? customFunctionSignatures[0] : null;
5858

5959
// Validate inputs
6060
if (!filePath || typeof filePath !== 'string') {

src/analyze/ruby/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
const fs = require('fs');
77
const TrackingVisitor = require('./visitor');
8-
const { parseCustomFunctionSignature } = require('../utils/customFunctionParser');
98

109
// Lazy-loaded parse function from Ruby Prism
1110
let parse = null;
@@ -17,7 +16,7 @@ let parse = null;
1716
* @returns {Promise<Array>} Array of tracking events found in the file
1817
* @throws {Error} If the file cannot be read or parsed
1918
*/
20-
async function analyzeRubyFile(filePath, customFunctionSignature) {
19+
async function analyzeRubyFile(filePath, customFunctionSignatures = null) {
2120
// Lazy load the Ruby Prism parser
2221
if (!parse) {
2322
const { loadPrism } = await import('@ruby/prism');
@@ -37,8 +36,10 @@ async function analyzeRubyFile(filePath, customFunctionSignature) {
3736
return []; // Return empty events array if parsing fails
3837
}
3938

39+
// temporary: only support one custom function signature for now, will add support for multiple in the future
40+
const customConfig = !!customFunctionSignatures?.length ? customFunctionSignatures[0] : null;
41+
4042
// Create a visitor and analyze the AST
41-
const customConfig = customFunctionSignature ? parseCustomFunctionSignature(customFunctionSignature) : null;
4243
const visitor = new TrackingVisitor(code, filePath, customConfig);
4344
const events = await visitor.analyze(ast);
4445

src/analyze/typescript/index.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
const { getProgram, findTrackingEvents, ProgramError, SourceFileError } = require('./parser');
7-
const { parseCustomFunctionSignature } = require('../utils/customFunctionParser');
87

98
/**
109
* Analyzes a TypeScript file for analytics tracking calls
@@ -13,9 +12,11 @@ const { parseCustomFunctionSignature } = require('../utils/customFunctionParser'
1312
* @param {string} [customFunctionSignature] - Optional custom function signature to detect
1413
* @returns {Array<Object>} Array of tracking events found in the file
1514
*/
16-
function analyzeTsFile(filePath, program = null, customFunctionSignature = null) {
15+
function analyzeTsFile(filePath, program = null, customFunctionSignatures = null) {
1716
const events = [];
18-
const customConfig = customFunctionSignature ? parseCustomFunctionSignature(customFunctionSignature) : null;
17+
18+
// temporary: only support one custom function signature for now, will add support for multiple in the future
19+
const customConfig = !!customFunctionSignatures?.length ? customFunctionSignatures[0] : null;
1920

2021
try {
2122
// Get or create TypeScript program

src/index.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ const { generateDescriptions } = require('./generateDescriptions');
1111
const { ChatOpenAI } = require('@langchain/openai');
1212
const { ChatVertexAI } = require('@langchain/google-vertexai');
1313

14-
async function run(targetDir, outputPath, customFunction, customSourceDetails, generateDescription, provider, model, stdout, format) {
15-
let events = await analyzeDirectory(targetDir, customFunction);
14+
async function run(targetDir, outputPath, customFunctions, customSourceDetails, generateDescription, provider, model, stdout, format) {
15+
let events = await analyzeDirectory(targetDir, customFunctions);
1616
if (generateDescription) {
1717
let llm;
1818
if (provider === 'openai') {

tests/analyzeGo.test.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ const test = require('node:test');
22
const assert = require('node:assert');
33
const path = require('path');
44
const { analyzeGoFile } = require('../src/analyze/go');
5+
const { parseCustomFunctionSignature } = require('../src/analyze/utils/customFunctionParser');
56

67
test.describe('analyzeGoFile', () => {
78
const fixturesDir = path.join(__dirname, 'fixtures');
89
const testFilePath = path.join(fixturesDir, 'go', 'main.go');
910

1011
test('should correctly analyze Go file with multiple tracking providers', async () => {
1112
const customFunction = 'customTrackFunction(userId, EVENT_NAME, PROPERTIES)';
12-
const events = await analyzeGoFile(testFilePath, customFunction);
13+
const customFunctionSignatures = [parseCustomFunctionSignature(customFunction)];
14+
const events = await analyzeGoFile(testFilePath, customFunctionSignatures);
1315

1416
// Sort events by eventName for consistent ordering
1517
events.sort((a, b) => a.eventName.localeCompare(b.eventName));
@@ -103,7 +105,8 @@ test.describe('analyzeGoFile', () => {
103105

104106
test('should handle files without tracking events', async () => {
105107
const emptyTestFile = path.join(fixturesDir, 'go', 'empty.go');
106-
const events = await analyzeGoFile(emptyTestFile, 'customTrack');
108+
const customFunctionSignatures = [parseCustomFunctionSignature('customTrack')];
109+
const events = await analyzeGoFile(emptyTestFile, customFunctionSignatures);
107110
assert.deepStrictEqual(events, []);
108111
});
109112

@@ -117,7 +120,8 @@ test.describe('analyzeGoFile', () => {
117120

118121
test('should handle nested property types correctly', async () => {
119122
const customFunction = 'customTrackFunction(userId, EVENT_NAME, PROPERTIES)';
120-
const events = await analyzeGoFile(testFilePath, customFunction);
123+
const customFunctionSignatures = [parseCustomFunctionSignature(customFunction)];
124+
const events = await analyzeGoFile(testFilePath, customFunctionSignatures);
121125

122126
const customEvent = events.find(e => e.eventName === 'custom_event');
123127
assert.ok(customEvent);
@@ -141,7 +145,8 @@ test.describe('analyzeGoFile', () => {
141145

142146
test('should match expected tracking-schema.yaml output', async () => {
143147
const customFunction = 'customTrackFunction(userId, EVENT_NAME, PROPERTIES)';
144-
const events = await analyzeGoFile(testFilePath, customFunction);
148+
const customFunctionSignatures = [parseCustomFunctionSignature(customFunction)];
149+
const events = await analyzeGoFile(testFilePath, customFunctionSignatures);
145150

146151
// Create a map of events by name for easier verification
147152
const eventMap = {};
@@ -252,7 +257,8 @@ test.describe('analyzeGoFile', () => {
252257
];
253258

254259
for (const { sig, event } of variants) {
255-
const events = await analyzeGoFile(testFilePath, sig);
260+
const customFunctionSignatures = [parseCustomFunctionSignature(sig)];
261+
const events = await analyzeGoFile(testFilePath, customFunctionSignatures);
256262
const found = events.find(e => e.eventName === event && e.source === 'custom');
257263
assert.ok(found, `Should detect ${event} for signature ${sig}`);
258264
}

0 commit comments

Comments
 (0)