33 * @module analyze/typescript
44 */
55
6- const { getProgram, findTrackingEvents, ProgramError, SourceFileError } = require ( './parser' ) ;
6+ const { getProgram, findTrackingEvents, ProgramError, SourceFileError, DEFAULT_COMPILER_OPTIONS } = require ( './parser' ) ;
7+ const ts = require ( 'typescript' ) ;
8+ const path = require ( 'path' ) ;
9+
10+ /**
11+ * Creates a standalone TypeScript program for a single file
12+ * This is used as a fallback when the main program can't resolve the file
13+ * @param {string } filePath - Path to the TypeScript file
14+ * @returns {Object } TypeScript program
15+ */
16+ function createStandaloneProgram ( filePath ) {
17+ const compilerOptions = {
18+ ...DEFAULT_COMPILER_OPTIONS ,
19+ noResolve : true , // Don't try to resolve imports - just analyze the file content
20+ isolatedModules : true
21+ } ;
22+
23+ return ts . createProgram ( [ filePath ] , compilerOptions ) ;
24+ }
25+
26+ /**
27+ * Deduplicates events based on source, eventName, line, and functionName
28+ * @param {Array<Object> } events - Array of events to deduplicate
29+ * @returns {Array<Object> } Deduplicated events
30+ */
31+ function deduplicateEvents ( events ) {
32+ const uniqueEvents = new Map ( ) ;
33+
34+ for ( const event of events ) {
35+ const key = `${ event . source } |${ event . eventName } |${ event . line } |${ event . functionName } ` ;
36+ if ( ! uniqueEvents . has ( key ) ) {
37+ uniqueEvents . set ( key , event ) ;
38+ }
39+ }
40+
41+ return Array . from ( uniqueEvents . values ( ) ) ;
42+ }
43+
44+ /**
45+ * Attempts to analyze a file using a standalone program as fallback
46+ * @param {string } filePath - Path to the TypeScript file
47+ * @param {Array } customFunctionSignatures - Custom function signatures to detect
48+ * @returns {Array<Object> } Array of events or empty array if failed
49+ */
50+ function tryStandaloneAnalysis ( filePath , customFunctionSignatures ) {
51+ try {
52+ console . warn ( `Unable to resolve ${ filePath } in main program. Attempting standalone analysis.` ) ;
53+
54+ const standaloneProgram = createStandaloneProgram ( filePath ) ;
55+ const sourceFile = standaloneProgram . getSourceFile ( filePath ) ;
56+
57+ if ( ! sourceFile ) {
58+ console . warn ( `Standalone analysis failed: could not get source file for ${ filePath } ` ) ;
59+ return [ ] ;
60+ }
61+
62+ const checker = standaloneProgram . getTypeChecker ( ) ;
63+ const events = findTrackingEvents ( sourceFile , checker , filePath , customFunctionSignatures || [ ] ) ;
64+
65+ return deduplicateEvents ( events ) ;
66+ } catch ( standaloneError ) {
67+ console . warn ( `Standalone analysis failed for ${ filePath } : ${ standaloneError . message } ` ) ;
68+ return [ ] ;
69+ }
70+ }
71+
72+ /**
73+ * Gets or creates a cached TypeScript program for efficient reuse
74+ * @param {string } filePath - Path to the TypeScript file
75+ * @param {Map } programCache - Map of tsconfig paths to programs
76+ * @returns {Object } TypeScript program
77+ */
78+ function getCachedTsProgram ( filePath , programCache ) {
79+ // Find the nearest tsconfig.json
80+ const searchPath = path . dirname ( filePath ) ;
81+ const configPath = ts . findConfigFile ( searchPath , ts . sys . fileExists , 'tsconfig.json' ) ;
82+
83+ // Use config path as cache key, or 'standalone' if no config found
84+ const cacheKey = configPath || 'standalone' ;
85+
86+ // Return cached program if available
87+ if ( programCache . has ( cacheKey ) ) {
88+ return programCache . get ( cacheKey ) ;
89+ }
90+
91+ // Create new program and cache it
92+ const program = getProgram ( filePath , null ) ;
93+ programCache . set ( cacheKey , program ) ;
94+
95+ return program ;
96+ }
797
898/**
999 * Analyzes a TypeScript file for analytics tracking calls
10100 * @param {string } filePath - Path to the TypeScript file to analyze
11101 * @param {Object } [program] - Optional existing TypeScript program to reuse
12- * @param {string } [customFunctionSignature ] - Optional custom function signature to detect
102+ * @param {Array } [customFunctionSignatures ] - Optional custom function signatures to detect
13103 * @returns {Array<Object> } Array of tracking events found in the file
14104 */
15105function analyzeTsFile ( filePath , program = null , customFunctionSignatures = null ) {
16106 try {
17- // Get or create TypeScript program (only once)
107+ // Get or create TypeScript program
18108 const tsProgram = getProgram ( filePath , program ) ;
19109
20110 // Get source file from program
21111 const sourceFile = tsProgram . getSourceFile ( filePath ) ;
22112 if ( ! sourceFile ) {
23- throw new SourceFileError ( filePath ) ;
113+ // Try standalone analysis as fallback
114+ return tryStandaloneAnalysis ( filePath , customFunctionSignatures ) ;
24115 }
25116
26- // Get type checker
117+ // Get type checker and find tracking events
27118 const checker = tsProgram . getTypeChecker ( ) ;
28-
29- // Single-pass collection covering built-in + all custom configs
30119 const events = findTrackingEvents ( sourceFile , checker , filePath , customFunctionSignatures || [ ] ) ;
31120
32- // Deduplicate events
33- const unique = new Map ( ) ;
34- for ( const evt of events ) {
35- const key = `${ evt . source } |${ evt . eventName } |${ evt . line } |${ evt . functionName } ` ;
36- if ( ! unique . has ( key ) ) unique . set ( key , evt ) ;
37- }
38-
39- return Array . from ( unique . values ( ) ) ;
121+ return deduplicateEvents ( events ) ;
40122
41123 } catch ( error ) {
42124 if ( error instanceof ProgramError ) {
@@ -46,9 +128,37 @@ function analyzeTsFile(filePath, program = null, customFunctionSignatures = null
46128 } else {
47129 console . error ( `Error analyzing TypeScript file ${ filePath } : ${ error . message } ` ) ;
48130 }
131+
132+ return [ ] ;
49133 }
134+ }
50135
51- return [ ] ;
136+ /**
137+ * Analyzes multiple TypeScript files with program reuse for better performance
138+ * @param {Array<string> } tsFiles - Array of TypeScript file paths
139+ * @param {Array } customFunctionSignatures - Custom function signatures to detect
140+ * @returns {Array<Object> } Array of all tracking events found across all files
141+ */
142+ function analyzeTsFiles ( tsFiles , customFunctionSignatures ) {
143+ const allEvents = [ ] ;
144+ const tsProgramCache = new Map ( ) ; // tsconfig path -> program
145+
146+ for ( const file of tsFiles ) {
147+ try {
148+ // Use cached program or create new one
149+ const program = getCachedTsProgram ( file , tsProgramCache ) ;
150+ const events = analyzeTsFile ( file , program , customFunctionSignatures ) ;
151+
152+ allEvents . push ( ...events ) ;
153+ } catch ( error ) {
154+ console . warn ( `Error processing TypeScript file ${ file } : ${ error . message } ` ) ;
155+ }
156+ }
157+
158+ return allEvents ;
52159}
53160
54- module . exports = { analyzeTsFile } ;
161+ module . exports = {
162+ analyzeTsFile,
163+ analyzeTsFiles
164+ } ;
0 commit comments