gitnexus 1.4.0 → 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 +19 -18
- package/dist/cli/analyze.js +37 -28
- 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 +1 -0
- package/dist/cli/mcp.js +1 -1
- package/dist/cli/setup.js +25 -13
- 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 +94 -67
- 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 +52 -25
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/ingestion/call-processor.d.ts +6 -7
- package/dist/core/ingestion/call-processor.js +490 -127
- package/dist/core/ingestion/call-routing.d.ts +53 -0
- package/dist/core/ingestion/call-routing.js +108 -0
- package/dist/core/ingestion/entry-point-scoring.js +13 -2
- package/dist/core/ingestion/export-detection.js +1 -0
- package/dist/core/ingestion/filesystem-walker.js +4 -3
- package/dist/core/ingestion/framework-detection.js +9 -0
- package/dist/core/ingestion/heritage-processor.d.ts +3 -4
- package/dist/core/ingestion/heritage-processor.js +40 -50
- package/dist/core/ingestion/import-processor.d.ts +3 -5
- package/dist/core/ingestion/import-processor.js +41 -10
- package/dist/core/ingestion/parsing-processor.d.ts +2 -1
- package/dist/core/ingestion/parsing-processor.js +41 -4
- package/dist/core/ingestion/pipeline.d.ts +5 -1
- package/dist/core/ingestion/pipeline.js +174 -121
- package/dist/core/ingestion/resolution-context.d.ts +53 -0
- package/dist/core/ingestion/resolution-context.js +132 -0
- package/dist/core/ingestion/resolvers/index.d.ts +2 -0
- package/dist/core/ingestion/resolvers/index.js +2 -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/standard.js +0 -22
- package/dist/core/ingestion/resolvers/utils.js +2 -0
- package/dist/core/ingestion/symbol-table.d.ts +3 -0
- package/dist/core/ingestion/symbol-table.js +1 -0
- package/dist/core/ingestion/tree-sitter-queries.d.ts +3 -2
- package/dist/core/ingestion/tree-sitter-queries.js +53 -1
- package/dist/core/ingestion/type-env.d.ts +32 -10
- package/dist/core/ingestion/type-env.js +520 -47
- package/dist/core/ingestion/type-extractors/c-cpp.js +326 -1
- package/dist/core/ingestion/type-extractors/csharp.js +282 -2
- package/dist/core/ingestion/type-extractors/go.js +333 -2
- package/dist/core/ingestion/type-extractors/index.d.ts +3 -2
- package/dist/core/ingestion/type-extractors/index.js +3 -1
- package/dist/core/ingestion/type-extractors/jvm.js +537 -4
- package/dist/core/ingestion/type-extractors/php.js +387 -7
- package/dist/core/ingestion/type-extractors/python.js +356 -5
- 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.js +399 -2
- package/dist/core/ingestion/type-extractors/shared.d.ts +116 -1
- package/dist/core/ingestion/type-extractors/shared.js +488 -14
- package/dist/core/ingestion/type-extractors/swift.js +95 -1
- package/dist/core/ingestion/type-extractors/types.d.ts +81 -0
- package/dist/core/ingestion/type-extractors/typescript.js +436 -2
- package/dist/core/ingestion/utils.d.ts +33 -2
- package/dist/core/ingestion/utils.js +399 -27
- package/dist/core/ingestion/workers/parse-worker.d.ts +18 -1
- package/dist/core/ingestion/workers/parse-worker.js +169 -19
- package/dist/core/{kuzu → lbug}/csv-generator.d.ts +1 -1
- package/dist/core/{kuzu → lbug}/csv-generator.js +1 -1
- 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} +70 -65
- package/dist/core/{kuzu → lbug}/schema.d.ts +1 -1
- package/dist/core/{kuzu → lbug}/schema.js +1 -1
- package/dist/core/search/bm25-index.d.ts +4 -4
- package/dist/core/search/bm25-index.js +10 -10
- package/dist/core/search/hybrid-search.d.ts +2 -2
- package/dist/core/search/hybrid-search.js +6 -6
- package/dist/core/tree-sitter/parser-loader.js +9 -2
- package/dist/core/wiki/generator.d.ts +2 -2
- package/dist/core/wiki/generator.js +4 -4
- package/dist/core/wiki/graph-queries.d.ts +4 -4
- package/dist/core/wiki/graph-queries.js +7 -7
- package/dist/mcp/core/{kuzu-adapter.d.ts → lbug-adapter.d.ts} +7 -7
- package/dist/mcp/core/{kuzu-adapter.js → lbug-adapter.js} +72 -43
- package/dist/mcp/local/local-backend.d.ts +6 -6
- package/dist/mcp/local/local-backend.js +25 -18
- 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/package.json +5 -3
- package/dist/core/ingestion/symbol-resolver.d.ts +0 -32
- package/dist/core/ingestion/symbol-resolver.js +0 -83
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractSimpleTypeName, extractVarName } from './shared.js';
|
|
1
|
+
import { extractSimpleTypeName, extractVarName, resolveIterableElementType, methodToTypeArgPosition } from './shared.js';
|
|
2
2
|
const DECLARATION_NODE_TYPES = new Set([
|
|
3
3
|
'declaration',
|
|
4
4
|
]);
|
|
@@ -29,6 +29,77 @@ const extractDeclaration = (node, env) => {
|
|
|
29
29
|
if (varName)
|
|
30
30
|
env.set(varName, typeName);
|
|
31
31
|
};
|
|
32
|
+
/** C++: auto x = new User(); auto x = User(); */
|
|
33
|
+
const extractInitializer = (node, env, classNames) => {
|
|
34
|
+
const typeNode = node.childForFieldName('type');
|
|
35
|
+
if (!typeNode)
|
|
36
|
+
return;
|
|
37
|
+
// Only handle auto/placeholder — typed declarations are handled by extractDeclaration
|
|
38
|
+
const typeText = typeNode.text;
|
|
39
|
+
if (typeText !== 'auto' &&
|
|
40
|
+
typeText !== 'decltype(auto)' &&
|
|
41
|
+
typeNode.type !== 'placeholder_type_specifier')
|
|
42
|
+
return;
|
|
43
|
+
const declarator = node.childForFieldName('declarator');
|
|
44
|
+
if (!declarator)
|
|
45
|
+
return;
|
|
46
|
+
// Must be an init_declarator (i.e., has an initializer value)
|
|
47
|
+
if (declarator.type !== 'init_declarator')
|
|
48
|
+
return;
|
|
49
|
+
const value = declarator.childForFieldName('value');
|
|
50
|
+
if (!value)
|
|
51
|
+
return;
|
|
52
|
+
// Resolve the variable name, unwrapping pointer/reference declarators
|
|
53
|
+
const nameNode = declarator.childForFieldName('declarator');
|
|
54
|
+
if (!nameNode)
|
|
55
|
+
return;
|
|
56
|
+
const finalName = nameNode.type === 'pointer_declarator' || nameNode.type === 'reference_declarator'
|
|
57
|
+
? nameNode.firstNamedChild
|
|
58
|
+
: nameNode;
|
|
59
|
+
if (!finalName)
|
|
60
|
+
return;
|
|
61
|
+
const varName = extractVarName(finalName);
|
|
62
|
+
if (!varName)
|
|
63
|
+
return;
|
|
64
|
+
// auto x = new User() — new_expression
|
|
65
|
+
if (value.type === 'new_expression') {
|
|
66
|
+
const ctorType = value.childForFieldName('type');
|
|
67
|
+
if (ctorType) {
|
|
68
|
+
const typeName = extractSimpleTypeName(ctorType);
|
|
69
|
+
if (typeName)
|
|
70
|
+
env.set(varName, typeName);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
// auto x = User() — call_expression where function is a type name
|
|
75
|
+
// tree-sitter-cpp may parse the constructor name as type_identifier or identifier.
|
|
76
|
+
// For plain identifiers, verify against known class names from the file's AST
|
|
77
|
+
// to distinguish constructor calls (User()) from function calls (getUser()).
|
|
78
|
+
if (value.type === 'call_expression') {
|
|
79
|
+
const func = value.childForFieldName('function');
|
|
80
|
+
if (!func)
|
|
81
|
+
return;
|
|
82
|
+
if (func.type === 'type_identifier') {
|
|
83
|
+
const typeName = func.text;
|
|
84
|
+
if (typeName)
|
|
85
|
+
env.set(varName, typeName);
|
|
86
|
+
}
|
|
87
|
+
else if (func.type === 'identifier') {
|
|
88
|
+
const text = func.text;
|
|
89
|
+
if (text && classNames.has(text))
|
|
90
|
+
env.set(varName, text);
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// auto x = User{} — compound_literal_expression (brace initialization)
|
|
95
|
+
// AST: compound_literal_expression > type_identifier + initializer_list
|
|
96
|
+
if (value.type === 'compound_literal_expression') {
|
|
97
|
+
const typeId = value.firstNamedChild;
|
|
98
|
+
const typeName = typeId ? extractSimpleTypeName(typeId) : undefined;
|
|
99
|
+
if (typeName)
|
|
100
|
+
env.set(varName, typeName);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
32
103
|
/** C/C++: parameter_declaration → type declarator */
|
|
33
104
|
const extractParameter = (node, env) => {
|
|
34
105
|
let nameNode = null;
|
|
@@ -53,8 +124,262 @@ const extractParameter = (node, env) => {
|
|
|
53
124
|
if (varName && typeName)
|
|
54
125
|
env.set(varName, typeName);
|
|
55
126
|
};
|
|
127
|
+
/** C/C++: auto x = User() where function is an identifier (not type_identifier) */
|
|
128
|
+
const scanConstructorBinding = (node) => {
|
|
129
|
+
if (node.type !== 'declaration')
|
|
130
|
+
return undefined;
|
|
131
|
+
const typeNode = node.childForFieldName('type');
|
|
132
|
+
if (!typeNode)
|
|
133
|
+
return undefined;
|
|
134
|
+
const typeText = typeNode.text;
|
|
135
|
+
if (typeText !== 'auto' && typeText !== 'decltype(auto)' && typeNode.type !== 'placeholder_type_specifier')
|
|
136
|
+
return undefined;
|
|
137
|
+
const declarator = node.childForFieldName('declarator');
|
|
138
|
+
if (!declarator || declarator.type !== 'init_declarator')
|
|
139
|
+
return undefined;
|
|
140
|
+
const value = declarator.childForFieldName('value');
|
|
141
|
+
if (!value || value.type !== 'call_expression')
|
|
142
|
+
return undefined;
|
|
143
|
+
const func = value.childForFieldName('function');
|
|
144
|
+
if (!func)
|
|
145
|
+
return undefined;
|
|
146
|
+
if (func.type === 'qualified_identifier' || func.type === 'scoped_identifier') {
|
|
147
|
+
const last = func.lastNamedChild;
|
|
148
|
+
if (!last)
|
|
149
|
+
return undefined;
|
|
150
|
+
const nameNode = declarator.childForFieldName('declarator');
|
|
151
|
+
if (!nameNode)
|
|
152
|
+
return undefined;
|
|
153
|
+
const finalName = nameNode.type === 'pointer_declarator' || nameNode.type === 'reference_declarator'
|
|
154
|
+
? nameNode.firstNamedChild : nameNode;
|
|
155
|
+
if (!finalName)
|
|
156
|
+
return undefined;
|
|
157
|
+
return { varName: finalName.text, calleeName: last.text };
|
|
158
|
+
}
|
|
159
|
+
if (func.type !== 'identifier')
|
|
160
|
+
return undefined;
|
|
161
|
+
const nameNode = declarator.childForFieldName('declarator');
|
|
162
|
+
if (!nameNode)
|
|
163
|
+
return undefined;
|
|
164
|
+
const finalName = nameNode.type === 'pointer_declarator' || nameNode.type === 'reference_declarator'
|
|
165
|
+
? nameNode.firstNamedChild : nameNode;
|
|
166
|
+
if (!finalName)
|
|
167
|
+
return undefined;
|
|
168
|
+
const varName = finalName.text;
|
|
169
|
+
if (!varName)
|
|
170
|
+
return undefined;
|
|
171
|
+
return { varName, calleeName: func.text };
|
|
172
|
+
};
|
|
173
|
+
/** C++: auto alias = user → declaration with auto type + init_declarator where value is identifier */
|
|
174
|
+
const extractPendingAssignment = (node, scopeEnv) => {
|
|
175
|
+
if (node.type !== 'declaration')
|
|
176
|
+
return undefined;
|
|
177
|
+
const typeNode = node.childForFieldName('type');
|
|
178
|
+
if (!typeNode)
|
|
179
|
+
return undefined;
|
|
180
|
+
// Only handle auto — typed declarations already resolved by extractDeclaration
|
|
181
|
+
const typeText = typeNode.text;
|
|
182
|
+
if (typeText !== 'auto' && typeText !== 'decltype(auto)'
|
|
183
|
+
&& typeNode.type !== 'placeholder_type_specifier')
|
|
184
|
+
return undefined;
|
|
185
|
+
const declarator = node.childForFieldName('declarator');
|
|
186
|
+
if (!declarator || declarator.type !== 'init_declarator')
|
|
187
|
+
return undefined;
|
|
188
|
+
const value = declarator.childForFieldName('value');
|
|
189
|
+
if (!value || value.type !== 'identifier')
|
|
190
|
+
return undefined;
|
|
191
|
+
const nameNode = declarator.childForFieldName('declarator');
|
|
192
|
+
if (!nameNode)
|
|
193
|
+
return undefined;
|
|
194
|
+
const finalName = nameNode.type === 'pointer_declarator' || nameNode.type === 'reference_declarator'
|
|
195
|
+
? nameNode.firstNamedChild : nameNode;
|
|
196
|
+
if (!finalName)
|
|
197
|
+
return undefined;
|
|
198
|
+
const lhs = extractVarName(finalName);
|
|
199
|
+
if (!lhs || scopeEnv.has(lhs))
|
|
200
|
+
return undefined;
|
|
201
|
+
return { lhs, rhs: value.text };
|
|
202
|
+
};
|
|
203
|
+
// --- For-loop Tier 1c ---
|
|
204
|
+
const FOR_LOOP_NODE_TYPES = new Set(['for_range_loop']);
|
|
205
|
+
/** Extract template type arguments from a C++ template_type node.
|
|
206
|
+
* C++ template_type uses template_argument_list (not type_arguments), and each
|
|
207
|
+
* argument is a type_descriptor with a 'type' field containing the type_specifier. */
|
|
208
|
+
const extractCppTemplateTypeArgs = (templateTypeNode) => {
|
|
209
|
+
const argsNode = templateTypeNode.childForFieldName('arguments');
|
|
210
|
+
if (!argsNode || argsNode.type !== 'template_argument_list')
|
|
211
|
+
return [];
|
|
212
|
+
const result = [];
|
|
213
|
+
for (let i = 0; i < argsNode.namedChildCount; i++) {
|
|
214
|
+
let argNode = argsNode.namedChild(i);
|
|
215
|
+
if (!argNode)
|
|
216
|
+
continue;
|
|
217
|
+
// type_descriptor wraps the actual type specifier in a 'type' field
|
|
218
|
+
if (argNode.type === 'type_descriptor') {
|
|
219
|
+
const inner = argNode.childForFieldName('type');
|
|
220
|
+
if (inner)
|
|
221
|
+
argNode = inner;
|
|
222
|
+
}
|
|
223
|
+
const name = extractSimpleTypeName(argNode);
|
|
224
|
+
if (name)
|
|
225
|
+
result.push(name);
|
|
226
|
+
}
|
|
227
|
+
return result;
|
|
228
|
+
};
|
|
229
|
+
/** Extract element type from a C++ type annotation AST node.
|
|
230
|
+
* Handles: template_type (vector<User>, map<string, User>),
|
|
231
|
+
* pointer/reference types (User*, User&). */
|
|
232
|
+
const extractCppElementTypeFromTypeNode = (typeNode, pos = 'last', depth = 0) => {
|
|
233
|
+
if (depth > 50)
|
|
234
|
+
return undefined;
|
|
235
|
+
// template_type: vector<User>, map<string, User> — extract type arg based on position
|
|
236
|
+
if (typeNode.type === 'template_type') {
|
|
237
|
+
const args = extractCppTemplateTypeArgs(typeNode);
|
|
238
|
+
if (args.length >= 1)
|
|
239
|
+
return pos === 'first' ? args[0] : args[args.length - 1];
|
|
240
|
+
}
|
|
241
|
+
// reference/pointer types: unwrap and recurse (vector<User>& → vector<User>)
|
|
242
|
+
if (typeNode.type === 'reference_type' || typeNode.type === 'pointer_type'
|
|
243
|
+
|| typeNode.type === 'type_descriptor') {
|
|
244
|
+
const inner = typeNode.lastNamedChild;
|
|
245
|
+
if (inner)
|
|
246
|
+
return extractCppElementTypeFromTypeNode(inner, pos, depth + 1);
|
|
247
|
+
}
|
|
248
|
+
// qualified/scoped types: std::vector<User> → unwrap to template_type child
|
|
249
|
+
if (typeNode.type === 'qualified_identifier' || typeNode.type === 'scoped_type_identifier') {
|
|
250
|
+
const inner = typeNode.lastNamedChild;
|
|
251
|
+
if (inner)
|
|
252
|
+
return extractCppElementTypeFromTypeNode(inner, pos, depth + 1);
|
|
253
|
+
}
|
|
254
|
+
return undefined;
|
|
255
|
+
};
|
|
256
|
+
/** Walk up from a for-range-loop to the enclosing function_definition and search parameters
|
|
257
|
+
* for one named `iterableName`. Returns the element type from its annotation. */
|
|
258
|
+
const findCppParamElementType = (iterableName, startNode, pos = 'last') => {
|
|
259
|
+
let current = startNode.parent;
|
|
260
|
+
while (current) {
|
|
261
|
+
if (current.type === 'function_definition') {
|
|
262
|
+
const declarator = current.childForFieldName('declarator');
|
|
263
|
+
// function_definition > declarator (function_declarator) > parameters (parameter_list)
|
|
264
|
+
const paramsNode = declarator?.childForFieldName('parameters');
|
|
265
|
+
if (paramsNode) {
|
|
266
|
+
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
267
|
+
const param = paramsNode.namedChild(i);
|
|
268
|
+
if (!param || param.type !== 'parameter_declaration')
|
|
269
|
+
continue;
|
|
270
|
+
const paramDeclarator = param.childForFieldName('declarator');
|
|
271
|
+
if (!paramDeclarator)
|
|
272
|
+
continue;
|
|
273
|
+
// Unwrap reference/pointer declarators: vector<User>& users → &users
|
|
274
|
+
let identNode = paramDeclarator;
|
|
275
|
+
if (identNode.type === 'reference_declarator' || identNode.type === 'pointer_declarator') {
|
|
276
|
+
identNode = identNode.firstNamedChild ?? identNode;
|
|
277
|
+
}
|
|
278
|
+
if (identNode.text !== iterableName)
|
|
279
|
+
continue;
|
|
280
|
+
const typeNode = param.childForFieldName('type');
|
|
281
|
+
if (typeNode)
|
|
282
|
+
return extractCppElementTypeFromTypeNode(typeNode, pos);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
break;
|
|
286
|
+
}
|
|
287
|
+
current = current.parent;
|
|
288
|
+
}
|
|
289
|
+
return undefined;
|
|
290
|
+
};
|
|
291
|
+
/** C++: for (auto& user : users) — extract loop variable binding.
|
|
292
|
+
* Handles explicit types (for (User& user : users)) and auto (for (auto& user : users)).
|
|
293
|
+
* For auto, resolves element type from the iterable's container type. */
|
|
294
|
+
const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
|
|
295
|
+
if (node.type !== 'for_range_loop')
|
|
296
|
+
return;
|
|
297
|
+
const typeNode = node.childForFieldName('type');
|
|
298
|
+
const declaratorNode = node.childForFieldName('declarator');
|
|
299
|
+
const rightNode = node.childForFieldName('right');
|
|
300
|
+
if (!typeNode || !declaratorNode || !rightNode)
|
|
301
|
+
return;
|
|
302
|
+
// Unwrap reference/pointer declarator to get the loop variable name
|
|
303
|
+
let nameNode = declaratorNode;
|
|
304
|
+
if (nameNode.type === 'reference_declarator' || nameNode.type === 'pointer_declarator') {
|
|
305
|
+
nameNode = nameNode.firstNamedChild ?? nameNode;
|
|
306
|
+
}
|
|
307
|
+
// Handle structured bindings: auto& [key, value] or auto [key, value]
|
|
308
|
+
// Bind the last identifier (value heuristic for [key, value] patterns)
|
|
309
|
+
let loopVarName;
|
|
310
|
+
if (nameNode.type === 'structured_binding_declarator') {
|
|
311
|
+
const lastChild = nameNode.lastNamedChild;
|
|
312
|
+
if (lastChild?.type === 'identifier') {
|
|
313
|
+
loopVarName = lastChild.text;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
else if (declaratorNode.type === 'structured_binding_declarator') {
|
|
317
|
+
const lastChild = declaratorNode.lastNamedChild;
|
|
318
|
+
if (lastChild?.type === 'identifier') {
|
|
319
|
+
loopVarName = lastChild.text;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const varName = loopVarName ?? extractVarName(nameNode);
|
|
323
|
+
if (!varName)
|
|
324
|
+
return;
|
|
325
|
+
// Check if the type is auto/placeholder — if not, use the explicit type directly
|
|
326
|
+
const isAuto = typeNode.type === 'placeholder_type_specifier'
|
|
327
|
+
|| typeNode.text === 'auto'
|
|
328
|
+
|| typeNode.text === 'const auto'
|
|
329
|
+
|| typeNode.text === 'decltype(auto)';
|
|
330
|
+
if (!isAuto) {
|
|
331
|
+
// Explicit type: for (User& user : users) — extract directly
|
|
332
|
+
const typeName = extractSimpleTypeName(typeNode);
|
|
333
|
+
if (typeName)
|
|
334
|
+
scopeEnv.set(varName, typeName);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// auto/const auto/auto& — resolve from the iterable's container type
|
|
338
|
+
// Extract iterable name + optional method
|
|
339
|
+
let iterableName;
|
|
340
|
+
let methodName;
|
|
341
|
+
if (rightNode.type === 'identifier') {
|
|
342
|
+
iterableName = rightNode.text;
|
|
343
|
+
}
|
|
344
|
+
else if (rightNode.type === 'field_expression') {
|
|
345
|
+
const prop = rightNode.lastNamedChild;
|
|
346
|
+
if (prop)
|
|
347
|
+
iterableName = prop.text;
|
|
348
|
+
}
|
|
349
|
+
else if (rightNode.type === 'call_expression') {
|
|
350
|
+
// users.begin() is NOT used in range-for, but container.items() etc. might be
|
|
351
|
+
const fieldExpr = rightNode.childForFieldName('function');
|
|
352
|
+
if (fieldExpr?.type === 'field_expression') {
|
|
353
|
+
const obj = fieldExpr.firstNamedChild;
|
|
354
|
+
if (obj?.type === 'identifier')
|
|
355
|
+
iterableName = obj.text;
|
|
356
|
+
const field = fieldExpr.lastNamedChild;
|
|
357
|
+
if (field?.type === 'field_identifier')
|
|
358
|
+
methodName = field.text;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
else if (rightNode.type === 'pointer_expression') {
|
|
362
|
+
// Dereference: for (auto& user : *ptr) → pointer_expression > identifier
|
|
363
|
+
// Only handles simple *identifier; *this->field and **ptr are not resolved.
|
|
364
|
+
const operand = rightNode.lastNamedChild;
|
|
365
|
+
if (operand?.type === 'identifier')
|
|
366
|
+
iterableName = operand.text;
|
|
367
|
+
}
|
|
368
|
+
if (!iterableName)
|
|
369
|
+
return;
|
|
370
|
+
const containerTypeName = scopeEnv.get(iterableName);
|
|
371
|
+
const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
|
|
372
|
+
const elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractCppElementTypeFromTypeNode, findCppParamElementType, typeArgPos);
|
|
373
|
+
if (elementType)
|
|
374
|
+
scopeEnv.set(varName, elementType);
|
|
375
|
+
};
|
|
56
376
|
export const typeConfig = {
|
|
57
377
|
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
378
|
+
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
|
58
379
|
extractDeclaration,
|
|
59
380
|
extractParameter,
|
|
381
|
+
extractInitializer,
|
|
382
|
+
scanConstructorBinding,
|
|
383
|
+
extractForLoopBinding,
|
|
384
|
+
extractPendingAssignment,
|
|
60
385
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { extractSimpleTypeName, extractVarName, findChildByType } from './shared.js';
|
|
1
|
+
import { extractSimpleTypeName, extractVarName, findChildByType, unwrapAwait, resolveIterableElementType, methodToTypeArgPosition } from './shared.js';
|
|
2
|
+
/** Known container property accessors that operate on the container itself (e.g., dict.Keys, dict.Values) */
|
|
3
|
+
const KNOWN_CONTAINER_PROPS = new Set(['Keys', 'Values']);
|
|
2
4
|
const DECLARATION_NODE_TYPES = new Set([
|
|
3
5
|
'local_declaration_statement',
|
|
4
6
|
'variable_declaration',
|
|
@@ -38,7 +40,8 @@ const extractDeclaration = (node, env) => {
|
|
|
38
40
|
let typeName;
|
|
39
41
|
if (typeNode.type === 'implicit_type' && typeNode.text === 'var') {
|
|
40
42
|
// Try to infer from initializer: var x = new Foo()
|
|
41
|
-
//
|
|
43
|
+
// tree-sitter-c-sharp may put object_creation_expression as direct child
|
|
44
|
+
// or inside equals_value_clause depending on grammar version
|
|
42
45
|
if (declarators.length === 1) {
|
|
43
46
|
const initializer = findChildByType(declarators[0], 'object_creation_expression')
|
|
44
47
|
?? findChildByType(declarators[0], 'equals_value_clause')?.firstNamedChild;
|
|
@@ -82,8 +85,285 @@ const extractParameter = (node, env) => {
|
|
|
82
85
|
if (varName && typeName)
|
|
83
86
|
env.set(varName, typeName);
|
|
84
87
|
};
|
|
88
|
+
/** C#: var x = SomeFactory(...) → bind x to SomeFactory (constructor-like call) */
|
|
89
|
+
const scanConstructorBinding = (node) => {
|
|
90
|
+
if (node.type !== 'variable_declaration')
|
|
91
|
+
return undefined;
|
|
92
|
+
// Find type and declarator children by iterating (C# grammar doesn't expose 'type' as a named field)
|
|
93
|
+
let typeNode = null;
|
|
94
|
+
let declarator = null;
|
|
95
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
96
|
+
const child = node.namedChild(i);
|
|
97
|
+
if (!child)
|
|
98
|
+
continue;
|
|
99
|
+
if (child.type === 'variable_declarator') {
|
|
100
|
+
if (!declarator)
|
|
101
|
+
declarator = child;
|
|
102
|
+
}
|
|
103
|
+
else if (!typeNode) {
|
|
104
|
+
typeNode = child;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Only handle implicit_type (var) — explicit types handled by extractDeclaration
|
|
108
|
+
if (!typeNode || typeNode.type !== 'implicit_type')
|
|
109
|
+
return undefined;
|
|
110
|
+
if (!declarator)
|
|
111
|
+
return undefined;
|
|
112
|
+
const nameNode = declarator.childForFieldName('name') ?? declarator.firstNamedChild;
|
|
113
|
+
if (!nameNode || nameNode.type !== 'identifier')
|
|
114
|
+
return undefined;
|
|
115
|
+
// Find the initializer value: either inside equals_value_clause or as a direct child
|
|
116
|
+
// (tree-sitter-c-sharp puts invocation_expression directly inside variable_declarator)
|
|
117
|
+
let value = null;
|
|
118
|
+
for (let i = 0; i < declarator.namedChildCount; i++) {
|
|
119
|
+
const child = declarator.namedChild(i);
|
|
120
|
+
if (!child)
|
|
121
|
+
continue;
|
|
122
|
+
if (child.type === 'equals_value_clause') {
|
|
123
|
+
value = child.firstNamedChild;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
if (child.type === 'invocation_expression' || child.type === 'object_creation_expression' || child.type === 'await_expression') {
|
|
127
|
+
value = child;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (!value)
|
|
132
|
+
return undefined;
|
|
133
|
+
// Unwrap await: `var user = await svc.GetUserAsync()` → await_expression wraps invocation_expression
|
|
134
|
+
value = unwrapAwait(value);
|
|
135
|
+
if (!value)
|
|
136
|
+
return undefined;
|
|
137
|
+
// Skip object_creation_expression (new User()) — handled by extractInitializer
|
|
138
|
+
if (value.type === 'object_creation_expression')
|
|
139
|
+
return undefined;
|
|
140
|
+
if (value.type !== 'invocation_expression')
|
|
141
|
+
return undefined;
|
|
142
|
+
const func = value.firstNamedChild;
|
|
143
|
+
if (!func)
|
|
144
|
+
return undefined;
|
|
145
|
+
const calleeName = extractSimpleTypeName(func);
|
|
146
|
+
if (!calleeName)
|
|
147
|
+
return undefined;
|
|
148
|
+
return { varName: nameNode.text, calleeName };
|
|
149
|
+
};
|
|
150
|
+
const FOR_LOOP_NODE_TYPES = new Set([
|
|
151
|
+
'foreach_statement',
|
|
152
|
+
]);
|
|
153
|
+
/** Extract element type from a C# type annotation AST node.
|
|
154
|
+
* Handles generic_name (List<User>), array_type (User[]), nullable_type (?).
|
|
155
|
+
* `pos` selects which type arg: 'first' for keys, 'last' for values (default). */
|
|
156
|
+
const extractCSharpElementTypeFromTypeNode = (typeNode, pos = 'last', depth = 0) => {
|
|
157
|
+
if (depth > 50)
|
|
158
|
+
return undefined;
|
|
159
|
+
// generic_name: List<User>, IEnumerable<User>, Dictionary<string, User>
|
|
160
|
+
// C# uses generic_name (not generic_type)
|
|
161
|
+
if (typeNode.type === 'generic_name') {
|
|
162
|
+
const argList = findChildByType(typeNode, 'type_argument_list');
|
|
163
|
+
if (argList && argList.namedChildCount >= 1) {
|
|
164
|
+
if (pos === 'first') {
|
|
165
|
+
const firstArg = argList.namedChild(0);
|
|
166
|
+
if (firstArg)
|
|
167
|
+
return extractSimpleTypeName(firstArg);
|
|
168
|
+
}
|
|
169
|
+
else {
|
|
170
|
+
const lastArg = argList.namedChild(argList.namedChildCount - 1);
|
|
171
|
+
if (lastArg)
|
|
172
|
+
return extractSimpleTypeName(lastArg);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
// array_type: User[]
|
|
177
|
+
if (typeNode.type === 'array_type') {
|
|
178
|
+
const elemNode = typeNode.firstNamedChild;
|
|
179
|
+
if (elemNode)
|
|
180
|
+
return extractSimpleTypeName(elemNode);
|
|
181
|
+
}
|
|
182
|
+
// nullable_type: unwrap and recurse (List<User>? → List<User> → User)
|
|
183
|
+
if (typeNode.type === 'nullable_type') {
|
|
184
|
+
const inner = typeNode.firstNamedChild;
|
|
185
|
+
if (inner)
|
|
186
|
+
return extractCSharpElementTypeFromTypeNode(inner, pos, depth + 1);
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
};
|
|
190
|
+
/** Walk up from a foreach to the enclosing method and search parameters. */
|
|
191
|
+
const findCSharpParamElementType = (iterableName, startNode, pos = 'last') => {
|
|
192
|
+
let current = startNode.parent;
|
|
193
|
+
while (current) {
|
|
194
|
+
if (current.type === 'method_declaration' || current.type === 'local_function_statement') {
|
|
195
|
+
const paramsNode = current.childForFieldName('parameters');
|
|
196
|
+
if (paramsNode) {
|
|
197
|
+
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
198
|
+
const param = paramsNode.namedChild(i);
|
|
199
|
+
if (!param || param.type !== 'parameter')
|
|
200
|
+
continue;
|
|
201
|
+
const nameNode = param.childForFieldName('name');
|
|
202
|
+
if (nameNode?.text !== iterableName)
|
|
203
|
+
continue;
|
|
204
|
+
const typeNode = param.childForFieldName('type');
|
|
205
|
+
if (typeNode)
|
|
206
|
+
return extractCSharpElementTypeFromTypeNode(typeNode, pos);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
break;
|
|
210
|
+
}
|
|
211
|
+
current = current.parent;
|
|
212
|
+
}
|
|
213
|
+
return undefined;
|
|
214
|
+
};
|
|
215
|
+
/** C#: foreach (User user in users) — extract loop variable binding.
|
|
216
|
+
* Tier 1c: for `foreach (var user in users)`, resolves element type from iterable. */
|
|
217
|
+
const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
|
|
218
|
+
const typeNode = node.childForFieldName('type');
|
|
219
|
+
const nameNode = node.childForFieldName('left');
|
|
220
|
+
if (!typeNode || !nameNode)
|
|
221
|
+
return;
|
|
222
|
+
const varName = extractVarName(nameNode);
|
|
223
|
+
if (!varName)
|
|
224
|
+
return;
|
|
225
|
+
// Explicit type (existing behavior): foreach (User user in users)
|
|
226
|
+
if (!(typeNode.type === 'implicit_type' && typeNode.text === 'var')) {
|
|
227
|
+
const typeName = extractSimpleTypeName(typeNode);
|
|
228
|
+
if (typeName)
|
|
229
|
+
scopeEnv.set(varName, typeName);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// Tier 1c: implicit type (var) — resolve from iterable's container type
|
|
233
|
+
const rightNode = node.childForFieldName('right');
|
|
234
|
+
let iterableName;
|
|
235
|
+
let methodName;
|
|
236
|
+
if (rightNode?.type === 'identifier') {
|
|
237
|
+
iterableName = rightNode.text;
|
|
238
|
+
}
|
|
239
|
+
else if (rightNode?.type === 'member_access_expression') {
|
|
240
|
+
// C# property access: data.Keys, data.Values → member_access_expression
|
|
241
|
+
// Also handles bare member access: this.users, repo.users → use property as iterableName
|
|
242
|
+
const obj = rightNode.childForFieldName('expression');
|
|
243
|
+
const prop = rightNode.childForFieldName('name');
|
|
244
|
+
const propText = prop?.type === 'identifier' ? prop.text : undefined;
|
|
245
|
+
if (propText && KNOWN_CONTAINER_PROPS.has(propText)) {
|
|
246
|
+
if (obj?.type === 'identifier') {
|
|
247
|
+
iterableName = obj.text;
|
|
248
|
+
}
|
|
249
|
+
else if (obj?.type === 'member_access_expression') {
|
|
250
|
+
// Nested member access: this.data.Values → obj is "this.data", extract "data"
|
|
251
|
+
const innerProp = obj.childForFieldName('name');
|
|
252
|
+
if (innerProp)
|
|
253
|
+
iterableName = innerProp.text;
|
|
254
|
+
}
|
|
255
|
+
methodName = propText;
|
|
256
|
+
}
|
|
257
|
+
else if (propText) {
|
|
258
|
+
// Bare member access: this.users → use property name for scopeEnv lookup
|
|
259
|
+
iterableName = propText;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
else if (rightNode?.type === 'invocation_expression') {
|
|
263
|
+
// C# method call: data.Select(...) → invocation_expression > member_access_expression
|
|
264
|
+
const fn = rightNode.firstNamedChild;
|
|
265
|
+
if (fn?.type === 'member_access_expression') {
|
|
266
|
+
const obj = fn.childForFieldName('expression');
|
|
267
|
+
const prop = fn.childForFieldName('name');
|
|
268
|
+
if (obj?.type === 'identifier')
|
|
269
|
+
iterableName = obj.text;
|
|
270
|
+
if (prop?.type === 'identifier')
|
|
271
|
+
methodName = prop.text;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (!iterableName)
|
|
275
|
+
return;
|
|
276
|
+
const containerTypeName = scopeEnv.get(iterableName);
|
|
277
|
+
const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
|
|
278
|
+
const elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractCSharpElementTypeFromTypeNode, findCSharpParamElementType, typeArgPos);
|
|
279
|
+
if (elementType)
|
|
280
|
+
scopeEnv.set(varName, elementType);
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* C# pattern binding extractor for `obj is Type variable` (type pattern).
|
|
284
|
+
*
|
|
285
|
+
* AST structure:
|
|
286
|
+
* is_pattern_expression
|
|
287
|
+
* expression: (the variable being tested)
|
|
288
|
+
* pattern: declaration_pattern
|
|
289
|
+
* type: (the declared type)
|
|
290
|
+
* name: single_variable_designation > identifier (the new variable name)
|
|
291
|
+
*
|
|
292
|
+
* Conservative: returns undefined when the pattern field is absent, is not a
|
|
293
|
+
* declaration_pattern, or when the type/name cannot be extracted.
|
|
294
|
+
* No scopeEnv lookup is needed — the pattern explicitly declares the new variable's type.
|
|
295
|
+
*/
|
|
296
|
+
const extractPatternBinding = (node) => {
|
|
297
|
+
// is_pattern_expression: `obj is User user` — has a declaration_pattern child
|
|
298
|
+
if (node.type === 'is_pattern_expression') {
|
|
299
|
+
const pattern = node.childForFieldName('pattern');
|
|
300
|
+
if (pattern?.type !== 'declaration_pattern' && pattern?.type !== 'recursive_pattern')
|
|
301
|
+
return undefined;
|
|
302
|
+
const typeNode = pattern.childForFieldName('type');
|
|
303
|
+
const nameNode = pattern.childForFieldName('name');
|
|
304
|
+
if (!typeNode || !nameNode)
|
|
305
|
+
return undefined;
|
|
306
|
+
const typeName = extractSimpleTypeName(typeNode);
|
|
307
|
+
const varName = extractVarName(nameNode);
|
|
308
|
+
if (!typeName || !varName)
|
|
309
|
+
return undefined;
|
|
310
|
+
return { varName, typeName };
|
|
311
|
+
}
|
|
312
|
+
// declaration_pattern / recursive_pattern: standalone in switch statements and switch expressions
|
|
313
|
+
// `case User u:` or `User u =>` or `User { Name: "Alice" } u =>`
|
|
314
|
+
// Both use the same 'type' and 'name' fields.
|
|
315
|
+
if (node.type === 'declaration_pattern' || node.type === 'recursive_pattern') {
|
|
316
|
+
const typeNode = node.childForFieldName('type');
|
|
317
|
+
const nameNode = node.childForFieldName('name');
|
|
318
|
+
if (!typeNode || !nameNode)
|
|
319
|
+
return undefined;
|
|
320
|
+
const typeName = extractSimpleTypeName(typeNode);
|
|
321
|
+
const varName = extractVarName(nameNode);
|
|
322
|
+
if (!typeName || !varName)
|
|
323
|
+
return undefined;
|
|
324
|
+
return { varName, typeName };
|
|
325
|
+
}
|
|
326
|
+
return undefined;
|
|
327
|
+
};
|
|
328
|
+
/** C#: var alias = u → variable_declarator with name + equals_value_clause.
|
|
329
|
+
* Only local_declaration_statement and variable_declaration contain variable_declarator children;
|
|
330
|
+
* is_pattern_expression and field_declaration never do — skip them early. */
|
|
331
|
+
const extractPendingAssignment = (node, scopeEnv) => {
|
|
332
|
+
if (node.type === 'is_pattern_expression' || node.type === 'field_declaration')
|
|
333
|
+
return undefined;
|
|
334
|
+
for (let i = 0; i < node.namedChildCount; i++) {
|
|
335
|
+
const child = node.namedChild(i);
|
|
336
|
+
if (!child || child.type !== 'variable_declarator')
|
|
337
|
+
continue;
|
|
338
|
+
const nameNode = child.childForFieldName('name');
|
|
339
|
+
if (!nameNode)
|
|
340
|
+
continue;
|
|
341
|
+
const lhs = nameNode.text;
|
|
342
|
+
if (scopeEnv.has(lhs))
|
|
343
|
+
continue;
|
|
344
|
+
// C# wraps value in equals_value_clause; fall back to last named child
|
|
345
|
+
let evc = null;
|
|
346
|
+
for (let j = 0; j < child.childCount; j++) {
|
|
347
|
+
if (child.child(j)?.type === 'equals_value_clause') {
|
|
348
|
+
evc = child.child(j);
|
|
349
|
+
break;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
const valueNode = evc?.firstNamedChild ?? child.namedChild(child.namedChildCount - 1);
|
|
353
|
+
if (valueNode && valueNode !== nameNode && (valueNode.type === 'identifier' || valueNode.type === 'simple_identifier')) {
|
|
354
|
+
return { lhs, rhs: valueNode.text };
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
return undefined;
|
|
358
|
+
};
|
|
85
359
|
export const typeConfig = {
|
|
86
360
|
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
361
|
+
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
|
362
|
+
patternBindingNodeTypes: new Set(['is_pattern_expression', 'declaration_pattern', 'recursive_pattern']),
|
|
87
363
|
extractDeclaration,
|
|
88
364
|
extractParameter,
|
|
365
|
+
scanConstructorBinding,
|
|
366
|
+
extractForLoopBinding,
|
|
367
|
+
extractPendingAssignment,
|
|
368
|
+
extractPatternBinding,
|
|
89
369
|
};
|