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.
Files changed (92) hide show
  1. package/README.md +22 -1
  2. package/dist/cli/ai-context.d.ts +1 -1
  3. package/dist/cli/ai-context.js +1 -1
  4. package/dist/cli/analyze.d.ts +2 -0
  5. package/dist/cli/analyze.js +54 -21
  6. package/dist/cli/index.js +2 -1
  7. package/dist/cli/setup.js +78 -1
  8. package/dist/config/supported-languages.d.ts +30 -0
  9. package/dist/config/supported-languages.js +30 -0
  10. package/dist/core/embeddings/embedder.d.ts +6 -1
  11. package/dist/core/embeddings/embedder.js +65 -5
  12. package/dist/core/embeddings/embedding-pipeline.js +11 -9
  13. package/dist/core/embeddings/http-client.d.ts +31 -0
  14. package/dist/core/embeddings/http-client.js +179 -0
  15. package/dist/core/embeddings/index.d.ts +1 -0
  16. package/dist/core/embeddings/index.js +1 -0
  17. package/dist/core/embeddings/types.d.ts +1 -1
  18. package/dist/core/graph/types.d.ts +2 -1
  19. package/dist/core/ingestion/ast-helpers.d.ts +80 -0
  20. package/dist/core/ingestion/ast-helpers.js +738 -0
  21. package/dist/core/ingestion/call-analysis.d.ts +73 -0
  22. package/dist/core/ingestion/call-analysis.js +490 -0
  23. package/dist/core/ingestion/call-processor.d.ts +48 -1
  24. package/dist/core/ingestion/call-processor.js +368 -7
  25. package/dist/core/ingestion/call-routing.d.ts +6 -0
  26. package/dist/core/ingestion/entry-point-scoring.js +36 -26
  27. package/dist/core/ingestion/framework-detection.d.ts +10 -2
  28. package/dist/core/ingestion/framework-detection.js +49 -12
  29. package/dist/core/ingestion/heritage-processor.js +47 -49
  30. package/dist/core/ingestion/import-processor.d.ts +1 -1
  31. package/dist/core/ingestion/import-processor.js +103 -194
  32. package/dist/core/ingestion/import-resolution.d.ts +101 -0
  33. package/dist/core/ingestion/import-resolution.js +251 -0
  34. package/dist/core/ingestion/language-config.d.ts +3 -0
  35. package/dist/core/ingestion/language-config.js +13 -0
  36. package/dist/core/ingestion/markdown-processor.d.ts +17 -0
  37. package/dist/core/ingestion/markdown-processor.js +124 -0
  38. package/dist/core/ingestion/mro-processor.js +8 -3
  39. package/dist/core/ingestion/named-binding-extraction.d.ts +9 -43
  40. package/dist/core/ingestion/named-binding-extraction.js +89 -79
  41. package/dist/core/ingestion/parsing-processor.d.ts +2 -2
  42. package/dist/core/ingestion/parsing-processor.js +14 -73
  43. package/dist/core/ingestion/pipeline.d.ts +10 -0
  44. package/dist/core/ingestion/pipeline.js +421 -4
  45. package/dist/core/ingestion/resolution-context.d.ts +5 -0
  46. package/dist/core/ingestion/resolution-context.js +7 -4
  47. package/dist/core/ingestion/resolvers/index.d.ts +1 -1
  48. package/dist/core/ingestion/resolvers/index.js +1 -1
  49. package/dist/core/ingestion/resolvers/jvm.d.ts +2 -1
  50. package/dist/core/ingestion/resolvers/jvm.js +25 -9
  51. package/dist/core/ingestion/resolvers/php.d.ts +14 -0
  52. package/dist/core/ingestion/resolvers/php.js +43 -3
  53. package/dist/core/ingestion/resolvers/utils.d.ts +5 -0
  54. package/dist/core/ingestion/resolvers/utils.js +16 -0
  55. package/dist/core/ingestion/symbol-table.d.ts +16 -0
  56. package/dist/core/ingestion/symbol-table.js +20 -6
  57. package/dist/core/ingestion/tree-sitter-queries.d.ts +4 -4
  58. package/dist/core/ingestion/tree-sitter-queries.js +43 -2
  59. package/dist/core/ingestion/type-env.d.ts +28 -1
  60. package/dist/core/ingestion/type-env.js +419 -96
  61. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
  62. package/dist/core/ingestion/type-extractors/c-cpp.js +119 -0
  63. package/dist/core/ingestion/type-extractors/csharp.js +149 -16
  64. package/dist/core/ingestion/type-extractors/index.d.ts +1 -1
  65. package/dist/core/ingestion/type-extractors/index.js +1 -1
  66. package/dist/core/ingestion/type-extractors/jvm.js +169 -66
  67. package/dist/core/ingestion/type-extractors/rust.js +35 -1
  68. package/dist/core/ingestion/type-extractors/shared.d.ts +0 -2
  69. package/dist/core/ingestion/type-extractors/shared.js +5 -10
  70. package/dist/core/ingestion/type-extractors/swift.js +7 -6
  71. package/dist/core/ingestion/type-extractors/types.d.ts +37 -7
  72. package/dist/core/ingestion/type-extractors/typescript.js +141 -9
  73. package/dist/core/ingestion/utils.d.ts +2 -120
  74. package/dist/core/ingestion/utils.js +3 -1051
  75. package/dist/core/ingestion/workers/parse-worker.d.ts +13 -4
  76. package/dist/core/ingestion/workers/parse-worker.js +66 -87
  77. package/dist/core/lbug/csv-generator.js +18 -1
  78. package/dist/core/lbug/lbug-adapter.d.ts +10 -0
  79. package/dist/core/lbug/lbug-adapter.js +69 -4
  80. package/dist/core/lbug/schema.d.ts +5 -3
  81. package/dist/core/lbug/schema.js +26 -2
  82. package/dist/mcp/core/embedder.js +11 -3
  83. package/dist/mcp/core/lbug-adapter.js +12 -1
  84. package/dist/mcp/local/local-backend.d.ts +22 -0
  85. package/dist/mcp/local/local-backend.js +133 -29
  86. package/dist/mcp/resources.js +2 -0
  87. package/dist/mcp/tools.js +2 -2
  88. package/dist/server/api.d.ts +19 -1
  89. package/dist/server/api.js +66 -6
  90. package/dist/storage/git.d.ts +12 -0
  91. package/dist/storage/git.js +21 -0
  92. package/package.json +10 -2
@@ -1,4 +1,5 @@
1
- import { extractSimpleTypeName, extractVarName, findChildByType, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
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 = findChildByType(node, 'variable_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 = findChildByType(node, 'variable_declaration');
352
+ const varDecl = findChild(node, 'variable_declaration');
318
353
  if (varDecl) {
319
- const nameNode = findChildByType(varDecl, 'simple_identifier');
320
- const typeNode = findChildByType(varDecl, 'user_type');
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
- ?? findChildByType(node, 'simple_identifier');
367
+ ?? findChild(node, 'simple_identifier');
332
368
  const typeNode = node.childForFieldName('type')
333
- ?? findChildByType(node, 'user_type');
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 = findChildByType(node, 'simple_identifier');
344
- const typeNode = findChildByType(node, 'user_type');
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
- * findChildByType when childForFieldName returns null. */
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 = findChildByType(node, 'simple_identifier');
406
+ nameNode = findChild(node, 'simple_identifier');
371
407
  if (!typeNode)
372
- typeNode = findChildByType(node, 'user_type');
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
- /** Kotlin: val user = User() infer type from call_expression when callee is a known class.
381
- * Kotlin constructors are syntactically identical to function calls, so we verify
382
- * against classNames (which may include cross-file SymbolTable lookups). */
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
- ?? findChildByType(node, 'call_expression');
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
- ? findChildByType(varDecl, 'simple_identifier')
405
- : findChildByType(node, 'simple_identifier');
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 = findChildByType(node, 'variable_declaration');
466
+ const varDecl = findChild(node, 'variable_declaration');
417
467
  if (!varDecl)
418
468
  return undefined;
419
- if (findChildByType(varDecl, 'user_type'))
469
+ if (findChild(varDecl, 'user_type'))
420
470
  return undefined;
421
- const callExpr = findChildByType(node, 'call_expression');
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 = findChildByType(varDecl, 'simple_identifier');
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 = findChildByType(typeNode, 'type_arguments');
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 = findChildByType(current, 'function_value_parameters');
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 = findChildByType(param, 'simple_identifier');
535
+ const nameNode = findChild(param, 'simple_identifier');
486
536
  if (nameNode?.text !== iterableName)
487
537
  continue;
488
- const typeNode = findChildByType(param, 'user_type');
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 = findChildByType(node, 'variable_declaration');
553
+ const varDecl = findChild(node, 'variable_declaration');
504
554
  if (!varDecl)
505
555
  return;
506
- const nameNode = findChildByType(varDecl, 'simple_identifier');
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 = findChildByType(varDecl, 'user_type');
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 = findChildByType(child, 'navigation_suffix');
544
- const prop = suffix ? findChildByType(suffix, 'simple_identifier') : null;
545
- const hasCallSuffix = suffix ? findChildByType(suffix, 'call_suffix') !== null : false;
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 = findChildByType(callee, 'navigation_suffix');
615
+ const suffix = findChild(callee, 'navigation_suffix');
566
616
  if (suffix) {
567
- const prop = findChildByType(suffix, 'simple_identifier');
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 = findChildByType(node, 'variable_declaration');
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 = findChildByType(node, 'simple_identifier');
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
- if (node.type !== 'type_test')
722
- return undefined;
723
- const typeNode = node.lastNamedChild;
724
- if (!typeNode)
725
- return undefined;
726
- const typeName = extractSimpleTypeName(typeNode);
727
- if (!typeName)
728
- return undefined;
729
- const whenExpr = findAncestorByType(node, 'when_expression');
730
- if (!whenExpr)
731
- return undefined;
732
- const whenSubject = whenExpr.namedChild(0);
733
- const subject = whenSubject?.firstNamedChild ?? whenSubject;
734
- if (!subject)
735
- return undefined;
736
- const varName = extractVarName(subject);
737
- if (!varName)
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
- return { varName, typeName };
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
- if (typeNode.type === 'primitive_type' || typeNode.type === 'predefined_type') {
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, findChildByType, hasTypeAnnotation } from './shared.js';
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
- ?? findChildByType(node, 'pattern');
10
+ ?? findChild(node, 'pattern');
10
11
  const typeAnnotation = node.childForFieldName('type')
11
- ?? findChildByType(node, 'type_annotation');
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') || findChildByType(node, 'type_annotation'))
47
+ if (node.childForFieldName('type') || findChild(node, 'type_annotation'))
47
48
  return;
48
49
  // Find pattern (variable name)
49
- const pattern = node.childForFieldName('pattern') ?? findChildByType(node, '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 = findChildByType(node, 'call_expression');
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
- * Examples: `if let Some(user) = opt` (Rust), `x instanceof User user` (Java).
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
  }