gitnexus 1.4.6 → 1.4.8
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 -1
- package/dist/cli/ai-context.d.ts +1 -1
- package/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +54 -21
- package/dist/cli/index.js +2 -1
- package/dist/cli/setup.js +78 -1
- package/dist/config/supported-languages.d.ts +30 -0
- package/dist/config/supported-languages.js +30 -0
- package/dist/core/embeddings/embedder.d.ts +6 -1
- package/dist/core/embeddings/embedder.js +65 -5
- package/dist/core/embeddings/embedding-pipeline.js +11 -9
- package/dist/core/embeddings/http-client.d.ts +31 -0
- package/dist/core/embeddings/http-client.js +179 -0
- package/dist/core/embeddings/index.d.ts +1 -0
- package/dist/core/embeddings/index.js +1 -0
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/types.d.ts +4 -3
- package/dist/core/ingestion/ast-helpers.d.ts +80 -0
- package/dist/core/ingestion/ast-helpers.js +738 -0
- package/dist/core/ingestion/call-analysis.d.ts +73 -0
- package/dist/core/ingestion/call-analysis.js +490 -0
- package/dist/core/ingestion/call-processor.d.ts +55 -2
- package/dist/core/ingestion/call-processor.js +673 -108
- package/dist/core/ingestion/call-routing.d.ts +23 -2
- package/dist/core/ingestion/call-routing.js +21 -0
- package/dist/core/ingestion/entry-point-scoring.js +36 -26
- package/dist/core/ingestion/framework-detection.d.ts +10 -2
- package/dist/core/ingestion/framework-detection.js +49 -12
- package/dist/core/ingestion/heritage-processor.js +47 -49
- package/dist/core/ingestion/import-processor.d.ts +1 -1
- package/dist/core/ingestion/import-processor.js +103 -194
- package/dist/core/ingestion/import-resolution.d.ts +101 -0
- package/dist/core/ingestion/import-resolution.js +251 -0
- package/dist/core/ingestion/language-config.d.ts +3 -0
- package/dist/core/ingestion/language-config.js +13 -0
- package/dist/core/ingestion/markdown-processor.d.ts +17 -0
- package/dist/core/ingestion/markdown-processor.js +124 -0
- package/dist/core/ingestion/mro-processor.js +8 -3
- package/dist/core/ingestion/named-binding-extraction.d.ts +9 -43
- package/dist/core/ingestion/named-binding-extraction.js +89 -79
- package/dist/core/ingestion/parsing-processor.d.ts +3 -2
- package/dist/core/ingestion/parsing-processor.js +27 -60
- package/dist/core/ingestion/pipeline.d.ts +10 -0
- package/dist/core/ingestion/pipeline.js +425 -4
- package/dist/core/ingestion/resolution-context.d.ts +5 -0
- package/dist/core/ingestion/resolution-context.js +7 -4
- package/dist/core/ingestion/resolvers/index.d.ts +1 -1
- package/dist/core/ingestion/resolvers/index.js +1 -1
- package/dist/core/ingestion/resolvers/jvm.d.ts +2 -1
- package/dist/core/ingestion/resolvers/jvm.js +25 -9
- package/dist/core/ingestion/resolvers/php.d.ts +14 -0
- package/dist/core/ingestion/resolvers/php.js +43 -3
- package/dist/core/ingestion/resolvers/utils.d.ts +5 -0
- package/dist/core/ingestion/resolvers/utils.js +16 -0
- package/dist/core/ingestion/symbol-table.d.ts +29 -3
- package/dist/core/ingestion/symbol-table.js +42 -9
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
- package/dist/core/ingestion/tree-sitter-queries.js +243 -2
- package/dist/core/ingestion/type-env.d.ts +28 -1
- package/dist/core/ingestion/type-env.js +451 -72
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +146 -2
- package/dist/core/ingestion/type-extractors/csharp.js +189 -16
- package/dist/core/ingestion/type-extractors/go.js +45 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +1 -1
- package/dist/core/ingestion/type-extractors/index.js +1 -1
- package/dist/core/ingestion/type-extractors/jvm.js +244 -69
- package/dist/core/ingestion/type-extractors/php.js +31 -4
- package/dist/core/ingestion/type-extractors/python.js +89 -17
- package/dist/core/ingestion/type-extractors/ruby.js +17 -2
- package/dist/core/ingestion/type-extractors/rust.js +72 -4
- package/dist/core/ingestion/type-extractors/shared.d.ts +12 -2
- package/dist/core/ingestion/type-extractors/shared.js +115 -13
- package/dist/core/ingestion/type-extractors/swift.js +7 -6
- package/dist/core/ingestion/type-extractors/types.d.ts +54 -11
- package/dist/core/ingestion/type-extractors/typescript.js +171 -9
- package/dist/core/ingestion/utils.d.ts +2 -95
- package/dist/core/ingestion/utils.js +3 -892
- package/dist/core/ingestion/workers/parse-worker.d.ts +36 -11
- package/dist/core/ingestion/workers/parse-worker.js +116 -95
- package/dist/core/lbug/csv-generator.js +18 -1
- package/dist/core/lbug/lbug-adapter.d.ts +12 -0
- package/dist/core/lbug/lbug-adapter.js +71 -4
- package/dist/core/lbug/schema.d.ts +6 -4
- package/dist/core/lbug/schema.js +27 -3
- package/dist/mcp/core/embedder.js +11 -3
- package/dist/mcp/core/lbug-adapter.d.ts +22 -0
- package/dist/mcp/core/lbug-adapter.js +178 -23
- package/dist/mcp/local/local-backend.d.ts +22 -0
- package/dist/mcp/local/local-backend.js +136 -32
- package/dist/mcp/resources.js +13 -0
- package/dist/mcp/server.js +26 -4
- package/dist/mcp/tools.js +17 -7
- package/dist/server/api.d.ts +19 -1
- package/dist/server/api.js +66 -6
- package/dist/storage/git.d.ts +12 -0
- package/dist/storage/git.js +21 -0
- package/package.json +12 -4
|
@@ -1,2 +1,7 @@
|
|
|
1
|
+
import type { SyntaxNode } from '../utils.js';
|
|
1
2
|
import type { LanguageTypeConfig } from './types.js';
|
|
3
|
+
/** Extract the first type name from a template_argument_list child.
|
|
4
|
+
* Unwraps type_descriptor wrappers common in tree-sitter-cpp ASTs.
|
|
5
|
+
* Returns undefined if no template arguments or no type found. */
|
|
6
|
+
export declare const extractFirstTemplateTypeArg: (parentNode: SyntaxNode) => string | undefined;
|
|
2
7
|
export declare const typeConfig: LanguageTypeConfig;
|
|
@@ -2,6 +2,28 @@ import { extractSimpleTypeName, extractVarName, resolveIterableElementType, meth
|
|
|
2
2
|
const DECLARATION_NODE_TYPES = new Set([
|
|
3
3
|
'declaration',
|
|
4
4
|
]);
|
|
5
|
+
/** Smart pointer factory function names that create a typed object. */
|
|
6
|
+
const SMART_PTR_FACTORIES = new Set([
|
|
7
|
+
'make_shared', 'make_unique', 'make_shared_for_overwrite',
|
|
8
|
+
]);
|
|
9
|
+
/** Smart pointer wrapper type names. When the declared type is a smart pointer,
|
|
10
|
+
* the inner template type is extracted for virtual dispatch comparison. */
|
|
11
|
+
const SMART_PTR_WRAPPERS = new Set(['shared_ptr', 'unique_ptr', 'weak_ptr']);
|
|
12
|
+
/** Extract the first type name from a template_argument_list child.
|
|
13
|
+
* Unwraps type_descriptor wrappers common in tree-sitter-cpp ASTs.
|
|
14
|
+
* Returns undefined if no template arguments or no type found. */
|
|
15
|
+
export const extractFirstTemplateTypeArg = (parentNode) => {
|
|
16
|
+
const templateArgs = parentNode.children.find((c) => c.type === 'template_argument_list');
|
|
17
|
+
if (!templateArgs?.firstNamedChild)
|
|
18
|
+
return undefined;
|
|
19
|
+
let argNode = templateArgs.firstNamedChild;
|
|
20
|
+
if (argNode.type === 'type_descriptor') {
|
|
21
|
+
const inner = argNode.childForFieldName('type');
|
|
22
|
+
if (inner)
|
|
23
|
+
argNode = inner;
|
|
24
|
+
}
|
|
25
|
+
return extractSimpleTypeName(argNode) ?? undefined;
|
|
26
|
+
};
|
|
5
27
|
/** C++: Type x = ...; Type* x; Type& x; */
|
|
6
28
|
const extractDeclaration = (node, env) => {
|
|
7
29
|
const typeNode = node.childForFieldName('type');
|
|
@@ -89,6 +111,29 @@ const extractInitializer = (node, env, classNames) => {
|
|
|
89
111
|
if (text && classNames.has(text))
|
|
90
112
|
env.set(varName, text);
|
|
91
113
|
}
|
|
114
|
+
else {
|
|
115
|
+
// auto x = std::make_shared<Dog>() — smart pointer factory via template_function.
|
|
116
|
+
// AST: call_expression > function: qualified_identifier > template_function
|
|
117
|
+
// or: call_expression > function: template_function (unqualified)
|
|
118
|
+
const templateFunc = func.type === 'template_function'
|
|
119
|
+
? func
|
|
120
|
+
: (func.type === 'qualified_identifier' || func.type === 'scoped_identifier')
|
|
121
|
+
? func.namedChildren.find((c) => c.type === 'template_function') ?? null
|
|
122
|
+
: null;
|
|
123
|
+
if (templateFunc) {
|
|
124
|
+
const nameNode = templateFunc.firstNamedChild;
|
|
125
|
+
if (nameNode) {
|
|
126
|
+
const funcName = (nameNode.type === 'qualified_identifier' || nameNode.type === 'scoped_identifier')
|
|
127
|
+
? nameNode.lastNamedChild?.text ?? ''
|
|
128
|
+
: nameNode.text;
|
|
129
|
+
if (SMART_PTR_FACTORIES.has(funcName)) {
|
|
130
|
+
const typeName = extractFirstTemplateTypeArg(templateFunc);
|
|
131
|
+
if (typeName)
|
|
132
|
+
env.set(varName, typeName);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
92
137
|
return;
|
|
93
138
|
}
|
|
94
139
|
// auto x = User{} — compound_literal_expression (brace initialization)
|
|
@@ -186,7 +231,7 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
186
231
|
if (!declarator || declarator.type !== 'init_declarator')
|
|
187
232
|
return undefined;
|
|
188
233
|
const value = declarator.childForFieldName('value');
|
|
189
|
-
if (!value
|
|
234
|
+
if (!value)
|
|
190
235
|
return undefined;
|
|
191
236
|
const nameNode = declarator.childForFieldName('declarator');
|
|
192
237
|
if (!nameNode)
|
|
@@ -198,7 +243,32 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
198
243
|
const lhs = extractVarName(finalName);
|
|
199
244
|
if (!lhs || scopeEnv.has(lhs))
|
|
200
245
|
return undefined;
|
|
201
|
-
|
|
246
|
+
if (value.type === 'identifier')
|
|
247
|
+
return { kind: 'copy', lhs, rhs: value.text };
|
|
248
|
+
// field_expression RHS → fieldAccess (a.field)
|
|
249
|
+
if (value.type === 'field_expression') {
|
|
250
|
+
const obj = value.firstNamedChild;
|
|
251
|
+
const field = value.lastNamedChild;
|
|
252
|
+
if (obj?.type === 'identifier' && field?.type === 'field_identifier') {
|
|
253
|
+
return { kind: 'fieldAccess', lhs, receiver: obj.text, field: field.text };
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// call_expression RHS
|
|
257
|
+
if (value.type === 'call_expression') {
|
|
258
|
+
const funcNode = value.childForFieldName('function');
|
|
259
|
+
if (funcNode?.type === 'identifier') {
|
|
260
|
+
return { kind: 'callResult', lhs, callee: funcNode.text };
|
|
261
|
+
}
|
|
262
|
+
// method call with receiver: call_expression → function: field_expression
|
|
263
|
+
if (funcNode?.type === 'field_expression') {
|
|
264
|
+
const obj = funcNode.firstNamedChild;
|
|
265
|
+
const field = funcNode.lastNamedChild;
|
|
266
|
+
if (obj?.type === 'identifier' && field?.type === 'field_identifier') {
|
|
267
|
+
return { kind: 'methodCallResult', lhs, receiver: obj.text, method: field.text };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return undefined;
|
|
202
272
|
};
|
|
203
273
|
// --- For-loop Tier 1c ---
|
|
204
274
|
const FOR_LOOP_NODE_TYPES = new Set(['for_range_loop']);
|
|
@@ -373,6 +443,77 @@ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope })
|
|
|
373
443
|
if (elementType)
|
|
374
444
|
scopeEnv.set(varName, elementType);
|
|
375
445
|
};
|
|
446
|
+
/** Infer the type of a literal AST node for C++ overload disambiguation. */
|
|
447
|
+
const inferLiteralType = (node) => {
|
|
448
|
+
switch (node.type) {
|
|
449
|
+
case 'number_literal': {
|
|
450
|
+
const t = node.text;
|
|
451
|
+
// Float suffixes
|
|
452
|
+
if (t.endsWith('f') || t.endsWith('F'))
|
|
453
|
+
return 'float';
|
|
454
|
+
if (t.includes('.') || t.includes('e') || t.includes('E'))
|
|
455
|
+
return 'double';
|
|
456
|
+
// Long suffix
|
|
457
|
+
if (t.endsWith('L') || t.endsWith('l') || t.endsWith('LL') || t.endsWith('ll'))
|
|
458
|
+
return 'long';
|
|
459
|
+
return 'int';
|
|
460
|
+
}
|
|
461
|
+
case 'string_literal':
|
|
462
|
+
case 'raw_string_literal':
|
|
463
|
+
case 'concatenated_string':
|
|
464
|
+
return 'string';
|
|
465
|
+
case 'char_literal':
|
|
466
|
+
return 'char';
|
|
467
|
+
case 'true':
|
|
468
|
+
case 'false':
|
|
469
|
+
return 'bool';
|
|
470
|
+
case 'null':
|
|
471
|
+
case 'nullptr':
|
|
472
|
+
return 'null';
|
|
473
|
+
default:
|
|
474
|
+
return undefined;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
477
|
+
/** C++: detect constructor type from smart pointer factory calls (make_shared<Dog>()).
|
|
478
|
+
* Extracts the template type argument as the constructor type for virtual dispatch. */
|
|
479
|
+
const detectCppConstructorType = (node, classNames) => {
|
|
480
|
+
// Navigate to the initializer value in the declaration
|
|
481
|
+
const declarator = node.childForFieldName('declarator');
|
|
482
|
+
const initDecl = declarator?.type === 'init_declarator' ? declarator : undefined;
|
|
483
|
+
if (!initDecl)
|
|
484
|
+
return undefined;
|
|
485
|
+
const value = initDecl.childForFieldName('value');
|
|
486
|
+
if (!value || value.type !== 'call_expression')
|
|
487
|
+
return undefined;
|
|
488
|
+
// Check for template_function pattern: make_shared<Dog>()
|
|
489
|
+
const func = value.childForFieldName('function');
|
|
490
|
+
if (!func || func.type !== 'template_function')
|
|
491
|
+
return undefined;
|
|
492
|
+
// Extract function name (possibly qualified: std::make_shared)
|
|
493
|
+
const nameNode = func.firstNamedChild;
|
|
494
|
+
if (!nameNode)
|
|
495
|
+
return undefined;
|
|
496
|
+
let funcName;
|
|
497
|
+
if (nameNode.type === 'qualified_identifier' || nameNode.type === 'scoped_identifier') {
|
|
498
|
+
funcName = nameNode.lastNamedChild?.text ?? '';
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
funcName = nameNode.text;
|
|
502
|
+
}
|
|
503
|
+
if (!SMART_PTR_FACTORIES.has(funcName))
|
|
504
|
+
return undefined;
|
|
505
|
+
// Extract template type argument
|
|
506
|
+
return extractFirstTemplateTypeArg(func);
|
|
507
|
+
};
|
|
508
|
+
/** Unwrap a C++ smart pointer declared type to its inner template type.
|
|
509
|
+
* E.g., shared_ptr<Animal> → Animal. Returns the original name if not a smart pointer. */
|
|
510
|
+
const unwrapCppDeclaredType = (declaredType, typeNode) => {
|
|
511
|
+
if (!SMART_PTR_WRAPPERS.has(declaredType))
|
|
512
|
+
return declaredType;
|
|
513
|
+
if (typeNode.type !== 'template_type')
|
|
514
|
+
return declaredType;
|
|
515
|
+
return extractFirstTemplateTypeArg(typeNode) ?? declaredType;
|
|
516
|
+
};
|
|
376
517
|
export const typeConfig = {
|
|
377
518
|
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
378
519
|
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
|
@@ -382,4 +523,7 @@ export const typeConfig = {
|
|
|
382
523
|
scanConstructorBinding,
|
|
383
524
|
extractForLoopBinding,
|
|
384
525
|
extractPendingAssignment,
|
|
526
|
+
inferLiteralType,
|
|
527
|
+
detectConstructorType: detectCppConstructorType,
|
|
528
|
+
unwrapDeclaredType: unwrapCppDeclaredType,
|
|
385
529
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { extractSimpleTypeName, extractVarName,
|
|
1
|
+
import { extractSimpleTypeName, extractVarName, unwrapAwait, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
|
|
2
|
+
import { findChild } from '../resolvers/utils.js';
|
|
2
3
|
/** Known container property accessors that operate on the container itself (e.g., dict.Keys, dict.Values) */
|
|
3
4
|
const KNOWN_CONTAINER_PROPS = new Set(['Keys', 'Values']);
|
|
4
5
|
const DECLARATION_NODE_TYPES = new Set([
|
|
@@ -43,8 +44,8 @@ const extractDeclaration = (node, env) => {
|
|
|
43
44
|
// tree-sitter-c-sharp may put object_creation_expression as direct child
|
|
44
45
|
// or inside equals_value_clause depending on grammar version
|
|
45
46
|
if (declarators.length === 1) {
|
|
46
|
-
const initializer =
|
|
47
|
-
??
|
|
47
|
+
const initializer = findChild(declarators[0], 'object_creation_expression')
|
|
48
|
+
?? findChild(declarators[0], 'equals_value_clause')?.firstNamedChild;
|
|
48
49
|
if (initializer?.type === 'object_creation_expression') {
|
|
49
50
|
const ctorType = initializer.childForFieldName('type');
|
|
50
51
|
if (ctorType)
|
|
@@ -159,7 +160,7 @@ const extractCSharpElementTypeFromTypeNode = (typeNode, pos = 'last', depth = 0)
|
|
|
159
160
|
// generic_name: List<User>, IEnumerable<User>, Dictionary<string, User>
|
|
160
161
|
// C# uses generic_name (not generic_type)
|
|
161
162
|
if (typeNode.type === 'generic_name') {
|
|
162
|
-
const argList =
|
|
163
|
+
const argList = findChild(typeNode, 'type_argument_list');
|
|
163
164
|
if (argList && argList.namedChildCount >= 1) {
|
|
164
165
|
if (pos === 'first') {
|
|
165
166
|
const firstArg = argList.namedChild(0);
|
|
@@ -307,21 +308,89 @@ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, re
|
|
|
307
308
|
* declaration_pattern, or when the type/name cannot be extracted.
|
|
308
309
|
* No scopeEnv lookup is needed — the pattern explicitly declares the new variable's type.
|
|
309
310
|
*/
|
|
310
|
-
|
|
311
|
+
/**
|
|
312
|
+
* Find the if-body (consequence) block for a C# null-check.
|
|
313
|
+
* Walks up from the expression to find the enclosing if_statement,
|
|
314
|
+
* then returns its first block child (the truthy branch body).
|
|
315
|
+
*/
|
|
316
|
+
const findCSharpIfConsequenceBlock = (expr) => {
|
|
317
|
+
let current = expr.parent;
|
|
318
|
+
while (current) {
|
|
319
|
+
if (current.type === 'if_statement') {
|
|
320
|
+
// C# if_statement consequence is the 'consequence' field or first block child
|
|
321
|
+
const consequence = current.childForFieldName('consequence');
|
|
322
|
+
if (consequence)
|
|
323
|
+
return consequence;
|
|
324
|
+
for (let i = 0; i < current.childCount; i++) {
|
|
325
|
+
const child = current.child(i);
|
|
326
|
+
if (child?.type === 'block')
|
|
327
|
+
return child;
|
|
328
|
+
}
|
|
329
|
+
return undefined;
|
|
330
|
+
}
|
|
331
|
+
if (current.type === 'block' || current.type === 'method_declaration'
|
|
332
|
+
|| current.type === 'constructor_declaration' || current.type === 'local_function_statement'
|
|
333
|
+
|| current.type === 'lambda_expression')
|
|
334
|
+
return undefined;
|
|
335
|
+
current = current.parent;
|
|
336
|
+
}
|
|
337
|
+
return undefined;
|
|
338
|
+
};
|
|
339
|
+
/** Check if a C# declaration type node represents a nullable type.
|
|
340
|
+
* Checks for nullable_type AST node or '?' in the type text (e.g., User?). */
|
|
341
|
+
const isCSharpNullableDecl = (declTypeNode) => {
|
|
342
|
+
if (declTypeNode.type === 'nullable_type')
|
|
343
|
+
return true;
|
|
344
|
+
return declTypeNode.text.includes('?');
|
|
345
|
+
};
|
|
346
|
+
const extractPatternBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
|
|
311
347
|
// is_pattern_expression: `obj is User user` — has a declaration_pattern child
|
|
348
|
+
// Also handles `x is not null` for null-check narrowing
|
|
312
349
|
if (node.type === 'is_pattern_expression') {
|
|
313
350
|
const pattern = node.childForFieldName('pattern');
|
|
314
|
-
if (pattern
|
|
351
|
+
if (!pattern)
|
|
315
352
|
return undefined;
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
353
|
+
// Standard type pattern: `obj is User user`
|
|
354
|
+
if (pattern.type === 'declaration_pattern' || pattern.type === 'recursive_pattern') {
|
|
355
|
+
const typeNode = pattern.childForFieldName('type');
|
|
356
|
+
const nameNode = pattern.childForFieldName('name');
|
|
357
|
+
if (!typeNode || !nameNode)
|
|
358
|
+
return undefined;
|
|
359
|
+
const typeName = extractSimpleTypeName(typeNode);
|
|
360
|
+
const varName = extractVarName(nameNode);
|
|
361
|
+
if (!typeName || !varName)
|
|
362
|
+
return undefined;
|
|
363
|
+
return { varName, typeName };
|
|
364
|
+
}
|
|
365
|
+
// Null-check: `x is not null` — negated_pattern > constant_pattern > null_literal
|
|
366
|
+
if (pattern.type === 'negated_pattern') {
|
|
367
|
+
const inner = pattern.firstNamedChild;
|
|
368
|
+
if (inner?.type === 'constant_pattern') {
|
|
369
|
+
const literal = inner.firstNamedChild ?? inner.firstChild;
|
|
370
|
+
if (literal?.type === 'null_literal' || literal?.text === 'null') {
|
|
371
|
+
const expr = node.childForFieldName('expression');
|
|
372
|
+
if (!expr || expr.type !== 'identifier')
|
|
373
|
+
return undefined;
|
|
374
|
+
const varName = expr.text;
|
|
375
|
+
const resolvedType = scopeEnv.get(varName);
|
|
376
|
+
if (!resolvedType)
|
|
377
|
+
return undefined;
|
|
378
|
+
// Verify the original declaration was nullable
|
|
379
|
+
const declTypeNode = declarationTypeNodes.get(`${scope}\0${varName}`);
|
|
380
|
+
if (!declTypeNode || !isCSharpNullableDecl(declTypeNode))
|
|
381
|
+
return undefined;
|
|
382
|
+
const ifBody = findCSharpIfConsequenceBlock(node);
|
|
383
|
+
if (!ifBody)
|
|
384
|
+
return undefined;
|
|
385
|
+
return {
|
|
386
|
+
varName,
|
|
387
|
+
typeName: resolvedType,
|
|
388
|
+
narrowingRange: { startIndex: ifBody.startIndex, endIndex: ifBody.endIndex },
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return undefined;
|
|
325
394
|
}
|
|
326
395
|
// declaration_pattern / recursive_pattern: standalone in switch statements and switch expressions
|
|
327
396
|
// `case User u:` or `User u =>` or `User { Name: "Alice" } u =>`
|
|
@@ -337,6 +406,41 @@ const extractPatternBinding = (node) => {
|
|
|
337
406
|
return undefined;
|
|
338
407
|
return { varName, typeName };
|
|
339
408
|
}
|
|
409
|
+
// Null-check: `x != null` — binary_expression with != operator
|
|
410
|
+
if (node.type === 'binary_expression') {
|
|
411
|
+
const op = node.children.find(c => !c.isNamed && c.text === '!=');
|
|
412
|
+
if (!op)
|
|
413
|
+
return undefined;
|
|
414
|
+
const left = node.namedChild(0);
|
|
415
|
+
const right = node.namedChild(1);
|
|
416
|
+
if (!left || !right)
|
|
417
|
+
return undefined;
|
|
418
|
+
let varNode;
|
|
419
|
+
if (left.type === 'identifier' && (right.type === 'null_literal' || right.text === 'null')) {
|
|
420
|
+
varNode = left;
|
|
421
|
+
}
|
|
422
|
+
else if (right.type === 'identifier' && (left.type === 'null_literal' || left.text === 'null')) {
|
|
423
|
+
varNode = right;
|
|
424
|
+
}
|
|
425
|
+
if (!varNode)
|
|
426
|
+
return undefined;
|
|
427
|
+
const varName = varNode.text;
|
|
428
|
+
const resolvedType = scopeEnv.get(varName);
|
|
429
|
+
if (!resolvedType)
|
|
430
|
+
return undefined;
|
|
431
|
+
// Verify the original declaration was nullable
|
|
432
|
+
const declTypeNode = declarationTypeNodes.get(`${scope}\0${varName}`);
|
|
433
|
+
if (!declTypeNode || !isCSharpNullableDecl(declTypeNode))
|
|
434
|
+
return undefined;
|
|
435
|
+
const ifBody = findCSharpIfConsequenceBlock(node);
|
|
436
|
+
if (!ifBody)
|
|
437
|
+
return undefined;
|
|
438
|
+
return {
|
|
439
|
+
varName,
|
|
440
|
+
typeName: resolvedType,
|
|
441
|
+
narrowingRange: { startIndex: ifBody.startIndex, endIndex: ifBody.endIndex },
|
|
442
|
+
};
|
|
443
|
+
}
|
|
340
444
|
return undefined;
|
|
341
445
|
};
|
|
342
446
|
/** C#: var alias = u → variable_declarator with name + equals_value_clause.
|
|
@@ -367,17 +471,86 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
367
471
|
if (valueNode && valueNode !== nameNode && (valueNode.type === 'identifier' || valueNode.type === 'simple_identifier')) {
|
|
368
472
|
return { kind: 'copy', lhs, rhs: valueNode.text };
|
|
369
473
|
}
|
|
474
|
+
// member_access_expression RHS → fieldAccess (a.Field)
|
|
475
|
+
if (valueNode?.type === 'member_access_expression') {
|
|
476
|
+
const expr = valueNode.childForFieldName('expression');
|
|
477
|
+
const name = valueNode.childForFieldName('name');
|
|
478
|
+
if (expr?.type === 'identifier' && name?.type === 'identifier') {
|
|
479
|
+
return { kind: 'fieldAccess', lhs, receiver: expr.text, field: name.text };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// invocation_expression RHS
|
|
483
|
+
if (valueNode?.type === 'invocation_expression') {
|
|
484
|
+
const funcNode = valueNode.firstNamedChild;
|
|
485
|
+
if (funcNode?.type === 'identifier_name' || funcNode?.type === 'identifier') {
|
|
486
|
+
return { kind: 'callResult', lhs, callee: funcNode.text };
|
|
487
|
+
}
|
|
488
|
+
// method call with receiver → methodCallResult: a.GetC()
|
|
489
|
+
if (funcNode?.type === 'member_access_expression') {
|
|
490
|
+
const expr = funcNode.childForFieldName('expression');
|
|
491
|
+
const name = funcNode.childForFieldName('name');
|
|
492
|
+
if (expr?.type === 'identifier' && name?.type === 'identifier') {
|
|
493
|
+
return { kind: 'methodCallResult', lhs, receiver: expr.text, method: name.text };
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
// await_expression → unwrap and check inner
|
|
498
|
+
if (valueNode?.type === 'await_expression') {
|
|
499
|
+
const inner = valueNode.firstNamedChild;
|
|
500
|
+
if (inner?.type === 'invocation_expression') {
|
|
501
|
+
const funcNode = inner.firstNamedChild;
|
|
502
|
+
if (funcNode?.type === 'identifier_name' || funcNode?.type === 'identifier') {
|
|
503
|
+
return { kind: 'callResult', lhs, callee: funcNode.text };
|
|
504
|
+
}
|
|
505
|
+
if (funcNode?.type === 'member_access_expression') {
|
|
506
|
+
const expr = funcNode.childForFieldName('expression');
|
|
507
|
+
const name = funcNode.childForFieldName('name');
|
|
508
|
+
if (expr?.type === 'identifier' && name?.type === 'identifier') {
|
|
509
|
+
return { kind: 'methodCallResult', lhs, receiver: expr.text, method: name.text };
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
}
|
|
370
514
|
}
|
|
371
515
|
return undefined;
|
|
372
516
|
};
|
|
517
|
+
/** Infer the type of a literal AST node for C# overload disambiguation. */
|
|
518
|
+
const inferLiteralType = (node) => {
|
|
519
|
+
switch (node.type) {
|
|
520
|
+
case 'integer_literal':
|
|
521
|
+
if (node.text.endsWith('L') || node.text.endsWith('l'))
|
|
522
|
+
return 'long';
|
|
523
|
+
return 'int';
|
|
524
|
+
case 'real_literal':
|
|
525
|
+
if (node.text.endsWith('f') || node.text.endsWith('F'))
|
|
526
|
+
return 'float';
|
|
527
|
+
if (node.text.endsWith('m') || node.text.endsWith('M'))
|
|
528
|
+
return 'decimal';
|
|
529
|
+
return 'double';
|
|
530
|
+
case 'string_literal':
|
|
531
|
+
case 'verbatim_string_literal':
|
|
532
|
+
case 'raw_string_literal':
|
|
533
|
+
case 'interpolated_string_expression':
|
|
534
|
+
return 'string';
|
|
535
|
+
case 'character_literal':
|
|
536
|
+
return 'char';
|
|
537
|
+
case 'boolean_literal':
|
|
538
|
+
return 'bool';
|
|
539
|
+
case 'null_literal':
|
|
540
|
+
return 'null';
|
|
541
|
+
default:
|
|
542
|
+
return undefined;
|
|
543
|
+
}
|
|
544
|
+
};
|
|
373
545
|
export const typeConfig = {
|
|
374
546
|
declarationNodeTypes: DECLARATION_NODE_TYPES,
|
|
375
547
|
forLoopNodeTypes: FOR_LOOP_NODE_TYPES,
|
|
376
|
-
patternBindingNodeTypes: new Set(['is_pattern_expression', 'declaration_pattern', 'recursive_pattern']),
|
|
548
|
+
patternBindingNodeTypes: new Set(['is_pattern_expression', 'declaration_pattern', 'recursive_pattern', 'binary_expression']),
|
|
377
549
|
extractDeclaration,
|
|
378
550
|
extractParameter,
|
|
379
551
|
scanConstructorBinding,
|
|
380
552
|
extractForLoopBinding,
|
|
381
553
|
extractPendingAssignment,
|
|
382
554
|
extractPatternBinding,
|
|
555
|
+
inferLiteralType,
|
|
383
556
|
};
|
|
@@ -419,6 +419,29 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
419
419
|
return undefined;
|
|
420
420
|
if (rhsNode.type === 'identifier')
|
|
421
421
|
return { kind: 'copy', lhs, rhs: rhsNode.text };
|
|
422
|
+
// selector_expression RHS → fieldAccess (a.field)
|
|
423
|
+
if (rhsNode.type === 'selector_expression') {
|
|
424
|
+
const operand = rhsNode.childForFieldName('operand');
|
|
425
|
+
const field = rhsNode.childForFieldName('field');
|
|
426
|
+
if (operand?.type === 'identifier' && field) {
|
|
427
|
+
return { kind: 'fieldAccess', lhs, receiver: operand.text, field: field.text };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// call_expression RHS
|
|
431
|
+
if (rhsNode.type === 'call_expression') {
|
|
432
|
+
const funcNode = rhsNode.childForFieldName('function');
|
|
433
|
+
if (funcNode?.type === 'identifier') {
|
|
434
|
+
return { kind: 'callResult', lhs, callee: funcNode.text };
|
|
435
|
+
}
|
|
436
|
+
// method call with receiver: call_expression → function: selector_expression
|
|
437
|
+
if (funcNode?.type === 'selector_expression') {
|
|
438
|
+
const operand = funcNode.childForFieldName('operand');
|
|
439
|
+
const field = funcNode.childForFieldName('field');
|
|
440
|
+
if (operand?.type === 'identifier' && field) {
|
|
441
|
+
return { kind: 'methodCallResult', lhs, receiver: operand.text, method: field.text };
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
422
445
|
return undefined;
|
|
423
446
|
}
|
|
424
447
|
if (node.type === 'var_spec' || node.type === 'var_declaration') {
|
|
@@ -452,6 +475,28 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
452
475
|
const rhsNode = exprList?.firstNamedChild;
|
|
453
476
|
if (rhsNode?.type === 'identifier')
|
|
454
477
|
return { kind: 'copy', lhs, rhs: rhsNode.text };
|
|
478
|
+
// selector_expression RHS → fieldAccess
|
|
479
|
+
if (rhsNode?.type === 'selector_expression') {
|
|
480
|
+
const operand = rhsNode.childForFieldName('operand');
|
|
481
|
+
const field = rhsNode.childForFieldName('field');
|
|
482
|
+
if (operand?.type === 'identifier' && field) {
|
|
483
|
+
return { kind: 'fieldAccess', lhs, receiver: operand.text, field: field.text };
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// call_expression RHS
|
|
487
|
+
if (rhsNode?.type === 'call_expression') {
|
|
488
|
+
const funcNode = rhsNode.childForFieldName('function');
|
|
489
|
+
if (funcNode?.type === 'identifier') {
|
|
490
|
+
return { kind: 'callResult', lhs, callee: funcNode.text };
|
|
491
|
+
}
|
|
492
|
+
if (funcNode?.type === 'selector_expression') {
|
|
493
|
+
const operand = funcNode.childForFieldName('operand');
|
|
494
|
+
const field = funcNode.childForFieldName('field');
|
|
495
|
+
if (operand?.type === 'identifier' && field) {
|
|
496
|
+
return { kind: 'methodCallResult', lhs, receiver: operand.text, method: field.text };
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
455
500
|
}
|
|
456
501
|
}
|
|
457
502
|
return undefined;
|
|
@@ -19,4 +19,4 @@ export declare const typeConfigs: {
|
|
|
19
19
|
ruby: LanguageTypeConfig;
|
|
20
20
|
};
|
|
21
21
|
export type { LanguageTypeConfig, TypeBindingExtractor, ParameterExtractor, ConstructorBindingScanner, ForLoopExtractor, PendingAssignmentExtractor, PatternBindingExtractor, } from './types.js';
|
|
22
|
-
export { TYPED_PARAMETER_TYPES, extractSimpleTypeName, extractGenericTypeArgs, extractVarName,
|
|
22
|
+
export { TYPED_PARAMETER_TYPES, extractSimpleTypeName, extractGenericTypeArgs, extractVarName, extractRubyConstructorAssignment } from './shared.js';
|
|
@@ -28,4 +28,4 @@ export const typeConfigs = {
|
|
|
28
28
|
[SupportedLanguages.PHP]: phpConfig,
|
|
29
29
|
[SupportedLanguages.Ruby]: rubyConfig,
|
|
30
30
|
};
|
|
31
|
-
export { TYPED_PARAMETER_TYPES, extractSimpleTypeName, extractGenericTypeArgs, extractVarName,
|
|
31
|
+
export { TYPED_PARAMETER_TYPES, extractSimpleTypeName, extractGenericTypeArgs, extractVarName, extractRubyConstructorAssignment } from './shared.js';
|