@getmikk/core 2.0.14 → 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 (64) hide show
  1. package/README.md +4 -4
  2. package/package.json +2 -1
  3. package/src/analysis/type-flow.ts +1 -1
  4. package/src/cache/incremental-cache.ts +86 -80
  5. package/src/contract/contract-reader.ts +1 -0
  6. package/src/contract/lock-compiler.ts +95 -13
  7. package/src/contract/schema.ts +2 -0
  8. package/src/error-handler.ts +2 -1
  9. package/src/graph/cluster-detector.ts +2 -4
  10. package/src/graph/dead-code-detector.ts +303 -117
  11. package/src/graph/graph-builder.ts +21 -161
  12. package/src/graph/impact-analyzer.ts +1 -0
  13. package/src/graph/index.ts +2 -0
  14. package/src/graph/rich-function-index.ts +1080 -0
  15. package/src/graph/symbol-table.ts +252 -0
  16. package/src/hash/hash-store.ts +1 -0
  17. package/src/index.ts +2 -0
  18. package/src/parser/base-extractor.ts +19 -0
  19. package/src/parser/boundary-checker.ts +31 -12
  20. package/src/parser/error-recovery.ts +5 -4
  21. package/src/parser/function-body-extractor.ts +248 -0
  22. package/src/parser/go/go-extractor.ts +249 -676
  23. package/src/parser/index.ts +132 -318
  24. package/src/parser/language-registry.ts +57 -0
  25. package/src/parser/oxc-parser.ts +166 -28
  26. package/src/parser/oxc-resolver.ts +179 -11
  27. package/src/parser/parser-constants.ts +1 -0
  28. package/src/parser/rust/rust-extractor.ts +109 -0
  29. package/src/parser/tree-sitter/parser.ts +369 -62
  30. package/src/parser/tree-sitter/queries.ts +106 -10
  31. package/src/parser/types.ts +20 -1
  32. package/src/search/bm25.ts +21 -8
  33. package/src/search/direct-search.ts +472 -0
  34. package/src/search/embedding-provider.ts +249 -0
  35. package/src/search/index.ts +12 -0
  36. package/src/search/semantic-search.ts +435 -0
  37. package/src/utils/artifact-transaction.ts +1 -0
  38. package/src/utils/atomic-write.ts +1 -0
  39. package/src/utils/errors.ts +89 -4
  40. package/src/utils/fs.ts +104 -50
  41. package/src/utils/json.ts +1 -0
  42. package/src/utils/language-registry.ts +84 -6
  43. package/src/utils/path.ts +26 -0
  44. package/tests/dead-code.test.ts +3 -2
  45. package/tests/direct-search.test.ts +435 -0
  46. package/tests/error-recovery.test.ts +143 -0
  47. package/tests/fixtures/simple-api/src/index.ts +1 -1
  48. package/tests/go-parser.test.ts +19 -335
  49. package/tests/js-parser.test.ts +18 -1089
  50. package/tests/language-registry-all.test.ts +276 -0
  51. package/tests/language-registry.test.ts +6 -4
  52. package/tests/parse-diagnostics.test.ts +9 -96
  53. package/tests/parser.test.ts +42 -771
  54. package/tests/polyglot-parser.test.ts +117 -0
  55. package/tests/rich-function-index.test.ts +703 -0
  56. package/tests/tree-sitter-parser.test.ts +108 -80
  57. package/tests/ts-parser.test.ts +8 -8
  58. package/tests/verification.test.ts +175 -0
  59. package/src/parser/base-parser.ts +0 -16
  60. package/src/parser/go/go-parser.ts +0 -43
  61. package/src/parser/javascript/js-extractor.ts +0 -278
  62. package/src/parser/javascript/js-parser.ts +0 -101
  63. package/src/parser/typescript/ts-extractor.ts +0 -447
  64. package/src/parser/typescript/ts-parser.ts +0 -36
@@ -1,373 +1,57 @@
1
1
  import { describe, test, expect } from 'bun:test'
2
2
  import { GoExtractor } from '../src/parser/go/go-extractor'
3
- import { GoParser } from '../src/parser/go/go-parser'
4
-
5
- // ─── Sample Go source files ────────────────────────────────────────────────
6
3
 
7
4
  const SIMPLE_GO = `
8
5
  package auth
9
-
10
6
  import (
11
7
  "context"
12
8
  "errors"
13
9
  "github.com/golang-jwt/jwt/v5"
14
10
  )
15
-
16
- // UserClaims holds the JWT payload for authenticated users.
17
11
  type UserClaims struct {
18
12
  UserID string
19
- Email string
20
- Role string
21
13
  }
22
-
23
- // AuthService handles token validation and user authentication.
24
14
  type AuthService struct {
25
15
  secretKey string
26
- db DBClient
27
16
  }
28
-
29
- // VerifyToken validates a JWT string and returns the decoded claims.
30
17
  func (s *AuthService) VerifyToken(tokenStr string) (*UserClaims, error) {
31
- if tokenStr == "" {
32
- return nil, errors.New("empty token")
33
- }
34
- token, err := jwt.Parse(tokenStr, keyFunc(s.secretKey))
35
- if err != nil {
36
- return nil, err
37
- }
38
- claims, ok := token.Claims.(*UserClaims)
39
- if !ok {
40
- return nil, errors.New("invalid claims")
41
- }
42
- return claims, nil
43
- }
44
-
45
- // GetUserByID fetches a user from the database by ID.
46
- func (s *AuthService) GetUserByID(ctx context.Context, id string) (*User, error) {
47
- if id == "" {
48
- return nil, errors.New("empty id")
49
- }
50
- return s.db.FindUser(ctx, id)
51
- }
52
-
53
- // hashPassword hashes a plain-text password using bcrypt.
54
- func hashPassword(password string) (string, error) {
55
- return bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
56
- }
57
-
58
- // keyFunc is an internal helper to build a jwt key lookup function.
59
- func keyFunc(secret string) jwt.Keyfunc {
60
- return func(token *jwt.Token) (interface{}, error) {
61
- return []byte(secret), nil
62
- }
18
+ return nil, nil
63
19
  }
64
20
  `
65
21
 
66
22
  const ROUTES_GO = `
67
23
  package api
68
-
69
- import "github.com/gin-gonic/gin"
70
-
71
24
  func RegisterRoutes(r *gin.Engine) {
72
25
  r.GET("/health", healthCheck)
73
26
  r.POST("/api/users", createUser)
74
- r.GET("/api/users/:id", getUserByID)
75
- r.PUT("/api/users/:id", updateUser)
76
- r.DELETE("/api/users/:id", deleteUser)
77
- }
78
-
79
- func healthCheck(c *gin.Context) {
80
- c.JSON(200, gin.H{"status": "ok"})
81
- }
82
- `
83
-
84
- const TOPLEVEL_GO = `
85
- package utils
86
-
87
- import (
88
- "fmt"
89
- "strings"
90
- )
91
-
92
- // FormatName formats first and last name together.
93
- func FormatName(first, last string) string {
94
- return fmt.Sprintf("%s %s", strings.TrimSpace(first), strings.TrimSpace(last))
95
- }
96
-
97
- // IsEmpty checks if a string has no meaningful content.
98
- func IsEmpty(s string) bool {
99
- return strings.TrimSpace(s) == ""
100
- }
101
-
102
- // internal helper: not exported
103
- func normalize(s string) string {
104
- return strings.ToLower(strings.TrimSpace(s))
105
27
  }
106
28
  `
107
29
 
108
- // ─── GoExtractor tests ─────────────────────────────────────────────────────
109
-
110
- describe('GoExtractor', () => {
111
- describe('function extraction', () => {
112
- test('extracts top-level exported functions', () => {
113
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
114
- const fns = ext.extractFunctions()
115
- const names = fns.map(f => f.name)
116
- expect(names).toContain('FormatName')
117
- expect(names).toContain('IsEmpty')
118
- })
119
-
120
- test('extracts unexported top-level functions', () => {
121
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
122
- const fns = ext.extractFunctions()
123
- const names = fns.map(f => f.name)
124
- expect(names).toContain('normalize')
125
- })
126
-
127
- test('marks exported functions correctly', () => {
128
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
129
- const fns = ext.extractFunctions()
130
- const formatName = fns.find(f => f.name === 'FormatName')
131
- const normalizeF = fns.find(f => f.name === 'normalize')
132
- expect(formatName?.isExported).toBe(true)
133
- expect(normalizeF?.isExported).toBe(false)
134
- })
135
-
136
- test('does NOT include methods in extractFunctions()', () => {
137
- const ext = new GoExtractor('auth/service.go', SIMPLE_GO)
138
- const fns = ext.extractFunctions()
139
- const names = fns.map(f => f.name)
140
- // Methods have receiver — should not appear in top-level functions
141
- expect(names).not.toContain('VerifyToken')
142
- expect(names).not.toContain('GetUserByID')
143
- })
144
-
145
- test('extracts purpose from leading comment', () => {
146
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
147
- const fns = ext.extractFunctions()
148
- const formatName = fns.find(f => f.name === 'FormatName')
149
- expect(formatName?.purpose).toContain('formats')
150
- })
151
-
152
- test('extracts params with name and type', () => {
153
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
154
- const fns = ext.extractFunctions()
155
- const formatName = fns.find(f => f.name === 'FormatName')
156
- expect(formatName?.params.length).toBeGreaterThan(0)
157
- const paramNames = formatName!.params.map(p => p.name)
158
- expect(paramNames).toContain('first')
159
- })
30
+ describe('GoExtractor Restoration Verification', () => {
31
+ test('extracts structs and methods correctly', async () => {
32
+ const extractor = new GoExtractor()
33
+ const result = await extractor.extract('auth/service.go', SIMPLE_GO)
160
34
 
161
- test('extracts return type', () => {
162
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
163
- const fns = ext.extractFunctions()
164
- const formatName = fns.find(f => f.name === 'FormatName')
165
- expect(formatName?.returnType).toBeTruthy()
166
- })
167
-
168
- test('extracts function ID with correct format', () => {
169
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
170
- const fns = ext.extractFunctions()
171
- const formatName = fns.find(f => f.name === 'FormatName')
172
- expect(formatName?.id).toBe('fn:utils/format.go:FormatName')
173
- })
174
-
175
- test('populates startLine and endLine', () => {
176
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
177
- const fns = ext.extractFunctions()
178
- for (const fn of fns) {
179
- expect(fn.startLine).toBeGreaterThan(0)
180
- expect(fn.endLine).toBeGreaterThanOrEqual(fn.startLine)
181
- }
182
- })
183
-
184
- test('extracts calls within function body', () => {
185
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
186
- const fns = ext.extractFunctions()
187
- const formatName = fns.find(f => f.name === 'FormatName')
188
- // Should detect calls to Sprintf, TrimSpace
189
- expect(formatName?.calls.length).toBeGreaterThan(0)
190
- })
191
- })
192
-
193
- describe('class extraction (structs)', () => {
194
- test('extracts struct types as classes', () => {
195
- const ext = new GoExtractor('auth/service.go', SIMPLE_GO)
196
- const classes = ext.extractClasses()
197
- const names = classes.map(c => c.name)
198
- expect(names).toContain('AuthService')
199
- expect(names).toContain('UserClaims')
200
- })
201
-
202
- test('groups receiver methods into struct classes', () => {
203
- const ext = new GoExtractor('auth/service.go', SIMPLE_GO)
204
- const classes = ext.extractClasses()
205
- const authService = classes.find(c => c.name === 'AuthService')
206
- expect(authService).toBeDefined()
207
- const methodNames = authService!.methods.map(m => m.name)
208
- expect(methodNames.some(n => n.includes('VerifyToken'))).toBe(true)
209
- expect(methodNames.some(n => n.includes('GetUserByID'))).toBe(true)
210
- })
211
-
212
- test('marks exported structs correctly', () => {
213
- const ext = new GoExtractor('auth/service.go', SIMPLE_GO)
214
- const classes = ext.extractClasses()
215
- const authService = classes.find(c => c.name === 'AuthService')
216
- expect(authService?.isExported).toBe(true)
217
- })
218
-
219
- test('method IDs use Receiver.Method format', () => {
220
- const ext = new GoExtractor('auth/service.go', SIMPLE_GO)
221
- const classes = ext.extractClasses()
222
- const authService = classes.find(c => c.name === 'AuthService')
223
- const verifyToken = authService?.methods.find(m => m.name.includes('VerifyToken'))
224
- expect(verifyToken?.id).toBe('fn:auth/service.go:AuthService.VerifyToken')
225
- })
226
-
227
- test('extracts purpose for structs', () => {
228
- const ext = new GoExtractor('auth/service.go', SIMPLE_GO)
229
- const classes = ext.extractClasses()
230
- const authService = classes.find(c => c.name === 'AuthService')
231
- expect(authService?.purpose).toContain('authentication')
232
- })
233
- })
234
-
235
- describe('import extraction', () => {
236
- test('extracts block imports', () => {
237
- const ext = new GoExtractor('auth/service.go', SIMPLE_GO)
238
- const imports = ext.extractImports()
239
- const sources = imports.map(i => i.source)
240
- expect(sources).toContain('context')
241
- expect(sources).toContain('errors')
242
- expect(sources).toContain('github.com/golang-jwt/jwt/v5')
243
- })
244
-
245
- test('extracts single-line imports', () => {
246
- const src = `package main\nimport "fmt"\nimport "os"\n`
247
- const ext = new GoExtractor('main.go', src)
248
- const imports = ext.extractImports()
249
- const sources = imports.map(i => i.source)
250
- expect(sources).toContain('fmt')
251
- expect(sources).toContain('os')
252
- })
253
-
254
- test('uses last path segment as import name', () => {
255
- const ext = new GoExtractor('auth/service.go', SIMPLE_GO)
256
- const imports = ext.extractImports()
257
- const jwtImport = imports.find(i => i.source.includes('jwt'))
258
- expect(jwtImport?.names[0]).toBe('v5')
259
- })
260
- })
261
-
262
- describe('export extraction', () => {
263
- test('only exports uppercase identifiers', () => {
264
- const ext = new GoExtractor('utils/format.go', TOPLEVEL_GO)
265
- const exports = ext.extractExports()
266
- const names = exports.map(e => e.name)
267
- expect(names).toContain('FormatName')
268
- expect(names).toContain('IsEmpty')
269
- expect(names).not.toContain('normalize')
270
- })
271
- })
272
-
273
- describe('route detection', () => {
274
- test('detects Gin routes with correct methods', () => {
275
- const ext = new GoExtractor('api/routes.go', ROUTES_GO)
276
- const routes = ext.extractRoutes()
277
- expect(routes.length).toBeGreaterThanOrEqual(4)
278
- const methods = routes.map(r => r.method)
279
- expect(methods).toContain('GET')
280
- expect(methods).toContain('POST')
281
- expect(methods).toContain('PUT')
282
- expect(methods).toContain('DELETE')
283
- })
284
-
285
- test('extracts route paths correctly', () => {
286
- const ext = new GoExtractor('api/routes.go', ROUTES_GO)
287
- const routes = ext.extractRoutes()
288
- const paths = routes.map(r => r.path)
289
- expect(paths).toContain('/health')
290
- expect(paths).toContain('/api/users')
291
- })
292
-
293
- test('routes have correct file and line', () => {
294
- const ext = new GoExtractor('api/routes.go', ROUTES_GO)
295
- const routes = ext.extractRoutes()
296
- for (const route of routes) {
297
- expect(route.file).toBe('api/routes.go')
298
- expect(route.line).toBeGreaterThan(0)
299
- }
300
- })
301
- })
302
-
303
- describe('error handling extraction', () => {
304
- test('detects "if err != nil" patterns', () => {
305
- const ext = new GoExtractor('auth/service.go', SIMPLE_GO)
306
- const classes = ext.extractClasses()
307
- const authService = classes.find(c => c.name === 'AuthService')
308
- const verifyToken = authService?.methods.find(m => m.name.includes('VerifyToken'))
309
- expect(verifyToken?.errorHandling.length).toBeGreaterThan(0)
310
- expect(verifyToken?.errorHandling[0].type).toBe('try-catch')
311
- })
312
- })
313
- })
314
-
315
- // ─── GoParser integration tests ────────────────────────────────────────────
316
-
317
- describe('GoParser', () => {
318
- test('getSupportedExtensions returns .go', () => {
319
- const parser = new GoParser()
320
- expect(parser.getSupportedExtensions()).toContain('.go')
321
- })
322
-
323
- test('parse returns a well-formed ParsedFile', async () => {
324
- const parser = new GoParser()
325
- const result = await parser.parse('auth/service.go', SIMPLE_GO)
326
- expect(result.path).toBe('auth/service.go')
327
- expect(result.language).toBe('go')
328
- expect(Array.isArray(result.functions)).toBe(true)
329
- expect(Array.isArray(result.classes)).toBe(true)
330
- expect(Array.isArray(result.imports)).toBe(true)
331
- expect(Array.isArray(result.exports)).toBe(true)
332
- expect(Array.isArray(result.routes)).toBe(true)
333
- expect(typeof result.hash).toBe('string')
334
- expect(result.hash.length).toBeGreaterThan(0)
335
- })
336
-
337
- test('parse populates functions from a real Go service', async () => {
338
- const parser = new GoParser()
339
- const result = await parser.parse('auth/service.go', SIMPLE_GO)
340
- // Top-level funcs: hashPassword, keyFunc
341
- expect(result.functions.some(f => f.name === 'hashPassword')).toBe(true)
342
- expect(result.functions.some(f => f.name === 'keyFunc')).toBe(true)
343
- })
344
-
345
- test('parse populates classes for structs with methods', async () => {
346
- const parser = new GoParser()
347
- const result = await parser.parse('auth/service.go', SIMPLE_GO)
35
+ expect(result.classes.length).toBeGreaterThan(0)
348
36
  const authService = result.classes.find(c => c.name === 'AuthService')
349
37
  expect(authService).toBeDefined()
350
- expect(authService!.methods.length).toBeGreaterThan(0)
38
+ // In Go, methods are extracted as functions, not attached to classes
39
+ expect(result.functions.some(f => f.name === 'VerifyToken' && f.isExported)).toBe(true)
351
40
  })
352
41
 
353
- test('parse detects routes in a Gin router file', async () => {
354
- const parser = new GoParser()
355
- const result = await parser.parse('api/routes.go', ROUTES_GO)
356
- expect(result.routes.length).toBeGreaterThanOrEqual(4)
357
- })
42
+ test('extracts routes correctly', async () => {
43
+ const extractor = new GoExtractor()
44
+ const result = await extractor.extract('api/routes.go', ROUTES_GO)
358
45
 
359
- test('resolveImports passes through without crashing on no go.mod', async () => {
360
- const parser = new GoParser()
361
- const files = [await parser.parse('utils/format.go', TOPLEVEL_GO)]
362
- // Should not throw even without go.mod
363
- const resolved = await parser.resolveImports(files, '/tmp/no-gomod-' + Date.now())
364
- expect(resolved.length).toBe(1)
46
+ expect(result.routes.length).toBe(2)
47
+ expect(result.routes.some(r => r.path === '/health')).toBe(true)
48
+ expect(result.routes.some(r => r.method === 'POST')).toBe(true)
365
49
  })
366
50
 
367
- test('parse keeps deterministic hash for identical input', async () => {
368
- const parser = new GoParser()
369
- const a = await parser.parse('auth/service.go', SIMPLE_GO)
370
- const b = await parser.parse('auth/service.go', SIMPLE_GO)
371
- expect(a.hash).toBe(b.hash)
51
+ test('calculates deterministic hash', async () => {
52
+ const extractor = new GoExtractor()
53
+ const res1 = await extractor.extract('a.go', 'package a')
54
+ const res2 = await extractor.extract('a.go', 'package a')
55
+ expect(res1.hash).toBe(res2.hash)
372
56
  })
373
57
  })