@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,447 +0,0 @@
1
- import ts from 'typescript'
2
- import type {
3
- ParsedFunction,
4
- ParsedClass,
5
- ParsedImport,
6
- ParsedExport,
7
- ParsedParam,
8
- ParsedGeneric,
9
- ParsedRoute,
10
- ParsedVariable,
11
- CallExpression
12
- } from '../types.js'
13
- import { hashContent } from '../../hash/file-hasher.js'
14
-
15
- export class TypeScriptExtractor {
16
- protected readonly sourceFile: ts.SourceFile
17
- private nameCounter = new Map<string, number>()
18
-
19
- constructor(
20
- protected readonly filePath: string,
21
- protected readonly content: string
22
- ) {
23
- this.sourceFile = ts.createSourceFile(
24
- filePath,
25
- content,
26
- ts.ScriptTarget.Latest,
27
- true,
28
- this.inferScriptKind(filePath)
29
- )
30
- }
31
-
32
- private inferScriptKind(filePath: string): ts.ScriptKind {
33
- if (filePath.endsWith('.tsx')) return ts.ScriptKind.TSX
34
- if (filePath.endsWith('.jsx')) return ts.ScriptKind.JSX
35
- if (filePath.endsWith('.js') || filePath.endsWith('.mjs') || filePath.endsWith('.cjs')) return ts.ScriptKind.JS
36
- return ts.ScriptKind.TS
37
- }
38
-
39
- private allocateId(prefix: string, name: string): string {
40
- const count = (this.nameCounter.get(name) ?? 0) + 1
41
- this.nameCounter.set(name, count)
42
- const suffix = count === 1 ? '' : `#${count}`
43
- const normalizedPath = this.filePath.replace(/\\/g, '/')
44
- return `${prefix}:${normalizedPath}:${name}${suffix}`.toLowerCase()
45
- }
46
-
47
- resetCounters(): void {
48
- this.nameCounter.clear()
49
- }
50
-
51
- extractFunctions(): ParsedFunction[] {
52
- const functions: ParsedFunction[] = []
53
- this.walkNode(this.sourceFile, (node) => {
54
- if (ts.isFunctionDeclaration(node) && node.name) {
55
- functions.push(this.parseFunctionDeclaration(node))
56
- }
57
- if (ts.isVariableStatement(node)) {
58
- for (const decl of node.declarationList.declarations) {
59
- if (decl.initializer && ts.isIdentifier(decl.name)) {
60
- if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
61
- functions.push(this.parseVariableFunction(node, decl, decl.initializer))
62
- }
63
- }
64
- }
65
- }
66
- })
67
- return functions
68
- }
69
-
70
- extractClasses(): ParsedClass[] {
71
- const classes: ParsedClass[] = []
72
- this.walkNode(this.sourceFile, (node) => {
73
- if (ts.isClassDeclaration(node) && node.name) {
74
- classes.push(this.parseClass(node))
75
- }
76
- })
77
- return classes
78
- }
79
-
80
- extractVariables(): ParsedVariable[] {
81
- const variables: ParsedVariable[] = []
82
- this.walkNode(this.sourceFile, (node) => {
83
- if (ts.isVariableStatement(node)) {
84
- for (const decl of node.declarationList.declarations) {
85
- if (ts.isIdentifier(decl.name) && !this.isFunctionLike(decl.initializer)) {
86
- variables.push(this.parseVariable(node, decl))
87
- }
88
- }
89
- }
90
- })
91
- return variables
92
- }
93
-
94
- private isFunctionLike(node?: ts.Node): boolean {
95
- return !!node && (ts.isArrowFunction(node) || ts.isFunctionExpression(node))
96
- }
97
-
98
- extractGenerics(): ParsedGeneric[] {
99
- const generics: ParsedGeneric[] = []
100
- this.walkNode(this.sourceFile, (node) => {
101
- if (ts.isInterfaceDeclaration(node)) {
102
- generics.push({
103
- id: this.allocateId('intf', node.name.text),
104
- name: node.name.text,
105
- type: 'interface',
106
- file: this.filePath,
107
- startLine: this.getLineNumber(node.getStart(this.sourceFile)),
108
- endLine: this.getLineNumber(node.getEnd()),
109
- isExported: this.hasExportModifier(node),
110
- typeParameters: this.extractTypeParameters(node.typeParameters),
111
- hash: hashContent(node.getText(this.sourceFile)),
112
- purpose: this.extractPurpose(node),
113
- })
114
- } else if (ts.isTypeAliasDeclaration(node)) {
115
- generics.push({
116
- id: this.allocateId('type', node.name.text),
117
- name: node.name.text,
118
- type: 'type',
119
- file: this.filePath,
120
- startLine: this.getLineNumber(node.getStart(this.sourceFile)),
121
- endLine: this.getLineNumber(node.getEnd()),
122
- isExported: this.hasExportModifier(node),
123
- typeParameters: this.extractTypeParameters(node.typeParameters),
124
- hash: hashContent(node.getText(this.sourceFile)),
125
- purpose: this.extractPurpose(node),
126
- })
127
- }
128
- })
129
- return generics
130
- }
131
-
132
- extractImports(): ParsedImport[] {
133
- const imports: ParsedImport[] = []
134
- this.walkNode(this.sourceFile, (node) => {
135
- if (ts.isImportDeclaration(node)) {
136
- if (node.importClause?.isTypeOnly) return;
137
- const parsed = this.parseImport(node)
138
- if (parsed) {
139
- // Filter out type-only named imports
140
- parsed.names = parsed.names.filter(n => !n.startsWith('type '))
141
- imports.push(parsed)
142
- }
143
- }
144
- })
145
- return imports
146
- }
147
-
148
- extractExports(): ParsedExport[] {
149
- const exports: ParsedExport[] = []
150
- this.walkNode(this.sourceFile, (node) => {
151
- if (ts.isFunctionDeclaration(node) && node.name && this.hasExportModifier(node)) {
152
- exports.push({ name: node.name.text, type: 'function', file: this.filePath })
153
- }
154
- if (ts.isClassDeclaration(node) && node.name && this.hasExportModifier(node)) {
155
- exports.push({ name: node.name.text, type: 'class', file: this.filePath })
156
- }
157
- if (ts.isVariableStatement(node) && this.hasExportModifier(node)) {
158
- node.declarationList.declarations.forEach(decl => {
159
- if (ts.isIdentifier(decl.name)) {
160
- exports.push({ name: decl.name.text, type: 'const', file: this.filePath })
161
- }
162
- })
163
- }
164
- if (ts.isExportAssignment(node)) {
165
- exports.push({ name: 'default', type: 'default', file: this.filePath })
166
- }
167
- })
168
- return exports
169
- }
170
-
171
- extractRoutes(): ParsedRoute[] {
172
- const routes: ParsedRoute[] = []
173
- this.walkNode(this.sourceFile, (node) => {
174
- if (ts.isCallExpression(node)) {
175
- const text = node.expression.getText(this.sourceFile)
176
- if (text.match(/^(router|app|express)\.(get|post|put|delete|patch)$/)) {
177
- const method = text.split('.')[1].toUpperCase() as any
178
- const pathArg = node.arguments[0]
179
- if (pathArg && ts.isStringLiteral(pathArg)) {
180
- const path = pathArg.text
181
- const handler = node.arguments[node.arguments.length - 1]
182
- const middlewares = node.arguments.slice(1, -1).map(a => a.getText(this.sourceFile))
183
- routes.push({
184
- method,
185
- path,
186
- handler: handler.getText(this.sourceFile),
187
- middlewares,
188
- file: this.filePath,
189
- line: this.getLineNumber(node.getStart(this.sourceFile))
190
- })
191
- }
192
- }
193
- }
194
- })
195
- return routes
196
- }
197
-
198
- extractModuleCalls(): CallExpression[] {
199
- // Calls occurring at the top level of the file
200
- const calls: CallExpression[] = []
201
- this.sourceFile.statements.forEach(stmt => {
202
- if (!ts.isFunctionDeclaration(stmt) && !ts.isClassDeclaration(stmt)) {
203
- calls.push(...this.extractCallsFromNode(stmt))
204
- }
205
- })
206
- return calls
207
- }
208
-
209
- // --- Private Parsers ---
210
-
211
- private parseFunctionDeclaration(node: ts.FunctionDeclaration): ParsedFunction {
212
- const name = node.name!.text
213
- const bodyText = node.getText(this.sourceFile)
214
- return {
215
- id: this.allocateId('fn', name),
216
- name,
217
- file: this.filePath,
218
- startLine: this.getLineNumber(node.getStart(this.sourceFile)),
219
- endLine: this.getLineNumber(node.getEnd()),
220
- params: this.extractParams(node.parameters),
221
- returnType: node.type ? node.type.getText(this.sourceFile) : 'void',
222
- isExported: this.hasExportModifier(node),
223
- isAsync: !!node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword),
224
- calls: this.extractCallsFromNode(node),
225
- hash: hashContent(bodyText),
226
- purpose: this.extractPurpose(node),
227
- edgeCasesHandled: this.extractEdgeCases(node),
228
- errorHandling: this.extractErrorHandling(node),
229
- detailedLines: [],
230
- }
231
- }
232
-
233
- private parseVariableFunction(stmt: ts.VariableStatement, decl: ts.VariableDeclaration, fn: ts.ArrowFunction | ts.FunctionExpression): ParsedFunction {
234
- const name = (decl.name as ts.Identifier).text
235
- const bodyText = stmt.getText(this.sourceFile)
236
- return {
237
- id: this.allocateId('fn', name),
238
- name,
239
- file: this.filePath,
240
- startLine: this.getLineNumber(stmt.getStart(this.sourceFile)),
241
- endLine: this.getLineNumber(stmt.getEnd()),
242
- params: this.extractParams(fn.parameters),
243
- returnType: fn.type ? fn.type.getText(this.sourceFile) : 'unknown',
244
- isExported: this.hasExportModifier(stmt),
245
- isAsync: !!fn.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword),
246
- calls: this.extractCallsFromNode(fn),
247
- hash: hashContent(bodyText),
248
- purpose: this.extractPurpose(stmt),
249
- edgeCasesHandled: this.extractEdgeCases(fn),
250
- errorHandling: this.extractErrorHandling(fn),
251
- detailedLines: [],
252
- }
253
- }
254
-
255
- private parseClass(node: ts.ClassDeclaration): ParsedClass {
256
- const name = node.name!.text
257
- const methods: ParsedFunction[] = []
258
- const properties: ParsedVariable[] = []
259
-
260
- for (const member of node.members) {
261
- if (ts.isMethodDeclaration(member) && member.name) {
262
- methods.push(this.parseMethod(name, member))
263
- } else if (ts.isPropertyDeclaration(member) && member.name) {
264
- properties.push(this.parseProperty(name, member))
265
- }
266
- }
267
-
268
- return {
269
- id: this.allocateId('class', name),
270
- name,
271
- file: this.filePath,
272
- startLine: this.getLineNumber(node.getStart(this.sourceFile)),
273
- endLine: this.getLineNumber(node.getEnd()),
274
- methods,
275
- properties,
276
- extends: node.heritageClauses?.find(c => c.token === ts.SyntaxKind.ExtendsKeyword)?.types[0]?.getText(this.sourceFile),
277
- implements: node.heritageClauses?.find(c => c.token === ts.SyntaxKind.ImplementsKeyword)?.types.map(t => t.getText(this.sourceFile)),
278
- isExported: this.hasExportModifier(node),
279
- hash: hashContent(node.getText(this.sourceFile)),
280
- purpose: this.extractPurpose(node),
281
- edgeCasesHandled: this.extractEdgeCases(node),
282
- errorHandling: this.extractErrorHandling(node),
283
- }
284
- }
285
-
286
- private parseMethod(className: string, node: ts.MethodDeclaration): ParsedFunction {
287
- const methodName = node.name.getText(this.sourceFile)
288
- const fullName = `${className}.${methodName}`
289
- return {
290
- id: this.allocateId('fn', fullName),
291
- name: fullName,
292
- file: this.filePath,
293
- startLine: this.getLineNumber(node.getStart(this.sourceFile)),
294
- endLine: this.getLineNumber(node.getEnd()),
295
- params: this.extractParams(node.parameters),
296
- returnType: node.type ? node.type.getText(this.sourceFile) : 'void',
297
- isExported: false,
298
- isAsync: !!node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword),
299
- calls: this.extractCallsFromNode(node),
300
- hash: hashContent(node.getText(this.sourceFile)),
301
- purpose: this.extractPurpose(node),
302
- edgeCasesHandled: this.extractEdgeCases(node),
303
- errorHandling: this.extractErrorHandling(node),
304
- detailedLines: [],
305
- }
306
- }
307
-
308
- private parseProperty(className: string, node: ts.PropertyDeclaration): ParsedVariable {
309
- const propName = node.name.getText(this.sourceFile)
310
- return {
311
- id: this.allocateId('var', `${className}.${propName}`),
312
- name: propName,
313
- type: node.type ? node.type.getText(this.sourceFile) : 'any',
314
- file: this.filePath,
315
- line: this.getLineNumber(node.getStart(this.sourceFile)),
316
- isExported: false,
317
- isStatic: !!node.modifiers?.some(m => m.kind === ts.SyntaxKind.StaticKeyword),
318
- }
319
- }
320
-
321
- private parseVariable(stmt: ts.VariableStatement, decl: ts.VariableDeclaration): ParsedVariable {
322
- const name = (decl.name as ts.Identifier).text
323
- return {
324
- id: this.allocateId('var', name),
325
- name,
326
- type: decl.type ? decl.type.getText(this.sourceFile) : 'any',
327
- file: this.filePath,
328
- line: this.getLineNumber(stmt.getStart(this.sourceFile)),
329
- isExported: this.hasExportModifier(stmt),
330
- }
331
- }
332
-
333
- private parseImport(node: ts.ImportDeclaration): ParsedImport {
334
- const source = (node.moduleSpecifier as ts.StringLiteral).text
335
- const names: string[] = []
336
- let isDefault = false
337
- if (node.importClause) {
338
- if (node.importClause.name) {
339
- names.push(node.importClause.name.text)
340
- isDefault = true
341
- }
342
- if (node.importClause.namedBindings && ts.isNamedImports(node.importClause.namedBindings)) {
343
- node.importClause.namedBindings.elements.forEach(el => names.push(el.name.text))
344
- }
345
- }
346
- return { source, resolvedPath: '', names, isDefault, isDynamic: false }
347
- }
348
-
349
- // --- Helpers ---
350
-
351
- protected extractCallsFromNode(node: ts.Node): CallExpression[] {
352
- const calls: CallExpression[] = []
353
- const walk = (n: ts.Node) => {
354
- if (ts.isCallExpression(n)) {
355
- calls.push({
356
- name: n.expression.getText(this.sourceFile),
357
- line: this.getLineNumber(n.getStart(this.sourceFile)),
358
- type: ts.isPropertyAccessExpression(n.expression) ? 'method' : 'function'
359
- })
360
- } else if (ts.isNewExpression(n)) {
361
- calls.push({
362
- name: n.expression.getText(this.sourceFile),
363
- line: this.getLineNumber(n.getStart(this.sourceFile)),
364
- type: 'function'
365
- })
366
- } else if (ts.isPropertyAccessExpression(n) && !ts.isCallExpression(n.parent)) {
367
- // Property access that isn't a call
368
- calls.push({
369
- name: n.getText(this.sourceFile),
370
- line: this.getLineNumber(n.getStart(this.sourceFile)),
371
- type: 'property'
372
- })
373
- }
374
- ts.forEachChild(n, walk)
375
- }
376
- walk(node)
377
- return calls
378
- }
379
-
380
- protected extractParams(params: ts.NodeArray<ts.ParameterDeclaration>): ParsedParam[] {
381
- return params.map(p => ({
382
- name: p.name.getText(this.sourceFile),
383
- type: p.type ? p.type.getText(this.sourceFile) : 'any',
384
- optional: !!p.questionToken || !!p.initializer,
385
- defaultValue: p.initializer ? p.initializer.getText(this.sourceFile) : undefined,
386
- }))
387
- }
388
-
389
- protected extractTypeParameters(typeParams?: ts.NodeArray<ts.TypeParameterDeclaration>): string[] {
390
- return typeParams?.map(t => t.name.text) || []
391
- }
392
-
393
- protected hasExportModifier(node: ts.Node): boolean {
394
- return !!ts.getModifiers(node as any)?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)
395
- }
396
-
397
- protected getLineNumber(pos: number): number {
398
- return this.sourceFile.getLineAndCharacterOfPosition(pos).line + 1
399
- }
400
-
401
- protected extractPurpose(node: ts.Node): string {
402
- const fullText = this.sourceFile.getFullText()
403
- const ranges = ts.getLeadingCommentRanges(fullText, node.pos)
404
- if (ranges && ranges.length > 0) {
405
- const lastComment = fullText.slice(ranges[ranges.length - 1].pos, ranges[ranges.length - 1].end)
406
- return lastComment.replace(/\/\*+|\*+\/|\/\/+/g, '').trim()
407
- }
408
- return ''
409
- }
410
-
411
- protected extractEdgeCases(node: ts.Node): string[] {
412
- const edgeCases: string[] = []
413
- const walk = (n: ts.Node) => {
414
- if (ts.isIfStatement(n) || ts.isConditionalExpression(n)) {
415
- edgeCases.push(n.getText(this.sourceFile).split('{')[0].trim())
416
- }
417
- ts.forEachChild(n, walk)
418
- }
419
- walk(node)
420
- return edgeCases
421
- }
422
-
423
- protected extractErrorHandling(node: ts.Node): { line: number, type: 'try-catch' | 'throw', detail: string }[] {
424
- const result: { line: number, type: 'try-catch' | 'throw', detail: string }[] = []
425
- const walk = (n: ts.Node) => {
426
- if (ts.isTryStatement(n)) {
427
- result.push({ line: this.getLineNumber(n.getStart(this.sourceFile)), type: 'try-catch', detail: 'try block' })
428
- } else if (ts.isThrowStatement(n)) {
429
- result.push({ line: this.getLineNumber(n.getStart(this.sourceFile)), type: 'throw', detail: n.expression?.getText(this.sourceFile) || 'unknown' })
430
- }
431
- ts.forEachChild(n, walk)
432
- }
433
- walk(node)
434
- return result
435
- }
436
-
437
- protected extractDetailedLines(node: ts.Node): { startLine: number; endLine: number; blockType: string }[] {
438
- return [] // Implementation for behavioral tracking
439
- }
440
-
441
- private walkNode(node: ts.Node, callback: (node: ts.Node) => void): void {
442
- ts.forEachChild(node, (child) => {
443
- callback(child)
444
- this.walkNode(child, callback)
445
- })
446
- }
447
- }
@@ -1,36 +0,0 @@
1
- import { BaseParser } from '../base-parser.js'
2
- import type { ParsedFile } from '../types.js'
3
- import { TypeScriptExtractor } from './ts-extractor.js'
4
- import { TypeScriptResolver } from './ts-resolver.js'
5
- import { hashContent } from '../../hash/file-hasher.js'
6
-
7
- export class TypeScriptParser extends BaseParser {
8
- public async parse(filePath: string, content: string): Promise<ParsedFile> {
9
- const extractor = new TypeScriptExtractor(filePath, content)
10
-
11
- return {
12
- path: filePath,
13
- language: 'typescript',
14
- functions: extractor.extractFunctions(),
15
- classes: extractor.extractClasses(),
16
- variables: extractor.extractVariables(),
17
- generics: extractor.extractGenerics(),
18
- imports: extractor.extractImports(),
19
- exports: extractor.extractExports(),
20
- routes: extractor.extractRoutes(),
21
- calls: extractor.extractModuleCalls(),
22
- hash: hashContent(content),
23
- parsedAt: Date.now()
24
- }
25
- }
26
-
27
- public async resolveImports(files: ParsedFile[], projectRoot: string): Promise<ParsedFile[]> {
28
- const resolver = new TypeScriptResolver(projectRoot)
29
- return resolver.resolveBatch(files)
30
- }
31
-
32
- public getSupportedExtensions(): string[] {
33
- return ['.ts', '.tsx']
34
- }
35
- }
36
-