@getmikk/core 2.0.13 → 2.0.15

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 (71) hide show
  1. package/README.md +4 -4
  2. package/package.json +2 -1
  3. package/src/analysis/index.ts +9 -0
  4. package/src/analysis/taint-analysis.ts +419 -0
  5. package/src/analysis/type-flow.ts +247 -0
  6. package/src/cache/incremental-cache.ts +278 -0
  7. package/src/cache/index.ts +1 -0
  8. package/src/contract/contract-generator.ts +31 -3
  9. package/src/contract/contract-reader.ts +1 -0
  10. package/src/contract/lock-compiler.ts +125 -12
  11. package/src/contract/schema.ts +4 -0
  12. package/src/error-handler.ts +2 -1
  13. package/src/graph/cluster-detector.ts +2 -4
  14. package/src/graph/dead-code-detector.ts +303 -117
  15. package/src/graph/graph-builder.ts +21 -161
  16. package/src/graph/impact-analyzer.ts +1 -0
  17. package/src/graph/index.ts +2 -0
  18. package/src/graph/rich-function-index.ts +1080 -0
  19. package/src/graph/symbol-table.ts +252 -0
  20. package/src/hash/hash-store.ts +1 -0
  21. package/src/index.ts +4 -0
  22. package/src/parser/base-extractor.ts +19 -0
  23. package/src/parser/boundary-checker.ts +31 -12
  24. package/src/parser/error-recovery.ts +647 -0
  25. package/src/parser/function-body-extractor.ts +248 -0
  26. package/src/parser/go/go-extractor.ts +249 -676
  27. package/src/parser/index.ts +138 -295
  28. package/src/parser/language-registry.ts +57 -0
  29. package/src/parser/oxc-parser.ts +166 -28
  30. package/src/parser/oxc-resolver.ts +179 -11
  31. package/src/parser/parser-constants.ts +1 -0
  32. package/src/parser/rust/rust-extractor.ts +109 -0
  33. package/src/parser/tree-sitter/parser.ts +400 -66
  34. package/src/parser/tree-sitter/queries.ts +106 -10
  35. package/src/parser/types.ts +20 -1
  36. package/src/search/bm25.ts +21 -8
  37. package/src/search/direct-search.ts +472 -0
  38. package/src/search/embedding-provider.ts +249 -0
  39. package/src/search/index.ts +12 -0
  40. package/src/search/semantic-search.ts +435 -0
  41. package/src/security/index.ts +1 -0
  42. package/src/security/scanner.ts +342 -0
  43. package/src/utils/artifact-transaction.ts +1 -0
  44. package/src/utils/atomic-write.ts +1 -0
  45. package/src/utils/errors.ts +89 -4
  46. package/src/utils/fs.ts +150 -65
  47. package/src/utils/json.ts +1 -0
  48. package/src/utils/language-registry.ts +96 -5
  49. package/src/utils/minimatch.ts +49 -6
  50. package/src/utils/path.ts +26 -0
  51. package/tests/dead-code.test.ts +3 -2
  52. package/tests/direct-search.test.ts +435 -0
  53. package/tests/error-recovery.test.ts +143 -0
  54. package/tests/fixtures/simple-api/src/index.ts +1 -1
  55. package/tests/go-parser.test.ts +19 -335
  56. package/tests/js-parser.test.ts +18 -1089
  57. package/tests/language-registry-all.test.ts +276 -0
  58. package/tests/language-registry.test.ts +6 -4
  59. package/tests/parse-diagnostics.test.ts +9 -96
  60. package/tests/parser.test.ts +42 -771
  61. package/tests/polyglot-parser.test.ts +117 -0
  62. package/tests/rich-function-index.test.ts +703 -0
  63. package/tests/tree-sitter-parser.test.ts +108 -80
  64. package/tests/ts-parser.test.ts +8 -8
  65. package/tests/verification.test.ts +175 -0
  66. package/src/parser/base-parser.ts +0 -16
  67. package/src/parser/go/go-parser.ts +0 -43
  68. package/src/parser/javascript/js-extractor.ts +0 -278
  69. package/src/parser/javascript/js-parser.ts +0 -101
  70. package/src/parser/typescript/ts-extractor.ts +0 -447
  71. package/src/parser/typescript/ts-parser.ts +0 -36
@@ -0,0 +1,435 @@
1
+ import { describe, it, expect, beforeEach } from 'bun:test'
2
+ import { DirectSearchEngine, createDirectSearch } from '../src/search/direct-search'
3
+ import type { MikkLock } from '../src/contract/schema'
4
+
5
+ function createMockLock(functions: Partial<MikkLock['functions']> = {}): MikkLock {
6
+ return {
7
+ version: '1.0.0',
8
+ generatedAt: new Date().toISOString(),
9
+ generatorVersion: '1.0.0',
10
+ projectRoot: '/test',
11
+ syncState: {
12
+ status: 'clean',
13
+ lastSyncAt: new Date().toISOString(),
14
+ lockHash: 'x',
15
+ contractHash: 'x',
16
+ },
17
+ graph: { nodes: 0, edges: 0, rootHash: 'x' },
18
+ functions: functions as MikkLock['functions'],
19
+ classes: {},
20
+ files: {},
21
+ modules: {},
22
+ routes: [],
23
+ }
24
+ }
25
+
26
+ describe('DirectSearchEngine', () => {
27
+ let engine: DirectSearchEngine
28
+
29
+ function createEngineWithData() {
30
+ const lock = createMockLock({
31
+ 'fn:src/auth.ts:login': {
32
+ id: 'fn:src/auth.ts:login',
33
+ name: 'login',
34
+ file: 'src/auth.ts',
35
+ startLine: 10,
36
+ endLine: 30,
37
+ hash: 'hash1',
38
+ calls: ['fn:src/db.ts:query'],
39
+ calledBy: [],
40
+ moduleId: 'auth',
41
+ params: [
42
+ { name: 'email', type: 'string' },
43
+ { name: 'password', type: 'string' },
44
+ ],
45
+ returnType: 'User',
46
+ isAsync: true,
47
+ },
48
+ 'fn:src/auth.ts:logout': {
49
+ id: 'fn:src/auth.ts:logout',
50
+ name: 'logout',
51
+ file: 'src/auth.ts',
52
+ startLine: 35,
53
+ endLine: 45,
54
+ hash: 'hash2',
55
+ calls: [],
56
+ calledBy: ['fn:src/auth.ts:login'],
57
+ moduleId: 'auth',
58
+ params: [],
59
+ returnType: 'void',
60
+ isAsync: false,
61
+ },
62
+ 'fn:src/db.ts:query': {
63
+ id: 'fn:src/db.ts:query',
64
+ name: 'query',
65
+ file: 'src/db.ts',
66
+ startLine: 1,
67
+ endLine: 20,
68
+ hash: 'hash3',
69
+ calls: [],
70
+ calledBy: ['fn:src/auth.ts:login'],
71
+ moduleId: 'db',
72
+ params: [
73
+ { name: 'sql', type: 'string' },
74
+ ],
75
+ returnType: 'Result',
76
+ isAsync: true,
77
+ },
78
+ 'fn:src/api.ts:getUser': {
79
+ id: 'fn:src/api.ts:getUser',
80
+ name: 'getUser',
81
+ file: 'src/api.ts',
82
+ startLine: 5,
83
+ endLine: 15,
84
+ hash: 'hash4',
85
+ calls: ['fn:src/auth.ts:login'],
86
+ calledBy: [],
87
+ moduleId: 'api',
88
+ params: [{ name: 'id', type: 'string' }],
89
+ returnType: 'User',
90
+ isAsync: true,
91
+ isExported: true,
92
+ },
93
+ })
94
+ return new DirectSearchEngine(lock)
95
+ }
96
+
97
+ beforeEach(() => {
98
+ engine = createEngineWithData()
99
+ })
100
+
101
+ describe('find', () => {
102
+ it('finds function by exact name', () => {
103
+ const fn = engine.find('login')
104
+ expect(fn).toBeDefined()
105
+ expect(fn?.name).toBe('login')
106
+ })
107
+
108
+ it('finds function by partial name match', () => {
109
+ const fn = engine.find('log')
110
+ expect(fn).toBeDefined()
111
+ expect(fn?.name).toBe('login')
112
+ })
113
+
114
+ it('returns undefined for non-existent function', () => {
115
+ const fn = engine.find('nonExistentFunction')
116
+ expect(fn).toBeUndefined()
117
+ })
118
+ })
119
+
120
+ describe('findAll', () => {
121
+ it('finds all functions matching name pattern', () => {
122
+ const fns = engine.findAll('log')
123
+ expect(fns.length).toBeGreaterThanOrEqual(2)
124
+ expect(fns.map(f => f.name).sort()).toContain('login')
125
+ expect(fns.map(f => f.name).sort()).toContain('logout')
126
+ })
127
+ })
128
+
129
+ describe('findBySignature', () => {
130
+ it('finds function by full signature', () => {
131
+ const fn = engine.findBySignature('async login(email: string, password: string): User')
132
+ expect(fn?.name).toBe('login')
133
+ })
134
+
135
+ it('returns undefined for non-existent signature', () => {
136
+ const fn = engine.findBySignature('nonExistent(params)')
137
+ expect(fn).toBeUndefined()
138
+ })
139
+ })
140
+
141
+ describe('findByLocation', () => {
142
+ it('finds function by file and line', () => {
143
+ const fn = engine.findByLocation('src/auth.ts', 15)
144
+ expect(fn?.name).toBe('login')
145
+ })
146
+
147
+ it('returns undefined for line outside all functions', () => {
148
+ const fn = engine.findByLocation('src/auth.ts', 100)
149
+ expect(fn).toBeUndefined()
150
+ })
151
+ })
152
+
153
+ describe('findInFile', () => {
154
+ it('finds all functions in a file', () => {
155
+ const fns = engine.findInFile('src/auth.ts')
156
+ expect(fns).toHaveLength(2)
157
+ expect(fns.map(f => f.name).sort()).toEqual(['login', 'logout'])
158
+ })
159
+
160
+ it('returns empty array for non-existent file', () => {
161
+ const fns = engine.findInFile('nonExistent.ts')
162
+ expect(fns).toHaveLength(0)
163
+ })
164
+ })
165
+
166
+ describe('findInModule', () => {
167
+ it('finds all functions in a module', () => {
168
+ const fns = engine.findInModule('auth')
169
+ expect(fns).toHaveLength(2)
170
+ })
171
+ })
172
+
173
+ describe('findExport', () => {
174
+ it('finds exported functions', () => {
175
+ const fns = engine.findExport()
176
+ expect(fns.some(f => f.name === 'getUser')).toBe(true)
177
+ expect(fns.every(f => f.isExported)).toBe(true)
178
+ })
179
+
180
+ it('filters exported by pattern', () => {
181
+ const fns = engine.findExport('user')
182
+ expect(fns.some(f => f.name === 'getUser')).toBe(true)
183
+ })
184
+ })
185
+
186
+ describe('findAsync', () => {
187
+ it('finds async functions', () => {
188
+ const fns = engine.findAsync()
189
+ expect(fns.every(f => f.isAsync)).toBe(true)
190
+ expect(fns.length).toBeGreaterThanOrEqual(2)
191
+ })
192
+
193
+ it('filters async by pattern', () => {
194
+ const fns = engine.findAsync('log')
195
+ expect(fns.length).toBeGreaterThanOrEqual(1)
196
+ expect(fns.every(f => f.name.includes('log'))).toBe(true)
197
+ })
198
+ })
199
+
200
+ describe('findByReturnType', () => {
201
+ it('finds functions by return type', () => {
202
+ const fns = engine.findByReturnType('User')
203
+ expect(fns.length).toBeGreaterThanOrEqual(2)
204
+ expect(fns.every(f => f.returnType === 'User')).toBe(true)
205
+ })
206
+ })
207
+
208
+ describe('search', () => {
209
+ it('searches by name', () => {
210
+ const fns = engine.search({ name: 'log' })
211
+ expect(fns.length).toBeGreaterThanOrEqual(2)
212
+ })
213
+
214
+ it('searches by exact name', () => {
215
+ const fns = engine.search({ exact: 'login' })
216
+ expect(fns).toHaveLength(1)
217
+ expect(fns[0].name).toBe('login')
218
+ })
219
+
220
+ it('searches by starts with', () => {
221
+ const fns = engine.search({ startsWith: 'get' })
222
+ expect(fns.some(f => f.name === 'getUser')).toBe(true)
223
+ })
224
+
225
+ it('searches by file', () => {
226
+ const fns = engine.search({ file: 'auth' })
227
+ expect(fns.every(f => f.file.includes('auth'))).toBe(true)
228
+ })
229
+
230
+ it('searches by module', () => {
231
+ const fns = engine.search({ module: 'auth' })
232
+ expect(fns.every(f => f.moduleId === 'auth')).toBe(true)
233
+ })
234
+
235
+ it('searches by exported', () => {
236
+ const fns = engine.search({ exported: true })
237
+ expect(fns.every(f => f.isExported)).toBe(true)
238
+ })
239
+
240
+ it('searches by async', () => {
241
+ const fns = engine.search({ async: true })
242
+ expect(fns.every(f => f.isAsync)).toBe(true)
243
+ })
244
+
245
+ it('searches by return type', () => {
246
+ const fns = engine.search({ returns: 'User' })
247
+ expect(fns.every(f => f.returnType === 'User')).toBe(true)
248
+ })
249
+
250
+ it('searches by param name', () => {
251
+ const fns = engine.search({ param: 'email' })
252
+ expect(fns.some(f => f.name === 'login')).toBe(true)
253
+ })
254
+
255
+ it('searches by keyword', () => {
256
+ const fns = engine.search({ keyword: 'async' })
257
+ expect(fns.every(f => f.keywords.includes('async'))).toBe(true)
258
+ })
259
+
260
+ it('searches by calls', () => {
261
+ const fns = engine.search({ calls: 'query' })
262
+ expect(fns.some(f => f.name === 'login')).toBe(true)
263
+ })
264
+
265
+ it('combines multiple criteria', () => {
266
+ const fns = engine.search({
267
+ name: 'log',
268
+ async: true,
269
+ module: 'auth',
270
+ })
271
+ expect(fns.some(f => f.name === 'login')).toBe(true)
272
+ })
273
+ })
274
+
275
+ describe('findCallers / findCallees', () => {
276
+ it('finds callers of a function', () => {
277
+ const callers = engine.findCallers('fn:src/db.ts:query')
278
+ expect(callers.some(f => f.name === 'login')).toBe(true)
279
+ })
280
+
281
+ it('finds callees of a function', () => {
282
+ const callees = engine.findCallees('fn:src/auth.ts:login')
283
+ expect(callees.some(f => f.name === 'query')).toBe(true)
284
+ })
285
+ })
286
+
287
+ describe('findRelated', () => {
288
+ it('finds related functions within depth', () => {
289
+ const related = engine.findRelated('fn:src/db.ts:query', 1)
290
+ expect(related.length).toBeGreaterThanOrEqual(1)
291
+ })
292
+ })
293
+
294
+ describe('getContext', () => {
295
+ it('gets full context', () => {
296
+ const ctx = engine.getContext('fn:src/auth.ts:login', { full: true })
297
+ expect(ctx).toBeDefined()
298
+ expect(ctx?.signature).toContain('login')
299
+ expect(ctx?.params).toHaveLength(2)
300
+ })
301
+
302
+ it('returns null for non-existent function', () => {
303
+ const ctx = engine.getContext('nonExistent', { full: true })
304
+ expect(ctx).toBeNull()
305
+ })
306
+ })
307
+
308
+ describe('getFunctionWithContext', () => {
309
+ it('returns rich function data', () => {
310
+ const fn = engine.getFunctionWithContext('fn:src/auth.ts:login')
311
+ expect(fn).toBeDefined()
312
+ expect(fn?.name).toBe('login')
313
+ expect(fn?.isAsync).toBe(true)
314
+ expect(fn?.params).toHaveLength(2)
315
+ })
316
+
317
+ it('returns null for non-existent function', () => {
318
+ const fn = engine.getFunctionWithContext('nonExistent')
319
+ expect(fn).toBeNull()
320
+ })
321
+ })
322
+
323
+ describe('getStats', () => {
324
+ it('returns correct statistics', () => {
325
+ const stats = engine.getStats()
326
+ expect(stats.totalFunctions).toBe(4)
327
+ expect(stats.exportedCount).toBe(1)
328
+ expect(stats.asyncCount).toBe(3)
329
+ })
330
+ })
331
+
332
+ describe('getAllSummaries', () => {
333
+ it('returns all function summaries', () => {
334
+ const summaries = engine.getAllSummaries()
335
+ expect(summaries).toHaveLength(4)
336
+ expect(summaries[0]).toHaveProperty('id')
337
+ expect(summaries[0]).toHaveProperty('name')
338
+ expect(summaries[0]).toHaveProperty('signature')
339
+ expect(summaries[0]).toHaveProperty('purpose')
340
+ expect(summaries[0]).toHaveProperty('file')
341
+ })
342
+ })
343
+
344
+ describe('quickSearch', () => {
345
+ it('performs quick text search', () => {
346
+ const fns = engine.quickSearch('login', 5)
347
+ expect(fns.length).toBeGreaterThan(0)
348
+ expect(fns[0].name).toBe('login')
349
+ })
350
+ })
351
+
352
+ describe('findSimilar', () => {
353
+ it('finds similar functions by name', () => {
354
+ const fns = engine.findSimilar({ name: 'loginn' })
355
+ expect(fns.length).toBeGreaterThan(0)
356
+ })
357
+
358
+ it('finds similar by param types', () => {
359
+ const fns = engine.findSimilar({ paramTypes: ['string'] })
360
+ expect(fns.length).toBeGreaterThan(0)
361
+ })
362
+
363
+ it('finds similar by return type', () => {
364
+ const fns = engine.findSimilar({ returnType: 'User' })
365
+ expect(fns.length).toBeGreaterThanOrEqual(2)
366
+ })
367
+
368
+ it('combines similarity criteria', () => {
369
+ const fns = engine.findSimilar({
370
+ returnType: 'User',
371
+ isAsync: true,
372
+ } as any)
373
+ expect(fns.every(f => f.returnType === 'User')).toBe(true)
374
+ })
375
+ })
376
+
377
+ describe('searchByPattern', () => {
378
+ it('searches by regex pattern', () => {
379
+ const fns = engine.searchByPattern('log')
380
+ expect(fns.some(f => f.name === 'login')).toBe(true)
381
+ expect(fns.some(f => f.name === 'logout')).toBe(true)
382
+ })
383
+ })
384
+
385
+ describe('exportAll', () => {
386
+ it('exports all function data', () => {
387
+ const exported = engine.exportAll()
388
+ expect(exported).toHaveLength(4)
389
+ expect(exported[0]).toHaveProperty('id')
390
+ expect(exported[0]).toHaveProperty('signature')
391
+ expect(exported[0]).toHaveProperty('fullSignature')
392
+ expect(exported[0]).toHaveProperty('paramCount')
393
+ })
394
+ })
395
+
396
+ describe('getById', () => {
397
+ it('gets function by ID', () => {
398
+ const fn = engine.getById('fn:src/auth.ts:login')
399
+ expect(fn).toBeDefined()
400
+ expect(fn?.name).toBe('login')
401
+ })
402
+
403
+ it('returns undefined for non-existent ID', () => {
404
+ const fn = engine.getById('nonExistent')
405
+ expect(fn).toBeUndefined()
406
+ })
407
+ })
408
+
409
+ describe('count', () => {
410
+ it('returns correct count', () => {
411
+ expect(engine.count()).toBe(4)
412
+ })
413
+ })
414
+
415
+ describe('createDirectSearch', () => {
416
+ it('creates search engine from lock', () => {
417
+ const lock = createMockLock({
418
+ 'fn:src/test.ts:hello': {
419
+ id: 'fn:src/test.ts:hello',
420
+ name: 'hello',
421
+ file: 'src/test.ts',
422
+ startLine: 1,
423
+ endLine: 10,
424
+ hash: 'x',
425
+ calls: [],
426
+ calledBy: [],
427
+ moduleId: 'test',
428
+ },
429
+ })
430
+ const search = createDirectSearch(lock)
431
+ expect(search.count()).toBe(1)
432
+ expect(search.find('hello')?.name).toBe('hello')
433
+ })
434
+ })
435
+ })
@@ -0,0 +1,143 @@
1
+ import { describe, it, expect, beforeEach } from 'bun:test';
2
+ import { parseFilesWithDiagnostics } from '../src/parser/index.js';
3
+ import { ErrorRecoveryEngine } from '../src/parser/error-recovery.js';
4
+ import * as fs from 'node:fs';
5
+ import * as path from 'node:path';
6
+ import * as os from 'node:os';
7
+
8
+ describe('Error Recovery Integration', () => {
9
+ let tmpDir: string;
10
+
11
+ beforeEach(() => {
12
+ tmpDir = path.join(os.tmpdir(), 'mikk-error-recovery-' + Math.random().toString(36).slice(2));
13
+ });
14
+
15
+ async function setupFiles(files: Record<string, string>) {
16
+ for (const [name, content] of Object.entries(files)) {
17
+ const filePath = path.join(tmpDir, name);
18
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
19
+ fs.writeFileSync(filePath, content);
20
+ }
21
+ const filePaths = Object.keys(files).map(f => path.join(tmpDir, f));
22
+ const readFile = async (p: string) => fs.readFileSync(p, 'utf-8');
23
+ return { filePaths, readFile };
24
+ }
25
+
26
+ describe('ErrorRecoveryEngine', () => {
27
+ it('extracts functions from malformed TypeScript', async () => {
28
+ const content = `
29
+ function validate(data) {
30
+ if (data === null) return
31
+ return data.trim()
32
+ }
33
+
34
+ class Handler {
35
+ process(x) { return x * 2 }
36
+ }
37
+
38
+ import { foo } from './utils';
39
+ `;
40
+
41
+ const engine = new ErrorRecoveryEngine();
42
+ const result = await engine.recover('/test.ts', content, 'typescript');
43
+
44
+ expect(result.success).toBe(true);
45
+ expect(result.parsed.functions.length).toBeGreaterThan(0);
46
+ expect(result.confidence).toBeGreaterThan(0);
47
+
48
+ const fnNames = result.parsed.functions.map(f => f.name);
49
+ expect(fnNames).toContain('validate');
50
+ });
51
+
52
+ it('extracts functions from malformed Python', async () => {
53
+ const content = `
54
+ def calculate(x, y):
55
+ if x is None:
56
+ return None
57
+ return x + y
58
+
59
+ class DataProcessor:
60
+ def run(self, data):
61
+ return data
62
+ `;
63
+
64
+ const engine = new ErrorRecoveryEngine();
65
+ const result = await engine.recover('/test.py', content, 'python');
66
+
67
+ expect(result.success).toBe(true);
68
+ expect(result.parsed.functions.length).toBeGreaterThanOrEqual(2);
69
+
70
+ const fnNames = result.parsed.functions.map(f => f.name);
71
+ expect(fnNames).toContain('calculate');
72
+ });
73
+
74
+ it('extracts imports from JavaScript', async () => {
75
+ const content = `
76
+ import { foo, bar } from './module';
77
+ import defaultExport from './other';
78
+ `;
79
+
80
+ const engine = new ErrorRecoveryEngine();
81
+ const result = await engine.recover('/test.js', content, 'javascript');
82
+
83
+ expect(result.success).toBe(true);
84
+ expect(result.parsed.imports.length).toBeGreaterThan(0);
85
+ });
86
+
87
+ it('returns zero confidence for completely invalid content', async () => {
88
+ const content = `{{{{ invalid !!!`;
89
+
90
+ const engine = new ErrorRecoveryEngine();
91
+ const result = await engine.recover('/test.ts', content, 'typescript');
92
+
93
+ expect(result.confidence).toBeLessThanOrEqual(0);
94
+ });
95
+ });
96
+
97
+ describe('parseFilesWithDiagnostics integration', () => {
98
+ it('handles unsupported extensions gracefully', async () => {
99
+ const { filePaths, readFile } = await setupFiles({
100
+ 'test.xyz': `function foo() {}`
101
+ });
102
+
103
+ const result = await parseFilesWithDiagnostics(filePaths, tmpDir, readFile);
104
+
105
+ expect(result.files.length).toBe(1);
106
+ expect(result.summary.unsupportedFiles).toBe(1);
107
+ });
108
+
109
+ it('parses valid TypeScript successfully', async () => {
110
+ const { filePaths, readFile } = await setupFiles({
111
+ 'valid.ts': `
112
+ export function processData(input: string): string {
113
+ return input.trim();
114
+ }
115
+
116
+ export class Handler {
117
+ handle(data: unknown) {}
118
+ }
119
+ `
120
+ });
121
+
122
+ const result = await parseFilesWithDiagnostics(filePaths, tmpDir, readFile);
123
+
124
+ expect(result.files.length).toBe(1);
125
+ const validFile = result.files[0];
126
+ expect(validFile.functions.length).toBeGreaterThan(0);
127
+ expect(result.diagnostics.filter(d => d.reason === 'parse-error')).toHaveLength(0);
128
+ });
129
+
130
+ it('handles unreadable files with diagnostic', async () => {
131
+ const nonExistentPath = path.join(tmpDir, 'does-not-exist.ts');
132
+
133
+ const result = await parseFilesWithDiagnostics(
134
+ [nonExistentPath],
135
+ tmpDir,
136
+ async () => { throw new Error('File not found'); }
137
+ );
138
+
139
+ expect(result.summary.unreadableFiles).toBe(1);
140
+ expect(result.diagnostics.some(d => d.reason === 'read-error')).toBe(true);
141
+ });
142
+ });
143
+ });
@@ -1,7 +1,7 @@
1
1
  import { authMiddleware } from './auth/middleware'
2
2
 
3
3
  const app = {
4
- use: (handler: any) => { },
4
+ use: (_handler: any) => { },
5
5
  listen: (port: number) => console.log(`Listening on ${port}`)
6
6
  }
7
7