@getmikk/core 1.8.3 → 2.0.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.
- package/package.json +6 -4
- package/src/constants.ts +285 -0
- package/src/contract/contract-generator.ts +7 -0
- package/src/contract/index.ts +2 -3
- package/src/contract/lock-compiler.ts +66 -35
- package/src/contract/lock-reader.ts +30 -5
- package/src/contract/schema.ts +21 -0
- package/src/error-handler.ts +432 -0
- package/src/graph/cluster-detector.ts +52 -22
- package/src/graph/confidence-engine.ts +85 -0
- package/src/graph/graph-builder.ts +298 -255
- package/src/graph/impact-analyzer.ts +132 -119
- package/src/graph/index.ts +4 -0
- package/src/graph/memory-manager.ts +186 -0
- package/src/graph/query-engine.ts +76 -0
- package/src/graph/risk-engine.ts +86 -0
- package/src/graph/types.ts +89 -65
- package/src/index.ts +2 -0
- package/src/parser/change-detector.ts +99 -0
- package/src/parser/go/go-extractor.ts +18 -8
- package/src/parser/go/go-parser.ts +2 -0
- package/src/parser/index.ts +86 -36
- package/src/parser/javascript/js-extractor.ts +1 -1
- package/src/parser/javascript/js-parser.ts +2 -0
- package/src/parser/oxc-parser.ts +708 -0
- package/src/parser/oxc-resolver.ts +83 -0
- package/src/parser/tree-sitter/parser.ts +19 -10
- package/src/parser/types.ts +100 -73
- package/src/parser/typescript/ts-extractor.ts +229 -589
- package/src/parser/typescript/ts-parser.ts +16 -171
- package/src/parser/typescript/ts-resolver.ts +11 -1
- package/src/search/bm25.ts +16 -4
- package/src/utils/minimatch.ts +1 -1
- package/tests/contract.test.ts +2 -2
- package/tests/dead-code.test.ts +7 -7
- package/tests/esm-resolver.test.ts +75 -0
- package/tests/graph.test.ts +20 -20
- package/tests/helpers.ts +11 -6
- package/tests/impact-classified.test.ts +37 -41
- package/tests/parser.test.ts +7 -5
- package/tests/ts-parser.test.ts +27 -52
- package/test-output.txt +0 -373
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { ResolverFactory } from 'oxc-resolver';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import type { ParsedFile } from './types.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* OxcResolver — Rust-backed compiler-grade module resolution.
|
|
8
|
+
*
|
|
9
|
+
* Resolution strategy:
|
|
10
|
+
* 1. If the resolved path is inside projectRoot → return project-relative posix path
|
|
11
|
+
* 2. If the resolved path is outside projectRoot (node_modules, monorepo peer) → return ''
|
|
12
|
+
* (external deps produce no graph edges; they're not in our file set)
|
|
13
|
+
* 3. On any error → return '' (unresolved, no false edges)
|
|
14
|
+
*
|
|
15
|
+
* All returned paths use forward slashes and are ABSOLUTE, matching what
|
|
16
|
+
* parseFiles passes to parse(). graph-builder only creates import edges when
|
|
17
|
+
* the target path exists as a file node — so no path format inconsistency can
|
|
18
|
+
* create false positive edges.
|
|
19
|
+
*/
|
|
20
|
+
export class OxcResolver {
|
|
21
|
+
private resolver: any;
|
|
22
|
+
private readonly normalizedRoot: string;
|
|
23
|
+
|
|
24
|
+
constructor(private readonly projectRoot: string) {
|
|
25
|
+
this.normalizedRoot = path.resolve(projectRoot).replace(/\\/g, '/');
|
|
26
|
+
|
|
27
|
+
const tsconfigPath = path.resolve(projectRoot, 'tsconfig.json');
|
|
28
|
+
const hasTsConfig = fs.existsSync(tsconfigPath);
|
|
29
|
+
|
|
30
|
+
this.resolver = new ResolverFactory({
|
|
31
|
+
extensions: ['.ts', '.tsx', '.js', '.jsx', '.json', '.mjs', '.cjs', '.mts', '.cts'],
|
|
32
|
+
mainFields: ['module', 'main', 'jsnext:main'],
|
|
33
|
+
mainFiles: ['index', 'main', 'app'],
|
|
34
|
+
conditionNames: ['import', 'require', 'node', 'default', 'types', 'browser'],
|
|
35
|
+
symlinks: true,
|
|
36
|
+
modules: ['node_modules'],
|
|
37
|
+
tsconfig: hasTsConfig ? {
|
|
38
|
+
configFile: tsconfigPath,
|
|
39
|
+
references: 'auto',
|
|
40
|
+
} : undefined,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve a single import source string relative to fromFile.
|
|
46
|
+
* fromFile MUST be an absolute path (as produced by parseFiles).
|
|
47
|
+
* Returns an absolute posix path, or '' if unresolvable/external.
|
|
48
|
+
*/
|
|
49
|
+
public resolve(source: string, fromFile: string): string {
|
|
50
|
+
try {
|
|
51
|
+
const absFrom = path.isAbsolute(fromFile)
|
|
52
|
+
? fromFile
|
|
53
|
+
: path.resolve(this.projectRoot, fromFile);
|
|
54
|
+
const dir = path.dirname(absFrom);
|
|
55
|
+
|
|
56
|
+
const result = this.resolver.sync(dir, source);
|
|
57
|
+
if (!result?.path) return '';
|
|
58
|
+
|
|
59
|
+
const resolved = result.path.replace(/\\/g, '/');
|
|
60
|
+
|
|
61
|
+
// Only include files within our project root in the graph.
|
|
62
|
+
// node_modules, hoisted workspace deps, etc. are external.
|
|
63
|
+
if (!resolved.startsWith(this.normalizedRoot + '/') && resolved !== this.normalizedRoot) {
|
|
64
|
+
return '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return resolved;
|
|
68
|
+
} catch {
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Resolve all imports for a batch of files in one pass */
|
|
74
|
+
public resolveBatch(files: ParsedFile[]): ParsedFile[] {
|
|
75
|
+
return files.map(file => ({
|
|
76
|
+
...file,
|
|
77
|
+
imports: file.imports.map(imp => ({
|
|
78
|
+
...imp,
|
|
79
|
+
resolvedPath: this.resolve(imp.source, file.path),
|
|
80
|
+
})),
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -109,8 +109,8 @@ function findFirstChild(node: any, predicate: (n: any) => boolean): any {
|
|
|
109
109
|
function assignCallsToFunctions(
|
|
110
110
|
functions: ParsedFunction[],
|
|
111
111
|
callEntries: Array<{ name: string; line: number }>
|
|
112
|
-
): string
|
|
113
|
-
const unassigned: string
|
|
112
|
+
): Array<{ name: string; line: number }> {
|
|
113
|
+
const unassigned: Array<{ name: string; line: number }> = []
|
|
114
114
|
for (const { name, line } of callEntries) {
|
|
115
115
|
// Find the innermost (smallest range) function that contains this line
|
|
116
116
|
let best: ParsedFunction | null = null
|
|
@@ -125,11 +125,11 @@ function assignCallsToFunctions(
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
if (best) {
|
|
128
|
-
if (!best.calls.
|
|
129
|
-
best.calls.push(name)
|
|
128
|
+
if (!best.calls.some(c => c.name === name && c.line === line)) {
|
|
129
|
+
best.calls.push({ name, line, type: 'function' })
|
|
130
130
|
}
|
|
131
131
|
} else {
|
|
132
|
-
unassigned.push(name)
|
|
132
|
+
unassigned.push({ name, line })
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
return unassigned
|
|
@@ -187,10 +187,13 @@ export class TreeSitterParser extends BaseParser {
|
|
|
187
187
|
// --- Calls: record name and line position ---
|
|
188
188
|
if (captures['call.name']) {
|
|
189
189
|
const callNode = captures['call.name']
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
190
|
+
const name = callNode.text
|
|
191
|
+
if (name) {
|
|
192
|
+
callEntries.push({
|
|
193
|
+
name,
|
|
194
|
+
line: (callNode.startPosition?.row ?? 0) + 1,
|
|
195
|
+
})
|
|
196
|
+
}
|
|
194
197
|
continue
|
|
195
198
|
}
|
|
196
199
|
|
|
@@ -282,7 +285,9 @@ export class TreeSitterParser extends BaseParser {
|
|
|
282
285
|
startLine,
|
|
283
286
|
endLine,
|
|
284
287
|
methods: [],
|
|
288
|
+
properties: [],
|
|
285
289
|
isExported: isExportedByLanguage(ext, clsName, nodeText),
|
|
290
|
+
hash: hashContent(nodeText),
|
|
286
291
|
})
|
|
287
292
|
}
|
|
288
293
|
}
|
|
@@ -305,7 +310,7 @@ export class TreeSitterParser extends BaseParser {
|
|
|
305
310
|
returnType: 'void',
|
|
306
311
|
isExported: false, // Don't export the synthetic module function
|
|
307
312
|
isAsync: false,
|
|
308
|
-
calls:
|
|
313
|
+
calls: unassignedCalls.map(c => ({ name: c.name, line: c.line, type: 'function' })),
|
|
309
314
|
hash: '',
|
|
310
315
|
purpose: 'Module-level initialization code',
|
|
311
316
|
edgeCasesHandled: [],
|
|
@@ -333,6 +338,8 @@ export class TreeSitterParser extends BaseParser {
|
|
|
333
338
|
file: filePath,
|
|
334
339
|
})),
|
|
335
340
|
routes: [],
|
|
341
|
+
variables: [],
|
|
342
|
+
calls: [],
|
|
336
343
|
hash: hashContent(content),
|
|
337
344
|
parsedAt: Date.now(),
|
|
338
345
|
}
|
|
@@ -355,6 +362,8 @@ export class TreeSitterParser extends BaseParser {
|
|
|
355
362
|
imports: [],
|
|
356
363
|
exports: [],
|
|
357
364
|
routes: [],
|
|
365
|
+
variables: [],
|
|
366
|
+
calls: [],
|
|
358
367
|
hash: hashContent(content),
|
|
359
368
|
parsedAt: Date.now(),
|
|
360
369
|
}
|
package/src/parser/types.ts
CHANGED
|
@@ -1,103 +1,130 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Parser types — data shapes that flow through the entire Mikk system.
|
|
3
|
-
* Parser produces them, graph consumes them, contract stores them.
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
5
|
/** A single parameter in a function signature */
|
|
7
6
|
export interface ParsedParam {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
name: string;
|
|
8
|
+
type: string;
|
|
9
|
+
optional: boolean;
|
|
10
|
+
defaultValue?: string;
|
|
12
11
|
}
|
|
13
12
|
|
|
13
|
+
/** A single call expression found in code (Mikk 2.0) */
|
|
14
|
+
export interface CallExpression {
|
|
15
|
+
name: string;
|
|
16
|
+
line: number;
|
|
17
|
+
type: 'function' | 'method' | 'property';
|
|
18
|
+
arguments?: string[];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A detailed function declaration */
|
|
14
22
|
export interface ParsedFunction {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
id: string; // unique normalized ID (file::name)
|
|
24
|
+
name: string;
|
|
25
|
+
file: string;
|
|
26
|
+
moduleId?: string;
|
|
27
|
+
startLine: number;
|
|
28
|
+
endLine: number;
|
|
29
|
+
params: ParsedParam[];
|
|
30
|
+
returnType: string;
|
|
31
|
+
isExported: boolean;
|
|
32
|
+
isAsync: boolean;
|
|
33
|
+
isGenerator?: boolean;
|
|
34
|
+
typeParameters?: string[];
|
|
35
|
+
calls: CallExpression[]; // Behavioral tracking (Upgraded from string[])
|
|
36
|
+
hash: string;
|
|
37
|
+
purpose: string;
|
|
38
|
+
edgeCasesHandled: string[];
|
|
39
|
+
errorHandling: { line: number; type: 'try-catch' | 'throw'; detail: string }[];
|
|
40
|
+
detailedLines: { startLine: number; endLine: number; blockType: string }[];
|
|
33
41
|
}
|
|
34
42
|
|
|
35
43
|
/** A single import statement */
|
|
36
44
|
export interface ParsedImport {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
45
|
+
source: string;
|
|
46
|
+
resolvedPath: string;
|
|
47
|
+
names: string[];
|
|
48
|
+
isDefault: boolean;
|
|
49
|
+
isDynamic: boolean;
|
|
42
50
|
}
|
|
43
51
|
|
|
44
52
|
/** A single exported symbol */
|
|
45
53
|
export interface ParsedExport {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
54
|
+
name: string;
|
|
55
|
+
type: 'function' | 'class' | 'const' | 'type' | 'default' | 'interface' | 'variable';
|
|
56
|
+
file: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** A single variable or property */
|
|
60
|
+
export interface ParsedVariable {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
type: string;
|
|
64
|
+
file: string;
|
|
65
|
+
line: number;
|
|
66
|
+
isExported: boolean;
|
|
67
|
+
isStatic?: boolean;
|
|
68
|
+
purpose?: string;
|
|
49
69
|
}
|
|
50
70
|
|
|
51
71
|
/** A parsed class */
|
|
52
72
|
export interface ParsedClass {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
73
|
+
id: string;
|
|
74
|
+
name: string;
|
|
75
|
+
file: string;
|
|
76
|
+
moduleId?: string;
|
|
77
|
+
startLine: number;
|
|
78
|
+
endLine: number;
|
|
79
|
+
methods: ParsedFunction[];
|
|
80
|
+
properties: ParsedVariable[];
|
|
81
|
+
extends?: string;
|
|
82
|
+
implements?: string[];
|
|
83
|
+
isExported: boolean;
|
|
84
|
+
decorators?: string[];
|
|
85
|
+
typeParameters?: string[];
|
|
86
|
+
hash: string;
|
|
87
|
+
purpose?: string;
|
|
88
|
+
edgeCasesHandled?: string[];
|
|
89
|
+
errorHandling?: { line: number; type: 'try-catch' | 'throw'; detail: string }[];
|
|
66
90
|
}
|
|
67
91
|
|
|
68
|
-
/** A
|
|
69
|
-
export interface
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
92
|
+
/** A generic declaration (interface, type aliase, etc.) */
|
|
93
|
+
export interface ParsedGeneric {
|
|
94
|
+
id: string;
|
|
95
|
+
name: string;
|
|
96
|
+
type: string; // "interface" | "type"
|
|
97
|
+
file: string;
|
|
98
|
+
startLine: number;
|
|
99
|
+
endLine: number;
|
|
100
|
+
isExported: boolean;
|
|
101
|
+
typeParameters?: string[];
|
|
102
|
+
hash: string;
|
|
103
|
+
purpose?: string;
|
|
76
104
|
}
|
|
77
105
|
|
|
78
|
-
/** A
|
|
79
|
-
export interface
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
isExported: boolean
|
|
87
|
-
typeParameters?: string[] // ["T", "K"] for generic interfaces/types
|
|
88
|
-
purpose?: string
|
|
106
|
+
/** A detected HTTP route registration */
|
|
107
|
+
export interface ParsedRoute {
|
|
108
|
+
method: string;
|
|
109
|
+
path: string;
|
|
110
|
+
handler: string;
|
|
111
|
+
middlewares: string[];
|
|
112
|
+
file: string;
|
|
113
|
+
line: number;
|
|
89
114
|
}
|
|
90
115
|
|
|
91
116
|
/** Everything extracted from a single file */
|
|
92
117
|
export interface ParsedFile {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
118
|
+
path: string; // normalized absolute path
|
|
119
|
+
language: 'python' | 'go' | 'typescript' | 'javascript' | 'java' | 'c' | 'cpp' | 'csharp' | 'rust' | 'php' | 'ruby' | 'unknown';
|
|
120
|
+
functions: ParsedFunction[];
|
|
121
|
+
classes: ParsedClass[];
|
|
122
|
+
variables: ParsedVariable[];
|
|
123
|
+
generics: ParsedGeneric[];
|
|
124
|
+
imports: ParsedImport[];
|
|
125
|
+
exports: ParsedExport[];
|
|
126
|
+
routes: ParsedRoute[];
|
|
127
|
+
calls: CallExpression[]; // module-level calls
|
|
128
|
+
hash: string;
|
|
129
|
+
parsedAt: number;
|
|
103
130
|
}
|