gitnexus 1.4.7 → 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 +2 -1
- 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 +48 -1
- package/dist/core/ingestion/call-processor.js +368 -7
- package/dist/core/ingestion/call-routing.d.ts +6 -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 +2 -2
- package/dist/core/ingestion/parsing-processor.js +14 -73
- package/dist/core/ingestion/pipeline.d.ts +10 -0
- package/dist/core/ingestion/pipeline.js +421 -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 +16 -0
- package/dist/core/ingestion/symbol-table.js +20 -6
- package/dist/core/ingestion/tree-sitter-queries.d.ts +4 -4
- package/dist/core/ingestion/tree-sitter-queries.js +43 -2
- package/dist/core/ingestion/type-env.d.ts +28 -1
- package/dist/core/ingestion/type-env.js +419 -96
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +119 -0
- package/dist/core/ingestion/type-extractors/csharp.js +149 -16
- 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 +169 -66
- package/dist/core/ingestion/type-extractors/rust.js +35 -1
- package/dist/core/ingestion/type-extractors/shared.d.ts +0 -2
- package/dist/core/ingestion/type-extractors/shared.js +5 -10
- package/dist/core/ingestion/type-extractors/swift.js +7 -6
- package/dist/core/ingestion/type-extractors/types.d.ts +37 -7
- package/dist/core/ingestion/type-extractors/typescript.js +141 -9
- package/dist/core/ingestion/utils.d.ts +2 -120
- package/dist/core/ingestion/utils.js +3 -1051
- package/dist/core/ingestion/workers/parse-worker.d.ts +13 -4
- package/dist/core/ingestion/workers/parse-worker.js +66 -87
- package/dist/core/lbug/csv-generator.js +18 -1
- package/dist/core/lbug/lbug-adapter.d.ts +10 -0
- package/dist/core/lbug/lbug-adapter.js +69 -4
- package/dist/core/lbug/schema.d.ts +5 -3
- package/dist/core/lbug/schema.js +26 -2
- package/dist/mcp/core/embedder.js +11 -3
- package/dist/mcp/core/lbug-adapter.js +12 -1
- package/dist/mcp/local/local-backend.d.ts +22 -0
- package/dist/mcp/local/local-backend.js +133 -29
- package/dist/mcp/resources.js +2 -0
- package/dist/mcp/tools.js +2 -2
- 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 +10 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { extractSimpleTypeName, extractVarName,
|
|
1
|
+
import { extractSimpleTypeName, extractVarName, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
|
|
2
|
+
import { findChild } from '../resolvers/utils.js';
|
|
2
3
|
// ── Java ──────────────────────────────────────────────────────────────────
|
|
3
4
|
const JAVA_DECLARATION_NODE_TYPES = new Set([
|
|
4
5
|
'local_variable_declaration',
|
|
@@ -78,7 +79,7 @@ const scanJavaConstructorBinding = (node) => {
|
|
|
78
79
|
return undefined;
|
|
79
80
|
if (typeNode.text !== 'var')
|
|
80
81
|
return undefined;
|
|
81
|
-
const declarator =
|
|
82
|
+
const declarator = findChild(node, 'variable_declarator');
|
|
82
83
|
if (!declarator)
|
|
83
84
|
return undefined;
|
|
84
85
|
const nameNode = declarator.childForFieldName('name');
|
|
@@ -293,6 +294,39 @@ const extractJavaPatternBinding = (node) => {
|
|
|
293
294
|
return undefined;
|
|
294
295
|
return { varName, typeName };
|
|
295
296
|
};
|
|
297
|
+
/** Infer the type of a literal AST node for Java/Kotlin overload disambiguation. */
|
|
298
|
+
const inferJvmLiteralType = (node) => {
|
|
299
|
+
switch (node.type) {
|
|
300
|
+
case 'decimal_integer_literal':
|
|
301
|
+
case 'integer_literal':
|
|
302
|
+
case 'hex_integer_literal':
|
|
303
|
+
case 'octal_integer_literal':
|
|
304
|
+
case 'binary_integer_literal':
|
|
305
|
+
// Check for long suffix
|
|
306
|
+
if (node.text.endsWith('L') || node.text.endsWith('l'))
|
|
307
|
+
return 'long';
|
|
308
|
+
return 'int';
|
|
309
|
+
case 'decimal_floating_point_literal':
|
|
310
|
+
case 'real_literal':
|
|
311
|
+
if (node.text.endsWith('f') || node.text.endsWith('F'))
|
|
312
|
+
return 'float';
|
|
313
|
+
return 'double';
|
|
314
|
+
case 'string_literal':
|
|
315
|
+
case 'line_string_literal':
|
|
316
|
+
case 'multi_line_string_literal':
|
|
317
|
+
return 'String';
|
|
318
|
+
case 'character_literal':
|
|
319
|
+
return 'char';
|
|
320
|
+
case 'true':
|
|
321
|
+
case 'false':
|
|
322
|
+
case 'boolean_literal':
|
|
323
|
+
return 'boolean';
|
|
324
|
+
case 'null_literal':
|
|
325
|
+
return 'null';
|
|
326
|
+
default:
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
};
|
|
296
330
|
export const javaTypeConfig = {
|
|
297
331
|
declarationNodeTypes: JAVA_DECLARATION_NODE_TYPES,
|
|
298
332
|
forLoopNodeTypes: JAVA_FOR_LOOP_NODE_TYPES,
|
|
@@ -304,6 +338,7 @@ export const javaTypeConfig = {
|
|
|
304
338
|
extractForLoopBinding: extractJavaForLoopBinding,
|
|
305
339
|
extractPendingAssignment: extractJavaPendingAssignment,
|
|
306
340
|
extractPatternBinding: extractJavaPatternBinding,
|
|
341
|
+
inferLiteralType: inferJvmLiteralType,
|
|
307
342
|
};
|
|
308
343
|
// ── Kotlin ────────────────────────────────────────────────────────────────
|
|
309
344
|
const KOTLIN_DECLARATION_NODE_TYPES = new Set([
|
|
@@ -314,10 +349,11 @@ const KOTLIN_DECLARATION_NODE_TYPES = new Set([
|
|
|
314
349
|
const extractKotlinDeclaration = (node, env) => {
|
|
315
350
|
if (node.type === 'property_declaration') {
|
|
316
351
|
// Kotlin property_declaration: name/type are inside a variable_declaration child
|
|
317
|
-
const varDecl =
|
|
352
|
+
const varDecl = findChild(node, 'variable_declaration');
|
|
318
353
|
if (varDecl) {
|
|
319
|
-
const nameNode =
|
|
320
|
-
const typeNode =
|
|
354
|
+
const nameNode = findChild(varDecl, 'simple_identifier');
|
|
355
|
+
const typeNode = findChild(varDecl, 'user_type')
|
|
356
|
+
?? findChild(varDecl, 'nullable_type');
|
|
321
357
|
if (!nameNode || !typeNode)
|
|
322
358
|
return;
|
|
323
359
|
const varName = extractVarName(nameNode);
|
|
@@ -328,9 +364,9 @@ const extractKotlinDeclaration = (node, env) => {
|
|
|
328
364
|
}
|
|
329
365
|
// Fallback: try direct fields
|
|
330
366
|
const nameNode = node.childForFieldName('name')
|
|
331
|
-
??
|
|
367
|
+
?? findChild(node, 'simple_identifier');
|
|
332
368
|
const typeNode = node.childForFieldName('type')
|
|
333
|
-
??
|
|
369
|
+
?? findChild(node, 'user_type');
|
|
334
370
|
if (!nameNode || !typeNode)
|
|
335
371
|
return;
|
|
336
372
|
const varName = extractVarName(nameNode);
|
|
@@ -340,8 +376,8 @@ const extractKotlinDeclaration = (node, env) => {
|
|
|
340
376
|
}
|
|
341
377
|
else if (node.type === 'variable_declaration') {
|
|
342
378
|
// variable_declaration directly inside functions
|
|
343
|
-
const nameNode =
|
|
344
|
-
const typeNode =
|
|
379
|
+
const nameNode = findChild(node, 'simple_identifier');
|
|
380
|
+
const typeNode = findChild(node, 'user_type');
|
|
345
381
|
if (nameNode && typeNode) {
|
|
346
382
|
const varName = extractVarName(nameNode);
|
|
347
383
|
const typeName = extractSimpleTypeName(typeNode);
|
|
@@ -353,7 +389,7 @@ const extractKotlinDeclaration = (node, env) => {
|
|
|
353
389
|
/** Kotlin: parameter / formal_parameter → type name.
|
|
354
390
|
* Kotlin's tree-sitter grammar uses positional children (simple_identifier, user_type)
|
|
355
391
|
* rather than named fields (name, type) on `parameter` nodes, so we fall back to
|
|
356
|
-
*
|
|
392
|
+
* findChild when childForFieldName returns null. */
|
|
357
393
|
const extractKotlinParameter = (node, env) => {
|
|
358
394
|
let nameNode = null;
|
|
359
395
|
let typeNode = null;
|
|
@@ -367,9 +403,10 @@ const extractKotlinParameter = (node, env) => {
|
|
|
367
403
|
}
|
|
368
404
|
// Fallback: Kotlin `parameter` nodes use positional children, not named fields
|
|
369
405
|
if (!nameNode)
|
|
370
|
-
nameNode =
|
|
406
|
+
nameNode = findChild(node, 'simple_identifier');
|
|
371
407
|
if (!typeNode)
|
|
372
|
-
typeNode =
|
|
408
|
+
typeNode = findChild(node, 'user_type')
|
|
409
|
+
?? findChild(node, 'nullable_type');
|
|
373
410
|
if (!nameNode || !typeNode)
|
|
374
411
|
return;
|
|
375
412
|
const varName = extractVarName(nameNode);
|
|
@@ -377,48 +414,61 @@ const extractKotlinParameter = (node, env) => {
|
|
|
377
414
|
if (varName && typeName)
|
|
378
415
|
env.set(varName, typeName);
|
|
379
416
|
};
|
|
380
|
-
/**
|
|
381
|
-
*
|
|
382
|
-
|
|
383
|
-
const extractKotlinInitializer = (node, env, classNames) => {
|
|
417
|
+
/** Find the constructor callee name in a Kotlin property_declaration's initializer.
|
|
418
|
+
* Returns the class name if the callee is a verified class constructor, undefined otherwise. */
|
|
419
|
+
const findKotlinConstructorCallee = (node, classNames) => {
|
|
384
420
|
if (node.type !== 'property_declaration')
|
|
385
|
-
return;
|
|
386
|
-
// Skip if there's an explicit type annotation — Tier 0 already handled it
|
|
387
|
-
const varDecl = findChildByType(node, 'variable_declaration');
|
|
388
|
-
if (varDecl && findChildByType(varDecl, 'user_type'))
|
|
389
|
-
return;
|
|
390
|
-
// Get the initializer value — the call_expression after '='
|
|
421
|
+
return undefined;
|
|
391
422
|
const value = node.childForFieldName('value')
|
|
392
|
-
??
|
|
423
|
+
?? findChild(node, 'call_expression');
|
|
393
424
|
if (!value || value.type !== 'call_expression')
|
|
394
|
-
return;
|
|
395
|
-
// The callee is the first child of call_expression (simple_identifier for direct calls)
|
|
425
|
+
return undefined;
|
|
396
426
|
const callee = value.firstNamedChild;
|
|
397
427
|
if (!callee || callee.type !== 'simple_identifier')
|
|
398
|
-
return;
|
|
428
|
+
return undefined;
|
|
399
429
|
const calleeName = callee.text;
|
|
400
430
|
if (!calleeName || !classNames.has(calleeName))
|
|
431
|
+
return undefined;
|
|
432
|
+
return calleeName;
|
|
433
|
+
};
|
|
434
|
+
/** Kotlin: val user = User() — infer type from call_expression when callee is a known class.
|
|
435
|
+
* Kotlin constructors are syntactically identical to function calls, so we verify
|
|
436
|
+
* against classNames (which may include cross-file SymbolTable lookups). */
|
|
437
|
+
const extractKotlinInitializer = (node, env, classNames) => {
|
|
438
|
+
// Skip if there's an explicit type annotation — Tier 0 already handled it
|
|
439
|
+
const varDecl = findChild(node, 'variable_declaration');
|
|
440
|
+
if (varDecl && findChild(varDecl, 'user_type'))
|
|
441
|
+
return;
|
|
442
|
+
const calleeName = findKotlinConstructorCallee(node, classNames);
|
|
443
|
+
if (!calleeName)
|
|
401
444
|
return;
|
|
402
445
|
// Extract the variable name from the variable_declaration inside property_declaration
|
|
403
446
|
const nameNode = varDecl
|
|
404
|
-
?
|
|
405
|
-
:
|
|
447
|
+
? findChild(varDecl, 'simple_identifier')
|
|
448
|
+
: findChild(node, 'simple_identifier');
|
|
406
449
|
if (!nameNode)
|
|
407
450
|
return;
|
|
408
451
|
const varName = extractVarName(nameNode);
|
|
409
452
|
if (varName)
|
|
410
453
|
env.set(varName, calleeName);
|
|
411
454
|
};
|
|
455
|
+
/** Kotlin: detect constructor type from call_expression in typed declarations.
|
|
456
|
+
* Unlike extractKotlinInitializer (which SKIPS typed declarations), this detects
|
|
457
|
+
* the constructor type EVEN when a type annotation exists, enabling virtual dispatch
|
|
458
|
+
* for patterns like `val a: Animal = Dog()`. */
|
|
459
|
+
const detectKotlinConstructorType = (node, classNames) => {
|
|
460
|
+
return findKotlinConstructorCallee(node, classNames);
|
|
461
|
+
};
|
|
412
462
|
/** Kotlin: val x = User(...) — constructor binding for property_declaration with call_expression */
|
|
413
463
|
const scanKotlinConstructorBinding = (node) => {
|
|
414
464
|
if (node.type !== 'property_declaration')
|
|
415
465
|
return undefined;
|
|
416
|
-
const varDecl =
|
|
466
|
+
const varDecl = findChild(node, 'variable_declaration');
|
|
417
467
|
if (!varDecl)
|
|
418
468
|
return undefined;
|
|
419
|
-
if (
|
|
469
|
+
if (findChild(varDecl, 'user_type'))
|
|
420
470
|
return undefined;
|
|
421
|
-
const callExpr =
|
|
471
|
+
const callExpr = findChild(node, 'call_expression');
|
|
422
472
|
if (!callExpr)
|
|
423
473
|
return undefined;
|
|
424
474
|
const callee = callExpr.firstNamedChild;
|
|
@@ -440,7 +490,7 @@ const scanKotlinConstructorBinding = (node) => {
|
|
|
440
490
|
}
|
|
441
491
|
if (!calleeName)
|
|
442
492
|
return undefined;
|
|
443
|
-
const nameNode =
|
|
493
|
+
const nameNode = findChild(varDecl, 'simple_identifier');
|
|
444
494
|
if (!nameNode)
|
|
445
495
|
return undefined;
|
|
446
496
|
return { varName: nameNode.text, calleeName };
|
|
@@ -453,7 +503,7 @@ const KOTLIN_FOR_LOOP_NODE_TYPES = new Set([
|
|
|
453
503
|
* Handles the type_projection wrapper that Kotlin uses for generic type arguments. */
|
|
454
504
|
const extractKotlinElementTypeFromTypeNode = (typeNode, pos = 'last') => {
|
|
455
505
|
if (typeNode.type === 'user_type') {
|
|
456
|
-
const argsNode =
|
|
506
|
+
const argsNode = findChild(typeNode, 'type_arguments');
|
|
457
507
|
if (argsNode && argsNode.namedChildCount >= 1) {
|
|
458
508
|
const targetArg = pos === 'first'
|
|
459
509
|
? argsNode.namedChild(0)
|
|
@@ -476,16 +526,16 @@ const findKotlinParamElementType = (iterableName, startNode, pos = 'last') => {
|
|
|
476
526
|
let current = startNode.parent;
|
|
477
527
|
while (current) {
|
|
478
528
|
if (current.type === 'function_declaration') {
|
|
479
|
-
const paramsNode =
|
|
529
|
+
const paramsNode = findChild(current, 'function_value_parameters');
|
|
480
530
|
if (paramsNode) {
|
|
481
531
|
for (let i = 0; i < paramsNode.namedChildCount; i++) {
|
|
482
532
|
const param = paramsNode.namedChild(i);
|
|
483
533
|
if (!param || param.type !== 'parameter')
|
|
484
534
|
continue;
|
|
485
|
-
const nameNode =
|
|
535
|
+
const nameNode = findChild(param, 'simple_identifier');
|
|
486
536
|
if (nameNode?.text !== iterableName)
|
|
487
537
|
continue;
|
|
488
|
-
const typeNode =
|
|
538
|
+
const typeNode = findChild(param, 'user_type');
|
|
489
539
|
if (typeNode)
|
|
490
540
|
return extractKotlinElementTypeFromTypeNode(typeNode, pos);
|
|
491
541
|
}
|
|
@@ -500,17 +550,17 @@ const findKotlinParamElementType = (iterableName, startNode, pos = 'last') => {
|
|
|
500
550
|
* Tier 1c: for `for (user in users)` without annotation, resolves from iterable. */
|
|
501
551
|
const extractKotlinForLoopBinding = (node, ctx) => {
|
|
502
552
|
const { scopeEnv, declarationTypeNodes, scope, returnTypeLookup } = ctx;
|
|
503
|
-
const varDecl =
|
|
553
|
+
const varDecl = findChild(node, 'variable_declaration');
|
|
504
554
|
if (!varDecl)
|
|
505
555
|
return;
|
|
506
|
-
const nameNode =
|
|
556
|
+
const nameNode = findChild(varDecl, 'simple_identifier');
|
|
507
557
|
if (!nameNode)
|
|
508
558
|
return;
|
|
509
559
|
const varName = extractVarName(nameNode);
|
|
510
560
|
if (!varName)
|
|
511
561
|
return;
|
|
512
562
|
// Explicit type annotation (existing behavior): for (user: User in users)
|
|
513
|
-
const typeNode =
|
|
563
|
+
const typeNode = findChild(varDecl, 'user_type');
|
|
514
564
|
if (typeNode) {
|
|
515
565
|
const typeName = extractSimpleTypeName(typeNode);
|
|
516
566
|
if (typeName)
|
|
@@ -540,9 +590,9 @@ const extractKotlinForLoopBinding = (node, ctx) => {
|
|
|
540
590
|
if (child.type === 'navigation_expression') {
|
|
541
591
|
// data.keys → navigation_expression > simple_identifier(data) + navigation_suffix > simple_identifier(keys)
|
|
542
592
|
const obj = child.firstNamedChild;
|
|
543
|
-
const suffix =
|
|
544
|
-
const prop = suffix ?
|
|
545
|
-
const hasCallSuffix = suffix ?
|
|
593
|
+
const suffix = findChild(child, 'navigation_suffix');
|
|
594
|
+
const prop = suffix ? findChild(suffix, 'simple_identifier') : null;
|
|
595
|
+
const hasCallSuffix = suffix ? findChild(suffix, 'call_suffix') !== null : false;
|
|
546
596
|
// Always try object as iterable + property as method first (handles data.values, data.keys).
|
|
547
597
|
// For bare property access without call_suffix, also save property as fallback
|
|
548
598
|
// (handles this.users, repo.items where the property IS the iterable).
|
|
@@ -562,9 +612,9 @@ const extractKotlinForLoopBinding = (node, ctx) => {
|
|
|
562
612
|
const obj = callee.firstNamedChild;
|
|
563
613
|
if (obj?.type === 'simple_identifier')
|
|
564
614
|
iterableName = obj.text;
|
|
565
|
-
const suffix =
|
|
615
|
+
const suffix = findChild(callee, 'navigation_suffix');
|
|
566
616
|
if (suffix) {
|
|
567
|
-
const prop =
|
|
617
|
+
const prop = findChild(suffix, 'simple_identifier');
|
|
568
618
|
if (prop)
|
|
569
619
|
methodName = prop.text;
|
|
570
620
|
}
|
|
@@ -606,7 +656,7 @@ const extractKotlinForLoopBinding = (node, ctx) => {
|
|
|
606
656
|
const extractKotlinPendingAssignment = (node, scopeEnv) => {
|
|
607
657
|
if (node.type === 'property_declaration') {
|
|
608
658
|
// Find the variable name from variable_declaration child
|
|
609
|
-
const varDecl =
|
|
659
|
+
const varDecl = findChild(node, 'variable_declaration');
|
|
610
660
|
if (!varDecl)
|
|
611
661
|
return undefined;
|
|
612
662
|
const nameNode = varDecl.firstNamedChild;
|
|
@@ -658,7 +708,7 @@ const extractKotlinPendingAssignment = (node, scopeEnv) => {
|
|
|
658
708
|
}
|
|
659
709
|
if (node.type === 'variable_declaration') {
|
|
660
710
|
// variable_declaration directly inside functions: simple_identifier children
|
|
661
|
-
const nameNode =
|
|
711
|
+
const nameNode = findChild(node, 'simple_identifier');
|
|
662
712
|
if (!nameNode)
|
|
663
713
|
return undefined;
|
|
664
714
|
const lhs = nameNode.text;
|
|
@@ -717,32 +767,83 @@ const findAncestorByType = (node, type) => {
|
|
|
717
767
|
}
|
|
718
768
|
return undefined;
|
|
719
769
|
};
|
|
720
|
-
const extractKotlinPatternBinding = (node) => {
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
770
|
+
const extractKotlinPatternBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
|
|
771
|
+
// Kotlin when/is smart casts (existing behavior)
|
|
772
|
+
if (node.type === 'type_test') {
|
|
773
|
+
const typeNode = node.lastNamedChild;
|
|
774
|
+
if (!typeNode)
|
|
775
|
+
return undefined;
|
|
776
|
+
const typeName = extractSimpleTypeName(typeNode);
|
|
777
|
+
if (!typeName)
|
|
778
|
+
return undefined;
|
|
779
|
+
const whenExpr = findAncestorByType(node, 'when_expression');
|
|
780
|
+
if (!whenExpr)
|
|
781
|
+
return undefined;
|
|
782
|
+
const whenSubject = whenExpr.namedChild(0);
|
|
783
|
+
const subject = whenSubject?.firstNamedChild ?? whenSubject;
|
|
784
|
+
if (!subject)
|
|
785
|
+
return undefined;
|
|
786
|
+
const varName = extractVarName(subject);
|
|
787
|
+
if (!varName)
|
|
788
|
+
return undefined;
|
|
789
|
+
return { varName, typeName };
|
|
790
|
+
}
|
|
791
|
+
// Null-check narrowing: if (x != null) { ... }
|
|
792
|
+
// Kotlin AST: equality_expression > simple_identifier, "!=" [anon], "null" [anon]
|
|
793
|
+
// Note: `null` is an anonymous node in tree-sitter-kotlin, not `null_literal`.
|
|
794
|
+
if (node.type === 'equality_expression') {
|
|
795
|
+
const op = node.children.find(c => !c.isNamed && c.text === '!=');
|
|
796
|
+
if (!op)
|
|
797
|
+
return undefined;
|
|
798
|
+
// `null` is anonymous in Kotlin grammar — use positional child scan
|
|
799
|
+
let varNode;
|
|
800
|
+
let hasNull = false;
|
|
801
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
802
|
+
const c = node.child(i);
|
|
803
|
+
if (!c)
|
|
804
|
+
continue;
|
|
805
|
+
if (c.type === 'simple_identifier')
|
|
806
|
+
varNode = c;
|
|
807
|
+
if (!c.isNamed && c.text === 'null')
|
|
808
|
+
hasNull = true;
|
|
809
|
+
}
|
|
810
|
+
if (!varNode || !hasNull)
|
|
811
|
+
return undefined;
|
|
812
|
+
const varName = varNode.text;
|
|
813
|
+
const resolvedType = scopeEnv.get(varName);
|
|
814
|
+
if (!resolvedType)
|
|
815
|
+
return undefined;
|
|
816
|
+
// Check if the original declaration type was nullable (ends with ?)
|
|
817
|
+
const declTypeNode = declarationTypeNodes.get(`${scope}\0${varName}`);
|
|
818
|
+
if (!declTypeNode)
|
|
819
|
+
return undefined;
|
|
820
|
+
const declText = declTypeNode.text;
|
|
821
|
+
if (!declText.includes('?') && !declText.includes('null'))
|
|
822
|
+
return undefined;
|
|
823
|
+
// Find the if-body: walk up to if_expression, then find control_structure_body
|
|
824
|
+
const ifExpr = findAncestorByType(node, 'if_expression');
|
|
825
|
+
if (!ifExpr)
|
|
826
|
+
return undefined;
|
|
827
|
+
// The consequence is the first control_structure_body child
|
|
828
|
+
for (let i = 0; i < ifExpr.childCount; i++) {
|
|
829
|
+
const child = ifExpr.child(i);
|
|
830
|
+
if (child?.type === 'control_structure_body') {
|
|
831
|
+
return {
|
|
832
|
+
varName,
|
|
833
|
+
typeName: resolvedType,
|
|
834
|
+
narrowingRange: { startIndex: child.startIndex, endIndex: child.endIndex },
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
}
|
|
738
838
|
return undefined;
|
|
739
|
-
|
|
839
|
+
}
|
|
840
|
+
return undefined;
|
|
740
841
|
};
|
|
741
842
|
export const kotlinTypeConfig = {
|
|
742
843
|
allowPatternBindingOverwrite: true,
|
|
743
844
|
declarationNodeTypes: KOTLIN_DECLARATION_NODE_TYPES,
|
|
744
845
|
forLoopNodeTypes: KOTLIN_FOR_LOOP_NODE_TYPES,
|
|
745
|
-
patternBindingNodeTypes: new Set(['type_test']),
|
|
846
|
+
patternBindingNodeTypes: new Set(['type_test', 'equality_expression']),
|
|
746
847
|
extractDeclaration: extractKotlinDeclaration,
|
|
747
848
|
extractParameter: extractKotlinParameter,
|
|
748
849
|
extractInitializer: extractKotlinInitializer,
|
|
@@ -750,4 +851,6 @@ export const kotlinTypeConfig = {
|
|
|
750
851
|
extractForLoopBinding: extractKotlinForLoopBinding,
|
|
751
852
|
extractPendingAssignment: extractKotlinPendingAssignment,
|
|
752
853
|
extractPatternBinding: extractKotlinPatternBinding,
|
|
854
|
+
inferLiteralType: inferJvmLiteralType,
|
|
855
|
+
detectConstructorType: detectKotlinConstructorType,
|
|
753
856
|
};
|
|
@@ -205,7 +205,8 @@ const scanConstructorBinding = (node) => {
|
|
|
205
205
|
return undefined;
|
|
206
206
|
return { varName: patternNode.text, calleeName };
|
|
207
207
|
};
|
|
208
|
-
/** Rust: let alias = u; → let_declaration with pattern + value fields
|
|
208
|
+
/** Rust: let alias = u; → let_declaration with pattern + value fields.
|
|
209
|
+
* Also handles struct destructuring: `let Point { x, y } = p` → N fieldAccess items. */
|
|
209
210
|
const extractPendingAssignment = (node, scopeEnv) => {
|
|
210
211
|
if (node.type !== 'let_declaration')
|
|
211
212
|
return undefined;
|
|
@@ -213,6 +214,39 @@ const extractPendingAssignment = (node, scopeEnv) => {
|
|
|
213
214
|
const value = node.childForFieldName('value');
|
|
214
215
|
if (!pattern || !value)
|
|
215
216
|
return undefined;
|
|
217
|
+
// Struct pattern destructuring: `let Point { x, y } = receiver`
|
|
218
|
+
// struct_pattern has a type child (struct name) and field_pattern children
|
|
219
|
+
if (pattern.type === 'struct_pattern' && value.type === 'identifier') {
|
|
220
|
+
const receiver = value.text;
|
|
221
|
+
const items = [];
|
|
222
|
+
for (let j = 0; j < pattern.namedChildCount; j++) {
|
|
223
|
+
const field = pattern.namedChild(j);
|
|
224
|
+
if (!field)
|
|
225
|
+
continue;
|
|
226
|
+
if (field.type === 'field_pattern') {
|
|
227
|
+
// `Point { x: local_x }` → field_pattern with name + pattern children
|
|
228
|
+
const nameNode = field.childForFieldName('name');
|
|
229
|
+
const patNode = field.childForFieldName('pattern');
|
|
230
|
+
if (nameNode && patNode) {
|
|
231
|
+
const fieldName = nameNode.text;
|
|
232
|
+
const varName = extractVarName(patNode);
|
|
233
|
+
if (varName && !scopeEnv.has(varName)) {
|
|
234
|
+
items.push({ kind: 'fieldAccess', lhs: varName, receiver, field: fieldName });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
else if (nameNode) {
|
|
238
|
+
// Shorthand: `Point { x }` → field_pattern with only name (varName = fieldName)
|
|
239
|
+
const varName = nameNode.text;
|
|
240
|
+
if (!scopeEnv.has(varName)) {
|
|
241
|
+
items.push({ kind: 'fieldAccess', lhs: varName, receiver, field: varName });
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
if (items.length > 0)
|
|
247
|
+
return items;
|
|
248
|
+
return undefined;
|
|
249
|
+
}
|
|
216
250
|
const lhs = extractVarName(pattern);
|
|
217
251
|
if (!lhs || scopeEnv.has(lhs))
|
|
218
252
|
return undefined;
|
|
@@ -108,8 +108,6 @@ export declare const unwrapAwait: (node: SyntaxNode | null) => SyntaxNode | null
|
|
|
108
108
|
* Navigates to the 'function' field (or first named child) and extracts a simple type name.
|
|
109
109
|
*/
|
|
110
110
|
export declare const extractCalleeName: (callNode: SyntaxNode) => string | undefined;
|
|
111
|
-
/** Find the first named child with the given node type */
|
|
112
|
-
export declare const findChildByType: (node: SyntaxNode, type: string) => SyntaxNode | null;
|
|
113
111
|
/**
|
|
114
112
|
* Extract element type from a container type string.
|
|
115
113
|
* Uses bracket-balanced parsing (no regex) for generic argument extraction.
|
|
@@ -237,7 +237,11 @@ export const extractSimpleTypeName = (typeNode, depth = 0) => {
|
|
|
237
237
|
}
|
|
238
238
|
// Primitive/predefined types: string, int, float, bool, number, unknown, any
|
|
239
239
|
// PHP: primitive_type; TS/JS: predefined_type
|
|
240
|
-
|
|
240
|
+
// Java: integral_type (int/long/short/byte), floating_point_type (float/double),
|
|
241
|
+
// boolean_type (boolean), void_type (void)
|
|
242
|
+
if (typeNode.type === 'primitive_type' || typeNode.type === 'predefined_type'
|
|
243
|
+
|| typeNode.type === 'integral_type' || typeNode.type === 'floating_point_type'
|
|
244
|
+
|| typeNode.type === 'boolean_type' || typeNode.type === 'void_type') {
|
|
241
245
|
return typeNode.text;
|
|
242
246
|
}
|
|
243
247
|
// PHP named_type / optional_type
|
|
@@ -454,15 +458,6 @@ export const extractCalleeName = (callNode) => {
|
|
|
454
458
|
return undefined;
|
|
455
459
|
return extractSimpleTypeName(func);
|
|
456
460
|
};
|
|
457
|
-
/** Find the first named child with the given node type */
|
|
458
|
-
export const findChildByType = (node, type) => {
|
|
459
|
-
for (let i = 0; i < node.namedChildCount; i++) {
|
|
460
|
-
const child = node.namedChild(i);
|
|
461
|
-
if (child?.type === type)
|
|
462
|
-
return child;
|
|
463
|
-
}
|
|
464
|
-
return null;
|
|
465
|
-
};
|
|
466
461
|
// Internal helper: extract the first comma-separated argument from a string,
|
|
467
462
|
// respecting nested angle-bracket and square-bracket depth.
|
|
468
463
|
function extractFirstArg(args) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { extractSimpleTypeName, extractVarName,
|
|
1
|
+
import { extractSimpleTypeName, extractVarName, hasTypeAnnotation } from './shared.js';
|
|
2
|
+
import { findChild } from '../resolvers/utils.js';
|
|
2
3
|
const DECLARATION_NODE_TYPES = new Set([
|
|
3
4
|
'property_declaration',
|
|
4
5
|
]);
|
|
@@ -6,9 +7,9 @@ const DECLARATION_NODE_TYPES = new Set([
|
|
|
6
7
|
const extractDeclaration = (node, env) => {
|
|
7
8
|
// Swift property_declaration has pattern and type_annotation
|
|
8
9
|
const pattern = node.childForFieldName('pattern')
|
|
9
|
-
??
|
|
10
|
+
?? findChild(node, 'pattern');
|
|
10
11
|
const typeAnnotation = node.childForFieldName('type')
|
|
11
|
-
??
|
|
12
|
+
?? findChild(node, 'type_annotation');
|
|
12
13
|
if (!pattern || !typeAnnotation)
|
|
13
14
|
return;
|
|
14
15
|
const varName = extractVarName(pattern) ?? pattern.text;
|
|
@@ -43,17 +44,17 @@ const extractInitializer = (node, env, classNames) => {
|
|
|
43
44
|
if (node.type !== 'property_declaration')
|
|
44
45
|
return;
|
|
45
46
|
// Skip if has type annotation — extractDeclaration handled it
|
|
46
|
-
if (node.childForFieldName('type') ||
|
|
47
|
+
if (node.childForFieldName('type') || findChild(node, 'type_annotation'))
|
|
47
48
|
return;
|
|
48
49
|
// Find pattern (variable name)
|
|
49
|
-
const pattern = node.childForFieldName('pattern') ??
|
|
50
|
+
const pattern = node.childForFieldName('pattern') ?? findChild(node, 'pattern');
|
|
50
51
|
if (!pattern)
|
|
51
52
|
return;
|
|
52
53
|
const varName = extractVarName(pattern) ?? pattern.text;
|
|
53
54
|
if (!varName || env.has(varName))
|
|
54
55
|
return;
|
|
55
56
|
// Find call_expression in the value
|
|
56
|
-
const callExpr =
|
|
57
|
+
const callExpr = findChild(node, 'call_expression');
|
|
57
58
|
if (!callExpr)
|
|
58
59
|
return;
|
|
59
60
|
const callee = callExpr.firstNamedChild;
|
|
@@ -23,6 +23,20 @@ export type ConstructorBindingScanner = (node: SyntaxNode) => {
|
|
|
23
23
|
* Used for languages where return types are expressed in comments (e.g. YARD @return [Type])
|
|
24
24
|
* rather than in AST fields. Returns undefined if no return type can be determined. */
|
|
25
25
|
export type ReturnTypeExtractor = (node: SyntaxNode) => string | undefined;
|
|
26
|
+
/** Infer the type name of a literal AST node for overload disambiguation.
|
|
27
|
+
* Returns the canonical type name (e.g. 'int', 'String', 'boolean') or undefined
|
|
28
|
+
* for non-literal nodes. Only used when resolveCallTarget has multiple candidates
|
|
29
|
+
* with parameterTypes — ~1-3% of call sites. */
|
|
30
|
+
export type LiteralTypeInferrer = (node: SyntaxNode) => string | undefined;
|
|
31
|
+
/** Detect constructor-style call expressions that don't use `new` keyword.
|
|
32
|
+
* Returns the constructor class name if the node's initializer is a constructor call,
|
|
33
|
+
* or undefined otherwise. Used for virtual dispatch in languages like Kotlin
|
|
34
|
+
* where constructors are syntactically identical to function calls, and C++
|
|
35
|
+
* where smart pointer factory functions (make_shared/make_unique) wrap constructors. */
|
|
36
|
+
export type ConstructorTypeDetector = (node: SyntaxNode, classNames: ClassNameLookup) => string | undefined;
|
|
37
|
+
/** Unwrap a declared type name to its inner type for virtual dispatch comparison.
|
|
38
|
+
* E.g., C++ shared_ptr<Animal> → Animal. Returns undefined if no unwrapping applies. */
|
|
39
|
+
export type DeclaredTypeUnwrapper = (declaredType: string, typeNode: SyntaxNode) => string | undefined;
|
|
26
40
|
/** Narrow lookup interface for resolving a callee name → return type name.
|
|
27
41
|
* Backed by SymbolTable.lookupFuzzyCallable; passed via ForLoopExtractorContext.
|
|
28
42
|
* Conservative: returns undefined when the callee is ambiguous (0 or 2+ matches). */
|
|
@@ -76,11 +90,27 @@ export type PendingAssignment = {
|
|
|
76
90
|
* Returns a PendingAssignment when the RHS is a bare identifier (`copy`), a
|
|
77
91
|
* call expression (`callResult`), a field access (`fieldAccess`), or a
|
|
78
92
|
* method call with receiver (`methodCallResult`) and the LHS has no resolved type yet.
|
|
93
|
+
* May return an array of PendingAssignment items for destructuring patterns
|
|
94
|
+
* (e.g., `const { a, b } = obj` emits N fieldAccess items).
|
|
79
95
|
* Returns undefined if the node is not a matching assignment. */
|
|
80
|
-
export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>) => PendingAssignment | undefined;
|
|
96
|
+
export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>) => PendingAssignment | PendingAssignment[] | undefined;
|
|
97
|
+
/** Result of a pattern binding extraction. */
|
|
98
|
+
export interface PatternBindingResult {
|
|
99
|
+
varName: string;
|
|
100
|
+
typeName: string;
|
|
101
|
+
/** Optional: AST node whose position range should be used for the patternOverride.
|
|
102
|
+
* When present, the override uses this node's range instead of the auto-detected
|
|
103
|
+
* branch scope. Used by null-check narrowing to target the if-body specifically. */
|
|
104
|
+
narrowingRange?: {
|
|
105
|
+
startIndex: number;
|
|
106
|
+
endIndex: number;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
81
109
|
/** Extracts a typed variable binding from a pattern-matching construct.
|
|
82
|
-
* Returns { varName, typeName } for patterns that introduce NEW variables
|
|
83
|
-
*
|
|
110
|
+
* Returns { varName, typeName } for patterns that introduce NEW variables
|
|
111
|
+
* or narrow existing variables (null-check narrowing).
|
|
112
|
+
* Examples: `if let Some(user) = opt` (Rust), `x instanceof User user` (Java),
|
|
113
|
+
* `if (x != null)` (null-check narrowing in TS/Kotlin/C#).
|
|
84
114
|
* Conservative: returns undefined when the source variable's type is unknown.
|
|
85
115
|
*
|
|
86
116
|
* @param scopeEnv Read-only view of already-resolved type bindings in the current scope.
|
|
@@ -88,10 +118,7 @@ export type PendingAssignmentExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMa
|
|
|
88
118
|
* annotation AST node. Allows extracting generic type arguments (e.g., T from Result<T,E>)
|
|
89
119
|
* that are stripped during normal TypeEnv extraction.
|
|
90
120
|
* @param scope Current scope key (e.g. `"process@42"`) for declarationTypeNodes lookups. */
|
|
91
|
-
export type PatternBindingExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>, declarationTypeNodes: ReadonlyMap<string, SyntaxNode>, scope: string) =>
|
|
92
|
-
varName: string;
|
|
93
|
-
typeName: string;
|
|
94
|
-
} | undefined;
|
|
121
|
+
export type PatternBindingExtractor = (node: SyntaxNode, scopeEnv: ReadonlyMap<string, string>, declarationTypeNodes: ReadonlyMap<string, SyntaxNode>, scope: string) => PatternBindingResult | undefined;
|
|
95
122
|
/** Per-language type extraction configuration */
|
|
96
123
|
export interface LanguageTypeConfig {
|
|
97
124
|
/** Allow pattern binding to overwrite existing scopeEnv entries.
|
|
@@ -137,4 +164,7 @@ export interface LanguageTypeConfig {
|
|
|
137
164
|
* The extractor receives the current scope's resolved bindings (read-only) to look up the
|
|
138
165
|
* source variable's type. Returns undefined for non-matching nodes or unknown source types. */
|
|
139
166
|
extractPatternBinding?: PatternBindingExtractor;
|
|
167
|
+
inferLiteralType?: LiteralTypeInferrer;
|
|
168
|
+
detectConstructorType?: ConstructorTypeDetector;
|
|
169
|
+
unwrapDeclaredType?: DeclaredTypeUnwrapper;
|
|
140
170
|
}
|