@getmikk/core 1.8.3 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/package.json +3 -1
  2. package/src/constants.ts +285 -0
  3. package/src/contract/contract-generator.ts +7 -0
  4. package/src/contract/index.ts +2 -3
  5. package/src/contract/lock-compiler.ts +66 -35
  6. package/src/contract/lock-reader.ts +24 -4
  7. package/src/contract/schema.ts +21 -0
  8. package/src/error-handler.ts +430 -0
  9. package/src/graph/cluster-detector.ts +45 -20
  10. package/src/graph/confidence-engine.ts +60 -0
  11. package/src/graph/graph-builder.ts +298 -255
  12. package/src/graph/impact-analyzer.ts +130 -119
  13. package/src/graph/index.ts +4 -0
  14. package/src/graph/memory-manager.ts +345 -0
  15. package/src/graph/query-engine.ts +79 -0
  16. package/src/graph/risk-engine.ts +86 -0
  17. package/src/graph/types.ts +89 -65
  18. package/src/parser/change-detector.ts +99 -0
  19. package/src/parser/go/go-extractor.ts +18 -8
  20. package/src/parser/go/go-parser.ts +2 -0
  21. package/src/parser/index.ts +88 -38
  22. package/src/parser/javascript/js-extractor.ts +1 -1
  23. package/src/parser/javascript/js-parser.ts +2 -0
  24. package/src/parser/oxc-parser.ts +675 -0
  25. package/src/parser/oxc-resolver.ts +83 -0
  26. package/src/parser/tree-sitter/parser.ts +19 -10
  27. package/src/parser/types.ts +100 -73
  28. package/src/parser/typescript/ts-extractor.ts +229 -589
  29. package/src/parser/typescript/ts-parser.ts +16 -171
  30. package/src/parser/typescript/ts-resolver.ts +11 -1
  31. package/src/search/bm25.ts +5 -2
  32. package/src/utils/minimatch.ts +1 -1
  33. package/tests/contract.test.ts +2 -2
  34. package/tests/dead-code.test.ts +7 -7
  35. package/tests/esm-resolver.test.ts +75 -0
  36. package/tests/graph.test.ts +20 -20
  37. package/tests/helpers.ts +11 -6
  38. package/tests/impact-classified.test.ts +37 -41
  39. package/tests/parser.test.ts +7 -5
  40. package/tests/ts-parser.test.ts +27 -52
  41. package/test-output.txt +0 -373
@@ -1,12 +1,22 @@
1
- import * as path from 'node:path'
1
+ import * as nodePath from 'node:path'
2
2
  import { BaseParser } from './base-parser.js'
3
- import { TypeScriptParser } from './typescript/ts-parser.js'
3
+ import { OxcParser } from './oxc-parser.js'
4
4
  import { GoParser } from './go/go-parser.js'
5
- import { JavaScriptParser } from './javascript/js-parser.js'
6
5
  import { UnsupportedLanguageError } from '../utils/errors.js'
7
6
  import type { ParsedFile } from './types.js'
8
7
 
9
- export type { ParsedFile, ParsedFunction, ParsedImport, ParsedExport, ParsedClass, ParsedParam } from './types.js'
8
+ export type {
9
+ ParsedFile,
10
+ ParsedFunction,
11
+ ParsedImport,
12
+ ParsedExport,
13
+ ParsedClass,
14
+ ParsedParam,
15
+ ParsedVariable,
16
+ CallExpression,
17
+ ParsedGeneric,
18
+ ParsedRoute
19
+ } from './types.js'
10
20
  export { BaseParser } from './base-parser.js'
11
21
  export { TypeScriptParser } from './typescript/ts-parser.js'
12
22
  export { TypeScriptExtractor } from './typescript/ts-extractor.js'
@@ -19,22 +29,20 @@ export { JavaScriptExtractor } from './javascript/js-extractor.js'
19
29
  export { JavaScriptResolver } from './javascript/js-resolver.js'
20
30
  export { BoundaryChecker } from './boundary-checker.js'
21
31
  export { TreeSitterParser } from './tree-sitter/parser.js'
22
- import { TreeSitterParser } from './tree-sitter/parser.js'
23
32
 
24
33
  /** Get the appropriate parser for a file based on its extension */
25
34
  export function getParser(filePath: string): BaseParser {
26
- const ext = path.extname(filePath)
35
+ const ext = nodePath.extname(filePath).toLowerCase()
27
36
  switch (ext) {
28
37
  case '.ts':
29
38
  case '.tsx':
30
- return new TypeScriptParser()
31
39
  case '.js':
32
40
  case '.mjs':
33
41
  case '.cjs':
34
42
  case '.jsx':
35
- return new JavaScriptParser()
43
+ return new OxcParser()
36
44
  case '.go':
37
- return new GoParser() // Mikk's custom Regex Go parser
45
+ return new GoParser()
38
46
  case '.py':
39
47
  case '.java':
40
48
  case '.c':
@@ -46,55 +54,97 @@ export function getParser(filePath: string): BaseParser {
46
54
  case '.rs':
47
55
  case '.php':
48
56
  case '.rb':
49
- return new TreeSitterParser()
57
+ throw new UnsupportedLanguageError(ext)
50
58
  default:
51
59
  throw new UnsupportedLanguageError(ext)
52
60
  }
53
61
  }
54
62
 
55
- /** Parse multiple files and resolve imports across them */
63
+ /**
64
+ * Parse multiple files, resolve their imports, and return ParsedFile[].
65
+ *
66
+ * Path contract (critical for graph correctness):
67
+ * - filePaths come from discoverFiles() as project-root-relative strings
68
+ * - We resolve them to ABSOLUTE posix paths before passing to parse()
69
+ * - ParsedFile.path is therefore always absolute + forward-slash
70
+ * - OxcResolver also returns absolute paths → import edges always consistent
71
+ */
56
72
  export async function parseFiles(
57
73
  filePaths: string[],
58
74
  projectRoot: string,
59
75
  readFile: (fp: string) => Promise<string>
60
76
  ): Promise<ParsedFile[]> {
61
- const parsersMap = new Map<BaseParser, ParsedFile[]>()
62
- // Re-use parser instances so they can share cache/bindings
63
- const tsParser = new TypeScriptParser()
64
- const jsParser = new JavaScriptParser()
77
+ // Shared parser instances avoid re-initialisation overhead per file
78
+ const oxcParser = new OxcParser()
65
79
  const goParser = new GoParser()
66
- const treeSitterParser = new TreeSitterParser()
67
80
 
68
- const getCachedParser = (ext: string): BaseParser | null => {
69
- switch (ext) {
70
- case '.ts': case '.tsx': return tsParser
71
- case '.js': case '.mjs': case '.cjs': case '.jsx': return jsParser
72
- case '.go': return goParser
73
- case '.py': case '.java': case '.c': case '.h': case '.cpp': case '.cc': case '.hpp': case '.cs': case '.rs': case '.php': case '.rb': return treeSitterParser
74
- default: return null
81
+ // Lazily loaded to avoid mandatory dep on tree-sitter
82
+ let treeSitterParser: BaseParser | null = null
83
+ const getTreeSitter = async (): Promise<BaseParser> => {
84
+ if (!treeSitterParser) {
85
+ const { TreeSitterParser } = await import('./tree-sitter/parser.js')
86
+ treeSitterParser = new TreeSitterParser()
75
87
  }
88
+ return treeSitterParser!
76
89
  }
77
90
 
78
- for (const fp of filePaths) {
79
- const ext = path.extname(fp).toLowerCase()
80
- const parser = getCachedParser(ext)
81
- if (!parser) continue
91
+ const tsExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs'])
92
+ const goExtensions = new Set(['.go'])
93
+ const treeSitterExtensions = new Set(['.py', '.java', '.c', '.h', '.cpp', '.cc', '.hpp', '.cs', '.rs', '.php', '.rb'])
94
+
95
+ // Normalised project root for absolute path construction
96
+ const normalizedRoot = nodePath.resolve(projectRoot).replace(/\\/g, '/')
97
+
98
+ // Group by parser to enable batch resolveImports
99
+ const oxcFiles: ParsedFile[] = []
100
+ const goFiles: ParsedFile[] = []
101
+ const treeFiles: ParsedFile[] = []
82
102
 
103
+ const parsePromises = filePaths.map(async (fp) => {
104
+ const ext = nodePath.extname(fp).toLowerCase()
105
+
106
+ // Build absolute posix path — this is the single source of truth for all IDs
107
+ const absoluteFp = nodePath.resolve(normalizedRoot, fp).replace(/\\/g, '/')
108
+
109
+ let content: string
83
110
  try {
84
- const content = await readFile(path.join(projectRoot, fp))
85
- const parsed = await parser.parse(fp, content)
86
-
87
- if (!parsersMap.has(parser)) parsersMap.set(parser, [])
88
- parsersMap.get(parser)!.push(parsed)
111
+ content = await readFile(absoluteFp)
89
112
  } catch {
90
- // Skip unreadable files (permissions, binary, etc.) — don't abort the whole parse
113
+ // File unreadable skip silently (deleted, permission error, binary)
114
+ return
91
115
  }
92
- }
93
116
 
94
- const allResolvedFiles: ParsedFile[] = []
95
- for (const [parser, files] of parsersMap.entries()) {
96
- allResolvedFiles.push(...parser.resolveImports(files, projectRoot))
117
+ try {
118
+ if (tsExtensions.has(ext)) {
119
+ const parsed = await oxcParser.parse(absoluteFp, content)
120
+ oxcFiles.push(parsed)
121
+ } else if (goExtensions.has(ext)) {
122
+ const parsed = await goParser.parse(absoluteFp, content)
123
+ goFiles.push(parsed)
124
+ } else if (treeSitterExtensions.has(ext)) {
125
+ const ts = await getTreeSitter()
126
+ const parsed = await ts.parse(absoluteFp, content)
127
+ treeFiles.push(parsed)
128
+ }
129
+ } catch {
130
+ // Parser error — skip this file, don't abort the whole run
131
+ }
132
+ })
133
+
134
+ await Promise.all(parsePromises)
135
+
136
+ // Resolve imports batch-wise per parser (each has its own resolver)
137
+ let resolvedTreeFiles: ParsedFile[] = treeFiles
138
+ if (treeFiles.length > 0) {
139
+ const treeParser = treeSitterParser ?? await getTreeSitter()
140
+ resolvedTreeFiles = treeParser.resolveImports(treeFiles, normalizedRoot)
97
141
  }
98
142
 
99
- return allResolvedFiles
143
+ const resolved: ParsedFile[] = [
144
+ ...oxcParser.resolveImports(oxcFiles, normalizedRoot),
145
+ ...goParser.resolveImports(goFiles, normalizedRoot),
146
+ ...resolvedTreeFiles,
147
+ ]
148
+
149
+ return resolved
100
150
  }
@@ -231,7 +231,7 @@ export class JavaScriptExtractor extends TypeScriptExtractor {
231
231
  returnType: rhs.type ? rhs.type.getText(this.sourceFile) : 'void',
232
232
  isExported: true,
233
233
  isAsync,
234
- calls: this.extractCalls(rhs),
234
+ calls: this.extractCallsFromNode(rhs),
235
235
  hash: hashContent(rhs.getText(this.sourceFile)),
236
236
  purpose: this.extractPurpose(node),
237
237
  edgeCasesHandled: this.extractEdgeCases(rhs),
@@ -48,6 +48,8 @@ export class JavaScriptParser extends BaseParser {
48
48
  imports,
49
49
  exports,
50
50
  routes,
51
+ variables: [],
52
+ calls: [],
51
53
  hash: hashContent(content),
52
54
  parsedAt: Date.now(),
53
55
  }