gitnexus 1.3.11 → 1.4.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/README.md +22 -2
- package/dist/cli/ai-context.d.ts +2 -1
- package/dist/cli/ai-context.js +15 -6
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +12 -2
- package/dist/cli/index.js +2 -0
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/core/graph/types.d.ts +5 -2
- package/dist/core/ingestion/call-processor.d.ts +5 -5
- package/dist/core/ingestion/call-processor.js +173 -260
- package/dist/core/ingestion/constants.d.ts +16 -0
- package/dist/core/ingestion/constants.js +16 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +2 -1
- package/dist/core/ingestion/entry-point-scoring.js +81 -22
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +230 -0
- package/dist/core/ingestion/framework-detection.d.ts +5 -1
- package/dist/core/ingestion/framework-detection.js +39 -8
- package/dist/core/ingestion/heritage-processor.d.ts +13 -4
- package/dist/core/ingestion/heritage-processor.js +92 -28
- package/dist/core/ingestion/import-processor.d.ts +17 -19
- package/dist/core/ingestion/import-processor.js +170 -695
- package/dist/core/ingestion/language-config.d.ts +46 -0
- package/dist/core/ingestion/language-config.js +167 -0
- package/dist/core/ingestion/mro-processor.d.ts +45 -0
- package/dist/core/ingestion/mro-processor.js +369 -0
- package/dist/core/ingestion/named-binding-extraction.d.ts +61 -0
- package/dist/core/ingestion/named-binding-extraction.js +363 -0
- package/dist/core/ingestion/parsing-processor.d.ts +1 -10
- package/dist/core/ingestion/parsing-processor.js +41 -177
- package/dist/core/ingestion/pipeline.js +26 -24
- package/dist/core/ingestion/process-processor.js +2 -1
- package/dist/core/ingestion/resolvers/csharp.d.ts +22 -0
- package/dist/core/ingestion/resolvers/csharp.js +109 -0
- package/dist/core/ingestion/resolvers/go.d.ts +19 -0
- package/dist/core/ingestion/resolvers/go.js +42 -0
- package/dist/core/ingestion/resolvers/index.d.ts +16 -0
- package/dist/core/ingestion/resolvers/index.js +11 -0
- package/dist/core/ingestion/resolvers/jvm.d.ts +23 -0
- package/dist/core/ingestion/resolvers/jvm.js +87 -0
- package/dist/core/ingestion/resolvers/php.d.ts +15 -0
- package/dist/core/ingestion/resolvers/php.js +35 -0
- package/dist/core/ingestion/resolvers/rust.d.ts +15 -0
- package/dist/core/ingestion/resolvers/rust.js +73 -0
- package/dist/core/ingestion/resolvers/standard.d.ts +28 -0
- package/dist/core/ingestion/resolvers/standard.js +145 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +120 -0
- package/dist/core/ingestion/symbol-resolver.d.ts +32 -0
- package/dist/core/ingestion/symbol-resolver.js +83 -0
- package/dist/core/ingestion/symbol-table.d.ts +12 -1
- package/dist/core/ingestion/symbol-table.js +19 -12
- package/dist/core/ingestion/tree-sitter-queries.d.ts +11 -11
- package/dist/core/ingestion/tree-sitter-queries.js +114 -9
- package/dist/core/ingestion/type-env.d.ts +27 -0
- package/dist/core/ingestion/type-env.js +86 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +60 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +89 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +105 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +21 -0
- package/dist/core/ingestion/type-extractors/index.js +29 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +121 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +31 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +41 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +39 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +17 -0
- package/dist/core/ingestion/type-extractors/shared.js +97 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +43 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +14 -0
- package/dist/core/ingestion/type-extractors/types.js +1 -0
- package/dist/core/ingestion/type-extractors/typescript.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/typescript.js +46 -0
- package/dist/core/ingestion/utils.d.ts +67 -0
- package/dist/core/ingestion/utils.js +691 -4
- package/dist/core/ingestion/workers/parse-worker.d.ts +20 -3
- package/dist/core/ingestion/workers/parse-worker.js +84 -345
- package/dist/core/kuzu/csv-generator.js +19 -3
- package/dist/core/kuzu/kuzu-adapter.js +3 -0
- package/dist/core/kuzu/schema.d.ts +3 -3
- package/dist/core/kuzu/schema.js +16 -1
- package/dist/mcp/tools.js +12 -3
- package/package.json +1 -1
|
@@ -1,44 +1,13 @@
|
|
|
1
|
+
import { isFileInPackageDir } from './import-processor.js';
|
|
2
|
+
import { resolveSymbolInternal } from './symbol-resolver.js';
|
|
3
|
+
import { walkBindingChain } from './named-binding-extraction.js';
|
|
1
4
|
import Parser from 'tree-sitter';
|
|
2
|
-
import { loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
5
|
+
import { isLanguageAvailable, loadParser, loadLanguage } from '../tree-sitter/parser-loader.js';
|
|
3
6
|
import { LANGUAGE_QUERIES } from './tree-sitter-queries.js';
|
|
4
7
|
import { generateId } from '../../lib/utils.js';
|
|
5
|
-
import { getLanguageFromFilename, yieldToEventLoop } from './utils.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
* Used to find the enclosing function for a call site.
|
|
9
|
-
*/
|
|
10
|
-
const FUNCTION_NODE_TYPES = new Set([
|
|
11
|
-
// TypeScript/JavaScript
|
|
12
|
-
'function_declaration',
|
|
13
|
-
'arrow_function',
|
|
14
|
-
'function_expression',
|
|
15
|
-
'method_definition',
|
|
16
|
-
'generator_function_declaration',
|
|
17
|
-
// Python
|
|
18
|
-
'function_definition',
|
|
19
|
-
// Common async variants
|
|
20
|
-
'async_function_declaration',
|
|
21
|
-
'async_arrow_function',
|
|
22
|
-
// Java
|
|
23
|
-
'method_declaration',
|
|
24
|
-
'constructor_declaration',
|
|
25
|
-
// C/C++
|
|
26
|
-
// 'function_definition' already included above
|
|
27
|
-
// Go
|
|
28
|
-
// 'method_declaration' already included from Java
|
|
29
|
-
// C#
|
|
30
|
-
'local_function_statement',
|
|
31
|
-
// Rust
|
|
32
|
-
'function_item',
|
|
33
|
-
'impl_item', // Methods inside impl blocks
|
|
34
|
-
// Kotlin (function_declaration already included above via JS/TS)
|
|
35
|
-
'anonymous_function',
|
|
36
|
-
'lambda_literal',
|
|
37
|
-
// PHP — no additional node types needed
|
|
38
|
-
// Swift
|
|
39
|
-
'init_declaration',
|
|
40
|
-
'deinit_declaration',
|
|
41
|
-
]);
|
|
8
|
+
import { getLanguageFromFilename, isVerboseIngestionEnabled, yieldToEventLoop, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, countCallArguments, inferCallForm, extractReceiverName, } from './utils.js';
|
|
9
|
+
import { buildTypeEnv, lookupTypeEnv } from './type-env.js';
|
|
10
|
+
import { getTreeSitterBufferSize } from './constants.js';
|
|
42
11
|
/**
|
|
43
12
|
* Walk up the AST from a node to find the enclosing function/method.
|
|
44
13
|
* Returns null if the call is at module/file level (top-level code).
|
|
@@ -47,88 +16,22 @@ const findEnclosingFunction = (node, filePath, symbolTable) => {
|
|
|
47
16
|
let current = node.parent;
|
|
48
17
|
while (current) {
|
|
49
18
|
if (FUNCTION_NODE_TYPES.has(current.type)) {
|
|
50
|
-
|
|
51
|
-
let funcName = null;
|
|
52
|
-
let label = 'Function';
|
|
53
|
-
// Different node types have different name locations
|
|
54
|
-
// Swift init/deinit — handle before generic cases (more specific)
|
|
55
|
-
if (current.type === 'init_declaration' || current.type === 'deinit_declaration') {
|
|
56
|
-
const funcName = current.type === 'init_declaration' ? 'init' : 'deinit';
|
|
57
|
-
return generateId('Constructor', `${filePath}:${funcName}`);
|
|
58
|
-
}
|
|
59
|
-
if (current.type === 'function_declaration' ||
|
|
60
|
-
current.type === 'function_definition' ||
|
|
61
|
-
current.type === 'async_function_declaration' ||
|
|
62
|
-
current.type === 'generator_function_declaration' ||
|
|
63
|
-
current.type === 'function_item') { // Rust function
|
|
64
|
-
// Named function: function foo() {}
|
|
65
|
-
const nameNode = current.childForFieldName?.('name') ||
|
|
66
|
-
current.children?.find((c) => c.type === 'identifier' || c.type === 'property_identifier');
|
|
67
|
-
funcName = nameNode?.text;
|
|
68
|
-
}
|
|
69
|
-
else if (current.type === 'impl_item') {
|
|
70
|
-
// Rust method inside impl block: wrapper around function_item or const_item
|
|
71
|
-
// We need to look inside for the function_item
|
|
72
|
-
const funcItem = current.children?.find((c) => c.type === 'function_item');
|
|
73
|
-
if (funcItem) {
|
|
74
|
-
const nameNode = funcItem.childForFieldName?.('name') ||
|
|
75
|
-
funcItem.children?.find((c) => c.type === 'identifier');
|
|
76
|
-
funcName = nameNode?.text;
|
|
77
|
-
label = 'Method';
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
else if (current.type === 'method_definition') {
|
|
81
|
-
// Method: foo() {} inside class (JS/TS)
|
|
82
|
-
const nameNode = current.childForFieldName?.('name') ||
|
|
83
|
-
current.children?.find((c) => c.type === 'property_identifier');
|
|
84
|
-
funcName = nameNode?.text;
|
|
85
|
-
label = 'Method';
|
|
86
|
-
}
|
|
87
|
-
else if (current.type === 'method_declaration') {
|
|
88
|
-
// Java method: public void foo() {}
|
|
89
|
-
const nameNode = current.childForFieldName?.('name') ||
|
|
90
|
-
current.children?.find((c) => c.type === 'identifier');
|
|
91
|
-
funcName = nameNode?.text;
|
|
92
|
-
label = 'Method';
|
|
93
|
-
}
|
|
94
|
-
else if (current.type === 'constructor_declaration') {
|
|
95
|
-
// Java constructor: public ClassName() {}
|
|
96
|
-
const nameNode = current.childForFieldName?.('name') ||
|
|
97
|
-
current.children?.find((c) => c.type === 'identifier');
|
|
98
|
-
funcName = nameNode?.text;
|
|
99
|
-
label = 'Method'; // Treat constructors as methods for process detection
|
|
100
|
-
}
|
|
101
|
-
else if (current.type === 'arrow_function' || current.type === 'function_expression') {
|
|
102
|
-
// Arrow/expression: const foo = () => {} - check parent variable declarator
|
|
103
|
-
const parent = current.parent;
|
|
104
|
-
if (parent?.type === 'variable_declarator') {
|
|
105
|
-
const nameNode = parent.childForFieldName?.('name') ||
|
|
106
|
-
parent.children?.find((c) => c.type === 'identifier');
|
|
107
|
-
funcName = nameNode?.text;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
19
|
+
const { funcName, label } = extractFunctionName(current);
|
|
110
20
|
if (funcName) {
|
|
111
|
-
// Look up the function in symbol table to get its node ID
|
|
112
|
-
// Try exact match first
|
|
113
21
|
const nodeId = symbolTable.lookupExact(filePath, funcName);
|
|
114
22
|
if (nodeId)
|
|
115
23
|
return nodeId;
|
|
116
|
-
|
|
117
|
-
// Format should match what parsing-processor generates: "Function:path/to/file:funcName"
|
|
118
|
-
// Check if we already have a node with this ID in the symbol table to be safe
|
|
119
|
-
const generatedId = generateId(label, `${filePath}:${funcName}`);
|
|
120
|
-
// Ideally we should verify this ID exists, but strictly speaking if we are inside it,
|
|
121
|
-
// it SHOULD exist. Returning it is better than falling back to File.
|
|
122
|
-
return generatedId;
|
|
24
|
+
return generateId(label, `${filePath}:${funcName}`);
|
|
123
25
|
}
|
|
124
|
-
// Couldn't determine function name - try parent (might be nested)
|
|
125
26
|
}
|
|
126
27
|
current = current.parent;
|
|
127
28
|
}
|
|
128
|
-
return null;
|
|
29
|
+
return null;
|
|
129
30
|
};
|
|
130
|
-
export const processCalls = async (graph, files, astCache, symbolTable, importMap, onProgress) => {
|
|
31
|
+
export const processCalls = async (graph, files, astCache, symbolTable, importMap, packageMap, onProgress, namedImportMap) => {
|
|
131
32
|
const parser = await loadParser();
|
|
33
|
+
const logSkipped = isVerboseIngestionEnabled();
|
|
34
|
+
const skippedByLang = logSkipped ? new Map() : null;
|
|
132
35
|
for (let i = 0; i < files.length; i++) {
|
|
133
36
|
const file = files[i];
|
|
134
37
|
onProgress?.(i + 1, files.length);
|
|
@@ -138,6 +41,12 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
|
|
|
138
41
|
const language = getLanguageFromFilename(file.path);
|
|
139
42
|
if (!language)
|
|
140
43
|
continue;
|
|
44
|
+
if (!isLanguageAvailable(language)) {
|
|
45
|
+
if (skippedByLang) {
|
|
46
|
+
skippedByLang.set(language, (skippedByLang.get(language) ?? 0) + 1);
|
|
47
|
+
}
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
141
50
|
const queryStr = LANGUAGE_QUERIES[language];
|
|
142
51
|
if (!queryStr)
|
|
143
52
|
continue;
|
|
@@ -150,7 +59,7 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
|
|
|
150
59
|
// Cache Miss: Re-parse
|
|
151
60
|
// Use larger bufferSize for files > 32KB
|
|
152
61
|
try {
|
|
153
|
-
tree = parser.parse(file.content, undefined, { bufferSize:
|
|
62
|
+
tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
|
|
154
63
|
}
|
|
155
64
|
catch (parseError) {
|
|
156
65
|
// Skip files that can't be parsed
|
|
@@ -171,6 +80,9 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
|
|
|
171
80
|
console.warn(`Query error for ${file.path}:`, queryError);
|
|
172
81
|
continue;
|
|
173
82
|
}
|
|
83
|
+
// Build per-file TypeEnv for receiver resolution
|
|
84
|
+
const lang = getLanguageFromFilename(file.path);
|
|
85
|
+
const typeEnv = lang ? buildTypeEnv(tree, lang) : new Map();
|
|
174
86
|
// 3. Process each call match
|
|
175
87
|
matches.forEach(match => {
|
|
176
88
|
const captureMap = {};
|
|
@@ -185,12 +97,20 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
|
|
|
185
97
|
// Skip common built-ins and noise
|
|
186
98
|
if (isBuiltInOrNoise(calledName))
|
|
187
99
|
return;
|
|
100
|
+
const callNode = captureMap['call'];
|
|
101
|
+
const callForm = inferCallForm(callNode, nameNode);
|
|
102
|
+
const receiverName = callForm === 'member' ? extractReceiverName(nameNode) : undefined;
|
|
103
|
+
const receiverTypeName = receiverName ? lookupTypeEnv(typeEnv, receiverName, callNode) : undefined;
|
|
188
104
|
// 4. Resolve the target using priority strategy (returns confidence)
|
|
189
|
-
const resolved = resolveCallTarget(
|
|
105
|
+
const resolved = resolveCallTarget({
|
|
106
|
+
calledName,
|
|
107
|
+
argCount: countCallArguments(callNode),
|
|
108
|
+
callForm,
|
|
109
|
+
receiverTypeName,
|
|
110
|
+
}, file.path, symbolTable, importMap, packageMap, namedImportMap);
|
|
190
111
|
if (!resolved)
|
|
191
112
|
return;
|
|
192
113
|
// 5. Find the enclosing function (caller)
|
|
193
|
-
const callNode = captureMap['call'];
|
|
194
114
|
const enclosingFuncId = findEnclosingFunction(callNode, file.path, symbolTable);
|
|
195
115
|
// Use enclosing function as source, fallback to file for top-level calls
|
|
196
116
|
const sourceId = enclosingFuncId || generateId('File', file.path);
|
|
@@ -206,149 +126,137 @@ export const processCalls = async (graph, files, astCache, symbolTable, importMa
|
|
|
206
126
|
});
|
|
207
127
|
// Tree is now owned by the LRU cache — no manual delete needed
|
|
208
128
|
}
|
|
129
|
+
if (skippedByLang && skippedByLang.size > 0) {
|
|
130
|
+
for (const [lang, count] of skippedByLang.entries()) {
|
|
131
|
+
console.warn(`[ingestion] Skipped ${count} ${lang} file(s) in call processing — ${lang} parser not available.`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const CALLABLE_SYMBOL_TYPES = new Set([
|
|
136
|
+
'Function',
|
|
137
|
+
'Method',
|
|
138
|
+
'Constructor',
|
|
139
|
+
'Macro',
|
|
140
|
+
'Delegate',
|
|
141
|
+
]);
|
|
142
|
+
const collectTieredCandidates = (calledName, currentFile, symbolTable, importMap, packageMap, namedImportMap) => {
|
|
143
|
+
const allDefs = symbolTable.lookupFuzzy(calledName);
|
|
144
|
+
// Tier 1: Same-file — highest priority, prevents imports from shadowing local defs
|
|
145
|
+
// (matches resolveSymbolInternal which checks lookupExactFull before named bindings)
|
|
146
|
+
const localDefs = allDefs.filter(def => def.filePath === currentFile);
|
|
147
|
+
if (localDefs.length > 0) {
|
|
148
|
+
return { candidates: localDefs, tier: 'same-file' };
|
|
149
|
+
}
|
|
150
|
+
// Tier 2a-named: Check named bindings with re-export chain following.
|
|
151
|
+
// Aliased imports (import { User as U }) mean lookupFuzzy('U') returns
|
|
152
|
+
// empty but we can resolve via the exported name.
|
|
153
|
+
// Re-exports (export { User } from './base') are followed up to 5 hops.
|
|
154
|
+
if (namedImportMap) {
|
|
155
|
+
const chainResult = resolveNamedBindingChainForCandidates(calledName, currentFile, symbolTable, namedImportMap, allDefs);
|
|
156
|
+
if (chainResult)
|
|
157
|
+
return chainResult;
|
|
158
|
+
}
|
|
159
|
+
if (allDefs.length === 0)
|
|
160
|
+
return null;
|
|
161
|
+
const importedFiles = importMap.get(currentFile);
|
|
162
|
+
if (importedFiles) {
|
|
163
|
+
const importedDefs = allDefs.filter(def => importedFiles.has(def.filePath));
|
|
164
|
+
if (importedDefs.length > 0) {
|
|
165
|
+
return { candidates: importedDefs, tier: 'import-scoped' };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
const importedPackages = packageMap?.get(currentFile);
|
|
169
|
+
if (importedPackages) {
|
|
170
|
+
const packageDefs = allDefs.filter(def => {
|
|
171
|
+
for (const dirSuffix of importedPackages) {
|
|
172
|
+
if (isFileInPackageDir(def.filePath, dirSuffix))
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
});
|
|
177
|
+
if (packageDefs.length > 0) {
|
|
178
|
+
return { candidates: packageDefs, tier: 'import-scoped' };
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// Tier 3: Global — pass all candidates through; filterCallableCandidates
|
|
182
|
+
// will narrow by kind/arity and resolveCallTarget only emits when exactly 1 remains.
|
|
183
|
+
return { candidates: allDefs, tier: 'unique-global' };
|
|
184
|
+
};
|
|
185
|
+
const CONSTRUCTOR_TARGET_TYPES = new Set(['Constructor', 'Class', 'Struct', 'Record']);
|
|
186
|
+
const filterCallableCandidates = (candidates, argCount, callForm) => {
|
|
187
|
+
let kindFiltered;
|
|
188
|
+
if (callForm === 'constructor') {
|
|
189
|
+
// For constructor calls, prefer Constructor > Class/Struct/Record > callable fallback
|
|
190
|
+
const constructors = candidates.filter(c => c.type === 'Constructor');
|
|
191
|
+
if (constructors.length > 0) {
|
|
192
|
+
kindFiltered = constructors;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
const types = candidates.filter(c => CONSTRUCTOR_TARGET_TYPES.has(c.type));
|
|
196
|
+
kindFiltered = types.length > 0 ? types : candidates.filter(c => CALLABLE_SYMBOL_TYPES.has(c.type));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
kindFiltered = candidates.filter(c => CALLABLE_SYMBOL_TYPES.has(c.type));
|
|
201
|
+
}
|
|
202
|
+
if (kindFiltered.length === 0)
|
|
203
|
+
return [];
|
|
204
|
+
if (argCount === undefined)
|
|
205
|
+
return kindFiltered;
|
|
206
|
+
const hasParameterMetadata = kindFiltered.some(candidate => candidate.parameterCount !== undefined);
|
|
207
|
+
if (!hasParameterMetadata)
|
|
208
|
+
return kindFiltered;
|
|
209
|
+
return kindFiltered.filter(candidate => candidate.parameterCount === undefined || candidate.parameterCount === argCount);
|
|
210
|
+
};
|
|
211
|
+
const toResolveResult = (definition, tier) => {
|
|
212
|
+
if (tier === 'same-file') {
|
|
213
|
+
return { nodeId: definition.nodeId, confidence: 0.95, reason: 'same-file' };
|
|
214
|
+
}
|
|
215
|
+
if (tier === 'import-scoped') {
|
|
216
|
+
return { nodeId: definition.nodeId, confidence: 0.9, reason: 'import-resolved' };
|
|
217
|
+
}
|
|
218
|
+
return { nodeId: definition.nodeId, confidence: 0.5, reason: 'unique-global' };
|
|
209
219
|
};
|
|
210
220
|
/**
|
|
211
221
|
* Resolve a function call to its target node ID using priority strategy:
|
|
212
|
-
* A.
|
|
213
|
-
* B.
|
|
214
|
-
* C.
|
|
222
|
+
* A. Narrow candidates by scope tier (same-file, import-scoped, unique-global)
|
|
223
|
+
* B. Filter to callable symbol kinds (constructor-aware when callForm is set)
|
|
224
|
+
* C. Apply arity filtering when parameter metadata is available
|
|
225
|
+
* D. Apply receiver-type filtering for member calls with typed receivers
|
|
215
226
|
*
|
|
216
|
-
*
|
|
227
|
+
* If filtering still leaves multiple candidates, refuse to emit a CALLS edge.
|
|
217
228
|
*/
|
|
218
|
-
const resolveCallTarget = (
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
//
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if (importedFiles.has(def.filePath)) {
|
|
233
|
-
return { nodeId: def.nodeId, confidence: 0.9, reason: 'import-resolved' };
|
|
234
|
-
}
|
|
229
|
+
const resolveCallTarget = (call, currentFile, symbolTable, importMap, packageMap, namedImportMap) => {
|
|
230
|
+
const tiered = collectTieredCandidates(call.calledName, currentFile, symbolTable, importMap, packageMap, namedImportMap);
|
|
231
|
+
if (!tiered)
|
|
232
|
+
return null;
|
|
233
|
+
const filteredCandidates = filterCallableCandidates(tiered.candidates, call.argCount, call.callForm);
|
|
234
|
+
// D. Receiver-type filtering: for member calls with a known receiver type,
|
|
235
|
+
// filter candidates by ownerId matching the resolved type's nodeId
|
|
236
|
+
if (call.callForm === 'member' && call.receiverTypeName && filteredCandidates.length > 1) {
|
|
237
|
+
const typeDefs = symbolTable.lookupFuzzy(call.receiverTypeName);
|
|
238
|
+
if (typeDefs.length > 0) {
|
|
239
|
+
const typeNodeIds = new Set(typeDefs.map(d => d.nodeId));
|
|
240
|
+
const ownerFiltered = filteredCandidates.filter(c => c.ownerId && typeNodeIds.has(c.ownerId));
|
|
241
|
+
if (ownerFiltered.length === 1) {
|
|
242
|
+
return toResolveResult(ownerFiltered[0], tiered.tier);
|
|
235
243
|
}
|
|
244
|
+
// If receiver filtering narrows to 0, fall through to name-only resolution
|
|
245
|
+
// If still 2+, refuse (don't guess)
|
|
246
|
+
if (ownerFiltered.length > 1)
|
|
247
|
+
return null;
|
|
236
248
|
}
|
|
237
|
-
// Strategy C: Fuzzy global (no import match found)
|
|
238
|
-
const confidence = allDefs.length === 1 ? 0.5 : 0.3;
|
|
239
|
-
return { nodeId: allDefs[0].nodeId, confidence, reason: 'fuzzy-global' };
|
|
240
249
|
}
|
|
241
|
-
|
|
250
|
+
if (filteredCandidates.length !== 1)
|
|
251
|
+
return null;
|
|
252
|
+
return toResolveResult(filteredCandidates[0], tiered.tier);
|
|
242
253
|
};
|
|
243
|
-
/**
|
|
244
|
-
* Filter out common built-in functions and noise
|
|
245
|
-
* that shouldn't be tracked as calls
|
|
246
|
-
*/
|
|
247
|
-
/** Pre-built set (module-level singleton) to avoid re-creating per call */
|
|
248
|
-
const BUILT_IN_NAMES = new Set([
|
|
249
|
-
// JavaScript/TypeScript built-ins
|
|
250
|
-
'console', 'log', 'warn', 'error', 'info', 'debug',
|
|
251
|
-
'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
|
252
|
-
'parseInt', 'parseFloat', 'isNaN', 'isFinite',
|
|
253
|
-
'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
|
|
254
|
-
'JSON', 'parse', 'stringify',
|
|
255
|
-
'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
|
|
256
|
-
'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
257
|
-
'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
|
|
258
|
-
'Math', 'Date', 'RegExp', 'Error',
|
|
259
|
-
'require', 'import', 'export',
|
|
260
|
-
'fetch', 'Response', 'Request',
|
|
261
|
-
// React hooks and common functions
|
|
262
|
-
'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
|
|
263
|
-
'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
|
|
264
|
-
'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
|
|
265
|
-
// Common array/object methods
|
|
266
|
-
'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
|
|
267
|
-
'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
|
|
268
|
-
'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
|
|
269
|
-
'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
|
|
270
|
-
'hasOwnProperty', 'toString', 'valueOf',
|
|
271
|
-
// Python built-ins
|
|
272
|
-
'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
|
|
273
|
-
'open', 'read', 'write', 'close', 'append', 'extend', 'update',
|
|
274
|
-
'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
|
|
275
|
-
'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
|
|
276
|
-
// Kotlin stdlib (IMPORTANT: keep in sync with parse-worker.ts BUILT_IN_NAMES)
|
|
277
|
-
'println', 'print', 'readLine', 'require', 'requireNotNull', 'check', 'assert', 'lazy', 'error',
|
|
278
|
-
'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf',
|
|
279
|
-
'arrayOf', 'sequenceOf', 'also', 'apply', 'run', 'with', 'takeIf', 'takeUnless',
|
|
280
|
-
'TODO', 'buildString', 'buildList', 'buildMap', 'buildSet',
|
|
281
|
-
'repeat', 'synchronized',
|
|
282
|
-
// Kotlin coroutine builders & scope functions
|
|
283
|
-
'launch', 'async', 'runBlocking', 'withContext', 'coroutineScope',
|
|
284
|
-
'supervisorScope', 'delay',
|
|
285
|
-
// Kotlin Flow operators
|
|
286
|
-
'flow', 'flowOf', 'collect', 'emit', 'onEach', 'catch',
|
|
287
|
-
'buffer', 'conflate', 'distinctUntilChanged',
|
|
288
|
-
'flatMapLatest', 'flatMapMerge', 'combine',
|
|
289
|
-
'stateIn', 'shareIn', 'launchIn',
|
|
290
|
-
// Kotlin infix stdlib functions
|
|
291
|
-
'to', 'until', 'downTo', 'step',
|
|
292
|
-
// C/C++ standard library and common kernel helpers
|
|
293
|
-
'printf', 'fprintf', 'sprintf', 'snprintf', 'vprintf', 'vfprintf', 'vsprintf', 'vsnprintf',
|
|
294
|
-
'scanf', 'fscanf', 'sscanf',
|
|
295
|
-
'malloc', 'calloc', 'realloc', 'free', 'memcpy', 'memmove', 'memset', 'memcmp',
|
|
296
|
-
'strlen', 'strcpy', 'strncpy', 'strcat', 'strncat', 'strcmp', 'strncmp', 'strstr', 'strchr', 'strrchr',
|
|
297
|
-
'atoi', 'atol', 'atof', 'strtol', 'strtoul', 'strtoll', 'strtoull', 'strtod',
|
|
298
|
-
'sizeof', 'offsetof', 'typeof',
|
|
299
|
-
'assert', 'abort', 'exit', '_exit',
|
|
300
|
-
'fopen', 'fclose', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind', 'fflush', 'fgets', 'fputs',
|
|
301
|
-
// Linux kernel common macros/helpers (not real call targets)
|
|
302
|
-
'likely', 'unlikely', 'BUG', 'BUG_ON', 'WARN', 'WARN_ON', 'WARN_ONCE',
|
|
303
|
-
'IS_ERR', 'PTR_ERR', 'ERR_PTR', 'IS_ERR_OR_NULL',
|
|
304
|
-
'ARRAY_SIZE', 'container_of', 'list_for_each_entry', 'list_for_each_entry_safe',
|
|
305
|
-
'min', 'max', 'clamp', 'abs', 'swap',
|
|
306
|
-
'pr_info', 'pr_warn', 'pr_err', 'pr_debug', 'pr_notice', 'pr_crit', 'pr_emerg',
|
|
307
|
-
'printk', 'dev_info', 'dev_warn', 'dev_err', 'dev_dbg',
|
|
308
|
-
'GFP_KERNEL', 'GFP_ATOMIC',
|
|
309
|
-
'spin_lock', 'spin_unlock', 'spin_lock_irqsave', 'spin_unlock_irqrestore',
|
|
310
|
-
'mutex_lock', 'mutex_unlock', 'mutex_init',
|
|
311
|
-
'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
|
|
312
|
-
'get', 'put',
|
|
313
|
-
// Swift/iOS built-ins and standard library
|
|
314
|
-
'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
|
|
315
|
-
'assert', 'assertionFailure', 'NSLog',
|
|
316
|
-
'abs', 'min', 'max', 'zip', 'stride', 'sequence', 'repeatElement',
|
|
317
|
-
'swap', 'withUnsafePointer', 'withUnsafeMutablePointer', 'withUnsafeBytes',
|
|
318
|
-
'autoreleasepool', 'unsafeBitCast', 'unsafeDowncast', 'numericCast',
|
|
319
|
-
'type', 'MemoryLayout',
|
|
320
|
-
// Swift collection/string methods (common noise)
|
|
321
|
-
'map', 'flatMap', 'compactMap', 'filter', 'reduce', 'forEach', 'contains',
|
|
322
|
-
'first', 'last', 'prefix', 'suffix', 'dropFirst', 'dropLast',
|
|
323
|
-
'sorted', 'reversed', 'enumerated', 'joined', 'split',
|
|
324
|
-
'append', 'insert', 'remove', 'removeAll', 'removeFirst', 'removeLast',
|
|
325
|
-
'isEmpty', 'count', 'index', 'startIndex', 'endIndex',
|
|
326
|
-
// UIKit/Foundation common methods (noise in call graph)
|
|
327
|
-
'addSubview', 'removeFromSuperview', 'layoutSubviews', 'setNeedsLayout',
|
|
328
|
-
'layoutIfNeeded', 'setNeedsDisplay', 'invalidateIntrinsicContentSize',
|
|
329
|
-
'addTarget', 'removeTarget', 'addGestureRecognizer',
|
|
330
|
-
'addConstraint', 'addConstraints', 'removeConstraint', 'removeConstraints',
|
|
331
|
-
'NSLocalizedString', 'Bundle',
|
|
332
|
-
'reloadData', 'reloadSections', 'reloadRows', 'performBatchUpdates',
|
|
333
|
-
'register', 'dequeueReusableCell', 'dequeueReusableSupplementaryView',
|
|
334
|
-
'beginUpdates', 'endUpdates', 'insertRows', 'deleteRows', 'insertSections', 'deleteSections',
|
|
335
|
-
'present', 'dismiss', 'pushViewController', 'popViewController', 'popToRootViewController',
|
|
336
|
-
'performSegue', 'prepare',
|
|
337
|
-
// GCD / async
|
|
338
|
-
'DispatchQueue', 'async', 'sync', 'asyncAfter',
|
|
339
|
-
'Task', 'withCheckedContinuation', 'withCheckedThrowingContinuation',
|
|
340
|
-
// Combine
|
|
341
|
-
'sink', 'store', 'assign', 'receive', 'subscribe',
|
|
342
|
-
// Notification / KVO
|
|
343
|
-
'addObserver', 'removeObserver', 'post', 'NotificationCenter',
|
|
344
|
-
]);
|
|
345
|
-
const isBuiltInOrNoise = (name) => BUILT_IN_NAMES.has(name);
|
|
346
254
|
/**
|
|
347
255
|
* Fast path: resolve pre-extracted call sites from workers.
|
|
348
256
|
* No AST parsing — workers already extracted calledName + sourceId.
|
|
349
257
|
* This function only does symbol table lookups + graph mutations.
|
|
350
258
|
*/
|
|
351
|
-
export const processCallsFromExtracted = async (graph, extractedCalls, symbolTable, importMap, onProgress) => {
|
|
259
|
+
export const processCallsFromExtracted = async (graph, extractedCalls, symbolTable, importMap, packageMap, onProgress, namedImportMap) => {
|
|
352
260
|
// Group by file for progress reporting
|
|
353
261
|
const byFile = new Map();
|
|
354
262
|
for (const call of extractedCalls) {
|
|
@@ -368,7 +276,7 @@ export const processCallsFromExtracted = async (graph, extractedCalls, symbolTab
|
|
|
368
276
|
await yieldToEventLoop();
|
|
369
277
|
}
|
|
370
278
|
for (const call of calls) {
|
|
371
|
-
const resolved = resolveCallTarget(call
|
|
279
|
+
const resolved = resolveCallTarget(call, call.filePath, symbolTable, importMap, packageMap, namedImportMap);
|
|
372
280
|
if (!resolved)
|
|
373
281
|
continue;
|
|
374
282
|
const relId = generateId('CALLS', `${call.sourceId}:${call.calledName}->${resolved.nodeId}`);
|
|
@@ -387,7 +295,7 @@ export const processCallsFromExtracted = async (graph, extractedCalls, symbolTab
|
|
|
387
295
|
/**
|
|
388
296
|
* Resolve pre-extracted Laravel routes to CALLS edges from route files to controller methods.
|
|
389
297
|
*/
|
|
390
|
-
export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolTable, importMap, onProgress) => {
|
|
298
|
+
export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolTable, importMap, packageMap, onProgress) => {
|
|
391
299
|
for (let i = 0; i < extractedRoutes.length; i++) {
|
|
392
300
|
const route = extractedRoutes[i];
|
|
393
301
|
if (i % 50 === 0) {
|
|
@@ -396,23 +304,16 @@ export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolT
|
|
|
396
304
|
}
|
|
397
305
|
if (!route.controllerName || !route.methodName)
|
|
398
306
|
continue;
|
|
399
|
-
// Resolve controller class
|
|
400
|
-
|
|
401
|
-
|
|
307
|
+
// Resolve controller class using shared resolver (Tier 1: same file,
|
|
308
|
+
// Tier 2: import-scoped, Tier 3: unique global).
|
|
309
|
+
const resolution = resolveSymbolInternal(route.controllerName, route.filePath, symbolTable, importMap, packageMap);
|
|
310
|
+
if (!resolution)
|
|
402
311
|
continue;
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
for (const def of controllerDefs) {
|
|
409
|
-
if (importedFiles.has(def.filePath)) {
|
|
410
|
-
controllerDef = def;
|
|
411
|
-
confidence = 0.9;
|
|
412
|
-
break;
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
312
|
+
const controllerDef = resolution.definition;
|
|
313
|
+
// Derive confidence from the resolution tier
|
|
314
|
+
const confidence = resolution.tier === 'same-file' ? 0.95
|
|
315
|
+
: resolution.tier === 'import-scoped' ? 0.9
|
|
316
|
+
: 0.7;
|
|
416
317
|
// Find the method on the controller
|
|
417
318
|
const methodId = symbolTable.lookupExact(controllerDef.filePath, route.methodName);
|
|
418
319
|
const sourceId = generateId('File', route.filePath);
|
|
@@ -442,3 +343,15 @@ export const processRoutesFromExtracted = async (graph, extractedRoutes, symbolT
|
|
|
442
343
|
}
|
|
443
344
|
onProgress?.(extractedRoutes.length, extractedRoutes.length);
|
|
444
345
|
};
|
|
346
|
+
/**
|
|
347
|
+
* Follow re-export chains through NamedImportMap for call candidate collection.
|
|
348
|
+
* Delegates chain-walking to the shared walkBindingChain utility, then
|
|
349
|
+
* applies call-processor semantics: any number of matches accepted.
|
|
350
|
+
*/
|
|
351
|
+
const resolveNamedBindingChainForCandidates = (calledName, currentFile, symbolTable, namedImportMap, allDefs) => {
|
|
352
|
+
const defs = walkBindingChain(calledName, currentFile, symbolTable, namedImportMap, allDefs);
|
|
353
|
+
if (defs && defs.length > 0) {
|
|
354
|
+
return { candidates: defs, tier: 'import-scoped' };
|
|
355
|
+
}
|
|
356
|
+
return null;
|
|
357
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default minimum buffer size for tree-sitter parsing (512 KB).
|
|
3
|
+
* tree-sitter requires bufferSize >= file size in bytes.
|
|
4
|
+
*/
|
|
5
|
+
export declare const TREE_SITTER_BUFFER_SIZE: number;
|
|
6
|
+
/**
|
|
7
|
+
* Maximum buffer size cap (32 MB) to prevent OOM on huge files.
|
|
8
|
+
* Also used as the file-size skip threshold — files larger than this are not parsed.
|
|
9
|
+
*/
|
|
10
|
+
export declare const TREE_SITTER_MAX_BUFFER: number;
|
|
11
|
+
/**
|
|
12
|
+
* Compute adaptive buffer size for tree-sitter parsing.
|
|
13
|
+
* Uses 2× file size, clamped between 512 KB and 32 MB.
|
|
14
|
+
* Previous 256 KB fixed limit silently skipped files > ~200 KB (e.g., imgui.h at 411 KB).
|
|
15
|
+
*/
|
|
16
|
+
export declare const getTreeSitterBufferSize: (contentLength: number) => number;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default minimum buffer size for tree-sitter parsing (512 KB).
|
|
3
|
+
* tree-sitter requires bufferSize >= file size in bytes.
|
|
4
|
+
*/
|
|
5
|
+
export const TREE_SITTER_BUFFER_SIZE = 512 * 1024;
|
|
6
|
+
/**
|
|
7
|
+
* Maximum buffer size cap (32 MB) to prevent OOM on huge files.
|
|
8
|
+
* Also used as the file-size skip threshold — files larger than this are not parsed.
|
|
9
|
+
*/
|
|
10
|
+
export const TREE_SITTER_MAX_BUFFER = 32 * 1024 * 1024;
|
|
11
|
+
/**
|
|
12
|
+
* Compute adaptive buffer size for tree-sitter parsing.
|
|
13
|
+
* Uses 2× file size, clamped between 512 KB and 32 MB.
|
|
14
|
+
* Previous 256 KB fixed limit silently skipped files > ~200 KB (e.g., imgui.h at 411 KB).
|
|
15
|
+
*/
|
|
16
|
+
export const getTreeSitterBufferSize = (contentLength) => Math.min(Math.max(contentLength * 2, TREE_SITTER_BUFFER_SIZE), TREE_SITTER_MAX_BUFFER);
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This module is language-agnostic - language-specific patterns are defined per language.
|
|
11
11
|
*/
|
|
12
|
+
import { SupportedLanguages } from '../../config/supported-languages.js';
|
|
12
13
|
export interface EntryPointScoreResult {
|
|
13
14
|
score: number;
|
|
14
15
|
reasons: string[];
|
|
@@ -26,7 +27,7 @@ export interface EntryPointScoreResult {
|
|
|
26
27
|
* @param calleeCount - Number of functions this function calls
|
|
27
28
|
* @returns Score and array of reasons explaining the score
|
|
28
29
|
*/
|
|
29
|
-
export declare function calculateEntryPointScore(name: string, language:
|
|
30
|
+
export declare function calculateEntryPointScore(name: string, language: SupportedLanguages, isExported: boolean, callerCount: number, calleeCount: number, filePath?: string): EntryPointScoreResult;
|
|
30
31
|
/**
|
|
31
32
|
* Check if a file path is a test file (should be excluded from entry points)
|
|
32
33
|
* Covers common test file patterns across all supported languages
|