baseguard 1.0.0

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.
Files changed (244) hide show
  1. package/.eslintrc.json +25 -0
  2. package/.prettierrc +8 -0
  3. package/README.md +94 -0
  4. package/bin/base.js +494 -0
  5. package/dist/ai/fix-manager.d.ts +67 -0
  6. package/dist/ai/fix-manager.d.ts.map +1 -0
  7. package/dist/ai/fix-manager.js +326 -0
  8. package/dist/ai/fix-manager.js.map +1 -0
  9. package/dist/ai/gemini-analyzer.d.ts +116 -0
  10. package/dist/ai/gemini-analyzer.d.ts.map +1 -0
  11. package/dist/ai/gemini-analyzer.js +572 -0
  12. package/dist/ai/gemini-analyzer.js.map +1 -0
  13. package/dist/ai/index.d.ts +4 -0
  14. package/dist/ai/index.d.ts.map +1 -0
  15. package/dist/ai/index.js +5 -0
  16. package/dist/ai/index.js.map +1 -0
  17. package/dist/ai/jules-implementer.d.ts +115 -0
  18. package/dist/ai/jules-implementer.d.ts.map +1 -0
  19. package/dist/ai/jules-implementer.js +387 -0
  20. package/dist/ai/jules-implementer.js.map +1 -0
  21. package/dist/commands/automation.d.ts +5 -0
  22. package/dist/commands/automation.d.ts.map +1 -0
  23. package/dist/commands/automation.js +305 -0
  24. package/dist/commands/automation.js.map +1 -0
  25. package/dist/commands/check.d.ts +9 -0
  26. package/dist/commands/check.d.ts.map +1 -0
  27. package/dist/commands/check.js +113 -0
  28. package/dist/commands/check.js.map +1 -0
  29. package/dist/commands/config.d.ts +11 -0
  30. package/dist/commands/config.d.ts.map +1 -0
  31. package/dist/commands/config.js +324 -0
  32. package/dist/commands/config.js.map +1 -0
  33. package/dist/commands/fix.d.ts +9 -0
  34. package/dist/commands/fix.d.ts.map +1 -0
  35. package/dist/commands/fix.js +207 -0
  36. package/dist/commands/fix.js.map +1 -0
  37. package/dist/commands/index.d.ts +6 -0
  38. package/dist/commands/index.d.ts.map +1 -0
  39. package/dist/commands/index.js +7 -0
  40. package/dist/commands/index.js.map +1 -0
  41. package/dist/commands/init.d.ts +9 -0
  42. package/dist/commands/init.d.ts.map +1 -0
  43. package/dist/commands/init.js +125 -0
  44. package/dist/commands/init.js.map +1 -0
  45. package/dist/core/api-key-manager.d.ts +83 -0
  46. package/dist/core/api-key-manager.d.ts.map +1 -0
  47. package/dist/core/api-key-manager.js +244 -0
  48. package/dist/core/api-key-manager.js.map +1 -0
  49. package/dist/core/baseguard.d.ts +46 -0
  50. package/dist/core/baseguard.d.ts.map +1 -0
  51. package/dist/core/baseguard.js +132 -0
  52. package/dist/core/baseguard.js.map +1 -0
  53. package/dist/core/baseline-checker.d.ts +63 -0
  54. package/dist/core/baseline-checker.d.ts.map +1 -0
  55. package/dist/core/baseline-checker.js +502 -0
  56. package/dist/core/baseline-checker.js.map +1 -0
  57. package/dist/core/cache-manager.d.ts +88 -0
  58. package/dist/core/cache-manager.d.ts.map +1 -0
  59. package/dist/core/cache-manager.js +213 -0
  60. package/dist/core/cache-manager.js.map +1 -0
  61. package/dist/core/configuration.d.ts +140 -0
  62. package/dist/core/configuration.d.ts.map +1 -0
  63. package/dist/core/configuration.js +474 -0
  64. package/dist/core/configuration.js.map +1 -0
  65. package/dist/core/directory-filter.d.ts +90 -0
  66. package/dist/core/directory-filter.d.ts.map +1 -0
  67. package/dist/core/directory-filter.js +319 -0
  68. package/dist/core/directory-filter.js.map +1 -0
  69. package/dist/core/error-handler.d.ts +110 -0
  70. package/dist/core/error-handler.d.ts.map +1 -0
  71. package/dist/core/error-handler.js +392 -0
  72. package/dist/core/error-handler.js.map +1 -0
  73. package/dist/core/file-processor.d.ts +80 -0
  74. package/dist/core/file-processor.d.ts.map +1 -0
  75. package/dist/core/file-processor.js +259 -0
  76. package/dist/core/file-processor.js.map +1 -0
  77. package/dist/core/gitignore-manager.d.ts +44 -0
  78. package/dist/core/gitignore-manager.d.ts.map +1 -0
  79. package/dist/core/gitignore-manager.js +147 -0
  80. package/dist/core/gitignore-manager.js.map +1 -0
  81. package/dist/core/index.d.ts +13 -0
  82. package/dist/core/index.d.ts.map +1 -0
  83. package/dist/core/index.js +13 -0
  84. package/dist/core/index.js.map +1 -0
  85. package/dist/core/lazy-loader.d.ts +68 -0
  86. package/dist/core/lazy-loader.d.ts.map +1 -0
  87. package/dist/core/lazy-loader.js +260 -0
  88. package/dist/core/lazy-loader.js.map +1 -0
  89. package/dist/core/memory-manager.d.ts +1 -0
  90. package/dist/core/memory-manager.d.ts.map +1 -0
  91. package/dist/core/memory-manager.js +2 -0
  92. package/dist/core/memory-manager.js.map +1 -0
  93. package/dist/core/startup-optimizer.d.ts +45 -0
  94. package/dist/core/startup-optimizer.d.ts.map +1 -0
  95. package/dist/core/startup-optimizer.js +140 -0
  96. package/dist/core/startup-optimizer.js.map +1 -0
  97. package/dist/git/automation-engine.d.ts +58 -0
  98. package/dist/git/automation-engine.d.ts.map +1 -0
  99. package/dist/git/automation-engine.js +318 -0
  100. package/dist/git/automation-engine.js.map +1 -0
  101. package/dist/git/github-manager.d.ts +71 -0
  102. package/dist/git/github-manager.d.ts.map +1 -0
  103. package/dist/git/github-manager.js +226 -0
  104. package/dist/git/github-manager.js.map +1 -0
  105. package/dist/git/hook-manager.d.ts +43 -0
  106. package/dist/git/hook-manager.d.ts.map +1 -0
  107. package/dist/git/hook-manager.js +191 -0
  108. package/dist/git/hook-manager.js.map +1 -0
  109. package/dist/git/index.d.ts +4 -0
  110. package/dist/git/index.d.ts.map +1 -0
  111. package/dist/git/index.js +5 -0
  112. package/dist/git/index.js.map +1 -0
  113. package/dist/index.d.ts +8 -0
  114. package/dist/index.d.ts.map +1 -0
  115. package/dist/index.js +9 -0
  116. package/dist/index.js.map +1 -0
  117. package/dist/parsers/feature-validator.d.ts +60 -0
  118. package/dist/parsers/feature-validator.d.ts.map +1 -0
  119. package/dist/parsers/feature-validator.js +483 -0
  120. package/dist/parsers/feature-validator.js.map +1 -0
  121. package/dist/parsers/index.d.ts +8 -0
  122. package/dist/parsers/index.d.ts.map +1 -0
  123. package/dist/parsers/index.js +9 -0
  124. package/dist/parsers/index.js.map +1 -0
  125. package/dist/parsers/parser-manager.d.ts +103 -0
  126. package/dist/parsers/parser-manager.d.ts.map +1 -0
  127. package/dist/parsers/parser-manager.js +321 -0
  128. package/dist/parsers/parser-manager.js.map +1 -0
  129. package/dist/parsers/parser.d.ts +23 -0
  130. package/dist/parsers/parser.d.ts.map +1 -0
  131. package/dist/parsers/parser.js +6 -0
  132. package/dist/parsers/parser.js.map +1 -0
  133. package/dist/parsers/react-parser.d.ts +22 -0
  134. package/dist/parsers/react-parser.d.ts.map +1 -0
  135. package/dist/parsers/react-parser.js +307 -0
  136. package/dist/parsers/react-parser.js.map +1 -0
  137. package/dist/parsers/svelte-parser.d.ts +33 -0
  138. package/dist/parsers/svelte-parser.d.ts.map +1 -0
  139. package/dist/parsers/svelte-parser.js +408 -0
  140. package/dist/parsers/svelte-parser.js.map +1 -0
  141. package/dist/parsers/vanilla-parser.d.ts +31 -0
  142. package/dist/parsers/vanilla-parser.d.ts.map +1 -0
  143. package/dist/parsers/vanilla-parser.js +590 -0
  144. package/dist/parsers/vanilla-parser.js.map +1 -0
  145. package/dist/parsers/vue-parser.d.ts +9 -0
  146. package/dist/parsers/vue-parser.d.ts.map +1 -0
  147. package/dist/parsers/vue-parser.js +16 -0
  148. package/dist/parsers/vue-parser.js.map +1 -0
  149. package/dist/terminal-header.d.ts +12 -0
  150. package/dist/terminal-header.js +45 -0
  151. package/dist/types/index.d.ts +83 -0
  152. package/dist/types/index.d.ts.map +1 -0
  153. package/dist/types/index.js +5 -0
  154. package/dist/types/index.js.map +1 -0
  155. package/dist/ui/components.d.ts +133 -0
  156. package/dist/ui/components.d.ts.map +1 -0
  157. package/dist/ui/components.js +482 -0
  158. package/dist/ui/components.js.map +1 -0
  159. package/dist/ui/help.d.ts +11 -0
  160. package/dist/ui/help.d.ts.map +1 -0
  161. package/dist/ui/help.js +161 -0
  162. package/dist/ui/help.js.map +1 -0
  163. package/dist/ui/index.d.ts +5 -0
  164. package/dist/ui/index.d.ts.map +1 -0
  165. package/dist/ui/index.js +5 -0
  166. package/dist/ui/index.js.map +1 -0
  167. package/dist/ui/prompts.d.ts +63 -0
  168. package/dist/ui/prompts.d.ts.map +1 -0
  169. package/dist/ui/prompts.js +611 -0
  170. package/dist/ui/prompts.js.map +1 -0
  171. package/dist/ui/terminal-header.d.ts +13 -0
  172. package/dist/ui/terminal-header.d.ts.map +1 -0
  173. package/dist/ui/terminal-header.js +46 -0
  174. package/dist/ui/terminal-header.js.map +1 -0
  175. package/package.json +80 -0
  176. package/src/ai/__tests__/gemini-analyzer.test.ts +181 -0
  177. package/src/ai/fix-manager.ts +362 -0
  178. package/src/ai/gemini-analyzer.ts +671 -0
  179. package/src/ai/index.ts +4 -0
  180. package/src/ai/jules-implementer.ts +459 -0
  181. package/src/commands/automation.ts +344 -0
  182. package/src/commands/check.ts +299 -0
  183. package/src/commands/config.ts +365 -0
  184. package/src/commands/fix.ts +234 -0
  185. package/src/commands/index.ts +6 -0
  186. package/src/commands/init.ts +142 -0
  187. package/src/commands/status.ts +0 -0
  188. package/src/core/api-key-manager.ts +298 -0
  189. package/src/core/baseguard.ts +742 -0
  190. package/src/core/baseline-checker.ts +563 -0
  191. package/src/core/cache-manager.ts +270 -0
  192. package/src/core/configuration-recovery.ts +676 -0
  193. package/src/core/configuration.ts +559 -0
  194. package/src/core/debug-logger.ts +590 -0
  195. package/src/core/directory-filter.ts +421 -0
  196. package/src/core/error-handler.ts +517 -0
  197. package/src/core/file-processor.ts +331 -0
  198. package/src/core/gitignore-manager.ts +169 -0
  199. package/src/core/graceful-degradation-manager.ts +596 -0
  200. package/src/core/index.ts +13 -0
  201. package/src/core/lazy-loader.ts +307 -0
  202. package/src/core/logger.ts +0 -0
  203. package/src/core/memory-manager.ts +294 -0
  204. package/src/core/startup-optimizer.ts +173 -0
  205. package/src/core/system-error-handler.ts +746 -0
  206. package/src/git/automation-engine.ts +361 -0
  207. package/src/git/github-manager.ts +260 -0
  208. package/src/git/hook-manager.ts +210 -0
  209. package/src/git/index.ts +4 -0
  210. package/src/index.ts +8 -0
  211. package/src/parsers/feature-validator.ts +559 -0
  212. package/src/parsers/index.ts +8 -0
  213. package/src/parsers/parser-manager.ts +419 -0
  214. package/src/parsers/parser.ts +26 -0
  215. package/src/parsers/react-parser-optimized.ts +161 -0
  216. package/src/parsers/react-parser.ts +359 -0
  217. package/src/parsers/svelte-parser.ts +506 -0
  218. package/src/parsers/vanilla-parser.ts +682 -0
  219. package/src/parsers/vue-parser.ts +472 -0
  220. package/src/types/index.ts +92 -0
  221. package/src/ui/components.ts +567 -0
  222. package/src/ui/help.ts +193 -0
  223. package/src/ui/index.ts +4 -0
  224. package/src/ui/prompts.ts +688 -0
  225. package/src/ui/terminal-header.ts +59 -0
  226. package/test-config-commands.js +56 -0
  227. package/test-header-simple.js +33 -0
  228. package/test-terminal-header.js +12 -0
  229. package/test-ui.js +29 -0
  230. package/tests/e2e/baseguard.e2e.test.ts +516 -0
  231. package/tests/e2e/cross-platform.e2e.test.ts +420 -0
  232. package/tests/e2e/git-integration.e2e.test.ts +487 -0
  233. package/tests/fixtures/react-project/package.json +14 -0
  234. package/tests/fixtures/react-project/src/App.css +76 -0
  235. package/tests/fixtures/react-project/src/App.tsx +77 -0
  236. package/tests/fixtures/svelte-project/package.json +11 -0
  237. package/tests/fixtures/svelte-project/src/App.svelte +369 -0
  238. package/tests/fixtures/vanilla-project/index.html +76 -0
  239. package/tests/fixtures/vanilla-project/script.js +331 -0
  240. package/tests/fixtures/vanilla-project/styles.css +359 -0
  241. package/tests/fixtures/vue-project/package.json +12 -0
  242. package/tests/fixtures/vue-project/src/App.vue +216 -0
  243. package/tsconfig.json +36 -0
  244. package/vitest.config.ts +10 -0
@@ -0,0 +1,419 @@
1
+ import type { DetectedFeature } from '../types/index.js';
2
+ import { Parser } from './parser.js';
3
+ import { FeatureValidator } from './feature-validator.js';
4
+ import { LazyLoader } from '../core/lazy-loader.js';
5
+ import { MemoryManager } from '../core/memory-manager.js';
6
+ import { readFile, stat } from 'fs/promises';
7
+ import { join } from 'path';
8
+
9
+ /**
10
+ * Parser manager that coordinates all parsers and provides concurrent processing
11
+ * for large codebases with advanced web APIs using lazy loading
12
+ */
13
+ export class ParserManager {
14
+ private parsers: Map<string, Parser> = new Map();
15
+ private readonly validator: FeatureValidator;
16
+ private readonly maxConcurrency: number;
17
+ private initialized = false;
18
+
19
+ constructor(maxConcurrency: number = 10) {
20
+ this.validator = new FeatureValidator();
21
+ this.maxConcurrency = maxConcurrency;
22
+ // Don't initialize parsers immediately - use lazy loading
23
+ }
24
+
25
+ /**
26
+ * Lazy load parsers only when needed
27
+ */
28
+ private async ensureParsersLoaded(): Promise<void> {
29
+ if (this.initialized) {
30
+ return;
31
+ }
32
+
33
+ try {
34
+ // Dynamically import parsers to reduce startup time
35
+ const [
36
+ { ReactParser },
37
+ { VueParser },
38
+ { SvelteParser },
39
+ { VanillaParser }
40
+ ] = await Promise.all([
41
+ import('./react-parser.js'),
42
+ import('./vue-parser.js'),
43
+ import('./svelte-parser.js'),
44
+ import('./vanilla-parser.js')
45
+ ]);
46
+
47
+ this.parsers.set('react', new ReactParser());
48
+ this.parsers.set('vue', new VueParser());
49
+ this.parsers.set('svelte', new SvelteParser());
50
+ this.parsers.set('vanilla', new VanillaParser());
51
+
52
+ this.initialized = true;
53
+ } catch (error) {
54
+ console.warn('Failed to load some parsers:', error);
55
+ this.initialized = true;
56
+ }
57
+ }
58
+
59
+ /**
60
+ * Get parser for file type with lazy loading
61
+ */
62
+ private async getParserForFile(filePath: string): Promise<Parser | null> {
63
+ await this.ensureParsersLoaded();
64
+
65
+ for (const parser of this.parsers.values()) {
66
+ if (parser.canParse(filePath)) {
67
+ return parser;
68
+ }
69
+ }
70
+
71
+ return null;
72
+ }
73
+
74
+ /**
75
+ * Parse multiple files concurrently with error handling
76
+ */
77
+ async parseFiles(filePaths: string[]): Promise<DetectedFeature[]> {
78
+ const allFeatures: DetectedFeature[] = [];
79
+
80
+ // Filter out unsupported files
81
+ const supportedFiles = filePaths.filter(filePath => this.canParseFile(filePath));
82
+
83
+ if (supportedFiles.length === 0) {
84
+ return [];
85
+ }
86
+
87
+ // Process files in batches for better performance
88
+ const batches = this.createBatches(supportedFiles, this.maxConcurrency);
89
+
90
+ for (const batch of batches) {
91
+ try {
92
+ const batchResults = await Promise.allSettled(
93
+ batch.map(filePath => this.parseFile(filePath))
94
+ );
95
+
96
+ batchResults.forEach((result, index) => {
97
+ if (result.status === 'fulfilled') {
98
+ allFeatures.push(...result.value);
99
+ } else {
100
+ console.warn(`Failed to parse ${batch[index]}: ${result.reason}`);
101
+ }
102
+ });
103
+
104
+ // Small delay between batches to prevent overwhelming the system
105
+ if (batches.length > 1) {
106
+ await this.sleep(10);
107
+ }
108
+
109
+ } catch (error) {
110
+ console.error(`Error processing batch: ${error instanceof Error ? error.message : 'Unknown error'}`);
111
+ }
112
+ }
113
+
114
+ // Validate and filter features
115
+ return this.validator.validateFeatures(allFeatures, this.maxConcurrency);
116
+ }
117
+
118
+ /**
119
+ * Parse a single file with memory optimization
120
+ */
121
+ async parseFile(filePath: string): Promise<DetectedFeature[]> {
122
+ try {
123
+ // Find appropriate parser
124
+ const parser = await this.getParserForFile(filePath);
125
+ if (!parser) {
126
+ return [];
127
+ }
128
+
129
+ // Check file size and use streaming for large files
130
+ const fileStats = await stat(filePath);
131
+
132
+ if (MemoryManager.shouldStream(fileStats.size)) {
133
+ return await this.parseFileStreaming(filePath, parser);
134
+ }
135
+
136
+ // Read file content for smaller files
137
+ const content = await readFile(filePath, 'utf-8');
138
+
139
+ // Parse features
140
+ const features = await parser.parseFeatures(content, filePath);
141
+
142
+ // Add file path to features for tracking and optimize memory
143
+ return features.map(feature => MemoryManager.optimizeObject({
144
+ ...feature,
145
+ file: filePath
146
+ }));
147
+
148
+ } catch (error) {
149
+ // Graceful error handling for unsupported files and syntax errors
150
+ if (error instanceof Error) {
151
+ if (error.message.includes('ENOENT')) {
152
+ console.warn(`File not found: ${filePath}`);
153
+ } else if (error.message.includes('SyntaxError') || error.message.includes('Unexpected token')) {
154
+ console.warn(`Syntax error in ${filePath}: ${error.message}`);
155
+ } else {
156
+ console.warn(`Error parsing ${filePath}: ${error.message}`);
157
+ }
158
+ } else {
159
+ console.warn(`Unknown error parsing ${filePath}`);
160
+ }
161
+ return [];
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Parse large files using streaming to reduce memory usage
167
+ */
168
+ private async parseFileStreaming(filePath: string, parser: Parser): Promise<DetectedFeature[]> {
169
+ const features: DetectedFeature[] = [];
170
+
171
+ await MemoryManager.readFileStreaming(filePath, async (chunk, startLine) => {
172
+ try {
173
+ const chunkFeatures = await parser.parseFeatures(chunk, filePath);
174
+
175
+ // Adjust line numbers for chunk offset
176
+ const adjustedFeatures = chunkFeatures.map(feature => ({
177
+ ...feature,
178
+ line: feature.line + startLine - 1,
179
+ file: filePath
180
+ }));
181
+
182
+ features.push(...adjustedFeatures);
183
+ } catch (error) {
184
+ // Continue processing other chunks even if one fails
185
+ console.warn(`Error parsing chunk in ${filePath}: ${error}`);
186
+ }
187
+ });
188
+
189
+ return features;
190
+ }
191
+
192
+ /**
193
+ * Check if a file can be parsed by any available parser
194
+ */
195
+ async canParseFile(filePath: string): Promise<boolean> {
196
+ await this.ensureParsersLoaded();
197
+
198
+ for (const parser of this.parsers.values()) {
199
+ if (parser.canParse(filePath)) {
200
+ return true;
201
+ }
202
+ }
203
+
204
+ return false;
205
+ }
206
+
207
+ /**
208
+ * Get all supported file extensions
209
+ */
210
+ async getSupportedExtensions(): Promise<string[]> {
211
+ await this.ensureParsersLoaded();
212
+
213
+ const extensions = new Set<string>();
214
+ this.parsers.forEach(parser => {
215
+ parser.getSupportedExtensions().forEach(ext => extensions.add(ext));
216
+ });
217
+ return Array.from(extensions);
218
+ }
219
+
220
+ /**
221
+ * Get parser information for debugging
222
+ */
223
+ async getParserInfo(): Promise<Array<{ name: string; extensions: string[] }>> {
224
+ await this.ensureParsersLoaded();
225
+
226
+ return Array.from(this.parsers.values()).map(parser => ({
227
+ name: parser.getName(),
228
+ extensions: parser.getSupportedExtensions()
229
+ }));
230
+ }
231
+
232
+ /**
233
+ * Filter files by supported extensions
234
+ */
235
+ async filterSupportedFiles(filePaths: string[]): Promise<string[]> {
236
+ const supportedExtensions = await this.getSupportedExtensions();
237
+ return filePaths.filter(filePath => {
238
+ const extension = this.getFileExtension(filePath);
239
+ return supportedExtensions.includes(extension);
240
+ });
241
+ }
242
+
243
+ /**
244
+ * Scan directory for supported files (recursive)
245
+ */
246
+ async scanDirectory(
247
+ directoryPath: string,
248
+ options: {
249
+ recursive?: boolean;
250
+ excludePatterns?: string[];
251
+ maxDepth?: number;
252
+ } = {}
253
+ ): Promise<string[]> {
254
+ const {
255
+ recursive = true,
256
+ excludePatterns = ['node_modules', '.git', 'dist', 'build', '.next', '.nuxt'],
257
+ maxDepth = 10
258
+ } = options;
259
+
260
+ const supportedFiles: string[] = [];
261
+
262
+ try {
263
+ await this.scanDirectoryRecursive(
264
+ directoryPath,
265
+ supportedFiles,
266
+ excludePatterns,
267
+ recursive,
268
+ 0,
269
+ maxDepth
270
+ );
271
+ } catch (error) {
272
+ console.error(`Error scanning directory ${directoryPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
273
+ }
274
+
275
+ return supportedFiles;
276
+ }
277
+
278
+ /**
279
+ * Recursive directory scanning helper
280
+ */
281
+ private async scanDirectoryRecursive(
282
+ currentPath: string,
283
+ results: string[],
284
+ excludePatterns: string[],
285
+ recursive: boolean,
286
+ currentDepth: number,
287
+ maxDepth: number
288
+ ): Promise<void> {
289
+ if (currentDepth >= maxDepth) {
290
+ return;
291
+ }
292
+
293
+ try {
294
+ const { readdir, stat } = await import('fs/promises');
295
+ const entries = await readdir(currentPath);
296
+
297
+ for (const entry of entries) {
298
+ const fullPath = join(currentPath, entry);
299
+
300
+ // Skip excluded patterns
301
+ if (excludePatterns.some(pattern => entry.includes(pattern))) {
302
+ continue;
303
+ }
304
+
305
+ try {
306
+ const stats = await stat(fullPath);
307
+
308
+ if (stats.isFile()) {
309
+ if (await this.canParseFile(fullPath)) {
310
+ results.push(fullPath);
311
+ }
312
+ } else if (stats.isDirectory() && recursive) {
313
+ await this.scanDirectoryRecursive(
314
+ fullPath,
315
+ results,
316
+ excludePatterns,
317
+ recursive,
318
+ currentDepth + 1,
319
+ maxDepth
320
+ );
321
+ }
322
+ } catch (error) {
323
+ // Skip files/directories that can't be accessed
324
+ continue;
325
+ }
326
+ }
327
+ } catch (error) {
328
+ console.warn(`Could not read directory ${currentPath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
329
+ }
330
+ }
331
+
332
+ /**
333
+ * Get processing statistics
334
+ */
335
+ async getProcessingStats(filePaths: string[]): Promise<{
336
+ totalFiles: number;
337
+ supportedFiles: number;
338
+ filesByType: Record<string, number>;
339
+ estimatedProcessingTime: number;
340
+ memoryEstimate: string;
341
+ }> {
342
+ const supportedFiles = await this.filterSupportedFiles(filePaths);
343
+ const filesByType: Record<string, number> = {};
344
+
345
+ supportedFiles.forEach(filePath => {
346
+ const extension = this.getFileExtension(filePath);
347
+ filesByType[extension] = (filesByType[extension] || 0) + 1;
348
+ });
349
+
350
+ // Rough estimation: 10ms per file on average
351
+ const estimatedProcessingTime = Math.ceil(supportedFiles.length * 10 / this.maxConcurrency);
352
+
353
+ // Memory estimate: ~50KB per file on average
354
+ const memoryEstimate = `${Math.round(supportedFiles.length * 50 / 1024)}MB`;
355
+
356
+ return {
357
+ totalFiles: filePaths.length,
358
+ supportedFiles: supportedFiles.length,
359
+ filesByType,
360
+ estimatedProcessingTime,
361
+ memoryEstimate
362
+ };
363
+ }
364
+
365
+ /**
366
+ * Get memory and performance statistics
367
+ */
368
+ getStats(): {
369
+ parsersLoaded: number;
370
+ maxConcurrency: number;
371
+ memoryStats: any;
372
+ } {
373
+ return {
374
+ parsersLoaded: this.parsers.size,
375
+ maxConcurrency: this.maxConcurrency,
376
+ memoryStats: MemoryManager.getMemoryStats()
377
+ };
378
+ }
379
+
380
+ /**
381
+ * Create batches for concurrent processing
382
+ */
383
+ private createBatches<T>(items: T[], batchSize: number): T[][] {
384
+ const batches: T[][] = [];
385
+ for (let i = 0; i < items.length; i += batchSize) {
386
+ batches.push(items.slice(i, i + batchSize));
387
+ }
388
+ return batches;
389
+ }
390
+
391
+ /**
392
+ * Get file extension
393
+ */
394
+ private getFileExtension(filePath: string): string {
395
+ const match = filePath.match(/\.[^.]+$/);
396
+ return match ? match[0] : '';
397
+ }
398
+
399
+ /**
400
+ * Sleep utility for batch processing
401
+ */
402
+ private sleep(ms: number): Promise<void> {
403
+ return new Promise(resolve => setTimeout(resolve, ms));
404
+ }
405
+
406
+ /**
407
+ * Get feature validator instance
408
+ */
409
+ getValidator(): FeatureValidator {
410
+ return this.validator;
411
+ }
412
+
413
+ /**
414
+ * Validate features without parsing (for external use)
415
+ */
416
+ async validateFeatures(features: DetectedFeature[]): Promise<DetectedFeature[]> {
417
+ return this.validator.validateFeatures(features, this.maxConcurrency);
418
+ }
419
+ }
@@ -0,0 +1,26 @@
1
+ import type { DetectedFeature } from '../types/index.js';
2
+
3
+ /**
4
+ * Abstract base class for all code parsers
5
+ */
6
+ export abstract class Parser {
7
+ /**
8
+ * Check if this parser can handle the given file
9
+ */
10
+ abstract canParse(filePath: string): boolean;
11
+
12
+ /**
13
+ * Parse features from file content
14
+ */
15
+ abstract parseFeatures(content: string, filePath: string): Promise<DetectedFeature[]>;
16
+
17
+ /**
18
+ * Get supported file extensions for this parser
19
+ */
20
+ abstract getSupportedExtensions(): string[];
21
+
22
+ /**
23
+ * Get parser name for identification
24
+ */
25
+ abstract getName(): string;
26
+ }
@@ -0,0 +1,161 @@
1
+ import { Parser } from './parser.js';
2
+ import type { DetectedFeature } from '../types/index.js';
3
+ import { LazyLoader } from '../core/lazy-loader.js';
4
+
5
+ /**
6
+ * Optimized React/JSX parser using lazy loading
7
+ */
8
+ export class ReactParser extends Parser {
9
+ getName(): string {
10
+ return 'ReactParser';
11
+ }
12
+
13
+ getSupportedExtensions(): string[] {
14
+ return ['.jsx', '.tsx'];
15
+ }
16
+
17
+ canParse(filePath: string): boolean {
18
+ return /\.(jsx|tsx)$/.test(filePath);
19
+ }
20
+
21
+ async parseFeatures(content: string, filePath: string): Promise<DetectedFeature[]> {
22
+ const features: DetectedFeature[] = [];
23
+
24
+ try {
25
+ // Lazy load Babel dependencies only when needed
26
+ const [parser, traverse] = await Promise.all([
27
+ LazyLoader.getBabelParser(),
28
+ LazyLoader.getBabelTraverse()
29
+ ]);
30
+
31
+ const ast = parser.parse(content, {
32
+ sourceType: 'module',
33
+ plugins: [
34
+ 'jsx',
35
+ 'typescript',
36
+ 'decorators-legacy',
37
+ 'classProperties',
38
+ 'objectRestSpread',
39
+ 'asyncGenerators',
40
+ 'functionBind',
41
+ 'exportDefaultFrom',
42
+ 'exportNamespaceFrom',
43
+ 'dynamicImport',
44
+ 'nullishCoalescingOperator',
45
+ 'optionalChaining',
46
+ 'topLevelAwait'
47
+ ]
48
+ });
49
+
50
+ // Simple feature extraction optimized for performance
51
+ traverse(ast, {
52
+ MemberExpression: (path: any) => {
53
+ const apiName = this.extractAPIName(path.node);
54
+ if (apiName && this.isWebPlatformAPI(apiName)) {
55
+ features.push({
56
+ feature: apiName,
57
+ type: 'js',
58
+ context: this.getContext(content, path.node.loc?.start.line || 0),
59
+ line: path.node.loc?.start.line || 0,
60
+ column: path.node.loc?.start.column || 0,
61
+ file: filePath
62
+ });
63
+ }
64
+ },
65
+
66
+ CallExpression: (path: any) => {
67
+ const apiName = this.extractCallName(path.node);
68
+ if (apiName && this.isWebPlatformAPI(apiName)) {
69
+ features.push({
70
+ feature: apiName,
71
+ type: 'js',
72
+ context: this.getContext(content, path.node.loc?.start.line || 0),
73
+ line: path.node.loc?.start.line || 0,
74
+ column: path.node.loc?.start.column || 0,
75
+ file: filePath
76
+ });
77
+ }
78
+ },
79
+
80
+ OptionalMemberExpression: (path: any) => {
81
+ features.push({
82
+ feature: 'optional-chaining',
83
+ type: 'js',
84
+ context: this.getContext(content, path.node.loc?.start.line || 0),
85
+ line: path.node.loc?.start.line || 0,
86
+ column: path.node.loc?.start.column || 0,
87
+ file: filePath
88
+ });
89
+ },
90
+
91
+ LogicalExpression: (path: any) => {
92
+ if (path.node.operator === '??') {
93
+ features.push({
94
+ feature: 'nullish-coalescing',
95
+ type: 'js',
96
+ context: this.getContext(content, path.node.loc?.start.line || 0),
97
+ line: path.node.loc?.start.line || 0,
98
+ column: path.node.loc?.start.column || 0,
99
+ file: filePath
100
+ });
101
+ }
102
+ }
103
+ });
104
+
105
+ } catch (error) {
106
+ console.warn(`Warning: Could not parse React file ${filePath}: ${error instanceof Error ? error.message : 'Unknown error'}`);
107
+ }
108
+
109
+ return features;
110
+ }
111
+
112
+ private extractAPIName(node: any): string | null {
113
+ try {
114
+ if (node.object && node.property) {
115
+ const objectName = node.object.name || 'unknown';
116
+ const propertyName = node.property.name || node.property.value || 'unknown';
117
+ return `${objectName}.${propertyName}`;
118
+ }
119
+ } catch {
120
+ // Ignore errors
121
+ }
122
+ return null;
123
+ }
124
+
125
+ private extractCallName(node: any): string | null {
126
+ try {
127
+ if (node.callee) {
128
+ if (node.callee.name) {
129
+ return node.callee.name;
130
+ }
131
+ if (node.callee.object && node.callee.property) {
132
+ const objectName = node.callee.object.name || 'unknown';
133
+ const propertyName = node.callee.property.name || node.callee.property.value || 'unknown';
134
+ return `${objectName}.${propertyName}`;
135
+ }
136
+ }
137
+ } catch {
138
+ // Ignore errors
139
+ }
140
+ return null;
141
+ }
142
+
143
+ private isWebPlatformAPI(apiName: string): boolean {
144
+ const webAPIs = [
145
+ 'fetch', 'XMLHttpRequest', 'WebSocket', 'EventSource',
146
+ 'navigator.serviceWorker', 'caches', 'Cache',
147
+ 'ResizeObserver', 'IntersectionObserver', 'MutationObserver',
148
+ 'canvas.getContext', 'WebGLRenderingContext', 'WebGL2RenderingContext',
149
+ 'RTCPeerConnection', 'getUserMedia', 'MediaStream',
150
+ 'WebAssembly', 'structuredClone', 'queueMicrotask',
151
+ 'requestAnimationFrame', 'requestIdleCallback'
152
+ ];
153
+
154
+ return webAPIs.some(api => apiName.includes(api) || api.includes(apiName));
155
+ }
156
+
157
+ private getContext(content: string, line: number): string {
158
+ const lines = content.split('\n');
159
+ return lines[line - 1]?.trim() || '';
160
+ }
161
+ }