@getmikk/core 2.0.0 → 2.0.11
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/CHANGELOG.md +37 -0
- package/package.json +39 -35
- package/src/graph/cluster-detector.ts +1 -1
- package/src/graph/confidence-engine.ts +41 -20
- package/src/graph/impact-analyzer.ts +21 -6
- package/src/parser/base-parser.ts +1 -1
- package/src/parser/go/go-extractor.ts +1 -1
- package/src/parser/go/go-parser.ts +1 -1
- package/src/parser/index.ts +74 -4
- package/src/parser/javascript/js-parser.ts +1 -1
- package/src/parser/oxc-parser.ts +10 -10
- package/src/parser/oxc-resolver.ts +50 -12
- package/src/parser/tree-sitter/parser.ts +380 -149
- package/src/parser/tree-sitter/queries.ts +102 -16
- package/src/parser/tree-sitter/resolver.ts +261 -0
- package/src/parser/typescript/ts-parser.ts +1 -1
- package/src/search/bm25.ts +16 -2
- package/src/utils/fs.ts +31 -1
- package/tests/esm-resolver.test.ts +6 -5
- package/tests/fixtures/python-service/src/auth.py +36 -0
- package/tests/go-parser.test.ts +1 -1
- package/tests/graph.test.ts +2 -1
- package/tests/js-parser.test.ts +445 -1
- package/tests/parser.test.ts +718 -184
- package/tests/tree-sitter-parser.test.ts +827 -130
- package/tests/ts-parser.test.ts +1 -1
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tree-sitter queries for extracting code definitions across 13 languages.
|
|
3
|
-
* Ported from GitNexus.
|
|
4
|
-
*
|
|
5
|
-
* NOTE: Some grammars (like Python and Ruby) use slightly different AST node
|
|
6
|
-
* types for equivalent structures. These queries are written against
|
|
7
|
-
* the standard tree-sitter grammars.
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
1
|
export const TYPESCRIPT_QUERIES = `
|
|
11
2
|
(class_declaration name: (type_identifier) @name) @definition.class
|
|
12
3
|
(interface_declaration name: (type_identifier) @name) @definition.interface
|
|
4
|
+
(class_declaration name: (type_identifier) @heritage.class (class_heritage (extends_clause value: (identifier) @heritage.extends))) @heritage
|
|
5
|
+
(class_declaration name: (type_identifier) @heritage.class (class_heritage (implements_clause (type_identifier) @heritage.implements))) @heritage.impl
|
|
13
6
|
(function_declaration name: (identifier) @name) @definition.function
|
|
14
7
|
(method_definition name: (property_identifier) @name) @definition.method
|
|
15
8
|
(lexical_declaration (variable_declarator name: (identifier) @name value: (arrow_function))) @definition.function
|
|
@@ -21,15 +14,16 @@ export const TYPESCRIPT_QUERIES = `
|
|
|
21
14
|
(call_expression function: (identifier) @call.name) @call
|
|
22
15
|
(call_expression function: (member_expression property: (property_identifier) @call.name)) @call
|
|
23
16
|
(new_expression constructor: (identifier) @call.name) @call
|
|
17
|
+
(new_expression constructor: (member_expression property: (property_identifier) @call.name)) @call
|
|
24
18
|
(public_field_definition name: (property_identifier) @name) @definition.property
|
|
25
19
|
(public_field_definition name: (private_property_identifier) @name) @definition.property
|
|
26
20
|
(required_parameter (accessibility_modifier) pattern: (identifier) @name) @definition.property
|
|
27
|
-
(
|
|
28
|
-
(class_declaration name: (type_identifier) @heritage.class (class_heritage (implements_clause (type_identifier) @heritage.implements))) @heritage.impl
|
|
21
|
+
(field_declaration name: (property_identifier) @name) @definition.property
|
|
29
22
|
`;
|
|
30
23
|
|
|
31
24
|
export const JAVASCRIPT_QUERIES = `
|
|
32
25
|
(class_declaration name: (identifier) @name) @definition.class
|
|
26
|
+
(class_declaration name: (identifier) @heritage.class (class_heritage (identifier) @heritage.extends)) @heritage
|
|
33
27
|
(function_declaration name: (identifier) @name) @definition.function
|
|
34
28
|
(method_definition name: (property_identifier) @name) @definition.method
|
|
35
29
|
(lexical_declaration (variable_declarator name: (identifier) @name value: (arrow_function))) @definition.function
|
|
@@ -42,11 +36,11 @@ export const JAVASCRIPT_QUERIES = `
|
|
|
42
36
|
(call_expression function: (member_expression property: (property_identifier) @call.name)) @call
|
|
43
37
|
(new_expression constructor: (identifier) @call.name) @call
|
|
44
38
|
(field_definition property: (property_identifier) @name) @definition.property
|
|
45
|
-
(class_declaration name: (identifier) @heritage.class (class_heritage (identifier) @heritage.extends)) @heritage
|
|
46
39
|
`;
|
|
47
40
|
|
|
48
41
|
export const PYTHON_QUERIES = `
|
|
49
42
|
(class_definition name: (identifier) @name) @definition.class
|
|
43
|
+
(class_definition name: (identifier) @heritage.class superclasses: (argument_list (identifier) @heritage.extends)) @heritage
|
|
50
44
|
(function_definition name: (identifier) @name) @definition.function
|
|
51
45
|
(import_statement name: (dotted_name) @import.source) @import
|
|
52
46
|
(import_from_statement module_name: (dotted_name) @import.source) @import
|
|
@@ -54,7 +48,6 @@ export const PYTHON_QUERIES = `
|
|
|
54
48
|
(call function: (identifier) @call.name) @call
|
|
55
49
|
(call function: (attribute attribute: (identifier) @call.name)) @call
|
|
56
50
|
(expression_statement (assignment left: (identifier) @name type: (type))) @definition.property
|
|
57
|
-
(class_definition name: (identifier) @heritage.class superclasses: (argument_list (identifier) @heritage.extends)) @heritage
|
|
58
51
|
`;
|
|
59
52
|
|
|
60
53
|
export const JAVA_QUERIES = `
|
|
@@ -111,8 +104,6 @@ export const CPP_QUERIES = `
|
|
|
111
104
|
(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function
|
|
112
105
|
(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method
|
|
113
106
|
(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function
|
|
114
|
-
(field_declaration declarator: (field_identifier) @name) @definition.property
|
|
115
|
-
(field_declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.method
|
|
116
107
|
(preproc_include path: (_) @import.source) @import
|
|
117
108
|
(call_expression function: (identifier) @call.name) @call
|
|
118
109
|
(call_expression function: (field_expression field: (field_identifier) @call.name)) @call
|
|
@@ -153,7 +144,6 @@ export const RUST_QUERIES = `
|
|
|
153
144
|
(call_expression function: (field_expression field: (field_identifier) @call.name)) @call
|
|
154
145
|
(call_expression function: (scoped_identifier name: (identifier) @call.name)) @call
|
|
155
146
|
(struct_expression name: (type_identifier) @call.name) @call
|
|
156
|
-
(field_declaration_list (field_declaration name: (field_identifier) @name)) @definition.property
|
|
157
147
|
`;
|
|
158
148
|
|
|
159
149
|
export const PHP_QUERIES = `
|
|
@@ -175,7 +165,103 @@ export const PHP_QUERIES = `
|
|
|
175
165
|
export const RUBY_QUERIES = `
|
|
176
166
|
(module name: (constant) @name) @definition.module
|
|
177
167
|
(class name: (constant) @name) @definition.class
|
|
168
|
+
(singleton_class value: (class name: (constant) @name)) @definition.class
|
|
178
169
|
(method name: (identifier) @name) @definition.method
|
|
179
170
|
(singleton_method name: (identifier) @name) @definition.method
|
|
180
171
|
(call method: (identifier) @call.name) @call
|
|
172
|
+
(call method: (call method: (identifier) @call.name)) @call
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
// Route detection queries - Express.js style (JS/TS)
|
|
176
|
+
export const EXPRESS_ROUTE_QUERIES = `
|
|
177
|
+
(call_expression
|
|
178
|
+
function: (member_expression
|
|
179
|
+
object: (call_expression
|
|
180
|
+
function: (identifier) @route.app)
|
|
181
|
+
property: (property_identifier) @route.method)
|
|
182
|
+
arguments: (argument_list
|
|
183
|
+
(string (string_content) @route.path)?)
|
|
184
|
+
) @route.def
|
|
185
|
+
(call_expression
|
|
186
|
+
function: (member_expression
|
|
187
|
+
object: (identifier) @route.app
|
|
188
|
+
property: (property_identifier) @route.method)
|
|
189
|
+
arguments: (argument_list
|
|
190
|
+
(string (string_content) @route.path)?)
|
|
191
|
+
) @route.def
|
|
192
|
+
`;
|
|
193
|
+
|
|
194
|
+
// Route detection queries - Flask/FastAPI style (Python)
|
|
195
|
+
export const FLASK_ROUTE_QUERIES = `
|
|
196
|
+
(decorated_definition
|
|
197
|
+
decorator: (call
|
|
198
|
+
function: (attribute
|
|
199
|
+
object: (identifier) @route.decorator)
|
|
200
|
+
arguments: (argument_list (string (string_content) @route.path)))
|
|
201
|
+
definition: (function_definition name: (identifier) @route.name))
|
|
202
|
+
) @route.def
|
|
203
|
+
|
|
204
|
+
(decorated_definition
|
|
205
|
+
decorator: (call
|
|
206
|
+
function: (identifier) @route.method
|
|
207
|
+
arguments: (argument_list (string (string_content) @route.path)))
|
|
208
|
+
definition: (function_definition name: (identifier) @route.name))
|
|
209
|
+
) @route.def
|
|
210
|
+
`;
|
|
211
|
+
|
|
212
|
+
// Route detection queries - Spring style (Java)
|
|
213
|
+
export const SPRING_ROUTE_QUERIES = `
|
|
214
|
+
(method_declaration
|
|
215
|
+
name: (identifier) @route.name
|
|
216
|
+
(annotation (name (identifier) @route.anno))
|
|
217
|
+
) @route.def
|
|
218
|
+
|
|
219
|
+
(class_declaration
|
|
220
|
+
name: (identifier) @route.name
|
|
221
|
+
(annotation (name (identifier) @route.anno))
|
|
222
|
+
) @route.def
|
|
223
|
+
`;
|
|
224
|
+
|
|
225
|
+
// Route detection queries - Gin style (Go)
|
|
226
|
+
export const GIN_ROUTE_QUERIES = `
|
|
227
|
+
(call_expression
|
|
228
|
+
function: (identifier) @route.method
|
|
229
|
+
arguments: (argument_list (string (string_content) @route.path)))
|
|
230
|
+
) @route.def
|
|
231
|
+
|
|
232
|
+
(call_expression
|
|
233
|
+
function: (call_expression
|
|
234
|
+
function: (identifier) @route.method)
|
|
235
|
+
arguments: (argument_list
|
|
236
|
+
(string (string_content) @route.path)?)
|
|
237
|
+
) @route.def
|
|
238
|
+
|
|
239
|
+
(call_expression
|
|
240
|
+
function: (selector_expression
|
|
241
|
+
field: (identifier) @route.method
|
|
242
|
+
object: (call_expression function: (identifier)))
|
|
243
|
+
arguments: (argument_list (string (string_content) @route.path)))
|
|
244
|
+
) @route.def
|
|
245
|
+
`;
|
|
246
|
+
|
|
247
|
+
// Route detection queries - Laravel style (PHP)
|
|
248
|
+
export const LARAVEL_ROUTE_QUERIES = `
|
|
249
|
+
(call_expression
|
|
250
|
+
function: (call_expression
|
|
251
|
+
function: (identifier) @route.method)
|
|
252
|
+
arguments: (argument_list (string (string_content) @route.path)))
|
|
253
|
+
) @route.def
|
|
254
|
+
|
|
255
|
+
(call_expression
|
|
256
|
+
function: (identifier) @route.method
|
|
257
|
+
arguments: (argument_list (string (string_content) @route.path)))
|
|
258
|
+
) @route.def
|
|
259
|
+
`;
|
|
260
|
+
|
|
261
|
+
// Route detection queries - Rails style (Ruby)
|
|
262
|
+
export const RAILS_ROUTE_QUERIES = `
|
|
263
|
+
(call
|
|
264
|
+
method: (identifier) @route.method
|
|
265
|
+
arguments: (argument_list (string (string_content) @route.path)))
|
|
266
|
+
) @route.def
|
|
181
267
|
`;
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import * as path from 'node:path'
|
|
2
|
+
import type { ParsedImport } from '../types.js'
|
|
3
|
+
|
|
4
|
+
export class TreeSitterResolver {
|
|
5
|
+
constructor(
|
|
6
|
+
private readonly projectRoot: string,
|
|
7
|
+
private readonly language: string,
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
resolve(imp: ParsedImport, fromFile: string, allProjectFiles: string[] = []): ParsedImport {
|
|
11
|
+
const source = imp.source
|
|
12
|
+
|
|
13
|
+
if (!this.isRelativeImport(source)) {
|
|
14
|
+
return { ...imp, resolvedPath: '' }
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const fileSet = allProjectFiles.length > 0 ? new Set(allProjectFiles.map(f => f.replace(/\\/g, '/'))) : null
|
|
18
|
+
const resolved = this.resolvePath(source, fromFile, fileSet)
|
|
19
|
+
|
|
20
|
+
return { ...imp, resolvedPath: resolved }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
resolveAll(imports: ParsedImport[], fromFile: string, allProjectFiles: string[] = []): ParsedImport[] {
|
|
24
|
+
const fileSet = allProjectFiles.length > 0
|
|
25
|
+
? new Set(allProjectFiles.map(f => f.replace(/\\/g, '/')))
|
|
26
|
+
: null
|
|
27
|
+
|
|
28
|
+
return imports.map(imp => {
|
|
29
|
+
if (!this.isRelativeImport(imp.source)) {
|
|
30
|
+
// For Java, also try resolving package imports if we have file set
|
|
31
|
+
if (this.language === 'java' && fileSet) {
|
|
32
|
+
const resolved = this.resolvePath(imp.source, fromFile, fileSet)
|
|
33
|
+
return { ...imp, resolvedPath: resolved }
|
|
34
|
+
}
|
|
35
|
+
return { ...imp, resolvedPath: '' }
|
|
36
|
+
}
|
|
37
|
+
const resolved = this.resolvePath(imp.source, fromFile, fileSet)
|
|
38
|
+
return { ...imp, resolvedPath: resolved }
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private isRelativeImport(source: string): boolean {
|
|
43
|
+
if (!source || source.trim() === '') return false
|
|
44
|
+
|
|
45
|
+
const trimmed = source.trim()
|
|
46
|
+
|
|
47
|
+
switch (this.language) {
|
|
48
|
+
case 'python':
|
|
49
|
+
return trimmed.startsWith('.') || trimmed.startsWith('/')
|
|
50
|
+
case 'java':
|
|
51
|
+
// For Java, both relative (.) and package imports can be resolved
|
|
52
|
+
return true
|
|
53
|
+
case 'c':
|
|
54
|
+
case 'cpp':
|
|
55
|
+
return trimmed.startsWith('"') || trimmed.startsWith('<')
|
|
56
|
+
case 'csharp':
|
|
57
|
+
return trimmed.startsWith('.')
|
|
58
|
+
case 'rust':
|
|
59
|
+
return trimmed.startsWith('crate::') || trimmed.startsWith('super::') || trimmed.startsWith('self::')
|
|
60
|
+
case 'php':
|
|
61
|
+
return trimmed.startsWith('\\') || trimmed.startsWith('..')
|
|
62
|
+
case 'ruby':
|
|
63
|
+
return trimmed.startsWith('./') || trimmed.startsWith('../')
|
|
64
|
+
case 'go':
|
|
65
|
+
return trimmed.startsWith('.') || trimmed.startsWith('/')
|
|
66
|
+
default:
|
|
67
|
+
return trimmed.startsWith('.') || trimmed.startsWith('/')
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private resolvePath(source: string, fromFile: string, fileSet: Set<string> | null): string {
|
|
72
|
+
const normalizedSource = this.normalizeSource(source)
|
|
73
|
+
const normalizedFrom = fromFile.replace(/\\/g, '/')
|
|
74
|
+
const baseDir = path.dirname(normalizedFrom)
|
|
75
|
+
|
|
76
|
+
let resolved: string
|
|
77
|
+
if (normalizedSource.startsWith('/')) {
|
|
78
|
+
resolved = normalizedSource.slice(1)
|
|
79
|
+
} else {
|
|
80
|
+
resolved = path.posix.normalize(path.posix.join(baseDir, normalizedSource))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return this.probeExtensions(resolved, fileSet) ?? ''
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private normalizeSource(source: string): string {
|
|
87
|
+
let normalized = source.trim()
|
|
88
|
+
|
|
89
|
+
if (!normalized) return ''
|
|
90
|
+
|
|
91
|
+
if (normalized.startsWith('"') && normalized.endsWith('"')) {
|
|
92
|
+
normalized = normalized.slice(1, -1)
|
|
93
|
+
}
|
|
94
|
+
if (normalized.startsWith('<') && normalized.endsWith('>')) {
|
|
95
|
+
normalized = normalized.slice(1, -1)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (normalized.startsWith('\\')) {
|
|
99
|
+
normalized = normalized.slice(1)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
normalized = normalized.replace(/^\.+/, '.')
|
|
103
|
+
|
|
104
|
+
if (normalized.startsWith('crate::')) {
|
|
105
|
+
normalized = normalized.replace('crate::', 'src/')
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (normalized.startsWith('super::')) {
|
|
109
|
+
normalized = normalized.replace('super::', '')
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (normalized.startsWith('self::')) {
|
|
113
|
+
normalized = normalized.replace('self::', '')
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
normalized = normalized.replace(/\\/g, '/')
|
|
117
|
+
|
|
118
|
+
normalized = this.handleLanguageSpecificImports(normalized)
|
|
119
|
+
|
|
120
|
+
return normalized
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private handleLanguageSpecificImports(normalized: string): string {
|
|
124
|
+
switch (this.language) {
|
|
125
|
+
case 'python':
|
|
126
|
+
// Handle relative imports: ./module, ../package, .
|
|
127
|
+
normalized = normalized.replace(/\./g, '/')
|
|
128
|
+
|
|
129
|
+
// Handle "from package import module" - module is the last part
|
|
130
|
+
// Convert to: package/module
|
|
131
|
+
if (normalized.includes('/')) {
|
|
132
|
+
const parts = normalized.split('/')
|
|
133
|
+
const last = parts[parts.length - 1]
|
|
134
|
+
// If last part doesn't look like a module, add __init__
|
|
135
|
+
if (last && !last.includes('.') && !last.includes('__init__')) {
|
|
136
|
+
// Check if it's a file pattern or directory
|
|
137
|
+
parts[parts.length - 1] = last
|
|
138
|
+
}
|
|
139
|
+
normalized = parts.join('/')
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Handle case where import is just a module name (no ./)
|
|
143
|
+
// e.g., "from os import path" -> os/path
|
|
144
|
+
break
|
|
145
|
+
|
|
146
|
+
case 'java':
|
|
147
|
+
// Convert package dots to path separators
|
|
148
|
+
// e.g., "java.util.List" -> "java/util/List"
|
|
149
|
+
normalized = normalized.replace(/\./g, '/')
|
|
150
|
+
break
|
|
151
|
+
|
|
152
|
+
case 'ruby':
|
|
153
|
+
normalized = normalized.replace(/::/g, '/')
|
|
154
|
+
if (normalized.startsWith('./')) {
|
|
155
|
+
normalized = normalized.slice(2)
|
|
156
|
+
}
|
|
157
|
+
if (normalized.startsWith('../')) {
|
|
158
|
+
normalized = normalized.replace('../', '')
|
|
159
|
+
}
|
|
160
|
+
break
|
|
161
|
+
|
|
162
|
+
case 'php':
|
|
163
|
+
normalized = normalized.replace(/\\/g, '/')
|
|
164
|
+
normalized = normalized.replace(/^App\//, '')
|
|
165
|
+
break
|
|
166
|
+
|
|
167
|
+
default:
|
|
168
|
+
break
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return normalized
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
private probeExtensions(resolved: string, fileSet: Set<string> | null): string | null {
|
|
175
|
+
const extensions = this.getProbeExtensions()
|
|
176
|
+
|
|
177
|
+
const directMatches = new Set<string>()
|
|
178
|
+
|
|
179
|
+
for (const ext of extensions) {
|
|
180
|
+
directMatches.add(resolved + ext)
|
|
181
|
+
|
|
182
|
+
const parts = resolved.split('/')
|
|
183
|
+
const lastIdx = parts.length - 1
|
|
184
|
+
const lastPart = parts[lastIdx]
|
|
185
|
+
|
|
186
|
+
if (lastPart && !lastPart.includes('.')) {
|
|
187
|
+
parts[lastIdx] = '__init__'
|
|
188
|
+
directMatches.add(parts.join('/') + ext)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (!resolved.includes('/index') && !resolved.endsWith('/')) {
|
|
192
|
+
directMatches.add(resolved + '/index' + ext)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (fileSet) {
|
|
197
|
+
for (const candidate of directMatches) {
|
|
198
|
+
if (fileSet.has(candidate)) {
|
|
199
|
+
return candidate
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const resolvedLower = resolved.toLowerCase()
|
|
204
|
+
for (const file of fileSet) {
|
|
205
|
+
if (file.toLowerCase() === resolvedLower + '.py' ||
|
|
206
|
+
file.toLowerCase() === resolvedLower + '.js' ||
|
|
207
|
+
file.toLowerCase() === resolvedLower + '.ts' ||
|
|
208
|
+
file.toLowerCase() === resolvedLower + '.java' ||
|
|
209
|
+
file.toLowerCase() === resolvedLower + '.go' ||
|
|
210
|
+
file.toLowerCase() === resolvedLower + '.rs' ||
|
|
211
|
+
file.toLowerCase() === resolvedLower + '.cs' ||
|
|
212
|
+
file.toLowerCase() === resolvedLower + '.c' ||
|
|
213
|
+
file.toLowerCase() === resolvedLower + '.cpp' ||
|
|
214
|
+
file.toLowerCase() === resolvedLower + '.php' ||
|
|
215
|
+
file.toLowerCase() === resolvedLower + '.rb') {
|
|
216
|
+
return file
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const resolvedEndsWith = resolved.split('/').pop()?.toLowerCase() || ''
|
|
221
|
+
for (const file of fileSet) {
|
|
222
|
+
const fileName = file.split('/').pop()?.toLowerCase() || ''
|
|
223
|
+
if (fileName.startsWith(resolvedEndsWith) ||
|
|
224
|
+
resolvedEndsWith.includes(fileName.replace(/\.[^.]+$/, ''))) {
|
|
225
|
+
return file
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (extensions.length > 0) {
|
|
231
|
+
return resolved + extensions[0]
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return null
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
private getProbeExtensions(): string[] {
|
|
238
|
+
switch (this.language) {
|
|
239
|
+
case 'python':
|
|
240
|
+
return ['.py', '/__init__.py']
|
|
241
|
+
case 'java':
|
|
242
|
+
return ['.java']
|
|
243
|
+
case 'c':
|
|
244
|
+
return ['.c', '.h']
|
|
245
|
+
case 'cpp':
|
|
246
|
+
return ['.cpp', '.cc', '.cxx', '.h', '.hpp', '.hh']
|
|
247
|
+
case 'csharp':
|
|
248
|
+
return ['.cs']
|
|
249
|
+
case 'rust':
|
|
250
|
+
return ['.rs', '/lib.rs', '/mod.rs']
|
|
251
|
+
case 'php':
|
|
252
|
+
return ['.php']
|
|
253
|
+
case 'ruby':
|
|
254
|
+
return ['.rb', '.rbw']
|
|
255
|
+
case 'go':
|
|
256
|
+
return ['.go']
|
|
257
|
+
default:
|
|
258
|
+
return ['']
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -24,7 +24,7 @@ export class TypeScriptParser extends BaseParser {
|
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
public resolveImports(files: ParsedFile[], projectRoot: string): ParsedFile[] {
|
|
27
|
+
public async resolveImports(files: ParsedFile[], projectRoot: string): Promise<ParsedFile[]> {
|
|
28
28
|
const resolver = new TypeScriptResolver(projectRoot)
|
|
29
29
|
return resolver.resolveBatch(files)
|
|
30
30
|
}
|
package/src/search/bm25.ts
CHANGED
|
@@ -97,8 +97,22 @@ export class BM25Index {
|
|
|
97
97
|
const tfNorm = (tf * (K1 + 1)) / (tf + K1 * (1 - B + B * (doc.length / this.avgDocLength)))
|
|
98
98
|
let termScore = idf * tfNorm
|
|
99
99
|
|
|
100
|
-
//
|
|
101
|
-
|
|
100
|
+
// Extract function name from ID for better matching
|
|
101
|
+
// ID format: fn:path:functionName
|
|
102
|
+
const fnNameInId = doc.id.includes(':')
|
|
103
|
+
? doc.id.split(':').pop()?.toLowerCase() ?? doc.id.toLowerCase()
|
|
104
|
+
: doc.id.toLowerCase()
|
|
105
|
+
|
|
106
|
+
// Strong bonus for name prefix match (login matches loginUser)
|
|
107
|
+
if (fnNameInId.startsWith(term.toLowerCase())) {
|
|
108
|
+
termScore += 2.0
|
|
109
|
+
}
|
|
110
|
+
// Bonus for name contains match
|
|
111
|
+
else if (fnNameInId.includes(term.toLowerCase())) {
|
|
112
|
+
termScore += 1.0
|
|
113
|
+
}
|
|
114
|
+
// Fallback: any ID match
|
|
115
|
+
else if (doc.id.toLowerCase().includes(term.toLowerCase())) {
|
|
102
116
|
termScore += 0.5
|
|
103
117
|
}
|
|
104
118
|
|
package/src/utils/fs.ts
CHANGED
|
@@ -275,7 +275,7 @@ function inferContextFileType(filePath: string): ContextFileType {
|
|
|
275
275
|
}
|
|
276
276
|
|
|
277
277
|
/** Recognised project language */
|
|
278
|
-
export type ProjectLanguage = 'typescript' | 'javascript' | 'python' | 'go' | 'rust' | 'java' | 'ruby' | 'php' | 'csharp' | 'unknown'
|
|
278
|
+
export type ProjectLanguage = 'typescript' | 'javascript' | 'python' | 'go' | 'rust' | 'java' | 'ruby' | 'php' | 'csharp' | 'c' | 'cpp' | 'unknown'
|
|
279
279
|
|
|
280
280
|
/** Auto-detect the project's primary language from manifest files */
|
|
281
281
|
export async function detectProjectLanguage(projectRoot: string): Promise<ProjectLanguage> {
|
|
@@ -295,6 +295,8 @@ export async function detectProjectLanguage(projectRoot: string): Promise<Projec
|
|
|
295
295
|
if (await exists('pom.xml') || await exists('build.gradle') || await exists('build.gradle.kts')) return 'java'
|
|
296
296
|
if (await exists('composer.json')) return 'php'
|
|
297
297
|
if (await hasGlob('*.csproj') || await hasGlob('*.sln')) return 'csharp'
|
|
298
|
+
if (await hasGlob('CMakeLists.txt') || await hasGlob('**/*.cmake') || await hasGlob('*.cpp')) return 'cpp'
|
|
299
|
+
if (await hasGlob('*.c') || await hasGlob('*.h')) return 'c'
|
|
298
300
|
if (await exists('package.json')) return 'javascript'
|
|
299
301
|
return 'unknown'
|
|
300
302
|
}
|
|
@@ -350,6 +352,16 @@ export function getDiscoveryPatterns(language: ProjectLanguage): { patterns: str
|
|
|
350
352
|
patterns: ['**/*.cs'],
|
|
351
353
|
ignore: [...commonIgnore, '**/bin/**', '**/obj/**'],
|
|
352
354
|
}
|
|
355
|
+
case 'cpp':
|
|
356
|
+
return {
|
|
357
|
+
patterns: ['**/*.cpp', '**/*.cc', '**/*.cxx', '**/*.hpp', '**/*.hxx', '**/*.h'],
|
|
358
|
+
ignore: [...commonIgnore, '**/build/**', '**/cmake-build-*/**'],
|
|
359
|
+
}
|
|
360
|
+
case 'c':
|
|
361
|
+
return {
|
|
362
|
+
patterns: ['**/*.c', '**/*.h'],
|
|
363
|
+
ignore: [...commonIgnore, '**/build/**'],
|
|
364
|
+
}
|
|
353
365
|
default:
|
|
354
366
|
// Fallback: discover JS/TS (most common)
|
|
355
367
|
return {
|
|
@@ -568,6 +580,24 @@ const LANGUAGE_IGNORE_TEMPLATES: Record<ProjectLanguage, string[]> = {
|
|
|
568
580
|
'obj/',
|
|
569
581
|
'',
|
|
570
582
|
],
|
|
583
|
+
c: [
|
|
584
|
+
'# Build artifacts',
|
|
585
|
+
'build/',
|
|
586
|
+
'obj/',
|
|
587
|
+
'*.o',
|
|
588
|
+
'*.a',
|
|
589
|
+
'',
|
|
590
|
+
],
|
|
591
|
+
cpp: [
|
|
592
|
+
'# Build artifacts',
|
|
593
|
+
'build/',
|
|
594
|
+
'cmake-build-*/',
|
|
595
|
+
'*.o',
|
|
596
|
+
'*.a',
|
|
597
|
+
'*.so',
|
|
598
|
+
'*.dll',
|
|
599
|
+
'',
|
|
600
|
+
],
|
|
571
601
|
unknown: [
|
|
572
602
|
'# Test files (add your patterns here)',
|
|
573
603
|
'tests/',
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
|
|
1
2
|
import { describe, it, expect, beforeAll, afterAll } from 'bun:test'
|
|
2
3
|
import * as path from 'node:path'
|
|
3
4
|
import * as fs from 'node:fs/promises'
|
|
@@ -49,19 +50,19 @@ describe('OxcResolver - ESM and CJS Resolution', () => {
|
|
|
49
50
|
await fs.rm(FIXTURE_DIR, { recursive: true, force: true })
|
|
50
51
|
})
|
|
51
52
|
|
|
52
|
-
it('resolves ESM exports correctly', () => {
|
|
53
|
+
it('resolves ESM exports correctly', async () => {
|
|
53
54
|
const resolver = new OxcResolver(FIXTURE_DIR)
|
|
54
55
|
|
|
55
56
|
// Resolve 'some-pkg' (should hit exports['.'].import)
|
|
56
|
-
const res = resolver.resolve('some-pkg', path.join(FIXTURE_DIR, 'index.ts'))
|
|
57
|
+
const res = await resolver.resolve('some-pkg', path.join(FIXTURE_DIR, 'index.ts'))
|
|
57
58
|
expect(res).toContain('node_modules/some-pkg/dist/esm/index.js')
|
|
58
59
|
})
|
|
59
60
|
|
|
60
|
-
it('resolves subpath exports correctly', () => {
|
|
61
|
+
it('resolves subpath exports correctly', async () => {
|
|
61
62
|
const resolver = new OxcResolver(FIXTURE_DIR)
|
|
62
63
|
|
|
63
64
|
// Resolve 'some-pkg/subpath'
|
|
64
|
-
const res = resolver.resolve('some-pkg/subpath', path.join(FIXTURE_DIR, 'index.ts'))
|
|
65
|
+
const res = await resolver.resolve('some-pkg/subpath', path.join(FIXTURE_DIR, 'index.ts'))
|
|
65
66
|
expect(res).toContain('node_modules/some-pkg/dist/sub.js')
|
|
66
67
|
})
|
|
67
68
|
|
|
@@ -69,7 +70,7 @@ describe('OxcResolver - ESM and CJS Resolution', () => {
|
|
|
69
70
|
const resolver = new OxcResolver(FIXTURE_DIR)
|
|
70
71
|
await fs.writeFile(path.join(FIXTURE_DIR, 'local.ts'), 'export const x = 1')
|
|
71
72
|
|
|
72
|
-
const res = resolver.resolve('./local', path.join(FIXTURE_DIR, 'index.ts'))
|
|
73
|
+
const res = await resolver.resolve('./local', path.join(FIXTURE_DIR, 'index.ts'))
|
|
73
74
|
expect(res).toContain('.test-fixture-esm/local.ts')
|
|
74
75
|
})
|
|
75
76
|
})
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Test module for Python parsing."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UserRepository:
|
|
9
|
+
"""Handles user data operations."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, db_path: str):
|
|
12
|
+
self.db_path = db_path
|
|
13
|
+
|
|
14
|
+
def find_by_id(self, user_id: int) -> Optional[dict]:
|
|
15
|
+
"""Find user by ID."""
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
def find_all(self) -> List[dict]:
|
|
19
|
+
"""Get all users."""
|
|
20
|
+
pass
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def authenticate(username: str, password: str) -> bool:
|
|
24
|
+
"""Authenticate user credentials."""
|
|
25
|
+
return True
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_user_profile(user_id: int) -> dict:
|
|
29
|
+
"""Get user profile data."""
|
|
30
|
+
repo = UserRepository("users.db")
|
|
31
|
+
return repo.find_by_id(user_id)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Private function - should NOT be exported
|
|
35
|
+
def _internal_helper():
|
|
36
|
+
pass
|
package/tests/go-parser.test.ts
CHANGED
|
@@ -360,7 +360,7 @@ describe('GoParser', () => {
|
|
|
360
360
|
const parser = new GoParser()
|
|
361
361
|
const files = [await parser.parse('utils/format.go', TOPLEVEL_GO)]
|
|
362
362
|
// Should not throw even without go.mod
|
|
363
|
-
const resolved = parser.resolveImports(files, '/tmp/no-gomod-' + Date.now())
|
|
363
|
+
const resolved = await parser.resolveImports(files, '/tmp/no-gomod-' + Date.now())
|
|
364
364
|
expect(resolved.length).toBe(1)
|
|
365
365
|
})
|
|
366
366
|
})
|
package/tests/graph.test.ts
CHANGED
|
@@ -118,7 +118,8 @@ describe('ImpactAnalyzer', () => {
|
|
|
118
118
|
])
|
|
119
119
|
const analyzer = new ImpactAnalyzer(graph)
|
|
120
120
|
const result = analyzer.analyze(['fn:src/b.ts:b'])
|
|
121
|
-
|
|
121
|
+
// With the fix: small paths (2 hops) should have high confidence
|
|
122
|
+
expect(result.confidence).toBeGreaterThanOrEqual(0.7)
|
|
122
123
|
})
|
|
123
124
|
|
|
124
125
|
it('does not include changed nodes in impacted', () => {
|