@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
@@ -1,790 +1,61 @@
1
- import { describe, it, expect, beforeEach } from 'bun:test'
2
- import { TypeScriptParser } from '../src/parser/typescript/ts-parser'
3
- import { OxcParser } from '../src/parser/oxc-parser'
4
- import { TypeScriptExtractor } from '../src/parser/typescript/ts-extractor'
5
- import { TypeScriptResolver } from '../src/parser/typescript/ts-resolver'
6
- import { getParser } from '../src/parser/index'
7
- import { UnsupportedLanguageError } from '../src/utils/errors'
8
- import type { ParsedFile, ParsedFunction } from '../src/parser/types'
9
-
10
- describe('TypeScriptExtractor - Comprehensive', () => {
11
- describe('Function Extraction', () => {
12
- it('extracts function declarations', () => {
13
- const extractor = new TypeScriptExtractor('src/auth.ts', `
14
- export function verifyToken(token: string): boolean {
15
- return true
16
- }
17
- `)
18
- const fns = extractor.extractFunctions()
19
- expect(fns).toHaveLength(1)
20
- expect(fns[0].name).toBe('verifyToken')
21
- expect(fns[0].isExported).toBe(true)
22
- expect(fns[0].params[0].name).toBe('token')
23
- expect(fns[0].params[0].type).toBe('string')
24
- expect(fns[0].returnType).toBe('boolean')
25
- })
26
-
27
- it('extracts arrow functions assigned to const', () => {
28
- const extractor = new TypeScriptExtractor('src/utils.ts', `
29
- export const greet = (name: string): string => {
30
- return 'Hello ' + name
31
- }
32
- `)
33
- const fns = extractor.extractFunctions()
34
- expect(fns).toHaveLength(1)
35
- expect(fns[0].name).toBe('greet')
36
- expect(fns[0].isExported).toBe(true)
37
- })
38
-
39
- it('extracts async functions', () => {
40
- const extractor = new TypeScriptExtractor('src/db.ts', `
41
- export async function findUser(id: string): Promise<User> {
42
- return await db.find(id)
43
- }
44
- `)
45
- const fns = extractor.extractFunctions()
46
- expect(fns[0].isAsync).toBe(true)
47
- })
48
-
49
- it('extracts generator functions', () => {
50
- const extractor = new TypeScriptExtractor('src/gen.ts', `
51
- function* numberGenerator(): Generator<number> {
52
- yield 1
53
- yield 2
54
- yield 3
55
- }
56
- `)
57
- const fns = extractor.extractFunctions()
58
- expect(fns).toHaveLength(1)
59
- expect(fns[0].name).toBe('numberGenerator')
60
- })
61
-
62
- it('extracts function overloads', () => {
63
- const extractor = new TypeScriptExtractor('src/overload.ts', `
64
- function parse(value: string): number
65
- function parse(value: string, base: number): number
66
- function parse(value: string, base?: number): number {
67
- return parseInt(value, base || 10)
68
- }
69
- `)
70
- const fns = extractor.extractFunctions()
71
- expect(fns.length).toBeGreaterThanOrEqual(1)
72
- })
73
-
74
- it('extracts call expressions from function bodies', () => {
75
- const extractor = new TypeScriptExtractor('src/auth.ts', `
76
- import { jwtDecode } from './jwt'
77
- function verifyToken(token: string) {
78
- const decoded = jwtDecode(token)
79
- console.log(decoded)
80
- return decoded.exp > Date.now()
81
- }
82
- `)
83
- const fns = extractor.extractFunctions()
84
- expect(fns[0].calls.some(c => c.name === 'jwtDecode')).toBe(true)
85
- })
86
-
87
- it('extracts nested function calls', () => {
88
- const extractor = new TypeScriptExtractor('src/nested.ts', `
89
- function outer() {
90
- return inner().value
91
- }
92
- function inner() {
93
- return { value: 42 }
94
- }
95
- `)
96
- const fns = extractor.extractFunctions()
97
- const outer = fns.find(f => f.name === 'outer')
98
- expect(outer?.calls.some(c => c.name === 'inner')).toBe(true)
99
- })
100
-
101
- it('extracts methods on objects', () => {
102
- const extractor = new TypeScriptExtractor('src/obj.ts', `
103
- function getUser() { return { name: 'test' } }
104
- function getAge() { return 25 }
105
- `)
106
- const fns = extractor.extractFunctions()
107
- expect(fns.length).toBe(2)
108
- })
109
-
110
- it('extracts class methods', () => {
111
- const extractor = new TypeScriptExtractor('src/service.ts', `
112
- export class AuthService {
113
- verify(token: string): boolean {
114
- return true
115
- }
116
- async refresh(): Promise<string> {
117
- return 'new-token'
118
- }
119
- }
120
- `)
121
- const classes = extractor.extractClasses()
122
- expect(classes).toHaveLength(1)
123
- expect(classes[0].name).toBe('AuthService')
124
- expect(classes[0].methods).toHaveLength(2)
125
- expect(classes[0].methods[0].name).toBe('AuthService.verify')
126
- expect(classes[0].methods[1].isAsync).toBe(true)
127
- expect(classes[0].isExported).toBe(true)
128
- })
129
-
130
- it('extracts static methods', () => {
131
- const extractor = new TypeScriptExtractor('src/static.ts', `
132
- class Config {
133
- static default(): Config {
134
- return new Config()
135
- }
136
- static load(path: string): Config {
137
- return new Config()
138
- }
139
- }
140
- `)
141
- const classes = extractor.extractClasses()
142
- const staticMethods = classes[0]?.methods.filter(m => m.name.includes('default') || m.name.includes('load'))
143
- expect(staticMethods?.length).toBeGreaterThanOrEqual(2)
144
- })
145
- })
146
-
147
- describe('Import Extraction', () => {
148
- it('extracts named imports', () => {
149
- const extractor = new TypeScriptExtractor('src/auth.ts', `
150
- import { jwtDecode, jwtSign } from '../utils/jwt'
151
- `)
152
- const imports = extractor.extractImports()
153
- expect(imports).toHaveLength(1)
154
- expect(imports[0].source).toBe('../utils/jwt')
155
- expect(imports[0].names).toContain('jwtDecode')
156
- expect(imports[0].names).toContain('jwtSign')
157
- })
158
-
159
- it('extracts default imports', () => {
160
- const extractor = new TypeScriptExtractor('src/auth.ts', `
161
- import express from 'express'
162
- `)
163
- const imports = extractor.extractImports()
164
- expect(imports).toHaveLength(1)
165
- expect(imports[0].source).toBe('express')
166
- expect(imports[0].isDefault).toBe(true)
167
- })
168
-
169
- it('extracts namespace imports', () => {
170
- const extractor = new TypeScriptExtractor('src/auth.ts', `
171
- import * as utils from './utils'
172
- `)
173
- const imports = extractor.extractImports()
174
- expect(imports).toHaveLength(1)
175
- expect(imports[0].source).toBe('./utils')
176
- })
177
-
178
- it('extracts mixed imports', () => {
179
- const extractor = new TypeScriptExtractor('src/auth.ts', `
180
- import express, { Router, Request, Response } from 'express'
181
- `)
182
- const imports = extractor.extractImports()
183
- expect(imports).toHaveLength(1)
184
- expect(imports[0].isDefault).toBe(true)
185
- expect(imports[0].names.length).toBeGreaterThan(0)
186
- })
187
-
188
- it('extracts type-only imports', () => {
189
- const extractor = new TypeScriptExtractor('src/auth.ts', `
190
- import type { User, Profile } from './types'
191
- import { verifyToken } from './verify'
192
- `)
193
- const imports = extractor.extractImports()
194
- expect(imports).toHaveLength(1)
195
- expect(imports[0].source).toBe('./verify')
196
- })
197
-
198
- it('extracts re-exported imports', () => {
199
- const extractor = new TypeScriptExtractor('src/index.ts', `
200
- export function verifyToken() {}
201
- `)
202
- const imports = extractor.extractImports()
203
- expect(imports).toBeDefined()
204
- })
205
-
206
- it('handles relative path imports', () => {
207
- const extractor = new TypeScriptExtractor('src/auth/login.ts', `
208
- import { db } from '../db'
209
- import { config } from './config'
210
- `)
211
- const imports = extractor.extractImports()
212
- expect(imports.length).toBe(2)
213
- })
214
- })
215
-
216
- describe('Export Extraction', () => {
217
- it('extracts named exports', () => {
218
- const extractor = new TypeScriptExtractor('src/auth.ts', `
219
- export function verifyToken() {}
220
- export const SECRET = 'abc'
221
- export class AuthService {}
222
- `)
223
- const exports = extractor.extractExports()
224
- expect(exports.length).toBeGreaterThanOrEqual(3)
225
- expect(exports.find(e => e.name === 'verifyToken')?.type).toBe('function')
226
- expect(exports.find(e => e.name === 'SECRET')?.type).toBe('const')
227
- expect(exports.find(e => e.name === 'AuthService')?.type).toBe('class')
228
- })
229
-
230
- it('extracts default exports', () => {
231
- const extractor = new TypeScriptExtractor('src/default.ts', `
232
- export default function AuthService() {}
233
- `)
234
- const exports = extractor.extractExports()
235
- expect(exports.length).toBeGreaterThanOrEqual(1)
236
- })
237
-
238
- it('extracts re-exports', () => {
239
- const extractor = new TypeScriptExtractor('src/index.ts', `
240
- export function combined() {}
241
- `)
242
- const exports = extractor.extractExports()
243
- expect(exports.length).toBeGreaterThanOrEqual(1)
244
- })
245
- })
246
-
247
- describe('Parameter Handling', () => {
248
- it('handles optional parameters', () => {
249
- const extractor = new TypeScriptExtractor('src/utils.ts', `
250
- function greet(name: string, greeting?: string) {}
251
- `)
252
- const fns = extractor.extractFunctions()
253
- expect(fns[0].params[1].optional).toBe(true)
254
- })
255
-
256
- it('handles default parameter values', () => {
257
- const extractor = new TypeScriptExtractor('src/utils.ts', `
258
- function config(port: number = 3000, host: string = 'localhost') {}
259
- `)
260
- const fns = extractor.extractFunctions()
261
- expect(fns[0].params[0].optional).toBe(true)
262
- expect(fns[0].params[0].defaultValue).toBeDefined()
263
- })
264
-
265
- it('handles rest parameters', () => {
266
- const extractor = new TypeScriptExtractor('src/rest.ts', `
267
- function sum(...numbers: number[]): number {
268
- return numbers.reduce((a, b) => a + b, 0)
269
- }
270
- `)
271
- const fns = extractor.extractFunctions()
272
- expect(fns[0].params[0].name).toBe('numbers')
273
- })
274
-
275
- it('handles destructured parameters', () => {
276
- const extractor = new TypeScriptExtractor('src/dest.ts', `
277
- function process({ name, age }: User): void {}
278
- function processArray([first, ...rest]: number[]): void {}
279
- `)
280
- const fns = extractor.extractFunctions()
281
- expect(fns.length).toBe(2)
282
- })
283
-
284
- it('handles typed parameters', () => {
285
- const extractor = new TypeScriptExtractor('src/typed.ts', `
286
- function createUser(name: string, age: number, options?: UserOptions): User {}
287
- `)
288
- const fns = extractor.extractFunctions()
289
- expect(fns[0].params.length).toBe(3)
290
- expect(fns[0].params[0].type).toBe('string')
291
- expect(fns[0].params[1].type).toBe('number')
292
- })
293
- })
294
-
295
- describe('Class Extraction', () => {
296
- it('extracts class with inheritance', () => {
297
- const extractor = new TypeScriptExtractor('src/class.ts', `
298
- export class UserService extends BaseService implements IUserService {
299
- private users: User[] = []
300
-
301
- constructor(private db: Database) {
302
- super()
303
- }
304
-
305
- async findById(id: string): Promise<User | null> {
306
- return this.users.find(u => u.id === id)
307
- }
308
- }
309
- `)
310
- const classes = extractor.extractClasses()
311
- expect(classes).toHaveLength(1)
312
- expect(classes[0].name).toBe('UserService')
313
- })
314
-
315
- it('extracts class getters and setters', () => {
316
- const extractor = new TypeScriptExtractor('src/getset.ts', `
317
- class User {
318
- private _name: string = ''
319
-
320
- get name(): string {
321
- return this._name
322
- }
323
-
324
- set name(value: string) {
325
- this._name = value
326
- }
327
- }
328
- `)
329
- const classes = extractor.extractClasses()
330
- // Getters/setters may be captured as part of class
331
- expect(classes.length).toBe(1)
332
- })
333
-
334
- it('extracts abstract classes', () => {
335
- const extractor = new TypeScriptExtractor('src/abstract.ts', `
336
- abstract class BaseService {
337
- abstract initialize(): void
338
- protected log(message: string): void {
339
- console.log(message)
340
- }
341
- }
342
- `)
343
- const classes = extractor.extractClasses()
344
- expect(classes).toHaveLength(1)
345
- expect(classes[0].name).toBe('BaseService')
346
- })
347
-
348
- it('extracts interfaces', () => {
349
- const extractor = new TypeScriptExtractor('src/interface.ts', `
350
- interface User {
351
- name: string
352
- age: number
353
- }
354
- `)
355
- const fns = extractor.extractFunctions()
356
- expect(fns.length).toBe(0)
357
- })
358
- })
359
-
360
- describe('Edge Cases', () => {
361
- it('handles empty files', () => {
362
- const extractor = new TypeScriptExtractor('src/empty.ts', '')
363
- expect(extractor.extractFunctions()).toHaveLength(0)
364
- expect(extractor.extractClasses()).toHaveLength(0)
365
- })
366
-
367
- it('handles comment-only files', () => {
368
- const extractor = new TypeScriptExtractor('src/comments.ts', `
369
- // This is a comment
370
- /* Multi-line comment
371
- spanning lines */
372
- /// Documentation comment
373
- `)
374
- expect(extractor.extractFunctions()).toHaveLength(0)
375
- })
376
-
377
- it('handles malformed syntax gracefully', () => {
378
- const extractor = new TypeScriptExtractor('src/malformed.ts', `
379
- function broken(() {
380
- this is not valid syntax
381
- `)
382
- // Should not throw
383
- expect(() => extractor.extractFunctions()).not.toThrow()
384
- })
385
-
386
- it('handles deeply nested code', () => {
387
- const extractor = new TypeScriptExtractor('src/deep.ts', `
388
- function level1() {
389
- function level2() {
390
- function level3() {
391
- return level4()
392
- }
1
+ import { describe, it, expect } from 'bun:test'
2
+ import { TypescriptExtractor } from '../src/parser/oxc-parser'
3
+ import { LanguageRegistry } from '../src/parser/index'
4
+
5
+ const _TreeSitterParser = { parse: () => Promise.resolve({}) }
6
+ const _UnsupportedLanguageError = class extends Error {}
7
+
8
+ describe('Unified Parser Integration', () => {
9
+ describe('TypescriptExtractor (OXC)', () => {
10
+ it('extracts function declarations and calls', async () => {
11
+ const extractor = new TypescriptExtractor()
12
+ const result = await extractor.extract('src/auth.ts', `
13
+ import { jwtDecode } from './jwt'
14
+ export function verifyToken(token: string) {
15
+ return jwtDecode(token).exp > Date.now()
393
16
  }
394
- }
395
- `)
396
- const fns = extractor.extractFunctions()
397
- expect(fns.length).toBeGreaterThanOrEqual(1)
398
- })
399
-
400
- it('handles template literals', () => {
401
- const extractor = new TypeScriptExtractor('src/template.ts', `
402
- const message = \`Hello \${name}!\`
403
- `)
404
- const fns = extractor.extractFunctions()
405
- expect(fns.length).toBe(0) // template literal is const, not function
406
- })
407
-
408
- it('handles decorators', () => {
409
- const extractor = new TypeScriptExtractor('src/decorator.ts', `
410
- @injectable()
411
- @lazy
412
- class UserService {}
413
- `)
414
- const classes = extractor.extractClasses()
415
- expect(classes.length).toBe(1)
416
- })
417
-
418
- it('handles async generators', () => {
419
- const extractor = new TypeScriptExtractor('src/async-gen.ts', `
420
- async function* streamData(): AsyncGenerator<Data> {
421
- yield await fetchData()
422
- }
423
- `)
424
- const fns = extractor.extractFunctions()
425
- expect(fns[0].isAsync).toBe(true)
426
- })
427
-
428
- it('handles conditional types', () => {
429
- const extractor = new TypeScriptExtractor('src/cond.ts', `
430
- type NonNullable<T> = T extends null | undefined ? never : T
431
- `)
432
- const classes = extractor.extractClasses()
433
- // Should not crash
434
- expect(classes).toBeDefined()
435
- })
436
-
437
- it('handles enum members', () => {
438
- const extractor = new TypeScriptExtractor('src/enum.ts', `
439
- enum Status {
440
- Pending = 'pending',
441
- Active = 'active',
442
- Completed = 'completed'
443
- }
444
- `)
445
- const classes = extractor.extractClasses()
446
- // Enum may or may not be captured depending on parser
447
- expect(classes).toBeDefined()
448
- })
449
- })
450
-
451
- describe('Type Extraction', () => {
452
- it('extracts return types', () => {
453
- const extractor = new TypeScriptExtractor('src/return.ts', `
454
- function getUser(): User {
455
- return {} as User
456
- }
457
- `)
458
- const fns = extractor.extractFunctions()
459
- expect(fns[0].returnType).toBe('User')
460
- })
461
-
462
- it('extracts generic functions', () => {
463
- const extractor = new TypeScriptExtractor('src/generic.ts', `
464
- function identity<T>(value: T): T {
465
- return value
466
- }
467
- `)
468
- const fns = extractor.extractFunctions()
469
- expect(fns[0].name).toBe('identity')
470
- })
471
-
472
- it('extracts union return types', () => {
473
- const extractor = new TypeScriptExtractor('src/union.ts', `
474
- function parse(value: string): string | number {
475
- return parseInt(value)
476
- }
477
17
  `)
478
- const fns = extractor.extractFunctions()
479
- expect(fns[0].returnType).toContain('string')
480
- })
481
- })
482
-
483
- describe('Purpose & Documentation', () => {
484
- it('extracts function purpose from comments', () => {
485
- const extractor = new TypeScriptExtractor('src/docs.ts', `
486
- /**
487
- * Verifies a JWT token and returns the payload
488
- * @param token - The JWT token to verify
489
- * @returns The decoded token payload
490
- */
491
- export function verifyToken(token: string): Payload {
492
- return jwtDecode(token)
493
- }
494
- `)
495
- const fns = extractor.extractFunctions()
496
- expect(fns[0].purpose).toBeDefined()
497
- expect(fns[0].purpose).toContain('Verifies')
498
- })
499
-
500
- it('extracts single-line doc comments', () => {
501
- const extractor = new TypeScriptExtractor('src/single.ts', `
502
- /// Gets the current user
503
- function getCurrentUser(): User | null {
504
- return null
505
- }
506
- `)
507
- const fns = extractor.extractFunctions()
508
- expect(fns[0].purpose).toBeDefined()
509
- })
510
- })
511
- })
512
-
513
- describe('TypeScriptParser - Comprehensive', () => {
514
- const parser = new TypeScriptParser()
515
-
516
- describe('Basic Parsing', () => {
517
- it('returns correct language', async () => {
518
- const result = await parser.parse('src/test.ts', 'const x = 1')
519
- expect(result.language).toBe('typescript')
520
- })
521
-
522
- it('parses a complete file', async () => {
523
- const result = await parser.parse('src/auth.ts', `
524
- import { jwtDecode } from '../utils/jwt'
525
- export function verifyToken(token: string): boolean {
526
- return jwtDecode(token).exp > Date.now()
527
- }
528
- `)
529
18
  expect(result.functions).toHaveLength(1)
19
+ expect(result.functions[0].name).toBe('verifyToken')
530
20
  expect(result.functions[0].calls.some(c => c.name === 'jwtDecode')).toBe(true)
531
- expect(result.imports).toHaveLength(1)
532
- expect(result.exports).toHaveLength(1)
533
- expect(result.hash).toBeDefined()
534
- expect(result.path).toBe('src/auth.ts')
535
21
  })
536
22
 
537
- it('parses .tsx files', async () => {
538
- const result = await parser.parse('src/App.tsx', 'export default function App() { return null }')
539
- expect(result.language).toBe('typescript')
540
- })
541
-
542
- it('parses .jsx files', async () => {
543
- const result = await parser.parse('src/App.jsx', 'export default function App() { return null }')
544
- // OxcParser treats .jsx as typescript
545
- expect(result.language).toBeDefined()
23
+ it('extracts class methods', async () => {
24
+ const extractor = new TypescriptExtractor()
25
+ const result = await extractor.extract('src/service.ts', `
26
+ export class AuthService {
27
+ verify(token: string): boolean { return true }
28
+ }
29
+ `)
30
+ expect(result.classes).toHaveLength(1)
31
+ expect(result.classes[0].name).toBe('AuthService')
32
+ expect(result.classes[0].methods[0].name).toBe('AuthService.verify')
546
33
  })
547
34
  })
548
35
 
549
- describe('File Metadata', () => {
550
- it('generates hash', async () => {
551
- const result = await parser.parse('src/test.ts', 'const x = 1')
552
- expect(result.hash).toBeDefined()
553
- expect(result.hash.length).toBe(64) // SHA-256 hex
36
+ describe('LanguageRegistry Dispatch', () => {
37
+ it('dispatches to OXC for TypeScript/JavaScript', () => {
38
+ const registry = LanguageRegistry.getInstance()
39
+ const ts = registry.getForFile('test.ts')
40
+ const js = registry.getForFile('test.js')
41
+ expect(ts?.name).toBe('typescript')
42
+ expect(js?.name).toBe('javascript')
43
+ expect(ts?.extractor).toBeInstanceOf(TypescriptExtractor)
554
44
  })
555
45
 
556
- it('sets parsedAt timestamp', async () => {
557
- const before = Date.now()
558
- const result = await parser.parse('src/test.ts', 'const x = 1')
559
- const after = Date.now()
560
- expect(result.parsedAt).toBeGreaterThanOrEqual(before)
561
- expect(result.parsedAt).toBeLessThanOrEqual(after)
46
+ it('dispatches to TreeSitter for other languages', () => {
47
+ const registry = LanguageRegistry.getInstance()
48
+ const py = registry.getForFile('test.py')
49
+ expect(py?.name).toBe('python')
50
+ // TreeSitterParser is registered for python
562
51
  })
563
52
  })
564
53
 
565
54
  describe('Error Handling', () => {
566
55
  it('handles completely empty files', async () => {
567
- const result = await parser.parse('src/empty.ts', '')
568
- expect(result.functions).toHaveLength(0)
569
- expect(result.classes).toHaveLength(0)
570
- })
571
-
572
- it('handles syntax errors gracefully', async () => {
573
- const result = await parser.parse('src/error.ts', 'function broken(() {')
574
- // Should not throw, return partial results
575
- expect(result.path).toBe('src/error.ts')
576
- })
577
- })
578
- })
579
-
580
- describe('TypeScriptResolver - Comprehensive', () => {
581
- describe('Import Resolution', () => {
582
- it('resolves relative imports', () => {
583
- const resolver = new TypeScriptResolver('/project')
584
- const result = resolver.resolve(
585
- { source: '../utils/jwt', names: ['jwtDecode'], resolvedPath: '', isDefault: false, isDynamic: false },
586
- 'src/auth/verify.ts',
587
- ['src/utils/jwt.ts']
588
- )
589
- expect(result.resolvedPath).toBe('src/utils/jwt.ts')
590
- })
591
-
592
- it('resolves index files', () => {
593
- const resolver = new TypeScriptResolver('/project')
594
- const result = resolver.resolve(
595
- { source: '../utils', names: ['helper'], resolvedPath: '', isDefault: false, isDynamic: false },
596
- 'src/auth/verify.ts',
597
- ['src/utils/index.ts']
598
- )
599
- expect(result.resolvedPath).toBe('src/utils/index.ts')
600
- })
601
-
602
- it('resolves with file extension variations', () => {
603
- const resolver = new TypeScriptResolver('/project')
604
- const result = resolver.resolve(
605
- { source: '../utils/jwt', names: [], resolvedPath: '', isDefault: false, isDynamic: false },
606
- 'src/auth/verify.ts',
607
- ['src/utils/jwt.ts', 'src/utils/jwt.tsx']
608
- )
609
- expect(result.resolvedPath).toBeDefined()
610
- })
611
-
612
- it('skips external packages', () => {
613
- const resolver = new TypeScriptResolver('/project')
614
- const result = resolver.resolve(
615
- { source: 'express', names: ['default'], resolvedPath: '', isDefault: true, isDynamic: false },
616
- 'src/index.ts'
617
- )
618
- expect(result.resolvedPath).toBe('')
619
- })
620
-
621
- it('resolves path aliases', () => {
622
- const resolver = new TypeScriptResolver('/project', {
623
- '@/*': ['src/*'],
624
- })
625
- const result = resolver.resolve(
626
- { source: '@/utils/jwt', names: ['jwtDecode'], resolvedPath: '', isDefault: false, isDynamic: false },
627
- 'src/auth/verify.ts',
628
- ['src/utils/jwt.ts']
629
- )
630
- expect(result.resolvedPath).toBe('src/utils/jwt.ts')
56
+ const extractor = new TypescriptExtractor()
57
+ const result = await extractor.extract('src/empty.ts', '')
58
+ expect(result.functions).toHaveLength(0)
631
59
  })
632
-
633
- it('handles multiple aliases', () => {
634
- const resolver = new TypeScriptResolver('/project', {
635
- '@/*': ['src/*'],
636
- '#/*': ['types/*'],
637
- })
638
- const result = resolver.resolve(
639
- { source: '@/utils', names: [], resolvedPath: '', isDefault: false, isDynamic: false },
640
- 'src/auth/verify.ts',
641
- ['src/utils/index.ts']
642
- )
643
- expect(result.resolvedPath).toBe('src/utils/index.ts')
644
- })
645
-
646
- it('resolves nested path aliases', () => {
647
- const resolver = new TypeScriptResolver('/project', {
648
- '@components/*': ['src/components/*'],
649
- })
650
- const result = resolver.resolve(
651
- { source: '@/utils', names: [], resolvedPath: '', isDefault: false, isDynamic: false },
652
- 'src/App.tsx',
653
- ['src/utils/index.ts']
654
- )
655
- expect(result.resolvedPath).toBeDefined()
656
- })
657
- })
658
-
659
- describe('Edge Cases', () => {
660
- it('handles empty files list', () => {
661
- const resolver = new TypeScriptResolver('/project')
662
- const result = resolver.resolve(
663
- { source: './utils', names: [], resolvedPath: '', isDefault: false, isDynamic: false },
664
- 'src/index.ts',
665
- []
666
- )
667
- expect(result.resolvedPath).toContain('.ts')
668
- })
669
-
670
- it('handles non-matching imports', () => {
671
- const resolver = new TypeScriptResolver('/project')
672
- const result = resolver.resolve(
673
- { source: '../nonexistent', names: [], resolvedPath: '', isDefault: false, isDynamic: false },
674
- 'src/index.ts',
675
- ['src/utils.ts']
676
- )
677
- // Resolver may return fallback path
678
- expect(result.resolvedPath).toBeDefined()
679
- })
680
-
681
- it('handles resolveAll', () => {
682
- const resolver = new TypeScriptResolver('/project')
683
- const imports = [
684
- { source: './a', names: [], resolvedPath: '', isDefault: false, isDynamic: false },
685
- { source: './b', names: [], resolvedPath: '', isDefault: false, isDynamic: false },
686
- ]
687
- const result = resolver.resolveAll(imports, 'src/index.ts', ['src/a.ts', 'src/b.ts'])
688
- expect(result.length).toBe(2)
689
- expect(result[0].resolvedPath).toBe('src/a.ts')
690
- })
691
- })
692
- })
693
-
694
- describe('getParser - Comprehensive', () => {
695
- it('returns OxcParser for .ts files', () => {
696
- const parser = getParser('src/auth.ts')
697
- expect(parser).toBeInstanceOf(OxcParser)
698
- })
699
-
700
- it('returns OxcParser for .tsx files', () => {
701
- const parser = getParser('src/App.tsx')
702
- expect(parser).toBeInstanceOf(OxcParser)
703
- })
704
-
705
- it('returns OxcParser for .js files', () => {
706
- const parser = getParser('src/app.js')
707
- expect(parser).toBeInstanceOf(OxcParser)
708
- })
709
-
710
- it('returns OxcParser for .jsx files', () => {
711
- const parser = getParser('src/App.jsx')
712
- expect(parser).toBeInstanceOf(OxcParser)
713
- })
714
-
715
- it('throws for unsupported extensions', () => {
716
- expect(() => getParser('src/auth.xyz')).toThrow(UnsupportedLanguageError)
717
- })
718
-
719
- it('throws for no extension', () => {
720
- expect(() => getParser('src/Makefile')).toThrow(UnsupportedLanguageError)
721
- })
722
-
723
- it('handles files with dots in name', () => {
724
- const parser = getParser('src/app.config.ts')
725
- expect(parser).toBeInstanceOf(OxcParser)
726
- })
727
-
728
- it('handles path with directories', () => {
729
- const parser = getParser('src/lib/utils/helper.ts')
730
- expect(parser).toBeInstanceOf(OxcParser)
731
- })
732
-
733
- it('supports C++ variant extensions via tree-sitter parser', () => {
734
- const cxxParser = getParser('src/engine.cxx')
735
- const hxxParser = getParser('src/engine.hxx')
736
- const hhParser = getParser('src/engine.hh')
737
-
738
- expect(cxxParser.getSupportedExtensions()).toContain('.cxx')
739
- expect(hxxParser.getSupportedExtensions()).toContain('.hxx')
740
- expect(hhParser.getSupportedExtensions()).toContain('.hh')
741
- })
742
-
743
- it('supports Rust/C#/Swift extensions via tree-sitter parser', () => {
744
- const rustParser = getParser('src/lib.rs')
745
- const csharpParser = getParser('src/Program.cs')
746
- const swiftParser = getParser('Sources/App/main.swift')
747
-
748
- expect(rustParser.getSupportedExtensions()).toContain('.rs')
749
- expect(csharpParser.getSupportedExtensions()).toContain('.cs')
750
- expect(swiftParser.getSupportedExtensions()).toContain('.swift')
751
- })
752
- })
753
-
754
- describe('OxcParser - Direct', () => {
755
- const parser = new OxcParser()
756
-
757
- it('parses TypeScript file', async () => {
758
- const result = await parser.parse('complex.ts', `
759
- const x = 1
760
- export function test() { return x }
761
- `)
762
-
763
- expect(result.functions.length).toBeGreaterThan(0)
764
- })
765
-
766
- it('handles large files', async () => {
767
- const content = Array.from({ length: 1000 }, (_, i) =>
768
- `export function function_${i}() { return ${i} }`
769
- ).join('\n')
770
-
771
- const result = await parser.parse('large.ts', content)
772
- expect(result.functions.length).toBe(1000)
773
- })
774
-
775
- it('extracts direct and method call expressions', async () => {
776
- const result = await parser.parse('calls.ts', `
777
- function b() { return 1 }
778
- const svc = { run() { return 2 } }
779
- export function a() {
780
- b()
781
- svc.run()
782
- }
783
- `)
784
-
785
- const a = result.functions.find(f => f.name === 'a')
786
- expect(a).toBeDefined()
787
- expect(a!.calls.some(c => c.name === 'b')).toBe(true)
788
- expect(a!.calls.some(c => c.name === 'svc.run')).toBe(true)
789
60
  })
790
61
  })