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.
Files changed (99) 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 +4 -3
  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 +55 -2
  24. package/dist/core/ingestion/call-processor.js +673 -108
  25. package/dist/core/ingestion/call-routing.d.ts +23 -2
  26. package/dist/core/ingestion/call-routing.js +21 -0
  27. package/dist/core/ingestion/entry-point-scoring.js +36 -26
  28. package/dist/core/ingestion/framework-detection.d.ts +10 -2
  29. package/dist/core/ingestion/framework-detection.js +49 -12
  30. package/dist/core/ingestion/heritage-processor.js +47 -49
  31. package/dist/core/ingestion/import-processor.d.ts +1 -1
  32. package/dist/core/ingestion/import-processor.js +103 -194
  33. package/dist/core/ingestion/import-resolution.d.ts +101 -0
  34. package/dist/core/ingestion/import-resolution.js +251 -0
  35. package/dist/core/ingestion/language-config.d.ts +3 -0
  36. package/dist/core/ingestion/language-config.js +13 -0
  37. package/dist/core/ingestion/markdown-processor.d.ts +17 -0
  38. package/dist/core/ingestion/markdown-processor.js +124 -0
  39. package/dist/core/ingestion/mro-processor.js +8 -3
  40. package/dist/core/ingestion/named-binding-extraction.d.ts +9 -43
  41. package/dist/core/ingestion/named-binding-extraction.js +89 -79
  42. package/dist/core/ingestion/parsing-processor.d.ts +3 -2
  43. package/dist/core/ingestion/parsing-processor.js +27 -60
  44. package/dist/core/ingestion/pipeline.d.ts +10 -0
  45. package/dist/core/ingestion/pipeline.js +425 -4
  46. package/dist/core/ingestion/resolution-context.d.ts +5 -0
  47. package/dist/core/ingestion/resolution-context.js +7 -4
  48. package/dist/core/ingestion/resolvers/index.d.ts +1 -1
  49. package/dist/core/ingestion/resolvers/index.js +1 -1
  50. package/dist/core/ingestion/resolvers/jvm.d.ts +2 -1
  51. package/dist/core/ingestion/resolvers/jvm.js +25 -9
  52. package/dist/core/ingestion/resolvers/php.d.ts +14 -0
  53. package/dist/core/ingestion/resolvers/php.js +43 -3
  54. package/dist/core/ingestion/resolvers/utils.d.ts +5 -0
  55. package/dist/core/ingestion/resolvers/utils.js +16 -0
  56. package/dist/core/ingestion/symbol-table.d.ts +29 -3
  57. package/dist/core/ingestion/symbol-table.js +42 -9
  58. package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
  59. package/dist/core/ingestion/tree-sitter-queries.js +243 -2
  60. package/dist/core/ingestion/type-env.d.ts +28 -1
  61. package/dist/core/ingestion/type-env.js +451 -72
  62. package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
  63. package/dist/core/ingestion/type-extractors/c-cpp.js +146 -2
  64. package/dist/core/ingestion/type-extractors/csharp.js +189 -16
  65. package/dist/core/ingestion/type-extractors/go.js +45 -0
  66. package/dist/core/ingestion/type-extractors/index.d.ts +1 -1
  67. package/dist/core/ingestion/type-extractors/index.js +1 -1
  68. package/dist/core/ingestion/type-extractors/jvm.js +244 -69
  69. package/dist/core/ingestion/type-extractors/php.js +31 -4
  70. package/dist/core/ingestion/type-extractors/python.js +89 -17
  71. package/dist/core/ingestion/type-extractors/ruby.js +17 -2
  72. package/dist/core/ingestion/type-extractors/rust.js +72 -4
  73. package/dist/core/ingestion/type-extractors/shared.d.ts +12 -2
  74. package/dist/core/ingestion/type-extractors/shared.js +115 -13
  75. package/dist/core/ingestion/type-extractors/swift.js +7 -6
  76. package/dist/core/ingestion/type-extractors/types.d.ts +54 -11
  77. package/dist/core/ingestion/type-extractors/typescript.js +171 -9
  78. package/dist/core/ingestion/utils.d.ts +2 -95
  79. package/dist/core/ingestion/utils.js +3 -892
  80. package/dist/core/ingestion/workers/parse-worker.d.ts +36 -11
  81. package/dist/core/ingestion/workers/parse-worker.js +116 -95
  82. package/dist/core/lbug/csv-generator.js +18 -1
  83. package/dist/core/lbug/lbug-adapter.d.ts +12 -0
  84. package/dist/core/lbug/lbug-adapter.js +71 -4
  85. package/dist/core/lbug/schema.d.ts +6 -4
  86. package/dist/core/lbug/schema.js +27 -3
  87. package/dist/mcp/core/embedder.js +11 -3
  88. package/dist/mcp/core/lbug-adapter.d.ts +22 -0
  89. package/dist/mcp/core/lbug-adapter.js +178 -23
  90. package/dist/mcp/local/local-backend.d.ts +22 -0
  91. package/dist/mcp/local/local-backend.js +136 -32
  92. package/dist/mcp/resources.js +13 -0
  93. package/dist/mcp/server.js +26 -4
  94. package/dist/mcp/tools.js +17 -7
  95. package/dist/server/api.d.ts +19 -1
  96. package/dist/server/api.js +66 -6
  97. package/dist/storage/git.d.ts +12 -0
  98. package/dist/storage/git.js +21 -0
  99. package/package.json +12 -4
@@ -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');
@@ -219,6 +220,32 @@ const extractJavaPendingAssignment = (node, scopeEnv) => {
219
220
  continue;
220
221
  if (valueNode.type === 'identifier' || valueNode.type === 'simple_identifier')
221
222
  return { kind: 'copy', lhs, rhs: valueNode.text };
223
+ // field_access RHS → fieldAccess (a.field)
224
+ if (valueNode.type === 'field_access') {
225
+ const obj = valueNode.childForFieldName('object');
226
+ const field = valueNode.childForFieldName('field');
227
+ if (obj?.type === 'identifier' && field) {
228
+ return { kind: 'fieldAccess', lhs, receiver: obj.text, field: field.text };
229
+ }
230
+ }
231
+ // method_invocation RHS
232
+ if (valueNode.type === 'method_invocation') {
233
+ const objField = valueNode.childForFieldName('object');
234
+ if (!objField) {
235
+ // No receiver → callResult
236
+ const nameField = valueNode.childForFieldName('name');
237
+ if (nameField?.type === 'identifier') {
238
+ return { kind: 'callResult', lhs, callee: nameField.text };
239
+ }
240
+ }
241
+ else if (objField.type === 'identifier') {
242
+ // With receiver → methodCallResult
243
+ const nameField = valueNode.childForFieldName('name');
244
+ if (nameField?.type === 'identifier') {
245
+ return { kind: 'methodCallResult', lhs, receiver: objField.text, method: nameField.text };
246
+ }
247
+ }
248
+ }
222
249
  }
223
250
  return undefined;
224
251
  };
@@ -267,6 +294,39 @@ const extractJavaPatternBinding = (node) => {
267
294
  return undefined;
268
295
  return { varName, typeName };
269
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
+ };
270
330
  export const javaTypeConfig = {
271
331
  declarationNodeTypes: JAVA_DECLARATION_NODE_TYPES,
272
332
  forLoopNodeTypes: JAVA_FOR_LOOP_NODE_TYPES,
@@ -278,6 +338,7 @@ export const javaTypeConfig = {
278
338
  extractForLoopBinding: extractJavaForLoopBinding,
279
339
  extractPendingAssignment: extractJavaPendingAssignment,
280
340
  extractPatternBinding: extractJavaPatternBinding,
341
+ inferLiteralType: inferJvmLiteralType,
281
342
  };
282
343
  // ── Kotlin ────────────────────────────────────────────────────────────────
283
344
  const KOTLIN_DECLARATION_NODE_TYPES = new Set([
@@ -288,10 +349,11 @@ const KOTLIN_DECLARATION_NODE_TYPES = new Set([
288
349
  const extractKotlinDeclaration = (node, env) => {
289
350
  if (node.type === 'property_declaration') {
290
351
  // Kotlin property_declaration: name/type are inside a variable_declaration child
291
- const varDecl = findChildByType(node, 'variable_declaration');
352
+ const varDecl = findChild(node, 'variable_declaration');
292
353
  if (varDecl) {
293
- const nameNode = findChildByType(varDecl, 'simple_identifier');
294
- 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');
295
357
  if (!nameNode || !typeNode)
296
358
  return;
297
359
  const varName = extractVarName(nameNode);
@@ -302,9 +364,9 @@ const extractKotlinDeclaration = (node, env) => {
302
364
  }
303
365
  // Fallback: try direct fields
304
366
  const nameNode = node.childForFieldName('name')
305
- ?? findChildByType(node, 'simple_identifier');
367
+ ?? findChild(node, 'simple_identifier');
306
368
  const typeNode = node.childForFieldName('type')
307
- ?? findChildByType(node, 'user_type');
369
+ ?? findChild(node, 'user_type');
308
370
  if (!nameNode || !typeNode)
309
371
  return;
310
372
  const varName = extractVarName(nameNode);
@@ -314,8 +376,8 @@ const extractKotlinDeclaration = (node, env) => {
314
376
  }
315
377
  else if (node.type === 'variable_declaration') {
316
378
  // variable_declaration directly inside functions
317
- const nameNode = findChildByType(node, 'simple_identifier');
318
- const typeNode = findChildByType(node, 'user_type');
379
+ const nameNode = findChild(node, 'simple_identifier');
380
+ const typeNode = findChild(node, 'user_type');
319
381
  if (nameNode && typeNode) {
320
382
  const varName = extractVarName(nameNode);
321
383
  const typeName = extractSimpleTypeName(typeNode);
@@ -327,7 +389,7 @@ const extractKotlinDeclaration = (node, env) => {
327
389
  /** Kotlin: parameter / formal_parameter → type name.
328
390
  * Kotlin's tree-sitter grammar uses positional children (simple_identifier, user_type)
329
391
  * rather than named fields (name, type) on `parameter` nodes, so we fall back to
330
- * findChildByType when childForFieldName returns null. */
392
+ * findChild when childForFieldName returns null. */
331
393
  const extractKotlinParameter = (node, env) => {
332
394
  let nameNode = null;
333
395
  let typeNode = null;
@@ -341,9 +403,10 @@ const extractKotlinParameter = (node, env) => {
341
403
  }
342
404
  // Fallback: Kotlin `parameter` nodes use positional children, not named fields
343
405
  if (!nameNode)
344
- nameNode = findChildByType(node, 'simple_identifier');
406
+ nameNode = findChild(node, 'simple_identifier');
345
407
  if (!typeNode)
346
- typeNode = findChildByType(node, 'user_type');
408
+ typeNode = findChild(node, 'user_type')
409
+ ?? findChild(node, 'nullable_type');
347
410
  if (!nameNode || !typeNode)
348
411
  return;
349
412
  const varName = extractVarName(nameNode);
@@ -351,48 +414,61 @@ const extractKotlinParameter = (node, env) => {
351
414
  if (varName && typeName)
352
415
  env.set(varName, typeName);
353
416
  };
354
- /** Kotlin: val user = User() infer type from call_expression when callee is a known class.
355
- * Kotlin constructors are syntactically identical to function calls, so we verify
356
- * against classNames (which may include cross-file SymbolTable lookups). */
357
- 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) => {
358
420
  if (node.type !== 'property_declaration')
359
- return;
360
- // Skip if there's an explicit type annotation — Tier 0 already handled it
361
- const varDecl = findChildByType(node, 'variable_declaration');
362
- if (varDecl && findChildByType(varDecl, 'user_type'))
363
- return;
364
- // Get the initializer value — the call_expression after '='
421
+ return undefined;
365
422
  const value = node.childForFieldName('value')
366
- ?? findChildByType(node, 'call_expression');
423
+ ?? findChild(node, 'call_expression');
367
424
  if (!value || value.type !== 'call_expression')
368
- return;
369
- // The callee is the first child of call_expression (simple_identifier for direct calls)
425
+ return undefined;
370
426
  const callee = value.firstNamedChild;
371
427
  if (!callee || callee.type !== 'simple_identifier')
372
- return;
428
+ return undefined;
373
429
  const calleeName = callee.text;
374
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)
375
444
  return;
376
445
  // Extract the variable name from the variable_declaration inside property_declaration
377
446
  const nameNode = varDecl
378
- ? findChildByType(varDecl, 'simple_identifier')
379
- : findChildByType(node, 'simple_identifier');
447
+ ? findChild(varDecl, 'simple_identifier')
448
+ : findChild(node, 'simple_identifier');
380
449
  if (!nameNode)
381
450
  return;
382
451
  const varName = extractVarName(nameNode);
383
452
  if (varName)
384
453
  env.set(varName, calleeName);
385
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
+ };
386
462
  /** Kotlin: val x = User(...) — constructor binding for property_declaration with call_expression */
387
463
  const scanKotlinConstructorBinding = (node) => {
388
464
  if (node.type !== 'property_declaration')
389
465
  return undefined;
390
- const varDecl = findChildByType(node, 'variable_declaration');
466
+ const varDecl = findChild(node, 'variable_declaration');
391
467
  if (!varDecl)
392
468
  return undefined;
393
- if (findChildByType(varDecl, 'user_type'))
469
+ if (findChild(varDecl, 'user_type'))
394
470
  return undefined;
395
- const callExpr = findChildByType(node, 'call_expression');
471
+ const callExpr = findChild(node, 'call_expression');
396
472
  if (!callExpr)
397
473
  return undefined;
398
474
  const callee = callExpr.firstNamedChild;
@@ -414,7 +490,7 @@ const scanKotlinConstructorBinding = (node) => {
414
490
  }
415
491
  if (!calleeName)
416
492
  return undefined;
417
- const nameNode = findChildByType(varDecl, 'simple_identifier');
493
+ const nameNode = findChild(varDecl, 'simple_identifier');
418
494
  if (!nameNode)
419
495
  return undefined;
420
496
  return { varName: nameNode.text, calleeName };
@@ -427,7 +503,7 @@ const KOTLIN_FOR_LOOP_NODE_TYPES = new Set([
427
503
  * Handles the type_projection wrapper that Kotlin uses for generic type arguments. */
428
504
  const extractKotlinElementTypeFromTypeNode = (typeNode, pos = 'last') => {
429
505
  if (typeNode.type === 'user_type') {
430
- const argsNode = findChildByType(typeNode, 'type_arguments');
506
+ const argsNode = findChild(typeNode, 'type_arguments');
431
507
  if (argsNode && argsNode.namedChildCount >= 1) {
432
508
  const targetArg = pos === 'first'
433
509
  ? argsNode.namedChild(0)
@@ -450,16 +526,16 @@ const findKotlinParamElementType = (iterableName, startNode, pos = 'last') => {
450
526
  let current = startNode.parent;
451
527
  while (current) {
452
528
  if (current.type === 'function_declaration') {
453
- const paramsNode = findChildByType(current, 'function_value_parameters');
529
+ const paramsNode = findChild(current, 'function_value_parameters');
454
530
  if (paramsNode) {
455
531
  for (let i = 0; i < paramsNode.namedChildCount; i++) {
456
532
  const param = paramsNode.namedChild(i);
457
533
  if (!param || param.type !== 'parameter')
458
534
  continue;
459
- const nameNode = findChildByType(param, 'simple_identifier');
535
+ const nameNode = findChild(param, 'simple_identifier');
460
536
  if (nameNode?.text !== iterableName)
461
537
  continue;
462
- const typeNode = findChildByType(param, 'user_type');
538
+ const typeNode = findChild(param, 'user_type');
463
539
  if (typeNode)
464
540
  return extractKotlinElementTypeFromTypeNode(typeNode, pos);
465
541
  }
@@ -474,17 +550,17 @@ const findKotlinParamElementType = (iterableName, startNode, pos = 'last') => {
474
550
  * Tier 1c: for `for (user in users)` without annotation, resolves from iterable. */
475
551
  const extractKotlinForLoopBinding = (node, ctx) => {
476
552
  const { scopeEnv, declarationTypeNodes, scope, returnTypeLookup } = ctx;
477
- const varDecl = findChildByType(node, 'variable_declaration');
553
+ const varDecl = findChild(node, 'variable_declaration');
478
554
  if (!varDecl)
479
555
  return;
480
- const nameNode = findChildByType(varDecl, 'simple_identifier');
556
+ const nameNode = findChild(varDecl, 'simple_identifier');
481
557
  if (!nameNode)
482
558
  return;
483
559
  const varName = extractVarName(nameNode);
484
560
  if (!varName)
485
561
  return;
486
562
  // Explicit type annotation (existing behavior): for (user: User in users)
487
- const typeNode = findChildByType(varDecl, 'user_type');
563
+ const typeNode = findChild(varDecl, 'user_type');
488
564
  if (typeNode) {
489
565
  const typeName = extractSimpleTypeName(typeNode);
490
566
  if (typeName)
@@ -514,9 +590,9 @@ const extractKotlinForLoopBinding = (node, ctx) => {
514
590
  if (child.type === 'navigation_expression') {
515
591
  // data.keys → navigation_expression > simple_identifier(data) + navigation_suffix > simple_identifier(keys)
516
592
  const obj = child.firstNamedChild;
517
- const suffix = findChildByType(child, 'navigation_suffix');
518
- const prop = suffix ? findChildByType(suffix, 'simple_identifier') : null;
519
- 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;
520
596
  // Always try object as iterable + property as method first (handles data.values, data.keys).
521
597
  // For bare property access without call_suffix, also save property as fallback
522
598
  // (handles this.users, repo.items where the property IS the iterable).
@@ -536,9 +612,9 @@ const extractKotlinForLoopBinding = (node, ctx) => {
536
612
  const obj = callee.firstNamedChild;
537
613
  if (obj?.type === 'simple_identifier')
538
614
  iterableName = obj.text;
539
- const suffix = findChildByType(callee, 'navigation_suffix');
615
+ const suffix = findChild(callee, 'navigation_suffix');
540
616
  if (suffix) {
541
- const prop = findChildByType(suffix, 'simple_identifier');
617
+ const prop = findChild(suffix, 'simple_identifier');
542
618
  if (prop)
543
619
  methodName = prop.text;
544
620
  }
@@ -580,7 +656,7 @@ const extractKotlinForLoopBinding = (node, ctx) => {
580
656
  const extractKotlinPendingAssignment = (node, scopeEnv) => {
581
657
  if (node.type === 'property_declaration') {
582
658
  // Find the variable name from variable_declaration child
583
- const varDecl = findChildByType(node, 'variable_declaration');
659
+ const varDecl = findChild(node, 'variable_declaration');
584
660
  if (!varDecl)
585
661
  return undefined;
586
662
  const nameNode = varDecl.firstNamedChild;
@@ -589,7 +665,7 @@ const extractKotlinPendingAssignment = (node, scopeEnv) => {
589
665
  const lhs = nameNode.text;
590
666
  if (scopeEnv.has(lhs))
591
667
  return undefined;
592
- // Find the RHS: a simple_identifier sibling after the "=" token
668
+ // Find the RHS after the "=" token
593
669
  let foundEq = false;
594
670
  for (let i = 0; i < node.childCount; i++) {
595
671
  const child = node.child(i);
@@ -602,19 +678,43 @@ const extractKotlinPendingAssignment = (node, scopeEnv) => {
602
678
  if (foundEq && child.type === 'simple_identifier') {
603
679
  return { kind: 'copy', lhs, rhs: child.text };
604
680
  }
681
+ // navigation_expression RHS → fieldAccess (a.field)
682
+ if (foundEq && child.type === 'navigation_expression') {
683
+ const recv = child.firstNamedChild;
684
+ const suffix = child.lastNamedChild;
685
+ const fieldNode = suffix?.type === 'navigation_suffix' ? suffix.lastNamedChild : suffix;
686
+ if (recv?.type === 'simple_identifier' && fieldNode?.type === 'simple_identifier') {
687
+ return { kind: 'fieldAccess', lhs, receiver: recv.text, field: fieldNode.text };
688
+ }
689
+ }
690
+ // call_expression RHS
691
+ if (foundEq && child.type === 'call_expression') {
692
+ const calleeNode = child.firstNamedChild;
693
+ if (calleeNode?.type === 'simple_identifier') {
694
+ return { kind: 'callResult', lhs, callee: calleeNode.text };
695
+ }
696
+ // navigation_expression callee → methodCallResult (a.method())
697
+ if (calleeNode?.type === 'navigation_expression') {
698
+ const recv = calleeNode.firstNamedChild;
699
+ const suffix = calleeNode.lastNamedChild;
700
+ const methodNode = suffix?.type === 'navigation_suffix' ? suffix.lastNamedChild : suffix;
701
+ if (recv?.type === 'simple_identifier' && methodNode?.type === 'simple_identifier') {
702
+ return { kind: 'methodCallResult', lhs, receiver: recv.text, method: methodNode.text };
703
+ }
704
+ }
705
+ }
605
706
  }
606
707
  return undefined;
607
708
  }
608
709
  if (node.type === 'variable_declaration') {
609
710
  // variable_declaration directly inside functions: simple_identifier children
610
- const nameNode = findChildByType(node, 'simple_identifier');
711
+ const nameNode = findChild(node, 'simple_identifier');
611
712
  if (!nameNode)
612
713
  return undefined;
613
714
  const lhs = nameNode.text;
614
715
  if (scopeEnv.has(lhs))
615
716
  return undefined;
616
- // Look for RHS simple_identifier after "=" in the parent (property_declaration)
617
- // variable_declaration itself doesn't contain "=" — it's in the parent
717
+ // Look for RHS after "=" in the parent (property_declaration)
618
718
  const parent = node.parent;
619
719
  if (!parent)
620
720
  return undefined;
@@ -630,6 +730,28 @@ const extractKotlinPendingAssignment = (node, scopeEnv) => {
630
730
  if (foundEq && child.type === 'simple_identifier') {
631
731
  return { kind: 'copy', lhs, rhs: child.text };
632
732
  }
733
+ if (foundEq && child.type === 'navigation_expression') {
734
+ const recv = child.firstNamedChild;
735
+ const suffix = child.lastNamedChild;
736
+ const fieldNode = suffix?.type === 'navigation_suffix' ? suffix.lastNamedChild : suffix;
737
+ if (recv?.type === 'simple_identifier' && fieldNode?.type === 'simple_identifier') {
738
+ return { kind: 'fieldAccess', lhs, receiver: recv.text, field: fieldNode.text };
739
+ }
740
+ }
741
+ if (foundEq && child.type === 'call_expression') {
742
+ const calleeNode = child.firstNamedChild;
743
+ if (calleeNode?.type === 'simple_identifier') {
744
+ return { kind: 'callResult', lhs, callee: calleeNode.text };
745
+ }
746
+ if (calleeNode?.type === 'navigation_expression') {
747
+ const recv = calleeNode.firstNamedChild;
748
+ const suffix = calleeNode.lastNamedChild;
749
+ const methodNode = suffix?.type === 'navigation_suffix' ? suffix.lastNamedChild : suffix;
750
+ if (recv?.type === 'simple_identifier' && methodNode?.type === 'simple_identifier') {
751
+ return { kind: 'methodCallResult', lhs, receiver: recv.text, method: methodNode.text };
752
+ }
753
+ }
754
+ }
633
755
  }
634
756
  return undefined;
635
757
  }
@@ -645,32 +767,83 @@ const findAncestorByType = (node, type) => {
645
767
  }
646
768
  return undefined;
647
769
  };
648
- const extractKotlinPatternBinding = (node) => {
649
- if (node.type !== 'type_test')
650
- return undefined;
651
- const typeNode = node.lastNamedChild;
652
- if (!typeNode)
653
- return undefined;
654
- const typeName = extractSimpleTypeName(typeNode);
655
- if (!typeName)
656
- return undefined;
657
- const whenExpr = findAncestorByType(node, 'when_expression');
658
- if (!whenExpr)
659
- return undefined;
660
- const whenSubject = whenExpr.namedChild(0);
661
- const subject = whenSubject?.firstNamedChild ?? whenSubject;
662
- if (!subject)
663
- return undefined;
664
- const varName = extractVarName(subject);
665
- 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
+ }
666
838
  return undefined;
667
- return { varName, typeName };
839
+ }
840
+ return undefined;
668
841
  };
669
842
  export const kotlinTypeConfig = {
670
843
  allowPatternBindingOverwrite: true,
671
844
  declarationNodeTypes: KOTLIN_DECLARATION_NODE_TYPES,
672
845
  forLoopNodeTypes: KOTLIN_FOR_LOOP_NODE_TYPES,
673
- patternBindingNodeTypes: new Set(['type_test']),
846
+ patternBindingNodeTypes: new Set(['type_test', 'equality_expression']),
674
847
  extractDeclaration: extractKotlinDeclaration,
675
848
  extractParameter: extractKotlinParameter,
676
849
  extractInitializer: extractKotlinInitializer,
@@ -678,4 +851,6 @@ export const kotlinTypeConfig = {
678
851
  extractForLoopBinding: extractKotlinForLoopBinding,
679
852
  extractPendingAssignment: extractKotlinPendingAssignment,
680
853
  extractPatternBinding: extractKotlinPatternBinding,
854
+ inferLiteralType: inferJvmLiteralType,
855
+ detectConstructorType: detectKotlinConstructorType,
681
856
  };
@@ -373,13 +373,40 @@ const extractPendingAssignment = (node, scopeEnv) => {
373
373
  const right = node.childForFieldName('right');
374
374
  if (!left || !right)
375
375
  return undefined;
376
- if (left.type !== 'variable_name' || right.type !== 'variable_name')
376
+ if (left.type !== 'variable_name')
377
377
  return undefined;
378
378
  const lhs = left.text;
379
- const rhs = right.text;
380
- if (!lhs || !rhs || scopeEnv.has(lhs))
379
+ if (!lhs || scopeEnv.has(lhs))
381
380
  return undefined;
382
- return { kind: 'copy', lhs, rhs };
381
+ if (right.type === 'variable_name') {
382
+ const rhs = right.text;
383
+ if (rhs)
384
+ return { kind: 'copy', lhs, rhs };
385
+ }
386
+ // member_access_expression RHS → fieldAccess ($a->field)
387
+ if (right.type === 'member_access_expression') {
388
+ const obj = right.childForFieldName('object');
389
+ const name = right.childForFieldName('name');
390
+ if (obj?.type === 'variable_name' && name) {
391
+ return { kind: 'fieldAccess', lhs, receiver: obj.text, field: name.text };
392
+ }
393
+ }
394
+ // function_call_expression RHS → callResult (bare function calls only)
395
+ if (right.type === 'function_call_expression') {
396
+ const funcNode = right.childForFieldName('function');
397
+ if (funcNode?.type === 'name') {
398
+ return { kind: 'callResult', lhs, callee: funcNode.text };
399
+ }
400
+ }
401
+ // member_call_expression RHS → methodCallResult ($a->method())
402
+ if (right.type === 'member_call_expression') {
403
+ const obj = right.childForFieldName('object');
404
+ const name = right.childForFieldName('name');
405
+ if (obj?.type === 'variable_name' && name) {
406
+ return { kind: 'methodCallResult', lhs, receiver: obj.text, method: name.text };
407
+ }
408
+ }
409
+ return undefined;
383
410
  };
384
411
  const FOR_LOOP_NODE_TYPES = new Set([
385
412
  'foreach_statement',