@@ -53,53 +53,69 @@ async function initPyodide() {
5353 * const events = await analyzePythonFile('./app.py', 'track_event');
5454 */
5555async 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 ;
58-
5956 // Validate inputs
6057 if ( ! filePath || typeof filePath !== 'string' ) {
6158 console . error ( 'Invalid file path provided' ) ;
6259 return [ ] ;
6360 }
6461
65- try {
66- // Check if file exists before reading
67- if ( ! fs . existsSync ( filePath ) ) {
68- console . error ( `File not found: ${ filePath } ` ) ;
69- return [ ] ;
70- }
62+ // Check if file exists before reading
63+ if ( ! fs . existsSync ( filePath ) ) {
64+ console . error ( `File not found: ${ filePath } ` ) ;
65+ return [ ] ;
66+ }
7167
72- // Read the Python file
68+ try {
69+ // Read the Python file only once
7370 const code = fs . readFileSync ( filePath , 'utf8' ) ;
74-
71+
7572 // Initialize Pyodide if not already done
7673 const py = await initPyodide ( ) ;
77-
78- // Load the Python analyzer code
74+
75+ // Load the Python analyzer code (idempotent – redefining functions is fine)
7976 const analyzerPath = path . join ( __dirname , 'pythonTrackingAnalyzer.py' ) ;
8077 if ( ! fs . existsSync ( analyzerPath ) ) {
8178 throw new Error ( `Python analyzer not found at: ${ analyzerPath } ` ) ;
8279 }
83-
8480 const analyzerCode = fs . readFileSync ( analyzerPath , 'utf8' ) ;
85-
86- // Set up Python environment with necessary variables
87- py . globals . set ( 'code' , code ) ;
88- py . globals . set ( 'filepath' , filePath ) ;
89- py . globals . set ( 'custom_config_json' , customConfig ? JSON . stringify ( customConfig ) : null ) ;
90- py . runPython ( 'import json' ) ;
91- py . runPython ( 'custom_config = None if custom_config_json == None else json.loads(custom_config_json)' ) ;
92- // Set __name__ to null to prevent execution of main block
81+ // Prevent the analyzer from executing any __main__ blocks that expect CLI usage
9382 py . globals . set ( '__name__' , null ) ;
94-
95- // Load and run the analyzer
9683 py . runPython ( analyzerCode ) ;
97-
98- // Execute the analysis and parse result
99- const result = py . runPython ( 'analyze_python_code(code, filepath, custom_config)' ) ;
100- const events = JSON . parse ( result ) ;
101-
102- return events ;
84+
85+ // Helper to run analysis with a given custom config (can be null)
86+ const runAnalysis = ( customConfig ) => {
87+ py . globals . set ( 'code' , code ) ;
88+ py . globals . set ( 'filepath' , filePath ) ;
89+ py . globals . set ( 'custom_config_json' , customConfig ? JSON . stringify ( customConfig ) : null ) ;
90+ py . runPython ( 'import json' ) ;
91+ py . runPython ( 'custom_config = None if custom_config_json == None else json.loads(custom_config_json)' ) ;
92+ const result = py . runPython ( 'analyze_python_code(code, filepath, custom_config)' ) ;
93+ return JSON . parse ( result ) ;
94+ } ;
95+
96+ const events = [ ] ;
97+
98+ // Built-in providers pass (no custom config)
99+ events . push ( ...runAnalysis ( null ) ) ;
100+
101+ // Custom configs passes
102+ if ( Array . isArray ( customFunctionSignatures ) && customFunctionSignatures . length > 0 ) {
103+ for ( const customConfig of customFunctionSignatures ) {
104+ if ( ! customConfig ) continue ;
105+ events . push ( ...runAnalysis ( customConfig ) ) ;
106+ }
107+ }
108+
109+ // Deduplicate events
110+ const uniqueEvents = new Map ( ) ;
111+ for ( const evt of events ) {
112+ const key = `${ evt . source } |${ evt . eventName } |${ evt . line } |${ evt . functionName } ` ;
113+ if ( ! uniqueEvents . has ( key ) ) {
114+ uniqueEvents . set ( key , evt ) ;
115+ }
116+ }
117+
118+ return Array . from ( uniqueEvents . values ( ) ) ;
103119 } catch ( error ) {
104120 // Log detailed error information for debugging
105121 console . error ( `Error analyzing Python file ${ filePath } :` , error ) ;
0 commit comments