gitnexus 1.4.1 → 1.4.5
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 +215 -194
- package/dist/cli/ai-context.d.ts +2 -1
- package/dist/cli/ai-context.js +117 -90
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +57 -30
- package/dist/cli/augment.js +1 -1
- package/dist/cli/eval-server.d.ts +1 -1
- package/dist/cli/eval-server.js +1 -1
- package/dist/cli/index.js +18 -25
- package/dist/cli/lazy-action.d.ts +6 -0
- package/dist/cli/lazy-action.js +18 -0
- package/dist/cli/mcp.js +1 -1
- package/dist/cli/setup.js +42 -32
- package/dist/cli/skill-gen.d.ts +26 -0
- package/dist/cli/skill-gen.js +549 -0
- package/dist/cli/status.js +13 -4
- package/dist/cli/tool.d.ts +1 -1
- package/dist/cli/tool.js +2 -2
- package/dist/cli/wiki.js +2 -2
- package/dist/config/ignore-service.d.ts +25 -0
- package/dist/config/ignore-service.js +76 -0
- package/dist/config/supported-languages.d.ts +1 -0
- package/dist/config/supported-languages.js +1 -1
- package/dist/core/augmentation/engine.js +99 -72
- package/dist/core/embeddings/embedder.d.ts +1 -1
- package/dist/core/embeddings/embedder.js +1 -1
- package/dist/core/embeddings/embedding-pipeline.d.ts +3 -3
- package/dist/core/embeddings/embedding-pipeline.js +74 -47
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/types.d.ts +5 -2
- package/dist/core/ingestion/ast-cache.js +3 -2
- package/dist/core/ingestion/call-processor.d.ts +6 -7
- package/dist/core/ingestion/call-processor.js +560 -282
- package/dist/core/ingestion/call-routing.d.ts +53 -0
- package/dist/core/ingestion/call-routing.js +108 -0
- package/dist/core/ingestion/cluster-enricher.js +16 -16
- 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 +94 -24
- package/dist/core/ingestion/export-detection.d.ts +18 -0
- package/dist/core/ingestion/export-detection.js +231 -0
- package/dist/core/ingestion/filesystem-walker.js +4 -3
- package/dist/core/ingestion/framework-detection.d.ts +5 -1
- package/dist/core/ingestion/framework-detection.js +48 -8
- package/dist/core/ingestion/heritage-processor.d.ts +13 -5
- package/dist/core/ingestion/heritage-processor.js +109 -55
- package/dist/core/ingestion/import-processor.d.ts +16 -20
- package/dist/core/ingestion/import-processor.js +202 -696
- 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 +3 -11
- package/dist/core/ingestion/parsing-processor.js +82 -181
- package/dist/core/ingestion/pipeline.d.ts +5 -1
- package/dist/core/ingestion/pipeline.js +192 -116
- package/dist/core/ingestion/process-processor.js +2 -1
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- 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 +18 -0
- package/dist/core/ingestion/resolvers/index.js +13 -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/python.d.ts +19 -0
- package/dist/core/ingestion/resolvers/python.js +52 -0
- package/dist/core/ingestion/resolvers/ruby.d.ts +12 -0
- package/dist/core/ingestion/resolvers/ruby.js +15 -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 +123 -0
- package/dist/core/ingestion/resolvers/utils.d.ts +33 -0
- package/dist/core/ingestion/resolvers/utils.js +122 -0
- package/dist/core/ingestion/symbol-table.d.ts +15 -1
- package/dist/core/ingestion/symbol-table.js +20 -12
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -11
- package/dist/core/ingestion/tree-sitter-queries.js +642 -485
- package/dist/core/ingestion/type-env.d.ts +49 -0
- package/dist/core/ingestion/type-env.js +559 -0
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +385 -0
- package/dist/core/ingestion/type-extractors/csharp.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/csharp.js +369 -0
- package/dist/core/ingestion/type-extractors/go.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/go.js +436 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +22 -0
- package/dist/core/ingestion/type-extractors/index.js +31 -0
- package/dist/core/ingestion/type-extractors/jvm.d.ts +3 -0
- package/dist/core/ingestion/type-extractors/jvm.js +654 -0
- package/dist/core/ingestion/type-extractors/php.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/php.js +411 -0
- package/dist/core/ingestion/type-extractors/python.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/python.js +392 -0
- package/dist/core/ingestion/type-extractors/ruby.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/ruby.js +389 -0
- package/dist/core/ingestion/type-extractors/rust.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/rust.js +436 -0
- package/dist/core/ingestion/type-extractors/shared.d.ts +132 -0
- package/dist/core/ingestion/type-extractors/shared.js +571 -0
- package/dist/core/ingestion/type-extractors/swift.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/swift.js +137 -0
- package/dist/core/ingestion/type-extractors/types.d.ts +95 -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 +480 -0
- package/dist/core/ingestion/utils.d.ts +98 -0
- package/dist/core/ingestion/utils.js +1064 -9
- package/dist/core/ingestion/workers/parse-worker.d.ts +38 -4
- package/dist/core/ingestion/workers/parse-worker.js +248 -359
- package/dist/core/ingestion/workers/worker-pool.js +8 -0
- package/dist/core/{kuzu → lbug}/csv-generator.d.ts +1 -1
- package/dist/core/{kuzu → lbug}/csv-generator.js +20 -4
- package/dist/core/{kuzu/kuzu-adapter.d.ts → lbug/lbug-adapter.d.ts} +19 -19
- package/dist/core/{kuzu/kuzu-adapter.js → lbug/lbug-adapter.js} +82 -82
- package/dist/core/{kuzu → lbug}/schema.d.ts +4 -4
- package/dist/core/{kuzu → lbug}/schema.js +304 -289
- package/dist/core/search/bm25-index.d.ts +4 -4
- package/dist/core/search/bm25-index.js +17 -16
- package/dist/core/search/hybrid-search.d.ts +2 -2
- package/dist/core/search/hybrid-search.js +9 -9
- package/dist/core/tree-sitter/parser-loader.js +9 -2
- package/dist/core/wiki/generator.d.ts +4 -52
- package/dist/core/wiki/generator.js +53 -552
- package/dist/core/wiki/graph-queries.d.ts +4 -46
- package/dist/core/wiki/graph-queries.js +103 -282
- package/dist/core/wiki/html-viewer.js +192 -192
- package/dist/core/wiki/llm-client.js +11 -73
- package/dist/core/wiki/prompts.d.ts +8 -52
- package/dist/core/wiki/prompts.js +86 -200
- package/dist/mcp/compatible-stdio-transport.d.ts +25 -0
- package/dist/mcp/compatible-stdio-transport.js +200 -0
- package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +7 -9
- package/dist/mcp/core/{kuzu-adapter.js → lbug-adapter.js} +77 -79
- package/dist/mcp/local/local-backend.d.ts +6 -6
- package/dist/mcp/local/local-backend.js +153 -146
- package/dist/mcp/resources.js +42 -42
- package/dist/mcp/server.js +18 -19
- package/dist/mcp/tools.js +103 -104
- package/dist/server/api.js +12 -12
- package/dist/server/mcp-http.d.ts +1 -1
- package/dist/server/mcp-http.js +1 -1
- package/dist/storage/repo-manager.d.ts +20 -2
- package/dist/storage/repo-manager.js +55 -1
- package/dist/types/pipeline.d.ts +1 -1
- package/hooks/claude/gitnexus-hook.cjs +238 -155
- package/hooks/claude/pre-tool-use.sh +79 -79
- package/hooks/claude/session-start.sh +42 -42
- package/package.json +98 -96
- package/scripts/patch-tree-sitter-swift.cjs +74 -74
- package/skills/gitnexus-cli.md +82 -82
- package/skills/gitnexus-debugging.md +89 -89
- package/skills/gitnexus-exploring.md +78 -78
- package/skills/gitnexus-guide.md +64 -64
- package/skills/gitnexus-impact-analysis.md +97 -97
- package/skills/gitnexus-pr-review.md +163 -163
- package/skills/gitnexus-refactoring.md +121 -121
- package/vendor/leiden/index.cjs +355 -355
- package/vendor/leiden/utils.cjs +392 -392
- package/dist/core/wiki/diagrams.d.ts +0 -27
- package/dist/core/wiki/diagrams.js +0 -163
|
@@ -9,11 +9,12 @@ import CPP from 'tree-sitter-cpp';
|
|
|
9
9
|
import CSharp from 'tree-sitter-c-sharp';
|
|
10
10
|
import Go from 'tree-sitter-go';
|
|
11
11
|
import Rust from 'tree-sitter-rust';
|
|
12
|
-
import Kotlin from 'tree-sitter-kotlin';
|
|
13
12
|
import PHP from 'tree-sitter-php';
|
|
13
|
+
import Ruby from 'tree-sitter-ruby';
|
|
14
14
|
import { createRequire } from 'node:module';
|
|
15
15
|
import { SupportedLanguages } from '../../../config/supported-languages.js';
|
|
16
16
|
import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js';
|
|
17
|
+
import { getTreeSitterBufferSize, TREE_SITTER_MAX_BUFFER } from '../constants.js';
|
|
17
18
|
// tree-sitter-swift is an optionalDependency — may not be installed
|
|
18
19
|
const _require = createRequire(import.meta.url);
|
|
19
20
|
let Swift = null;
|
|
@@ -21,9 +22,21 @@ try {
|
|
|
21
22
|
Swift = _require('tree-sitter-swift');
|
|
22
23
|
}
|
|
23
24
|
catch { }
|
|
24
|
-
|
|
25
|
+
// tree-sitter-kotlin is an optionalDependency — may not be installed
|
|
26
|
+
let Kotlin = null;
|
|
27
|
+
try {
|
|
28
|
+
Kotlin = _require('tree-sitter-kotlin');
|
|
29
|
+
}
|
|
30
|
+
catch { }
|
|
31
|
+
import { getLanguageFromFilename, FUNCTION_NODE_TYPES, extractFunctionName, isBuiltInOrNoise, getDefinitionNodeFromCaptures, findEnclosingClassId, extractMethodSignature, countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, CALL_EXPRESSION_TYPES, extractCallChain, } from '../utils.js';
|
|
32
|
+
import { buildTypeEnv } from '../type-env.js';
|
|
33
|
+
import { isNodeExported } from '../export-detection.js';
|
|
25
34
|
import { detectFrameworkFromAST } from '../framework-detection.js';
|
|
35
|
+
import { typeConfigs } from '../type-extractors/index.js';
|
|
26
36
|
import { generateId } from '../../../lib/utils.js';
|
|
37
|
+
import { extractNamedBindings } from '../named-binding-extraction.js';
|
|
38
|
+
import { appendKotlinWildcard } from '../resolvers/index.js';
|
|
39
|
+
import { callRouters } from '../call-routing.js';
|
|
27
40
|
// ============================================================================
|
|
28
41
|
// Worker-local parser + language map
|
|
29
42
|
// ============================================================================
|
|
@@ -39,10 +52,23 @@ const languageMap = {
|
|
|
39
52
|
[SupportedLanguages.CSharp]: CSharp,
|
|
40
53
|
[SupportedLanguages.Go]: Go,
|
|
41
54
|
[SupportedLanguages.Rust]: Rust,
|
|
42
|
-
[SupportedLanguages.Kotlin]: Kotlin,
|
|
55
|
+
...(Kotlin ? { [SupportedLanguages.Kotlin]: Kotlin } : {}),
|
|
43
56
|
[SupportedLanguages.PHP]: PHP.php_only,
|
|
57
|
+
[SupportedLanguages.Ruby]: Ruby,
|
|
44
58
|
...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
|
|
45
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* Check if a language grammar is available in this worker.
|
|
62
|
+
* Duplicated from parser-loader.ts because workers can't import from the main thread.
|
|
63
|
+
* Extra filePath parameter needed to distinguish .tsx from .ts (different grammars
|
|
64
|
+
* under the same SupportedLanguages.TypeScript key).
|
|
65
|
+
*/
|
|
66
|
+
const isLanguageAvailable = (language, filePath) => {
|
|
67
|
+
const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
|
|
68
|
+
? `${language}:tsx`
|
|
69
|
+
: language;
|
|
70
|
+
return key in languageMap && languageMap[key] != null;
|
|
71
|
+
};
|
|
46
72
|
const setLanguage = (language, filePath) => {
|
|
47
73
|
const key = language === SupportedLanguages.TypeScript && filePath.endsWith('.tsx')
|
|
48
74
|
? `${language}:tsx`
|
|
@@ -52,307 +78,24 @@ const setLanguage = (language, filePath) => {
|
|
|
52
78
|
throw new Error(`Unsupported language: ${language}`);
|
|
53
79
|
parser.setLanguage(lang);
|
|
54
80
|
};
|
|
55
|
-
//
|
|
56
|
-
// Export detection (copied — needs AST parent traversal, can't cross threads)
|
|
57
|
-
// ============================================================================
|
|
58
|
-
const isNodeExported = (node, name, language) => {
|
|
59
|
-
let current = node;
|
|
60
|
-
switch (language) {
|
|
61
|
-
case 'javascript':
|
|
62
|
-
case 'typescript':
|
|
63
|
-
while (current) {
|
|
64
|
-
const type = current.type;
|
|
65
|
-
if (type === 'export_statement' ||
|
|
66
|
-
type === 'export_specifier' ||
|
|
67
|
-
type === 'lexical_declaration' && current.parent?.type === 'export_statement') {
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
if (current.text?.startsWith('export ')) {
|
|
71
|
-
return true;
|
|
72
|
-
}
|
|
73
|
-
current = current.parent;
|
|
74
|
-
}
|
|
75
|
-
return false;
|
|
76
|
-
case 'python':
|
|
77
|
-
return !name.startsWith('_');
|
|
78
|
-
case 'java':
|
|
79
|
-
while (current) {
|
|
80
|
-
if (current.parent) {
|
|
81
|
-
const parent = current.parent;
|
|
82
|
-
for (let i = 0; i < parent.childCount; i++) {
|
|
83
|
-
const child = parent.child(i);
|
|
84
|
-
if (child?.type === 'modifiers' && child.text?.includes('public')) {
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
if (parent.type === 'method_declaration' || parent.type === 'constructor_declaration') {
|
|
89
|
-
if (parent.text?.trimStart().startsWith('public')) {
|
|
90
|
-
return true;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
current = current.parent;
|
|
95
|
-
}
|
|
96
|
-
return false;
|
|
97
|
-
case 'csharp':
|
|
98
|
-
while (current) {
|
|
99
|
-
if (current.type === 'modifier' || current.type === 'modifiers') {
|
|
100
|
-
if (current.text?.includes('public'))
|
|
101
|
-
return true;
|
|
102
|
-
}
|
|
103
|
-
current = current.parent;
|
|
104
|
-
}
|
|
105
|
-
return false;
|
|
106
|
-
case 'go':
|
|
107
|
-
if (name.length === 0)
|
|
108
|
-
return false;
|
|
109
|
-
const first = name[0];
|
|
110
|
-
return first === first.toUpperCase() && first !== first.toLowerCase();
|
|
111
|
-
case 'rust':
|
|
112
|
-
while (current) {
|
|
113
|
-
if (current.type === 'visibility_modifier') {
|
|
114
|
-
if (current.text?.includes('pub'))
|
|
115
|
-
return true;
|
|
116
|
-
}
|
|
117
|
-
current = current.parent;
|
|
118
|
-
}
|
|
119
|
-
return false;
|
|
120
|
-
// Kotlin: Default visibility is public (unlike Java)
|
|
121
|
-
// visibility_modifier is inside modifiers, a sibling of the name node within the declaration
|
|
122
|
-
case 'kotlin':
|
|
123
|
-
while (current) {
|
|
124
|
-
if (current.parent) {
|
|
125
|
-
const visMod = findSiblingChild(current.parent, 'modifiers', 'visibility_modifier');
|
|
126
|
-
if (visMod) {
|
|
127
|
-
const text = visMod.text;
|
|
128
|
-
if (text === 'private' || text === 'internal' || text === 'protected')
|
|
129
|
-
return false;
|
|
130
|
-
if (text === 'public')
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
current = current.parent;
|
|
135
|
-
}
|
|
136
|
-
// No visibility modifier = public (Kotlin default)
|
|
137
|
-
return true;
|
|
138
|
-
case 'c':
|
|
139
|
-
case 'cpp':
|
|
140
|
-
return false;
|
|
141
|
-
case 'php':
|
|
142
|
-
// Top-level classes/interfaces/traits are always accessible
|
|
143
|
-
// Methods/properties are exported only if they have 'public' modifier
|
|
144
|
-
while (current) {
|
|
145
|
-
if (current.type === 'class_declaration' ||
|
|
146
|
-
current.type === 'interface_declaration' ||
|
|
147
|
-
current.type === 'trait_declaration' ||
|
|
148
|
-
current.type === 'enum_declaration') {
|
|
149
|
-
return true;
|
|
150
|
-
}
|
|
151
|
-
if (current.type === 'visibility_modifier') {
|
|
152
|
-
return current.text === 'public';
|
|
153
|
-
}
|
|
154
|
-
current = current.parent;
|
|
155
|
-
}
|
|
156
|
-
// Top-level functions (no parent class) are globally accessible
|
|
157
|
-
return true;
|
|
158
|
-
case 'swift':
|
|
159
|
-
while (current) {
|
|
160
|
-
if (current.type === 'modifiers' || current.type === 'visibility_modifier') {
|
|
161
|
-
const text = current.text || '';
|
|
162
|
-
if (text.includes('public') || text.includes('open'))
|
|
163
|
-
return true;
|
|
164
|
-
}
|
|
165
|
-
current = current.parent;
|
|
166
|
-
}
|
|
167
|
-
return false;
|
|
168
|
-
default:
|
|
169
|
-
return false;
|
|
170
|
-
}
|
|
171
|
-
};
|
|
81
|
+
// isNodeExported imported from ../export-detection.js (shared module)
|
|
172
82
|
// ============================================================================
|
|
173
83
|
// Enclosing function detection (for call extraction)
|
|
174
84
|
// ============================================================================
|
|
175
|
-
const FUNCTION_NODE_TYPES = new Set([
|
|
176
|
-
'function_declaration', 'arrow_function', 'function_expression',
|
|
177
|
-
'method_definition', 'generator_function_declaration',
|
|
178
|
-
'function_definition', 'async_function_declaration', 'async_arrow_function',
|
|
179
|
-
'method_declaration', 'constructor_declaration',
|
|
180
|
-
'local_function_statement', 'function_item', 'impl_item',
|
|
181
|
-
// Kotlin
|
|
182
|
-
'lambda_literal',
|
|
183
|
-
// PHP
|
|
184
|
-
'anonymous_function',
|
|
185
|
-
// Swift initializers/deinitializers
|
|
186
|
-
'init_declaration', 'deinit_declaration',
|
|
187
|
-
]);
|
|
188
85
|
/** Walk up AST to find enclosing function, return its generateId or null for top-level */
|
|
189
86
|
const findEnclosingFunctionId = (node, filePath) => {
|
|
190
87
|
let current = node.parent;
|
|
191
88
|
while (current) {
|
|
192
89
|
if (FUNCTION_NODE_TYPES.has(current.type)) {
|
|
193
|
-
|
|
194
|
-
let label = 'Function';
|
|
195
|
-
if (current.type === 'init_declaration' || current.type === 'deinit_declaration') {
|
|
196
|
-
const funcName = current.type === 'init_declaration' ? 'init' : 'deinit';
|
|
197
|
-
const label = 'Constructor';
|
|
198
|
-
const startLine = current.startPosition?.row ?? 0;
|
|
199
|
-
return generateId(label, `${filePath}:${funcName}:${startLine}`);
|
|
200
|
-
}
|
|
201
|
-
if (['function_declaration', 'function_definition', 'async_function_declaration',
|
|
202
|
-
'generator_function_declaration', 'function_item'].includes(current.type)) {
|
|
203
|
-
const nameNode = current.childForFieldName?.('name') ||
|
|
204
|
-
current.children?.find((c) => c.type === 'identifier' || c.type === 'property_identifier');
|
|
205
|
-
funcName = nameNode?.text;
|
|
206
|
-
}
|
|
207
|
-
else if (current.type === 'impl_item') {
|
|
208
|
-
const funcItem = current.children?.find((c) => c.type === 'function_item');
|
|
209
|
-
if (funcItem) {
|
|
210
|
-
const nameNode = funcItem.childForFieldName?.('name') ||
|
|
211
|
-
funcItem.children?.find((c) => c.type === 'identifier');
|
|
212
|
-
funcName = nameNode?.text;
|
|
213
|
-
label = 'Method';
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
else if (current.type === 'method_definition') {
|
|
217
|
-
const nameNode = current.childForFieldName?.('name') ||
|
|
218
|
-
current.children?.find((c) => c.type === 'property_identifier');
|
|
219
|
-
funcName = nameNode?.text;
|
|
220
|
-
label = 'Method';
|
|
221
|
-
}
|
|
222
|
-
else if (current.type === 'method_declaration' || current.type === 'constructor_declaration') {
|
|
223
|
-
const nameNode = current.childForFieldName?.('name') ||
|
|
224
|
-
current.children?.find((c) => c.type === 'identifier');
|
|
225
|
-
funcName = nameNode?.text;
|
|
226
|
-
label = 'Method';
|
|
227
|
-
}
|
|
228
|
-
else if (current.type === 'arrow_function' || current.type === 'function_expression') {
|
|
229
|
-
const parent = current.parent;
|
|
230
|
-
if (parent?.type === 'variable_declarator') {
|
|
231
|
-
const nameNode = parent.childForFieldName?.('name') ||
|
|
232
|
-
parent.children?.find((c) => c.type === 'identifier');
|
|
233
|
-
funcName = nameNode?.text;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
90
|
+
const { funcName, label } = extractFunctionName(current);
|
|
236
91
|
if (funcName) {
|
|
237
|
-
|
|
238
|
-
return generateId(label, `${filePath}:${funcName}:${startLine}`);
|
|
92
|
+
return generateId(label, `${filePath}:${funcName}`);
|
|
239
93
|
}
|
|
240
94
|
}
|
|
241
95
|
current = current.parent;
|
|
242
96
|
}
|
|
243
97
|
return null;
|
|
244
98
|
};
|
|
245
|
-
const BUILT_INS = new Set([
|
|
246
|
-
// JavaScript/TypeScript
|
|
247
|
-
'console', 'log', 'warn', 'error', 'info', 'debug',
|
|
248
|
-
'setTimeout', 'setInterval', 'clearTimeout', 'clearInterval',
|
|
249
|
-
'parseInt', 'parseFloat', 'isNaN', 'isFinite',
|
|
250
|
-
'encodeURI', 'decodeURI', 'encodeURIComponent', 'decodeURIComponent',
|
|
251
|
-
'JSON', 'parse', 'stringify',
|
|
252
|
-
'Object', 'Array', 'String', 'Number', 'Boolean', 'Symbol', 'BigInt',
|
|
253
|
-
'Map', 'Set', 'WeakMap', 'WeakSet',
|
|
254
|
-
'Promise', 'resolve', 'reject', 'then', 'catch', 'finally',
|
|
255
|
-
'Math', 'Date', 'RegExp', 'Error',
|
|
256
|
-
'require', 'import', 'export', 'fetch', 'Response', 'Request',
|
|
257
|
-
'useState', 'useEffect', 'useCallback', 'useMemo', 'useRef', 'useContext',
|
|
258
|
-
'useReducer', 'useLayoutEffect', 'useImperativeHandle', 'useDebugValue',
|
|
259
|
-
'createElement', 'createContext', 'createRef', 'forwardRef', 'memo', 'lazy',
|
|
260
|
-
'map', 'filter', 'reduce', 'forEach', 'find', 'findIndex', 'some', 'every',
|
|
261
|
-
'includes', 'indexOf', 'slice', 'splice', 'concat', 'join', 'split',
|
|
262
|
-
'push', 'pop', 'shift', 'unshift', 'sort', 'reverse',
|
|
263
|
-
'keys', 'values', 'entries', 'assign', 'freeze', 'seal',
|
|
264
|
-
'hasOwnProperty', 'toString', 'valueOf',
|
|
265
|
-
// Python
|
|
266
|
-
'print', 'len', 'range', 'str', 'int', 'float', 'list', 'dict', 'set', 'tuple',
|
|
267
|
-
'open', 'read', 'write', 'close', 'append', 'extend', 'update',
|
|
268
|
-
'super', 'type', 'isinstance', 'issubclass', 'getattr', 'setattr', 'hasattr',
|
|
269
|
-
'enumerate', 'zip', 'sorted', 'reversed', 'min', 'max', 'sum', 'abs',
|
|
270
|
-
// Kotlin stdlib (IMPORTANT: keep in sync with call-processor.ts BUILT_IN_NAMES)
|
|
271
|
-
'println', 'print', 'readLine', 'require', 'requireNotNull', 'check', 'assert', 'lazy', 'error',
|
|
272
|
-
'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf',
|
|
273
|
-
'arrayOf', 'sequenceOf', 'also', 'apply', 'run', 'with', 'takeIf', 'takeUnless',
|
|
274
|
-
'TODO', 'buildString', 'buildList', 'buildMap', 'buildSet',
|
|
275
|
-
'repeat', 'synchronized',
|
|
276
|
-
// Kotlin coroutine builders & scope functions
|
|
277
|
-
'launch', 'async', 'runBlocking', 'withContext', 'coroutineScope',
|
|
278
|
-
'supervisorScope', 'delay',
|
|
279
|
-
// Kotlin Flow operators
|
|
280
|
-
'flow', 'flowOf', 'collect', 'emit', 'onEach', 'catch',
|
|
281
|
-
'buffer', 'conflate', 'distinctUntilChanged',
|
|
282
|
-
'flatMapLatest', 'flatMapMerge', 'combine',
|
|
283
|
-
'stateIn', 'shareIn', 'launchIn',
|
|
284
|
-
// Kotlin infix stdlib functions
|
|
285
|
-
'to', 'until', 'downTo', 'step',
|
|
286
|
-
// C/C++ standard library
|
|
287
|
-
'printf', 'fprintf', 'sprintf', 'snprintf', 'vprintf', 'vfprintf', 'vsprintf', 'vsnprintf',
|
|
288
|
-
'scanf', 'fscanf', 'sscanf',
|
|
289
|
-
'malloc', 'calloc', 'realloc', 'free', 'memcpy', 'memmove', 'memset', 'memcmp',
|
|
290
|
-
'strlen', 'strcpy', 'strncpy', 'strcat', 'strncat', 'strcmp', 'strncmp', 'strstr', 'strchr', 'strrchr',
|
|
291
|
-
'atoi', 'atol', 'atof', 'strtol', 'strtoul', 'strtoll', 'strtoull', 'strtod',
|
|
292
|
-
'sizeof', 'offsetof', 'typeof',
|
|
293
|
-
'assert', 'abort', 'exit', '_exit',
|
|
294
|
-
'fopen', 'fclose', 'fread', 'fwrite', 'fseek', 'ftell', 'rewind', 'fflush', 'fgets', 'fputs',
|
|
295
|
-
// Linux kernel common macros/helpers (not real call targets)
|
|
296
|
-
'likely', 'unlikely', 'BUG', 'BUG_ON', 'WARN', 'WARN_ON', 'WARN_ONCE',
|
|
297
|
-
'IS_ERR', 'PTR_ERR', 'ERR_PTR', 'IS_ERR_OR_NULL',
|
|
298
|
-
'ARRAY_SIZE', 'container_of', 'list_for_each_entry', 'list_for_each_entry_safe',
|
|
299
|
-
'min', 'max', 'clamp', 'abs', 'swap',
|
|
300
|
-
'pr_info', 'pr_warn', 'pr_err', 'pr_debug', 'pr_notice', 'pr_crit', 'pr_emerg',
|
|
301
|
-
'printk', 'dev_info', 'dev_warn', 'dev_err', 'dev_dbg',
|
|
302
|
-
'GFP_KERNEL', 'GFP_ATOMIC',
|
|
303
|
-
'spin_lock', 'spin_unlock', 'spin_lock_irqsave', 'spin_unlock_irqrestore',
|
|
304
|
-
'mutex_lock', 'mutex_unlock', 'mutex_init',
|
|
305
|
-
'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree',
|
|
306
|
-
'get', 'put',
|
|
307
|
-
// PHP built-ins
|
|
308
|
-
'echo', 'isset', 'empty', 'unset', 'list', 'array', 'compact', 'extract',
|
|
309
|
-
'count', 'strlen', 'strpos', 'strrpos', 'substr', 'strtolower', 'strtoupper', 'trim',
|
|
310
|
-
'ltrim', 'rtrim', 'str_replace', 'str_contains', 'str_starts_with', 'str_ends_with',
|
|
311
|
-
'sprintf', 'vsprintf', 'printf', 'number_format',
|
|
312
|
-
'array_map', 'array_filter', 'array_reduce', 'array_push', 'array_pop', 'array_shift',
|
|
313
|
-
'array_unshift', 'array_slice', 'array_splice', 'array_merge', 'array_keys', 'array_values',
|
|
314
|
-
'array_key_exists', 'in_array', 'array_search', 'array_unique', 'usort', 'rsort',
|
|
315
|
-
'json_encode', 'json_decode', 'serialize', 'unserialize',
|
|
316
|
-
'intval', 'floatval', 'strval', 'boolval', 'is_null', 'is_string', 'is_int', 'is_array',
|
|
317
|
-
'is_object', 'is_numeric', 'is_bool', 'is_float',
|
|
318
|
-
'var_dump', 'print_r', 'var_export',
|
|
319
|
-
'date', 'time', 'strtotime', 'mktime', 'microtime',
|
|
320
|
-
'file_exists', 'file_get_contents', 'file_put_contents', 'is_file', 'is_dir',
|
|
321
|
-
'preg_match', 'preg_match_all', 'preg_replace', 'preg_split',
|
|
322
|
-
'header', 'session_start', 'session_destroy', 'ob_start', 'ob_end_clean', 'ob_get_clean',
|
|
323
|
-
'dd', 'dump',
|
|
324
|
-
// Swift/iOS built-ins and standard library
|
|
325
|
-
'print', 'debugPrint', 'dump', 'fatalError', 'precondition', 'preconditionFailure',
|
|
326
|
-
'assert', 'assertionFailure', 'NSLog',
|
|
327
|
-
'abs', 'min', 'max', 'zip', 'stride', 'sequence', 'repeatElement',
|
|
328
|
-
'swap', 'withUnsafePointer', 'withUnsafeMutablePointer', 'withUnsafeBytes',
|
|
329
|
-
'autoreleasepool', 'unsafeBitCast', 'unsafeDowncast', 'numericCast',
|
|
330
|
-
'type', 'MemoryLayout',
|
|
331
|
-
// Swift collection/string methods (common noise)
|
|
332
|
-
'map', 'flatMap', 'compactMap', 'filter', 'reduce', 'forEach', 'contains',
|
|
333
|
-
'first', 'last', 'prefix', 'suffix', 'dropFirst', 'dropLast',
|
|
334
|
-
'sorted', 'reversed', 'enumerated', 'joined', 'split',
|
|
335
|
-
'append', 'insert', 'remove', 'removeAll', 'removeFirst', 'removeLast',
|
|
336
|
-
'isEmpty', 'count', 'index', 'startIndex', 'endIndex',
|
|
337
|
-
// UIKit/Foundation common methods (noise in call graph)
|
|
338
|
-
'addSubview', 'removeFromSuperview', 'layoutSubviews', 'setNeedsLayout',
|
|
339
|
-
'layoutIfNeeded', 'setNeedsDisplay', 'invalidateIntrinsicContentSize',
|
|
340
|
-
'addTarget', 'removeTarget', 'addGestureRecognizer',
|
|
341
|
-
'addConstraint', 'addConstraints', 'removeConstraint', 'removeConstraints',
|
|
342
|
-
'NSLocalizedString', 'Bundle',
|
|
343
|
-
'reloadData', 'reloadSections', 'reloadRows', 'performBatchUpdates',
|
|
344
|
-
'register', 'dequeueReusableCell', 'dequeueReusableSupplementaryView',
|
|
345
|
-
'beginUpdates', 'endUpdates', 'insertRows', 'deleteRows', 'insertSections', 'deleteSections',
|
|
346
|
-
'present', 'dismiss', 'pushViewController', 'popViewController', 'popToRootViewController',
|
|
347
|
-
'performSegue', 'prepare',
|
|
348
|
-
// GCD / async
|
|
349
|
-
'DispatchQueue', 'async', 'sync', 'asyncAfter',
|
|
350
|
-
'Task', 'withCheckedContinuation', 'withCheckedThrowingContinuation',
|
|
351
|
-
// Combine
|
|
352
|
-
'sink', 'store', 'assign', 'receive', 'subscribe',
|
|
353
|
-
// Notification / KVO
|
|
354
|
-
'addObserver', 'removeObserver', 'post', 'NotificationCenter',
|
|
355
|
-
]);
|
|
356
99
|
// ============================================================================
|
|
357
100
|
// Label detection from capture map
|
|
358
101
|
// ============================================================================
|
|
@@ -408,49 +151,7 @@ const getLabelFromCaptures = (captureMap) => {
|
|
|
408
151
|
return 'Template';
|
|
409
152
|
return 'CodeElement';
|
|
410
153
|
};
|
|
411
|
-
|
|
412
|
-
'definition.function',
|
|
413
|
-
'definition.class',
|
|
414
|
-
'definition.interface',
|
|
415
|
-
'definition.method',
|
|
416
|
-
'definition.struct',
|
|
417
|
-
'definition.enum',
|
|
418
|
-
'definition.namespace',
|
|
419
|
-
'definition.module',
|
|
420
|
-
'definition.trait',
|
|
421
|
-
'definition.impl',
|
|
422
|
-
'definition.type',
|
|
423
|
-
'definition.const',
|
|
424
|
-
'definition.static',
|
|
425
|
-
'definition.typedef',
|
|
426
|
-
'definition.macro',
|
|
427
|
-
'definition.union',
|
|
428
|
-
'definition.property',
|
|
429
|
-
'definition.record',
|
|
430
|
-
'definition.delegate',
|
|
431
|
-
'definition.annotation',
|
|
432
|
-
'definition.constructor',
|
|
433
|
-
'definition.template',
|
|
434
|
-
];
|
|
435
|
-
const getDefinitionNodeFromCaptures = (captureMap) => {
|
|
436
|
-
for (const key of DEFINITION_CAPTURE_KEYS) {
|
|
437
|
-
if (captureMap[key])
|
|
438
|
-
return captureMap[key];
|
|
439
|
-
}
|
|
440
|
-
return null;
|
|
441
|
-
};
|
|
442
|
-
/**
|
|
443
|
-
* Append .* to a Kotlin import path if the AST has a wildcard_import sibling node.
|
|
444
|
-
* Pure function — returns a new string without mutating the input.
|
|
445
|
-
*/
|
|
446
|
-
const appendKotlinWildcard = (importPath, importNode) => {
|
|
447
|
-
for (let i = 0; i < importNode.childCount; i++) {
|
|
448
|
-
if (importNode.child(i)?.type === 'wildcard_import') {
|
|
449
|
-
return importPath.endsWith('.*') ? importPath : `${importPath}.*`;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
return importPath;
|
|
453
|
-
};
|
|
154
|
+
// DEFINITION_CAPTURE_KEYS and getDefinitionNodeFromCaptures imported from ../utils.js
|
|
454
155
|
// ============================================================================
|
|
455
156
|
// Process a batch of files
|
|
456
157
|
// ============================================================================
|
|
@@ -463,6 +164,8 @@ const processBatch = (files, onProgress) => {
|
|
|
463
164
|
calls: [],
|
|
464
165
|
heritage: [],
|
|
465
166
|
routes: [],
|
|
167
|
+
constructorBindings: [],
|
|
168
|
+
skippedLanguages: {},
|
|
466
169
|
fileCount: 0,
|
|
467
170
|
};
|
|
468
171
|
// Group by language to minimize setLanguage calls
|
|
@@ -510,22 +213,32 @@ const processBatch = (files, onProgress) => {
|
|
|
510
213
|
}
|
|
511
214
|
// Process regular files for this language
|
|
512
215
|
if (regularFiles.length > 0) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
216
|
+
if (isLanguageAvailable(language, regularFiles[0].path)) {
|
|
217
|
+
try {
|
|
218
|
+
setLanguage(language, regularFiles[0].path);
|
|
219
|
+
processFileGroup(regularFiles, language, queryString, result, onFileProcessed);
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// parser unavailable — skip this language group
|
|
223
|
+
}
|
|
516
224
|
}
|
|
517
|
-
|
|
518
|
-
|
|
225
|
+
else {
|
|
226
|
+
result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + regularFiles.length;
|
|
519
227
|
}
|
|
520
228
|
}
|
|
521
229
|
// Process tsx files separately (different grammar)
|
|
522
230
|
if (tsxFiles.length > 0) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
231
|
+
if (isLanguageAvailable(language, tsxFiles[0].path)) {
|
|
232
|
+
try {
|
|
233
|
+
setLanguage(language, tsxFiles[0].path);
|
|
234
|
+
processFileGroup(tsxFiles, language, queryString, result, onFileProcessed);
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
// parser unavailable — skip this language group
|
|
238
|
+
}
|
|
526
239
|
}
|
|
527
|
-
|
|
528
|
-
|
|
240
|
+
else {
|
|
241
|
+
result.skippedLanguages[language] = (result.skippedLanguages[language] || 0) + tsxFiles.length;
|
|
529
242
|
}
|
|
530
243
|
}
|
|
531
244
|
}
|
|
@@ -988,27 +701,43 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
988
701
|
const lang = parser.getLanguage();
|
|
989
702
|
query = new Parser.Query(lang, queryString);
|
|
990
703
|
}
|
|
991
|
-
catch {
|
|
704
|
+
catch (err) {
|
|
705
|
+
const message = `Query compilation failed for ${language}: ${err instanceof Error ? err.message : String(err)}`;
|
|
706
|
+
if (parentPort) {
|
|
707
|
+
parentPort.postMessage({ type: 'warning', message });
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
console.warn(message);
|
|
711
|
+
}
|
|
992
712
|
return;
|
|
993
713
|
}
|
|
994
714
|
for (const file of files) {
|
|
995
|
-
// Skip
|
|
996
|
-
if (file.content.length >
|
|
715
|
+
// Skip files larger than the max tree-sitter buffer (32 MB)
|
|
716
|
+
if (file.content.length > TREE_SITTER_MAX_BUFFER)
|
|
997
717
|
continue;
|
|
998
718
|
let tree;
|
|
999
719
|
try {
|
|
1000
|
-
tree = parser.parse(file.content, undefined, { bufferSize:
|
|
720
|
+
tree = parser.parse(file.content, undefined, { bufferSize: getTreeSitterBufferSize(file.content.length) });
|
|
1001
721
|
}
|
|
1002
|
-
catch {
|
|
722
|
+
catch (err) {
|
|
723
|
+
console.warn(`Failed to parse file ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1003
724
|
continue;
|
|
1004
725
|
}
|
|
1005
726
|
result.fileCount++;
|
|
1006
727
|
onFileProcessed?.();
|
|
728
|
+
// Build per-file type environment + constructor bindings in a single AST walk.
|
|
729
|
+
// Constructor bindings are verified against the SymbolTable in processCallsFromExtracted.
|
|
730
|
+
const typeEnv = buildTypeEnv(tree, language);
|
|
731
|
+
const callRouter = callRouters[language];
|
|
732
|
+
if (typeEnv.constructorBindings.length > 0) {
|
|
733
|
+
result.constructorBindings.push({ filePath: file.path, bindings: [...typeEnv.constructorBindings] });
|
|
734
|
+
}
|
|
1007
735
|
let matches;
|
|
1008
736
|
try {
|
|
1009
737
|
matches = query.matches(tree.rootNode);
|
|
1010
738
|
}
|
|
1011
|
-
catch {
|
|
739
|
+
catch (err) {
|
|
740
|
+
console.warn(`Query execution failed for ${file.path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
1012
741
|
continue;
|
|
1013
742
|
}
|
|
1014
743
|
for (const match of matches) {
|
|
@@ -1021,10 +750,12 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1021
750
|
const rawImportPath = language === SupportedLanguages.Kotlin
|
|
1022
751
|
? appendKotlinWildcard(captureMap['import.source'].text.replace(/['"<>]/g, ''), captureMap['import'])
|
|
1023
752
|
: captureMap['import.source'].text.replace(/['"<>]/g, '');
|
|
753
|
+
const namedBindings = extractNamedBindings(captureMap['import'], language);
|
|
1024
754
|
result.imports.push({
|
|
1025
755
|
filePath: file.path,
|
|
1026
756
|
rawImportPath,
|
|
1027
757
|
language: language,
|
|
758
|
+
...(namedBindings ? { namedBindings } : {}),
|
|
1028
759
|
});
|
|
1029
760
|
continue;
|
|
1030
761
|
}
|
|
@@ -1033,11 +764,122 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1033
764
|
const callNameNode = captureMap['call.name'];
|
|
1034
765
|
if (callNameNode) {
|
|
1035
766
|
const calledName = callNameNode.text;
|
|
1036
|
-
|
|
767
|
+
// Dispatch: route language-specific calls (heritage, properties, imports)
|
|
768
|
+
const routed = callRouter(calledName, captureMap['call']);
|
|
769
|
+
if (routed) {
|
|
770
|
+
if (routed.kind === 'skip')
|
|
771
|
+
continue;
|
|
772
|
+
if (routed.kind === 'import') {
|
|
773
|
+
result.imports.push({
|
|
774
|
+
filePath: file.path,
|
|
775
|
+
rawImportPath: routed.importPath,
|
|
776
|
+
language,
|
|
777
|
+
});
|
|
778
|
+
continue;
|
|
779
|
+
}
|
|
780
|
+
if (routed.kind === 'heritage') {
|
|
781
|
+
for (const item of routed.items) {
|
|
782
|
+
result.heritage.push({
|
|
783
|
+
filePath: file.path,
|
|
784
|
+
className: item.enclosingClass,
|
|
785
|
+
parentName: item.mixinName,
|
|
786
|
+
kind: item.heritageKind,
|
|
787
|
+
});
|
|
788
|
+
}
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
if (routed.kind === 'properties') {
|
|
792
|
+
const propEnclosingClassId = findEnclosingClassId(captureMap['call'], file.path);
|
|
793
|
+
for (const item of routed.items) {
|
|
794
|
+
const nodeId = generateId('Property', `${file.path}:${item.propName}`);
|
|
795
|
+
result.nodes.push({
|
|
796
|
+
id: nodeId,
|
|
797
|
+
label: 'Property',
|
|
798
|
+
properties: {
|
|
799
|
+
name: item.propName,
|
|
800
|
+
filePath: file.path,
|
|
801
|
+
startLine: item.startLine,
|
|
802
|
+
endLine: item.endLine,
|
|
803
|
+
language,
|
|
804
|
+
isExported: true,
|
|
805
|
+
description: item.accessorType,
|
|
806
|
+
},
|
|
807
|
+
});
|
|
808
|
+
result.symbols.push({
|
|
809
|
+
filePath: file.path,
|
|
810
|
+
name: item.propName,
|
|
811
|
+
nodeId,
|
|
812
|
+
type: 'Property',
|
|
813
|
+
...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
|
|
814
|
+
});
|
|
815
|
+
const fileId = generateId('File', file.path);
|
|
816
|
+
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
|
817
|
+
result.relationships.push({
|
|
818
|
+
id: relId,
|
|
819
|
+
sourceId: fileId,
|
|
820
|
+
targetId: nodeId,
|
|
821
|
+
type: 'DEFINES',
|
|
822
|
+
confidence: 1.0,
|
|
823
|
+
reason: '',
|
|
824
|
+
});
|
|
825
|
+
if (propEnclosingClassId) {
|
|
826
|
+
result.relationships.push({
|
|
827
|
+
id: generateId('HAS_METHOD', `${propEnclosingClassId}->${nodeId}`),
|
|
828
|
+
sourceId: propEnclosingClassId,
|
|
829
|
+
targetId: nodeId,
|
|
830
|
+
type: 'HAS_METHOD',
|
|
831
|
+
confidence: 1.0,
|
|
832
|
+
reason: '',
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
// kind === 'call' — fall through to normal call processing below
|
|
839
|
+
}
|
|
840
|
+
if (!isBuiltInOrNoise(calledName)) {
|
|
1037
841
|
const callNode = captureMap['call'];
|
|
1038
842
|
const sourceId = findEnclosingFunctionId(callNode, file.path)
|
|
1039
843
|
|| generateId('File', file.path);
|
|
1040
|
-
|
|
844
|
+
const callForm = inferCallForm(callNode, callNameNode);
|
|
845
|
+
let receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
|
|
846
|
+
let receiverTypeName = receiverName ? typeEnv.lookup(receiverName, callNode) : undefined;
|
|
847
|
+
let receiverCallChain;
|
|
848
|
+
// When the receiver is a call_expression (e.g. svc.getUser().save()),
|
|
849
|
+
// extractReceiverName returns undefined because it refuses complex expressions.
|
|
850
|
+
// Instead, walk the receiver node to build a call chain for deferred resolution.
|
|
851
|
+
// We capture the base receiver name so processCallsFromExtracted can look it up
|
|
852
|
+
// from constructor bindings. receiverTypeName is intentionally left unset here —
|
|
853
|
+
// the chain resolver in processCallsFromExtracted needs the base type as input and
|
|
854
|
+
// produces the final receiver type as output.
|
|
855
|
+
if (callForm === 'member' && receiverName === undefined && !receiverTypeName) {
|
|
856
|
+
const receiverNode = extractReceiverNode(callNameNode);
|
|
857
|
+
if (receiverNode && CALL_EXPRESSION_TYPES.has(receiverNode.type)) {
|
|
858
|
+
const extracted = extractCallChain(receiverNode);
|
|
859
|
+
if (extracted) {
|
|
860
|
+
receiverCallChain = extracted.chain;
|
|
861
|
+
// Set receiverName to the base object so Step 1 in processCallsFromExtracted
|
|
862
|
+
// can resolve it via constructor bindings to a base type for the chain.
|
|
863
|
+
receiverName = extracted.baseReceiverName;
|
|
864
|
+
// Also try the type environment immediately (covers explicitly-typed locals
|
|
865
|
+
// and annotated parameters like `fn process(svc: &UserService)`).
|
|
866
|
+
// This sets a base type that chain resolution (Step 2) will use as input.
|
|
867
|
+
if (receiverName) {
|
|
868
|
+
receiverTypeName = typeEnv.lookup(receiverName, callNode);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
result.calls.push({
|
|
874
|
+
filePath: file.path,
|
|
875
|
+
calledName,
|
|
876
|
+
sourceId,
|
|
877
|
+
argCount: countCallArguments(callNode),
|
|
878
|
+
...(callForm !== undefined ? { callForm } : {}),
|
|
879
|
+
...(receiverName !== undefined ? { receiverName } : {}),
|
|
880
|
+
...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
|
|
881
|
+
...(receiverCallChain !== undefined ? { receiverCallChain } : {}),
|
|
882
|
+
});
|
|
1041
883
|
}
|
|
1042
884
|
}
|
|
1043
885
|
continue;
|
|
@@ -1045,12 +887,21 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1045
887
|
// Extract heritage (extends/implements)
|
|
1046
888
|
if (captureMap['heritage.class']) {
|
|
1047
889
|
if (captureMap['heritage.extends']) {
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
890
|
+
// Go struct embedding: the query matches ALL field_declarations with
|
|
891
|
+
// type_identifier, but only anonymous fields (no name) are embedded.
|
|
892
|
+
// Named fields like `Breed string` also match — skip them.
|
|
893
|
+
const extendsNode = captureMap['heritage.extends'];
|
|
894
|
+
const fieldDecl = extendsNode.parent;
|
|
895
|
+
const isNamedField = fieldDecl?.type === 'field_declaration'
|
|
896
|
+
&& fieldDecl.childForFieldName('name');
|
|
897
|
+
if (!isNamedField) {
|
|
898
|
+
result.heritage.push({
|
|
899
|
+
filePath: file.path,
|
|
900
|
+
className: captureMap['heritage.class'].text,
|
|
901
|
+
parentName: captureMap['heritage.extends'].text,
|
|
902
|
+
kind: 'extends',
|
|
903
|
+
});
|
|
904
|
+
}
|
|
1054
905
|
}
|
|
1055
906
|
if (captureMap['heritage.implements']) {
|
|
1056
907
|
result.heritage.push({
|
|
@@ -1082,7 +933,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1082
933
|
const nodeName = nameNode ? nameNode.text : 'init';
|
|
1083
934
|
const definitionNode = getDefinitionNodeFromCaptures(captureMap);
|
|
1084
935
|
const startLine = definitionNode ? definitionNode.startPosition.row : (nameNode ? nameNode.startPosition.row : 0);
|
|
1085
|
-
const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}
|
|
936
|
+
const nodeId = generateId(nodeLabel, `${file.path}:${nodeName}`);
|
|
1086
937
|
let description;
|
|
1087
938
|
if (language === SupportedLanguages.PHP) {
|
|
1088
939
|
if (nodeLabel === 'Property' && captureMap['definition.property']) {
|
|
@@ -1095,6 +946,20 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1095
946
|
const frameworkHint = definitionNode
|
|
1096
947
|
? detectFrameworkFromAST(language, (definitionNode.text || '').slice(0, 300))
|
|
1097
948
|
: null;
|
|
949
|
+
let parameterCount;
|
|
950
|
+
let returnType;
|
|
951
|
+
if (nodeLabel === 'Function' || nodeLabel === 'Method' || nodeLabel === 'Constructor') {
|
|
952
|
+
const sig = extractMethodSignature(definitionNode);
|
|
953
|
+
parameterCount = sig.parameterCount;
|
|
954
|
+
returnType = sig.returnType;
|
|
955
|
+
// Language-specific return type fallback (e.g. Ruby YARD @return [Type])
|
|
956
|
+
if (!returnType && definitionNode) {
|
|
957
|
+
const tc = typeConfigs[language];
|
|
958
|
+
if (tc?.extractReturnType) {
|
|
959
|
+
returnType = tc.extractReturnType(definitionNode);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
}
|
|
1098
963
|
result.nodes.push({
|
|
1099
964
|
id: nodeId,
|
|
1100
965
|
label: nodeLabel,
|
|
@@ -1110,13 +975,22 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1110
975
|
astFrameworkReason: frameworkHint.reason,
|
|
1111
976
|
} : {}),
|
|
1112
977
|
...(description !== undefined ? { description } : {}),
|
|
978
|
+
...(parameterCount !== undefined ? { parameterCount } : {}),
|
|
979
|
+
...(returnType !== undefined ? { returnType } : {}),
|
|
1113
980
|
},
|
|
1114
981
|
});
|
|
982
|
+
// Compute enclosing class for Method/Constructor/Property/Function — used for both ownerId and HAS_METHOD
|
|
983
|
+
// Function is included because Kotlin/Rust/Python capture class methods as Function nodes
|
|
984
|
+
const needsOwner = nodeLabel === 'Method' || nodeLabel === 'Constructor' || nodeLabel === 'Property' || nodeLabel === 'Function';
|
|
985
|
+
const enclosingClassId = needsOwner ? findEnclosingClassId(nameNode || definitionNode, file.path) : null;
|
|
1115
986
|
result.symbols.push({
|
|
1116
987
|
filePath: file.path,
|
|
1117
988
|
name: nodeName,
|
|
1118
989
|
nodeId,
|
|
1119
990
|
type: nodeLabel,
|
|
991
|
+
...(parameterCount !== undefined ? { parameterCount } : {}),
|
|
992
|
+
...(returnType !== undefined ? { returnType } : {}),
|
|
993
|
+
...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
|
|
1120
994
|
});
|
|
1121
995
|
const fileId = generateId('File', file.path);
|
|
1122
996
|
const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
|
|
@@ -1128,6 +1002,17 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1128
1002
|
confidence: 1.0,
|
|
1129
1003
|
reason: '',
|
|
1130
1004
|
});
|
|
1005
|
+
// ── HAS_METHOD: link method/constructor/property to enclosing class ──
|
|
1006
|
+
if (enclosingClassId) {
|
|
1007
|
+
result.relationships.push({
|
|
1008
|
+
id: generateId('HAS_METHOD', `${enclosingClassId}->${nodeId}`),
|
|
1009
|
+
sourceId: enclosingClassId,
|
|
1010
|
+
targetId: nodeId,
|
|
1011
|
+
type: 'HAS_METHOD',
|
|
1012
|
+
confidence: 1.0,
|
|
1013
|
+
reason: '',
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1131
1016
|
}
|
|
1132
1017
|
// Extract Laravel routes from route files via procedural AST walk
|
|
1133
1018
|
if (language === SupportedLanguages.PHP && (file.path.includes('/routes/') || file.path.startsWith('routes/')) && file.path.endsWith('.php')) {
|
|
@@ -1142,7 +1027,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1142
1027
|
/** Accumulated result across sub-batches */
|
|
1143
1028
|
let accumulated = {
|
|
1144
1029
|
nodes: [], relationships: [], symbols: [],
|
|
1145
|
-
imports: [], calls: [], heritage: [], routes: [], fileCount: 0,
|
|
1030
|
+
imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0,
|
|
1146
1031
|
};
|
|
1147
1032
|
let cumulativeProcessed = 0;
|
|
1148
1033
|
const mergeResult = (target, src) => {
|
|
@@ -1153,6 +1038,10 @@ const mergeResult = (target, src) => {
|
|
|
1153
1038
|
target.calls.push(...src.calls);
|
|
1154
1039
|
target.heritage.push(...src.heritage);
|
|
1155
1040
|
target.routes.push(...src.routes);
|
|
1041
|
+
target.constructorBindings.push(...src.constructorBindings);
|
|
1042
|
+
for (const [lang, count] of Object.entries(src.skippedLanguages)) {
|
|
1043
|
+
target.skippedLanguages[lang] = (target.skippedLanguages[lang] || 0) + count;
|
|
1044
|
+
}
|
|
1156
1045
|
target.fileCount += src.fileCount;
|
|
1157
1046
|
};
|
|
1158
1047
|
parentPort.on('message', (msg) => {
|
|
@@ -1172,7 +1061,7 @@ parentPort.on('message', (msg) => {
|
|
|
1172
1061
|
if (msg && msg.type === 'flush') {
|
|
1173
1062
|
parentPort.postMessage({ type: 'result', data: accumulated });
|
|
1174
1063
|
// Reset for potential reuse
|
|
1175
|
-
accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], fileCount: 0 };
|
|
1064
|
+
accumulated = { nodes: [], relationships: [], symbols: [], imports: [], calls: [], heritage: [], routes: [], constructorBindings: [], skippedLanguages: {}, fileCount: 0 };
|
|
1176
1065
|
cumulativeProcessed = 0;
|
|
1177
1066
|
return;
|
|
1178
1067
|
}
|