@skillsmith/core 0.2.0 → 2.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 (233) hide show
  1. package/README.md +233 -2
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/analysis/__tests__/incremental.test.d.ts +13 -0
  4. package/dist/src/analysis/__tests__/incremental.test.d.ts.map +1 -0
  5. package/dist/src/analysis/__tests__/incremental.test.js +515 -0
  6. package/dist/src/analysis/__tests__/incremental.test.js.map +1 -0
  7. package/dist/src/analysis/__tests__/integration.test.d.ts +14 -0
  8. package/dist/src/analysis/__tests__/integration.test.d.ts.map +1 -0
  9. package/dist/src/analysis/__tests__/integration.test.js +1059 -0
  10. package/dist/src/analysis/__tests__/integration.test.js.map +1 -0
  11. package/dist/src/analysis/__tests__/metrics.test.d.ts +9 -0
  12. package/dist/src/analysis/__tests__/metrics.test.d.ts.map +1 -0
  13. package/dist/src/analysis/__tests__/metrics.test.js +369 -0
  14. package/dist/src/analysis/__tests__/metrics.test.js.map +1 -0
  15. package/dist/src/analysis/__tests__/performance.test.d.ts +15 -0
  16. package/dist/src/analysis/__tests__/performance.test.d.ts.map +1 -0
  17. package/dist/src/analysis/__tests__/performance.test.js +402 -0
  18. package/dist/src/analysis/__tests__/performance.test.js.map +1 -0
  19. package/dist/src/analysis/adapters/__tests__/go.test.d.ts +12 -0
  20. package/dist/src/analysis/adapters/__tests__/go.test.d.ts.map +1 -0
  21. package/dist/src/analysis/adapters/__tests__/go.test.js +561 -0
  22. package/dist/src/analysis/adapters/__tests__/go.test.js.map +1 -0
  23. package/dist/src/analysis/adapters/__tests__/python.test.d.ts +11 -0
  24. package/dist/src/analysis/adapters/__tests__/python.test.d.ts.map +1 -0
  25. package/dist/src/analysis/adapters/__tests__/python.test.js +669 -0
  26. package/dist/src/analysis/adapters/__tests__/python.test.js.map +1 -0
  27. package/dist/src/analysis/adapters/__tests__/rust.test.d.ts +12 -0
  28. package/dist/src/analysis/adapters/__tests__/rust.test.d.ts.map +1 -0
  29. package/dist/src/analysis/adapters/__tests__/rust.test.js +676 -0
  30. package/dist/src/analysis/adapters/__tests__/rust.test.js.map +1 -0
  31. package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts +14 -0
  32. package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts.map +1 -0
  33. package/dist/src/analysis/adapters/__tests__/typescript.test.js +381 -0
  34. package/dist/src/analysis/adapters/__tests__/typescript.test.js.map +1 -0
  35. package/dist/src/analysis/adapters/base.d.ts +83 -0
  36. package/dist/src/analysis/adapters/base.d.ts.map +1 -0
  37. package/dist/src/analysis/adapters/base.js +40 -0
  38. package/dist/src/analysis/adapters/base.js.map +1 -0
  39. package/dist/src/analysis/adapters/factory.d.ts +150 -0
  40. package/dist/src/analysis/adapters/factory.d.ts.map +1 -0
  41. package/dist/src/analysis/adapters/factory.js +244 -0
  42. package/dist/src/analysis/adapters/factory.js.map +1 -0
  43. package/dist/src/analysis/adapters/go.d.ts +131 -0
  44. package/dist/src/analysis/adapters/go.d.ts.map +1 -0
  45. package/dist/src/analysis/adapters/go.js +414 -0
  46. package/dist/src/analysis/adapters/go.js.map +1 -0
  47. package/dist/src/analysis/adapters/index.d.ts +20 -0
  48. package/dist/src/analysis/adapters/index.d.ts.map +1 -0
  49. package/dist/src/analysis/adapters/index.js +23 -0
  50. package/dist/src/analysis/adapters/index.js.map +1 -0
  51. package/dist/src/analysis/adapters/java.d.ts +154 -0
  52. package/dist/src/analysis/adapters/java.d.ts.map +1 -0
  53. package/dist/src/analysis/adapters/java.js +407 -0
  54. package/dist/src/analysis/adapters/java.js.map +1 -0
  55. package/dist/src/analysis/adapters/python.d.ts +165 -0
  56. package/dist/src/analysis/adapters/python.d.ts.map +1 -0
  57. package/dist/src/analysis/adapters/python.js +475 -0
  58. package/dist/src/analysis/adapters/python.js.map +1 -0
  59. package/dist/src/analysis/adapters/rust.d.ts +116 -0
  60. package/dist/src/analysis/adapters/rust.d.ts.map +1 -0
  61. package/dist/src/analysis/adapters/rust.js +476 -0
  62. package/dist/src/analysis/adapters/rust.js.map +1 -0
  63. package/dist/src/analysis/adapters/typescript.d.ts +68 -0
  64. package/dist/src/analysis/adapters/typescript.d.ts.map +1 -0
  65. package/dist/src/analysis/adapters/typescript.js +79 -0
  66. package/dist/src/analysis/adapters/typescript.js.map +1 -0
  67. package/dist/src/analysis/aggregator.d.ts +193 -0
  68. package/dist/src/analysis/aggregator.d.ts.map +1 -0
  69. package/dist/src/analysis/aggregator.js +283 -0
  70. package/dist/src/analysis/aggregator.js.map +1 -0
  71. package/dist/src/analysis/cache.d.ts +180 -0
  72. package/dist/src/analysis/cache.d.ts.map +1 -0
  73. package/dist/src/analysis/cache.js +279 -0
  74. package/dist/src/analysis/cache.js.map +1 -0
  75. package/dist/src/analysis/file-streamer.d.ts +136 -0
  76. package/dist/src/analysis/file-streamer.d.ts.map +1 -0
  77. package/dist/src/analysis/file-streamer.js +291 -0
  78. package/dist/src/analysis/file-streamer.js.map +1 -0
  79. package/dist/src/analysis/incremental-parser.d.ts +186 -0
  80. package/dist/src/analysis/incremental-parser.d.ts.map +1 -0
  81. package/dist/src/analysis/incremental-parser.js +291 -0
  82. package/dist/src/analysis/incremental-parser.js.map +1 -0
  83. package/dist/src/analysis/incremental.d.ts +186 -0
  84. package/dist/src/analysis/incremental.d.ts.map +1 -0
  85. package/dist/src/analysis/incremental.js +247 -0
  86. package/dist/src/analysis/incremental.js.map +1 -0
  87. package/dist/src/analysis/index.d.ts +25 -3
  88. package/dist/src/analysis/index.d.ts.map +1 -1
  89. package/dist/src/analysis/index.js +45 -3
  90. package/dist/src/analysis/index.js.map +1 -1
  91. package/dist/src/analysis/language-detector.d.ts +92 -0
  92. package/dist/src/analysis/language-detector.d.ts.map +1 -0
  93. package/dist/src/analysis/language-detector.js +602 -0
  94. package/dist/src/analysis/language-detector.js.map +1 -0
  95. package/dist/src/analysis/memory-monitor.d.ts +199 -0
  96. package/dist/src/analysis/memory-monitor.d.ts.map +1 -0
  97. package/dist/src/analysis/memory-monitor.js +271 -0
  98. package/dist/src/analysis/memory-monitor.js.map +1 -0
  99. package/dist/src/analysis/metrics.d.ts +300 -0
  100. package/dist/src/analysis/metrics.d.ts.map +1 -0
  101. package/dist/src/analysis/metrics.js +537 -0
  102. package/dist/src/analysis/metrics.js.map +1 -0
  103. package/dist/src/analysis/router.d.ts +264 -0
  104. package/dist/src/analysis/router.d.ts.map +1 -0
  105. package/dist/src/analysis/router.js +398 -0
  106. package/dist/src/analysis/router.js.map +1 -0
  107. package/dist/src/analysis/tree-cache.d.ts +208 -0
  108. package/dist/src/analysis/tree-cache.d.ts.map +1 -0
  109. package/dist/src/analysis/tree-cache.js +288 -0
  110. package/dist/src/analysis/tree-cache.js.map +1 -0
  111. package/dist/src/analysis/tree-sitter/manager.d.ts +141 -0
  112. package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -0
  113. package/dist/src/analysis/tree-sitter/manager.js +239 -0
  114. package/dist/src/analysis/tree-sitter/manager.js.map +1 -0
  115. package/dist/src/analysis/types.d.ts +69 -6
  116. package/dist/src/analysis/types.d.ts.map +1 -1
  117. package/dist/src/analysis/types.js +23 -2
  118. package/dist/src/analysis/types.js.map +1 -1
  119. package/dist/src/analysis/worker-pool.d.ts +141 -0
  120. package/dist/src/analysis/worker-pool.d.ts.map +1 -0
  121. package/dist/src/analysis/worker-pool.js +418 -0
  122. package/dist/src/analysis/worker-pool.js.map +1 -0
  123. package/dist/src/analytics/schema.d.ts +1 -1
  124. package/dist/src/analytics/schema.d.ts.map +1 -1
  125. package/dist/src/analytics/schema.js +72 -0
  126. package/dist/src/analytics/schema.js.map +1 -1
  127. package/dist/src/api/cache.d.ts +24 -1
  128. package/dist/src/api/cache.d.ts.map +1 -1
  129. package/dist/src/api/cache.js +50 -2
  130. package/dist/src/api/cache.js.map +1 -1
  131. package/dist/src/api/client.d.ts +132 -2
  132. package/dist/src/api/client.d.ts.map +1 -1
  133. package/dist/src/api/client.js +214 -18
  134. package/dist/src/api/client.js.map +1 -1
  135. package/dist/src/api/index.d.ts +2 -0
  136. package/dist/src/api/index.d.ts.map +1 -1
  137. package/dist/src/api/index.js +7 -0
  138. package/dist/src/api/index.js.map +1 -1
  139. package/dist/src/api/types.d.ts +251 -0
  140. package/dist/src/api/types.d.ts.map +1 -0
  141. package/dist/src/api/types.js +9 -0
  142. package/dist/src/api/types.js.map +1 -0
  143. package/dist/src/benchmarks/memory/MemoryProfiler.d.ts.map +1 -1
  144. package/dist/src/benchmarks/memory/MemoryProfiler.js.map +1 -1
  145. package/dist/src/embeddings/index.d.ts.map +1 -1
  146. package/dist/src/embeddings/index.js.map +1 -1
  147. package/dist/src/errors.d.ts +1 -0
  148. package/dist/src/errors.d.ts.map +1 -1
  149. package/dist/src/errors.js +1 -0
  150. package/dist/src/errors.js.map +1 -1
  151. package/dist/src/index.d.ts +3 -3
  152. package/dist/src/index.d.ts.map +1 -1
  153. package/dist/src/index.js +4 -4
  154. package/dist/src/index.js.map +1 -1
  155. package/dist/src/repositories/IndexerRepository.d.ts.map +1 -1
  156. package/dist/src/repositories/IndexerRepository.js +1 -0
  157. package/dist/src/repositories/IndexerRepository.js.map +1 -1
  158. package/dist/src/repositories/SkillRepository.d.ts.map +1 -1
  159. package/dist/src/repositories/SkillRepository.js +1 -0
  160. package/dist/src/repositories/SkillRepository.js.map +1 -1
  161. package/dist/src/repositories/quarantine/QuarantineRepository.d.ts.map +1 -1
  162. package/dist/src/repositories/quarantine/QuarantineRepository.js.map +1 -1
  163. package/dist/src/repositories/quarantine/query-builder.d.ts.map +1 -1
  164. package/dist/src/repositories/quarantine/query-builder.js +1 -1
  165. package/dist/src/repositories/quarantine/query-builder.js.map +1 -1
  166. package/dist/src/scripts/__tests__/scan-imported-skills.test.js +3 -3
  167. package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
  168. package/dist/src/scripts/github-import/index.js.map +1 -1
  169. package/dist/src/scripts/import-github-skills.js +1 -1
  170. package/dist/src/scripts/import-github-skills.js.map +1 -1
  171. package/dist/src/scripts/skill-scanner/reporter.d.ts.map +1 -1
  172. package/dist/src/scripts/skill-scanner/reporter.js.map +1 -1
  173. package/dist/src/scripts/skill-scanner/scanner.d.ts.map +1 -1
  174. package/dist/src/scripts/skill-scanner/scanner.js.map +1 -1
  175. package/dist/src/scripts/skill-scanner/trust-scorer.d.ts.map +1 -1
  176. package/dist/src/scripts/skill-scanner/trust-scorer.js.map +1 -1
  177. package/dist/src/scripts/validation/index.js +1 -2
  178. package/dist/src/scripts/validation/index.js.map +1 -1
  179. package/dist/src/scripts/validation/pipeline.d.ts.map +1 -1
  180. package/dist/src/scripts/validation/pipeline.js.map +1 -1
  181. package/dist/src/scripts/validation/types.d.ts +2 -2
  182. package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
  183. package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
  184. package/dist/src/services/SearchService.d.ts.map +1 -1
  185. package/dist/src/services/SearchService.js +1 -0
  186. package/dist/src/services/SearchService.js.map +1 -1
  187. package/dist/src/session/SessionHealthMonitor.d.ts +1 -1
  188. package/dist/src/session/SessionHealthMonitor.d.ts.map +1 -1
  189. package/dist/src/session/SessionHealthMonitor.js +1 -1
  190. package/dist/src/session/SessionHealthMonitor.js.map +1 -1
  191. package/dist/src/telemetry/index.d.ts +1 -1
  192. package/dist/src/telemetry/index.d.ts.map +1 -1
  193. package/dist/src/telemetry/index.js +2 -2
  194. package/dist/src/telemetry/index.js.map +1 -1
  195. package/dist/src/telemetry/posthog.d.ts +27 -5
  196. package/dist/src/telemetry/posthog.d.ts.map +1 -1
  197. package/dist/src/telemetry/posthog.js +20 -5
  198. package/dist/src/telemetry/posthog.js.map +1 -1
  199. package/dist/src/types/skill.d.ts +3 -0
  200. package/dist/src/types/skill.d.ts.map +1 -1
  201. package/dist/src/types.d.ts +2 -1
  202. package/dist/src/types.d.ts.map +1 -1
  203. package/dist/src/types.js +2 -2
  204. package/dist/src/types.js.map +1 -1
  205. package/dist/tests/adapters-factory.test.d.ts +13 -0
  206. package/dist/tests/adapters-factory.test.d.ts.map +1 -0
  207. package/dist/tests/adapters-factory.test.js +308 -0
  208. package/dist/tests/adapters-factory.test.js.map +1 -0
  209. package/dist/tests/adapters-java.test.d.ts +13 -0
  210. package/dist/tests/adapters-java.test.d.ts.map +1 -0
  211. package/dist/tests/adapters-java.test.js +925 -0
  212. package/dist/tests/adapters-java.test.js.map +1 -0
  213. package/dist/tests/api/client.validation.test.d.ts +7 -0
  214. package/dist/tests/api/client.validation.test.d.ts.map +1 -0
  215. package/dist/tests/api/client.validation.test.js +183 -0
  216. package/dist/tests/api/client.validation.test.js.map +1 -0
  217. package/dist/tests/language-detector.test.d.ts +13 -0
  218. package/dist/tests/language-detector.test.d.ts.map +1 -0
  219. package/dist/tests/language-detector.test.js +674 -0
  220. package/dist/tests/language-detector.test.js.map +1 -0
  221. package/dist/tests/telemetry/posthog.test.d.ts +13 -0
  222. package/dist/tests/telemetry/posthog.test.d.ts.map +1 -0
  223. package/dist/tests/telemetry/posthog.test.js +600 -0
  224. package/dist/tests/telemetry/posthog.test.js.map +1 -0
  225. package/package.json +5 -6
  226. package/dist/src/security/RateLimiter.d.ts +0 -337
  227. package/dist/src/security/RateLimiter.d.ts.map +0 -1
  228. package/dist/src/security/RateLimiter.js +0 -782
  229. package/dist/src/security/RateLimiter.js.map +0 -1
  230. package/dist/src/security/scanner.d.ts +0 -151
  231. package/dist/src/security/scanner.d.ts.map +0 -1
  232. package/dist/src/security/scanner.js +0 -599
  233. package/dist/src/security/scanner.js.map +0 -1
@@ -0,0 +1,1059 @@
1
+ /**
2
+ * SMI-1336: Multi-Language Analysis Integration Tests
3
+ *
4
+ * Integration tests for the complete multi-language analysis system,
5
+ * testing interactions between:
6
+ * - LanguageRouter with all adapters
7
+ * - ParseCache across languages
8
+ * - ParserWorkerPool with mixed language batches
9
+ * - ResultAggregator combining results from multiple languages
10
+ *
11
+ * @see docs/architecture/multi-language-analysis.md
12
+ */
13
+ import { describe, it, expect, beforeEach, afterEach } from 'vitest';
14
+ import { LanguageRouter } from '../router.js';
15
+ import { ParseCache } from '../cache.js';
16
+ import { ParserWorkerPool } from '../worker-pool.js';
17
+ import { ResultAggregator } from '../aggregator.js';
18
+ import { TypeScriptAdapter } from '../adapters/typescript.js';
19
+ import { PythonAdapter } from '../adapters/python.js';
20
+ import { GoAdapter } from '../adapters/go.js';
21
+ import { RustAdapter } from '../adapters/rust.js';
22
+ import { JavaAdapter } from '../adapters/java.js';
23
+ // ============================================================
24
+ // Test Fixtures: Multi-Language Project Files
25
+ // ============================================================
26
+ const fixtures = {
27
+ typescript: {
28
+ path: 'src/api/handler.ts',
29
+ content: `
30
+ import { Request, Response } from 'express'
31
+ import type { User } from '../types'
32
+ import { validateUser } from '../validators'
33
+
34
+ export interface HandlerConfig {
35
+ timeout: number
36
+ maxRetries: number
37
+ }
38
+
39
+ export async function handleUserRequest(req: Request, res: Response): Promise<void> {
40
+ const user = req.body as User
41
+ if (validateUser(user)) {
42
+ res.json({ success: true, user })
43
+ } else {
44
+ res.status(400).json({ error: 'Invalid user' })
45
+ }
46
+ }
47
+
48
+ export const DEFAULT_CONFIG: HandlerConfig = {
49
+ timeout: 5000,
50
+ maxRetries: 3
51
+ }
52
+ `.trim(),
53
+ },
54
+ python: {
55
+ path: 'src/services/data_processor.py',
56
+ content: `
57
+ from typing import List, Dict, Optional
58
+ from dataclasses import dataclass
59
+ import pandas as pd
60
+ import numpy as np
61
+ from .utils import sanitize_input
62
+
63
+ @dataclass
64
+ class DataConfig:
65
+ batch_size: int
66
+ max_workers: int
67
+
68
+ class DataProcessor:
69
+ def __init__(self, config: DataConfig):
70
+ self.config = config
71
+ self._cache: Dict[str, pd.DataFrame] = {}
72
+
73
+ async def process_batch(self, data: List[Dict]) -> pd.DataFrame:
74
+ df = pd.DataFrame(data)
75
+ return self._apply_transformations(df)
76
+
77
+ def _apply_transformations(self, df: pd.DataFrame) -> pd.DataFrame:
78
+ return df.dropna().reset_index(drop=True)
79
+
80
+ def create_processor(batch_size: int = 100) -> DataProcessor:
81
+ config = DataConfig(batch_size=batch_size, max_workers=4)
82
+ return DataProcessor(config)
83
+ `.trim(),
84
+ },
85
+ go: {
86
+ path: 'src/server/main.go',
87
+ content: `
88
+ package server
89
+
90
+ import (
91
+ "context"
92
+ "encoding/json"
93
+ "net/http"
94
+
95
+ "github.com/gin-gonic/gin"
96
+ "gorm.io/gorm"
97
+ )
98
+
99
+ type Server struct {
100
+ router *gin.Engine
101
+ db *gorm.DB
102
+ }
103
+
104
+ type UserRequest struct {
105
+ Name string \`json:"name"\`
106
+ Email string \`json:"email"\`
107
+ }
108
+
109
+ func NewServer(db *gorm.DB) *Server {
110
+ router := gin.Default()
111
+ return &Server{router: router, db: db}
112
+ }
113
+
114
+ func (s *Server) Start(addr string) error {
115
+ return s.router.Run(addr)
116
+ }
117
+
118
+ func (s *Server) handleGetUser(c *gin.Context) {
119
+ id := c.Param("id")
120
+ c.JSON(http.StatusOK, gin.H{"id": id})
121
+ }
122
+
123
+ func parseJSON(data []byte) (UserRequest, error) {
124
+ var req UserRequest
125
+ err := json.Unmarshal(data, &req)
126
+ return req, err
127
+ }
128
+ `.trim(),
129
+ },
130
+ rust: {
131
+ path: 'src/lib.rs',
132
+ content: `
133
+ use std::collections::HashMap;
134
+ use serde::{Deserialize, Serialize};
135
+ use tokio::sync::RwLock;
136
+
137
+ #[derive(Debug, Clone, Serialize, Deserialize)]
138
+ pub struct Config {
139
+ pub host: String,
140
+ pub port: u16,
141
+ pub max_connections: usize,
142
+ }
143
+
144
+ pub trait Handler: Send + Sync {
145
+ fn handle(&self, request: &Request) -> Response;
146
+ }
147
+
148
+ pub struct Server {
149
+ config: Config,
150
+ handlers: HashMap<String, Box<dyn Handler>>,
151
+ connections: RwLock<Vec<Connection>>,
152
+ }
153
+
154
+ impl Server {
155
+ pub fn new(config: Config) -> Self {
156
+ Server {
157
+ config,
158
+ handlers: HashMap::new(),
159
+ connections: RwLock::new(Vec::new()),
160
+ }
161
+ }
162
+
163
+ pub async fn start(&self) -> Result<(), ServerError> {
164
+ println!("Starting server on {}:{}", self.config.host, self.config.port);
165
+ Ok(())
166
+ }
167
+
168
+ fn register_handler(&mut self, path: String, handler: Box<dyn Handler>) {
169
+ self.handlers.insert(path, handler);
170
+ }
171
+ }
172
+
173
+ pub fn create_default_config() -> Config {
174
+ Config {
175
+ host: "127.0.0.1".to_string(),
176
+ port: 8080,
177
+ max_connections: 100,
178
+ }
179
+ }
180
+ `.trim(),
181
+ },
182
+ java: {
183
+ path: 'src/main/java/com/example/UserService.java',
184
+ content: `
185
+ package com.example;
186
+
187
+ import java.util.List;
188
+ import java.util.Optional;
189
+ import java.util.stream.Collectors;
190
+
191
+ import org.springframework.stereotype.Service;
192
+ import org.springframework.transaction.annotation.Transactional;
193
+
194
+ @Service
195
+ public class UserService {
196
+ private final UserRepository userRepository;
197
+ private final CacheService cacheService;
198
+
199
+ public UserService(UserRepository userRepository, CacheService cacheService) {
200
+ this.userRepository = userRepository;
201
+ this.cacheService = cacheService;
202
+ }
203
+
204
+ @Transactional(readOnly = true)
205
+ public Optional<User> findById(Long id) {
206
+ return userRepository.findById(id);
207
+ }
208
+
209
+ @Transactional
210
+ public User createUser(CreateUserRequest request) {
211
+ User user = new User(request.getName(), request.getEmail());
212
+ return userRepository.save(user);
213
+ }
214
+
215
+ public List<UserDTO> getAllUsers() {
216
+ return userRepository.findAll()
217
+ .stream()
218
+ .map(this::toDTO)
219
+ .collect(Collectors.toList());
220
+ }
221
+
222
+ private UserDTO toDTO(User user) {
223
+ return new UserDTO(user.getId(), user.getName());
224
+ }
225
+ }
226
+ `.trim(),
227
+ },
228
+ };
229
+ // ============================================================
230
+ // LanguageRouter Integration Tests
231
+ // ============================================================
232
+ describe('SMI-1336: Multi-Language Integration Tests', () => {
233
+ describe('LanguageRouter with all adapters', () => {
234
+ let router;
235
+ beforeEach(() => {
236
+ router = new LanguageRouter();
237
+ router.registerAdapter(new TypeScriptAdapter());
238
+ router.registerAdapter(new PythonAdapter());
239
+ router.registerAdapter(new GoAdapter());
240
+ router.registerAdapter(new RustAdapter());
241
+ router.registerAdapter(new JavaAdapter());
242
+ });
243
+ afterEach(() => {
244
+ router.dispose();
245
+ });
246
+ it('registers all five language adapters', () => {
247
+ expect(router.adapterCount).toBe(5);
248
+ const languages = router.getSupportedLanguages();
249
+ expect(languages).toContain('typescript');
250
+ expect(languages).toContain('python');
251
+ expect(languages).toContain('go');
252
+ expect(languages).toContain('rust');
253
+ expect(languages).toContain('java');
254
+ });
255
+ it('routes files to correct adapters based on extension', () => {
256
+ // TypeScript/JavaScript
257
+ expect(router.getLanguage('file.ts')).toBe('typescript');
258
+ expect(router.getLanguage('file.tsx')).toBe('typescript');
259
+ expect(router.getLanguage('file.js')).toBe('typescript');
260
+ expect(router.getLanguage('file.jsx')).toBe('typescript');
261
+ // Python
262
+ expect(router.getLanguage('file.py')).toBe('python');
263
+ expect(router.getLanguage('file.pyi')).toBe('python');
264
+ // Go
265
+ expect(router.getLanguage('file.go')).toBe('go');
266
+ // Rust
267
+ expect(router.getLanguage('file.rs')).toBe('rust');
268
+ // Java
269
+ expect(router.getLanguage('file.java')).toBe('java');
270
+ });
271
+ it('returns null for unsupported extensions', () => {
272
+ expect(router.getLanguage('file.cpp')).toBeNull();
273
+ expect(router.getLanguage('file.rb')).toBeNull();
274
+ expect(router.getLanguage('file.php')).toBeNull();
275
+ });
276
+ it('canHandle returns correct values for all languages', () => {
277
+ expect(router.canHandle('src/main.ts')).toBe(true);
278
+ expect(router.canHandle('src/main.py')).toBe(true);
279
+ expect(router.canHandle('src/main.go')).toBe(true);
280
+ expect(router.canHandle('src/lib.rs')).toBe(true);
281
+ expect(router.canHandle('src/App.java')).toBe(true);
282
+ expect(router.canHandle('src/main.cpp')).toBe(false);
283
+ });
284
+ it('parses TypeScript files correctly', () => {
285
+ const result = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
286
+ expect(result.imports.length).toBeGreaterThan(0);
287
+ expect(result.imports.some((i) => i.module === 'express')).toBe(true);
288
+ expect(result.functions.length).toBeGreaterThan(0);
289
+ expect(result.functions.some((f) => f.name === 'handleUserRequest')).toBe(true);
290
+ expect(result.exports.length).toBeGreaterThan(0);
291
+ expect(result.exports.some((e) => e.name === 'HandlerConfig')).toBe(true);
292
+ });
293
+ it('parses Python files correctly', () => {
294
+ const result = router.parseFile(fixtures.python.content, fixtures.python.path);
295
+ expect(result.imports.length).toBeGreaterThan(0);
296
+ expect(result.imports.some((i) => i.module === 'pandas')).toBe(true);
297
+ expect(result.imports.some((i) => i.module === 'numpy')).toBe(true);
298
+ expect(result.functions.length).toBeGreaterThan(0);
299
+ expect(result.functions.some((f) => f.name === 'create_processor')).toBe(true);
300
+ expect(result.exports.length).toBeGreaterThan(0);
301
+ expect(result.exports.some((e) => e.name === 'DataProcessor')).toBe(true);
302
+ });
303
+ it('parses Go files correctly', () => {
304
+ const result = router.parseFile(fixtures.go.content, fixtures.go.path);
305
+ expect(result.imports.length).toBeGreaterThan(0);
306
+ expect(result.imports.some((i) => i.module === 'github.com/gin-gonic/gin')).toBe(true);
307
+ expect(result.functions.length).toBeGreaterThan(0);
308
+ expect(result.functions.some((f) => f.name === 'NewServer')).toBe(true);
309
+ expect(result.exports.length).toBeGreaterThan(0);
310
+ expect(result.exports.some((e) => e.name === 'Server')).toBe(true);
311
+ });
312
+ it('parses Rust files correctly', () => {
313
+ const result = router.parseFile(fixtures.rust.content, fixtures.rust.path);
314
+ expect(result.imports.length).toBeGreaterThan(0);
315
+ expect(result.imports.some((i) => i.module.includes('serde'))).toBe(true);
316
+ expect(result.functions.length).toBeGreaterThan(0);
317
+ expect(result.functions.some((f) => f.name === 'new')).toBe(true);
318
+ expect(result.exports.length).toBeGreaterThan(0);
319
+ expect(result.exports.some((e) => e.name === 'Config')).toBe(true);
320
+ });
321
+ it('parses Java files correctly', () => {
322
+ const result = router.parseFile(fixtures.java.content, fixtures.java.path);
323
+ expect(result.imports.length).toBeGreaterThan(0);
324
+ expect(result.imports.some((i) => i.module.includes('springframework'))).toBe(true);
325
+ expect(result.functions.length).toBeGreaterThan(0);
326
+ expect(result.functions.some((f) => f.name === 'findById')).toBe(true);
327
+ expect(result.exports.length).toBeGreaterThan(0);
328
+ expect(result.exports.some((e) => e.name === 'UserService')).toBe(true);
329
+ });
330
+ it('aggregates framework rules from all adapters', () => {
331
+ const rules = router.getAllFrameworkRules();
332
+ // Should have rules from all languages
333
+ expect(rules.length).toBeGreaterThan(10);
334
+ // TypeScript frameworks
335
+ expect(rules.some((r) => r.name === 'React')).toBe(true);
336
+ expect(rules.some((r) => r.name === 'Express')).toBe(true);
337
+ // Python frameworks
338
+ expect(rules.some((r) => r.name === 'Django')).toBe(true);
339
+ expect(rules.some((r) => r.name === 'FastAPI')).toBe(true);
340
+ // Go frameworks
341
+ expect(rules.some((r) => r.name === 'Gin')).toBe(true);
342
+ // Rust frameworks
343
+ expect(rules.some((r) => r.name === 'Actix')).toBe(true);
344
+ // Java frameworks
345
+ expect(rules.some((r) => r.name === 'Spring')).toBe(true);
346
+ });
347
+ it('throws error for unsupported file when adapter not found', () => {
348
+ expect(() => router.parseFile('content', 'file.xyz')).toThrow(/No adapter registered/);
349
+ });
350
+ });
351
+ // ============================================================
352
+ // ParseCache Integration Tests
353
+ // ============================================================
354
+ describe('ParseCache integration with adapters', () => {
355
+ let cache;
356
+ let router;
357
+ beforeEach(() => {
358
+ cache = new ParseCache({ maxMemoryMB: 50 });
359
+ router = new LanguageRouter();
360
+ router.registerAdapter(new TypeScriptAdapter());
361
+ router.registerAdapter(new PythonAdapter());
362
+ router.registerAdapter(new GoAdapter());
363
+ });
364
+ afterEach(() => {
365
+ cache.clear();
366
+ router.dispose();
367
+ });
368
+ it('caches parse results across different languages', () => {
369
+ // Parse and cache TypeScript
370
+ const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
371
+ cache.set(fixtures.typescript.path, fixtures.typescript.content, tsResult);
372
+ // Parse and cache Python
373
+ const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
374
+ cache.set(fixtures.python.path, fixtures.python.content, pyResult);
375
+ // Parse and cache Go
376
+ const goResult = router.parseFile(fixtures.go.content, fixtures.go.path);
377
+ cache.set(fixtures.go.path, fixtures.go.content, goResult);
378
+ // Verify all cached
379
+ expect(cache.size).toBe(3);
380
+ expect(cache.has(fixtures.typescript.path)).toBe(true);
381
+ expect(cache.has(fixtures.python.path)).toBe(true);
382
+ expect(cache.has(fixtures.go.path)).toBe(true);
383
+ });
384
+ it('returns cached results on cache hit', () => {
385
+ const result = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
386
+ cache.set(fixtures.typescript.path, fixtures.typescript.content, result);
387
+ const cached = cache.get(fixtures.typescript.path, fixtures.typescript.content);
388
+ expect(cached).not.toBeNull();
389
+ expect(cached).toEqual(result);
390
+ });
391
+ it('invalidates cache on content change', () => {
392
+ const result = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
393
+ cache.set(fixtures.typescript.path, fixtures.typescript.content, result);
394
+ const modifiedContent = fixtures.typescript.content + '\n// new comment';
395
+ const cached = cache.get(fixtures.typescript.path, modifiedContent);
396
+ expect(cached).toBeNull();
397
+ });
398
+ it('tracks hit/miss statistics correctly', () => {
399
+ const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
400
+ cache.set(fixtures.typescript.path, fixtures.typescript.content, tsResult);
401
+ // First access - hit
402
+ cache.get(fixtures.typescript.path, fixtures.typescript.content);
403
+ // Second access - hit
404
+ cache.get(fixtures.typescript.path, fixtures.typescript.content);
405
+ // Miss - file not cached
406
+ cache.get('not-cached.ts', 'content');
407
+ const stats = cache.getStats();
408
+ expect(stats.hitRate).toBeCloseTo(2 / 3, 2);
409
+ });
410
+ it('manages cache entries with add and invalidate', () => {
411
+ // Test cache management behavior with add and invalidate operations
412
+ const testCache = new ParseCache({ maxMemoryMB: 1 });
413
+ // Add multiple entries
414
+ for (let i = 0; i < 10; i++) {
415
+ const content = `export const x${i} = ${i}`;
416
+ const result = router.parseFile(content, `file${i}.ts`);
417
+ testCache.set(`file${i}.ts`, content, result);
418
+ }
419
+ // Verify entries were added
420
+ expect(testCache.size).toBe(10);
421
+ // Test that invalidation works (key cache behavior)
422
+ testCache.invalidate(['file0.ts', 'file1.ts', 'file2.ts']);
423
+ expect(testCache.size).toBe(7);
424
+ testCache.clear();
425
+ });
426
+ it('invalidates by pattern across languages', () => {
427
+ // Cache multiple files
428
+ const tsResult = router.parseFile(fixtures.typescript.content, 'src/api/handler.ts');
429
+ cache.set('src/api/handler.ts', fixtures.typescript.content, tsResult);
430
+ const pyResult = router.parseFile(fixtures.python.content, 'src/services/processor.py');
431
+ cache.set('src/services/processor.py', fixtures.python.content, pyResult);
432
+ const goResult = router.parseFile(fixtures.go.content, 'src/server/main.go');
433
+ cache.set('src/server/main.go', fixtures.go.content, goResult);
434
+ // Invalidate by pattern
435
+ cache.invalidatePattern('src/*.ts');
436
+ cache.invalidatePattern('src/*.py');
437
+ // Only Go file should remain
438
+ expect(cache.has('src/server/main.go')).toBe(true);
439
+ });
440
+ });
441
+ // ============================================================
442
+ // ParserWorkerPool Integration Tests
443
+ // ============================================================
444
+ describe('ParserWorkerPool with mixed language batches', () => {
445
+ let pool;
446
+ beforeEach(() => {
447
+ pool = new ParserWorkerPool({
448
+ poolSize: 2,
449
+ minBatchForWorkers: 5, // Lower threshold for testing
450
+ });
451
+ });
452
+ afterEach(() => {
453
+ pool.dispose();
454
+ });
455
+ it('processes single-language batch', async () => {
456
+ const tasks = [
457
+ {
458
+ filePath: 'a.ts',
459
+ content: 'export const a = 1',
460
+ language: 'typescript',
461
+ },
462
+ {
463
+ filePath: 'b.ts',
464
+ content: 'export const b = 2',
465
+ language: 'typescript',
466
+ },
467
+ {
468
+ filePath: 'c.ts',
469
+ content: 'export function c() {}',
470
+ language: 'typescript',
471
+ },
472
+ ];
473
+ const results = await pool.parseFiles(tasks);
474
+ expect(results).toHaveLength(3);
475
+ expect(results.every((r) => r.result !== undefined)).toBe(true);
476
+ expect(results.every((r) => r.error === undefined)).toBe(true);
477
+ });
478
+ it('processes mixed-language batch', async () => {
479
+ const tasks = [
480
+ {
481
+ filePath: 'handler.ts',
482
+ content: 'export function handle() {}',
483
+ language: 'typescript',
484
+ },
485
+ {
486
+ filePath: 'processor.py',
487
+ content: 'def process(): pass',
488
+ language: 'python',
489
+ },
490
+ {
491
+ filePath: 'main.go',
492
+ content: 'package main\nfunc main() {}',
493
+ language: 'go',
494
+ },
495
+ ];
496
+ const results = await pool.parseFiles(tasks);
497
+ expect(results).toHaveLength(3);
498
+ const tsResult = results.find((r) => r.filePath === 'handler.ts');
499
+ const pyResult = results.find((r) => r.filePath === 'processor.py');
500
+ const goResult = results.find((r) => r.filePath === 'main.go');
501
+ expect(tsResult?.result.functions.length).toBeGreaterThan(0);
502
+ expect(pyResult?.result.functions.length).toBeGreaterThan(0);
503
+ expect(goResult?.result.functions.length).toBeGreaterThan(0);
504
+ });
505
+ it('handles large mixed batch with workers', async () => {
506
+ // Create a batch large enough to use workers
507
+ const tasks = [];
508
+ // Add TypeScript files
509
+ for (let i = 0; i < 5; i++) {
510
+ tasks.push({
511
+ filePath: `src/ts/file${i}.ts`,
512
+ content: `export const value${i} = ${i}\nexport function fn${i}() { return ${i} }`,
513
+ language: 'typescript',
514
+ });
515
+ }
516
+ // Add Python files
517
+ for (let i = 0; i < 5; i++) {
518
+ tasks.push({
519
+ filePath: `src/py/file${i}.py`,
520
+ content: `def func${i}(): return ${i}\nclass Class${i}: pass`,
521
+ language: 'python',
522
+ });
523
+ }
524
+ // Add Go files
525
+ for (let i = 0; i < 5; i++) {
526
+ tasks.push({
527
+ filePath: `src/go/file${i}.go`,
528
+ content: `package main\nfunc Func${i}() int { return ${i} }`,
529
+ language: 'go',
530
+ });
531
+ }
532
+ const results = await pool.parseFiles(tasks);
533
+ expect(results).toHaveLength(15);
534
+ expect(results.filter((r) => r.filePath.endsWith('.ts'))).toHaveLength(5);
535
+ expect(results.filter((r) => r.filePath.endsWith('.py'))).toHaveLength(5);
536
+ expect(results.filter((r) => r.filePath.endsWith('.go'))).toHaveLength(5);
537
+ });
538
+ it('handles empty batch gracefully', async () => {
539
+ const results = await pool.parseFiles([]);
540
+ expect(results).toHaveLength(0);
541
+ });
542
+ it('handles parse errors gracefully', async () => {
543
+ const tasks = [
544
+ {
545
+ filePath: 'valid.ts',
546
+ content: 'export const x = 1',
547
+ language: 'typescript',
548
+ },
549
+ {
550
+ filePath: 'unknown.xyz',
551
+ content: 'invalid content',
552
+ language: 'unknown',
553
+ },
554
+ ];
555
+ const results = await pool.parseFiles(tasks);
556
+ expect(results).toHaveLength(2);
557
+ const validResult = results.find((r) => r.filePath === 'valid.ts');
558
+ expect(validResult?.error).toBeUndefined();
559
+ const invalidResult = results.find((r) => r.filePath === 'unknown.xyz');
560
+ expect(invalidResult?.error).toBeDefined();
561
+ });
562
+ it('reports timing information', async () => {
563
+ const tasks = [
564
+ {
565
+ filePath: 'test.ts',
566
+ content: fixtures.typescript.content,
567
+ language: 'typescript',
568
+ },
569
+ ];
570
+ const results = await pool.parseFiles(tasks);
571
+ expect(results[0].durationMs).toBeGreaterThanOrEqual(0);
572
+ });
573
+ it('provides pool statistics', () => {
574
+ const stats = pool.getStats();
575
+ expect(stats.poolSize).toBe(2);
576
+ expect(stats.activeWorkers).toBe(0);
577
+ expect(stats.queuedTasks).toBe(0);
578
+ });
579
+ });
580
+ // ============================================================
581
+ // ResultAggregator Integration Tests
582
+ // ============================================================
583
+ describe('ResultAggregator combining results from multiple languages', () => {
584
+ let aggregator;
585
+ let router;
586
+ beforeEach(() => {
587
+ aggregator = new ResultAggregator();
588
+ router = new LanguageRouter();
589
+ router.registerAdapter(new TypeScriptAdapter());
590
+ router.registerAdapter(new PythonAdapter());
591
+ router.registerAdapter(new GoAdapter());
592
+ router.registerAdapter(new RustAdapter());
593
+ router.registerAdapter(new JavaAdapter());
594
+ });
595
+ afterEach(() => {
596
+ aggregator.reset();
597
+ router.dispose();
598
+ });
599
+ it('aggregates results from all supported languages', () => {
600
+ // Parse and aggregate TypeScript
601
+ const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
602
+ aggregator.add({
603
+ filePath: fixtures.typescript.path,
604
+ language: 'typescript',
605
+ result: tsResult,
606
+ });
607
+ // Parse and aggregate Python
608
+ const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
609
+ aggregator.add({
610
+ filePath: fixtures.python.path,
611
+ language: 'python',
612
+ result: pyResult,
613
+ });
614
+ // Parse and aggregate Go
615
+ const goResult = router.parseFile(fixtures.go.content, fixtures.go.path);
616
+ aggregator.add({
617
+ filePath: fixtures.go.path,
618
+ language: 'go',
619
+ result: goResult,
620
+ });
621
+ // Parse and aggregate Rust
622
+ const rustResult = router.parseFile(fixtures.rust.content, fixtures.rust.path);
623
+ aggregator.add({
624
+ filePath: fixtures.rust.path,
625
+ language: 'rust',
626
+ result: rustResult,
627
+ });
628
+ // Parse and aggregate Java
629
+ const javaResult = router.parseFile(fixtures.java.content, fixtures.java.path);
630
+ aggregator.add({
631
+ filePath: fixtures.java.path,
632
+ language: 'java',
633
+ result: javaResult,
634
+ });
635
+ expect(aggregator.getFileCount()).toBe(5);
636
+ expect(aggregator.getLanguages()).toContain('typescript');
637
+ expect(aggregator.getLanguages()).toContain('python');
638
+ expect(aggregator.getLanguages()).toContain('go');
639
+ expect(aggregator.getLanguages()).toContain('rust');
640
+ expect(aggregator.getLanguages()).toContain('java');
641
+ });
642
+ it('aggregates imports from all languages with correct annotation', () => {
643
+ const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
644
+ aggregator.add({
645
+ filePath: fixtures.typescript.path,
646
+ language: 'typescript',
647
+ result: tsResult,
648
+ });
649
+ const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
650
+ aggregator.add({
651
+ filePath: fixtures.python.path,
652
+ language: 'python',
653
+ result: pyResult,
654
+ });
655
+ const imports = aggregator.getImports();
656
+ // Should have imports from both languages
657
+ const tsImports = imports.filter((i) => i.language === 'typescript');
658
+ const pyImports = imports.filter((i) => i.language === 'python');
659
+ expect(tsImports.length).toBeGreaterThan(0);
660
+ expect(pyImports.length).toBeGreaterThan(0);
661
+ // Verify source file annotation
662
+ expect(tsImports.every((i) => i.sourceFile === fixtures.typescript.path)).toBe(true);
663
+ expect(pyImports.every((i) => i.sourceFile === fixtures.python.path)).toBe(true);
664
+ });
665
+ it('aggregates exports from all languages with correct kind', () => {
666
+ const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
667
+ aggregator.add({
668
+ filePath: fixtures.typescript.path,
669
+ language: 'typescript',
670
+ result: tsResult,
671
+ });
672
+ const goResult = router.parseFile(fixtures.go.content, fixtures.go.path);
673
+ aggregator.add({
674
+ filePath: fixtures.go.path,
675
+ language: 'go',
676
+ result: goResult,
677
+ });
678
+ const exports = aggregator.getExports();
679
+ // TypeScript exports
680
+ expect(exports.some((e) => e.name === 'HandlerConfig' && e.kind === 'interface')).toBe(true);
681
+ // Go exports (structs)
682
+ expect(exports.some((e) => e.name === 'Server' && e.kind === 'struct')).toBe(true);
683
+ });
684
+ it('aggregates functions from all languages with async detection', () => {
685
+ const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
686
+ aggregator.add({
687
+ filePath: fixtures.typescript.path,
688
+ language: 'typescript',
689
+ result: tsResult,
690
+ });
691
+ const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
692
+ aggregator.add({
693
+ filePath: fixtures.python.path,
694
+ language: 'python',
695
+ result: pyResult,
696
+ });
697
+ const functions = aggregator.getFunctions();
698
+ // TypeScript async function
699
+ const tsAsyncFunc = functions.find((f) => f.name === 'handleUserRequest');
700
+ expect(tsAsyncFunc?.isAsync).toBe(true);
701
+ expect(tsAsyncFunc?.language).toBe('typescript');
702
+ // Python async function
703
+ const pyAsyncFunc = functions.find((f) => f.name === 'process_batch');
704
+ expect(pyAsyncFunc?.isAsync).toBe(true);
705
+ expect(pyAsyncFunc?.language).toBe('python');
706
+ });
707
+ it('builds complete CodebaseContext from multi-language project', () => {
708
+ // Add all fixture files
709
+ const files = [
710
+ { ...fixtures.typescript, language: 'typescript' },
711
+ { ...fixtures.python, language: 'python' },
712
+ { ...fixtures.go, language: 'go' },
713
+ { ...fixtures.rust, language: 'rust' },
714
+ { ...fixtures.java, language: 'java' },
715
+ ];
716
+ for (const file of files) {
717
+ const result = router.parseFile(file.content, file.path);
718
+ aggregator.add({
719
+ filePath: file.path,
720
+ language: file.language,
721
+ result,
722
+ });
723
+ aggregator.addLines(file.content.split('\n').length);
724
+ }
725
+ const context = aggregator.build('/project', [
726
+ { name: 'express', version: '^4.18.0', isDev: false },
727
+ { name: 'pandas', version: '>=2.0', isDev: false },
728
+ ], [
729
+ { name: 'Express', confidence: 0.9, evidence: ['import express'] },
730
+ { name: 'Gin', confidence: 0.9, evidence: ['import gin'] },
731
+ ], { durationMs: 500, version: '2.0.0', cacheHitRate: 0.8 });
732
+ expect(context.rootPath).toBe('/project');
733
+ expect(context.imports.length).toBeGreaterThan(0);
734
+ expect(context.exports.length).toBeGreaterThan(0);
735
+ expect(context.functions.length).toBeGreaterThan(0);
736
+ expect(context.frameworks).toHaveLength(2);
737
+ expect(context.dependencies).toHaveLength(2);
738
+ expect(context.stats.totalFiles).toBe(5);
739
+ expect(context.stats.totalLines).toBeGreaterThan(0);
740
+ expect(context.metadata.languages).toHaveLength(5);
741
+ expect(context.metadata.cacheHitRate).toBe(0.8);
742
+ });
743
+ it('provides accurate summary statistics', () => {
744
+ const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
745
+ aggregator.add({
746
+ filePath: fixtures.typescript.path,
747
+ language: 'typescript',
748
+ result: tsResult,
749
+ });
750
+ aggregator.addLines(fixtures.typescript.content.split('\n').length);
751
+ const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
752
+ aggregator.add({
753
+ filePath: fixtures.python.path,
754
+ language: 'python',
755
+ result: pyResult,
756
+ });
757
+ aggregator.addLines(fixtures.python.content.split('\n').length);
758
+ const summary = aggregator.getSummary();
759
+ expect(summary.files).toBe(2);
760
+ expect(summary.imports).toBeGreaterThan(0);
761
+ expect(summary.exports).toBeGreaterThan(0);
762
+ expect(summary.functions).toBeGreaterThan(0);
763
+ expect(summary.lines).toBeGreaterThan(0);
764
+ expect(summary.languages).toContain('typescript');
765
+ expect(summary.languages).toContain('python');
766
+ });
767
+ it('merges two aggregators correctly', () => {
768
+ const aggregator1 = new ResultAggregator();
769
+ const aggregator2 = new ResultAggregator();
770
+ const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
771
+ aggregator1.add({
772
+ filePath: fixtures.typescript.path,
773
+ language: 'typescript',
774
+ result: tsResult,
775
+ });
776
+ const pyResult = router.parseFile(fixtures.python.content, fixtures.python.path);
777
+ aggregator2.add({
778
+ filePath: fixtures.python.path,
779
+ language: 'python',
780
+ result: pyResult,
781
+ });
782
+ aggregator1.merge(aggregator2);
783
+ expect(aggregator1.getFileCount()).toBe(2);
784
+ expect(aggregator1.getLanguages()).toContain('typescript');
785
+ expect(aggregator1.getLanguages()).toContain('python');
786
+ });
787
+ it('resets state completely', () => {
788
+ const tsResult = router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
789
+ aggregator.add({
790
+ filePath: fixtures.typescript.path,
791
+ language: 'typescript',
792
+ result: tsResult,
793
+ });
794
+ aggregator.reset();
795
+ expect(aggregator.getFileCount()).toBe(0);
796
+ expect(aggregator.getImports()).toHaveLength(0);
797
+ expect(aggregator.getExports()).toHaveLength(0);
798
+ expect(aggregator.getFunctions()).toHaveLength(0);
799
+ expect(aggregator.getLanguages()).toHaveLength(0);
800
+ });
801
+ });
802
+ // ============================================================
803
+ // End-to-End Multi-Language Project Scenario
804
+ // ============================================================
805
+ describe('End-to-end multi-language project analysis', () => {
806
+ let router;
807
+ let cache;
808
+ let aggregator;
809
+ beforeEach(() => {
810
+ router = new LanguageRouter();
811
+ router.registerAdapter(new TypeScriptAdapter());
812
+ router.registerAdapter(new PythonAdapter());
813
+ router.registerAdapter(new GoAdapter());
814
+ cache = new ParseCache({ maxMemoryMB: 50 });
815
+ aggregator = new ResultAggregator();
816
+ });
817
+ afterEach(() => {
818
+ router.dispose();
819
+ cache.clear();
820
+ aggregator.reset();
821
+ });
822
+ it('analyzes a realistic mixed-language microservices project', () => {
823
+ // Simulate a microservices project with multiple languages
824
+ const projectFiles = [
825
+ {
826
+ path: 'api-gateway/src/server.ts',
827
+ content: `
828
+ import express from 'express'
829
+ import { createProxyMiddleware } from 'http-proxy-middleware'
830
+
831
+ export function createGateway(config: GatewayConfig) {
832
+ const app = express()
833
+ app.use('/users', createProxyMiddleware({ target: config.userServiceUrl }))
834
+ return app
835
+ }
836
+ `.trim(),
837
+ language: 'typescript',
838
+ },
839
+ {
840
+ path: 'user-service/app/main.py',
841
+ content: `
842
+ from fastapi import FastAPI, Depends
843
+ from sqlalchemy.orm import Session
844
+ from .database import get_db
845
+ from .models import User
846
+
847
+ app = FastAPI()
848
+
849
+ @app.get("/users/{user_id}")
850
+ async def get_user(user_id: int, db: Session = Depends(get_db)):
851
+ return db.query(User).filter(User.id == user_id).first()
852
+ `.trim(),
853
+ language: 'python',
854
+ },
855
+ {
856
+ path: 'notification-service/main.go',
857
+ content: `
858
+ package main
859
+
860
+ import (
861
+ "github.com/gin-gonic/gin"
862
+ "github.com/go-redis/redis/v8"
863
+ )
864
+
865
+ func main() {
866
+ router := gin.Default()
867
+ router.POST("/notify", HandleNotify)
868
+ router.Run(":8082")
869
+ }
870
+
871
+ func HandleNotify(c *gin.Context) {
872
+ c.JSON(200, gin.H{"status": "sent"})
873
+ }
874
+ `.trim(),
875
+ language: 'go',
876
+ },
877
+ ];
878
+ // Parse each file with caching
879
+ for (const file of projectFiles) {
880
+ // Check cache first
881
+ let result = cache.get(file.path, file.content);
882
+ if (!result) {
883
+ // Parse if not cached
884
+ result = router.parseFile(file.content, file.path);
885
+ cache.set(file.path, file.content, result);
886
+ }
887
+ // Aggregate
888
+ aggregator.add({
889
+ filePath: file.path,
890
+ language: file.language,
891
+ result,
892
+ });
893
+ aggregator.addLines(file.content.split('\n').length);
894
+ }
895
+ // Verify comprehensive analysis
896
+ const context = aggregator.build('/microservices-project', [], [], {
897
+ durationMs: 100,
898
+ version: '2.0.0',
899
+ cacheHitRate: 0,
900
+ });
901
+ // File counts
902
+ expect(context.stats.totalFiles).toBe(3);
903
+ expect(context.metadata.languages).toHaveLength(3);
904
+ // Cross-language imports
905
+ const imports = context.imports;
906
+ expect(imports.some((i) => i.module === 'express' && i.language === 'typescript')).toBe(true);
907
+ expect(imports.some((i) => i.module === 'fastapi' && i.language === 'python')).toBe(true);
908
+ expect(imports.some((i) => i.module === 'github.com/gin-gonic/gin' && i.language === 'go')).toBe(true);
909
+ // Cross-language functions
910
+ const functions = context.functions;
911
+ expect(functions.some((f) => f.name === 'createGateway' && f.language === 'typescript')).toBe(true);
912
+ expect(functions.some((f) => f.name === 'get_user' && f.language === 'python')).toBe(true);
913
+ expect(functions.some((f) => f.name === 'HandleNotify' && f.language === 'go')).toBe(true);
914
+ });
915
+ it('verifies cross-adapter output format consistency', () => {
916
+ // All adapters should produce consistent ParseResult structure
917
+ const testCases = [
918
+ { path: 'test.ts', content: 'export function test() {}' },
919
+ { path: 'test.py', content: 'def test(): pass' },
920
+ { path: 'test.go', content: 'package main\nfunc Test() {}' },
921
+ ];
922
+ for (const { path, content } of testCases) {
923
+ const result = router.parseFile(content, path);
924
+ // All results should have the required arrays
925
+ expect(Array.isArray(result.imports)).toBe(true);
926
+ expect(Array.isArray(result.exports)).toBe(true);
927
+ expect(Array.isArray(result.functions)).toBe(true);
928
+ // Functions should have consistent shape
929
+ for (const func of result.functions) {
930
+ expect(typeof func.name).toBe('string');
931
+ expect(typeof func.parameterCount).toBe('number');
932
+ expect(typeof func.isAsync).toBe('boolean');
933
+ expect(typeof func.isExported).toBe('boolean');
934
+ expect(typeof func.sourceFile).toBe('string');
935
+ expect(typeof func.line).toBe('number');
936
+ }
937
+ // Imports should have consistent shape
938
+ for (const imp of result.imports) {
939
+ expect(typeof imp.module).toBe('string');
940
+ expect(Array.isArray(imp.namedImports)).toBe(true);
941
+ expect(typeof imp.isTypeOnly).toBe('boolean');
942
+ expect(typeof imp.sourceFile).toBe('string');
943
+ }
944
+ // Exports should have consistent shape
945
+ for (const exp of result.exports) {
946
+ expect(typeof exp.name).toBe('string');
947
+ expect(typeof exp.kind).toBe('string');
948
+ expect(typeof exp.isDefault).toBe('boolean');
949
+ expect(typeof exp.sourceFile).toBe('string');
950
+ }
951
+ }
952
+ });
953
+ it('tests cache hit/miss behavior across languages', () => {
954
+ const files = [
955
+ { path: 'a.ts', content: 'export const a = 1' },
956
+ { path: 'b.py', content: 'a = 1' },
957
+ { path: 'c.go', content: 'package main\nvar a = 1' },
958
+ ];
959
+ // First pass - all misses
960
+ for (const file of files) {
961
+ const cached = cache.get(file.path, file.content);
962
+ expect(cached).toBeNull();
963
+ const result = router.parseFile(file.content, file.path);
964
+ cache.set(file.path, file.content, result);
965
+ }
966
+ // Second pass - all hits
967
+ for (const file of files) {
968
+ const cached = cache.get(file.path, file.content);
969
+ expect(cached).not.toBeNull();
970
+ }
971
+ const stats = cache.getStats();
972
+ // 3 misses + 3 hits = 50% hit rate
973
+ expect(stats.hitRate).toBeCloseTo(0.5, 2);
974
+ // Third pass with modified content - misses
975
+ for (const file of files) {
976
+ const modifiedContent = file.content + '\n// modified';
977
+ const cached = cache.get(file.path, modifiedContent);
978
+ expect(cached).toBeNull();
979
+ }
980
+ });
981
+ });
982
+ // ============================================================
983
+ // Performance Tests
984
+ // ============================================================
985
+ describe('Performance benchmarks', () => {
986
+ let router;
987
+ let pool;
988
+ beforeEach(() => {
989
+ router = new LanguageRouter();
990
+ router.registerAdapter(new TypeScriptAdapter());
991
+ router.registerAdapter(new PythonAdapter());
992
+ router.registerAdapter(new GoAdapter());
993
+ pool = new ParserWorkerPool({ poolSize: 4, minBatchForWorkers: 5 });
994
+ });
995
+ afterEach(() => {
996
+ router.dispose();
997
+ pool.dispose();
998
+ });
999
+ it('parses mixed-language batch under 500ms', async () => {
1000
+ const tasks = [];
1001
+ // Generate test files
1002
+ for (let i = 0; i < 10; i++) {
1003
+ tasks.push({
1004
+ filePath: `file${i}.ts`,
1005
+ content: fixtures.typescript.content,
1006
+ language: 'typescript',
1007
+ });
1008
+ tasks.push({
1009
+ filePath: `file${i}.py`,
1010
+ content: fixtures.python.content,
1011
+ language: 'python',
1012
+ });
1013
+ tasks.push({
1014
+ filePath: `file${i}.go`,
1015
+ content: fixtures.go.content,
1016
+ language: 'go',
1017
+ });
1018
+ }
1019
+ const start = performance.now();
1020
+ const results = await pool.parseFiles(tasks);
1021
+ const duration = performance.now() - start;
1022
+ expect(results).toHaveLength(30);
1023
+ expect(duration).toBeLessThan(500);
1024
+ });
1025
+ it('maintains consistent parsing speed across languages', () => {
1026
+ const iterations = 10;
1027
+ const times = {
1028
+ typescript: [],
1029
+ python: [],
1030
+ go: [],
1031
+ };
1032
+ for (let i = 0; i < iterations; i++) {
1033
+ // TypeScript
1034
+ let start = performance.now();
1035
+ router.parseFile(fixtures.typescript.content, fixtures.typescript.path);
1036
+ times.typescript.push(performance.now() - start);
1037
+ // Python
1038
+ start = performance.now();
1039
+ router.parseFile(fixtures.python.content, fixtures.python.path);
1040
+ times.python.push(performance.now() - start);
1041
+ // Go
1042
+ start = performance.now();
1043
+ router.parseFile(fixtures.go.content, fixtures.go.path);
1044
+ times.go.push(performance.now() - start);
1045
+ }
1046
+ // Calculate averages
1047
+ const avgTimes = {
1048
+ typescript: times.typescript.reduce((a, b) => a + b, 0) / iterations,
1049
+ python: times.python.reduce((a, b) => a + b, 0) / iterations,
1050
+ go: times.go.reduce((a, b) => a + b, 0) / iterations,
1051
+ };
1052
+ // All languages should parse in under 50ms average
1053
+ expect(avgTimes.typescript).toBeLessThan(50);
1054
+ expect(avgTimes.python).toBeLessThan(50);
1055
+ expect(avgTimes.go).toBeLessThan(50);
1056
+ });
1057
+ });
1058
+ });
1059
+ //# sourceMappingURL=integration.test.js.map