gitnexus 1.4.5 → 1.4.7

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 (49) hide show
  1. package/dist/cli/eval-server.js +13 -5
  2. package/dist/cli/index.js +0 -0
  3. package/dist/cli/tool.d.ts +3 -2
  4. package/dist/cli/tool.js +48 -13
  5. package/dist/core/graph/types.d.ts +2 -2
  6. package/dist/core/ingestion/call-processor.d.ts +7 -2
  7. package/dist/core/ingestion/call-processor.js +308 -235
  8. package/dist/core/ingestion/call-routing.d.ts +17 -2
  9. package/dist/core/ingestion/call-routing.js +21 -0
  10. package/dist/core/ingestion/parsing-processor.d.ts +2 -1
  11. package/dist/core/ingestion/parsing-processor.js +37 -8
  12. package/dist/core/ingestion/pipeline.js +5 -1
  13. package/dist/core/ingestion/symbol-table.d.ts +19 -3
  14. package/dist/core/ingestion/symbol-table.js +41 -2
  15. package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
  16. package/dist/core/ingestion/tree-sitter-queries.js +200 -0
  17. package/dist/core/ingestion/type-env.js +126 -18
  18. package/dist/core/ingestion/type-extractors/c-cpp.js +28 -3
  19. package/dist/core/ingestion/type-extractors/csharp.js +61 -7
  20. package/dist/core/ingestion/type-extractors/go.js +86 -10
  21. package/dist/core/ingestion/type-extractors/jvm.js +122 -23
  22. package/dist/core/ingestion/type-extractors/php.js +172 -7
  23. package/dist/core/ingestion/type-extractors/python.js +107 -21
  24. package/dist/core/ingestion/type-extractors/ruby.js +18 -3
  25. package/dist/core/ingestion/type-extractors/rust.js +61 -14
  26. package/dist/core/ingestion/type-extractors/shared.d.ts +13 -0
  27. package/dist/core/ingestion/type-extractors/shared.js +243 -4
  28. package/dist/core/ingestion/type-extractors/types.d.ts +57 -12
  29. package/dist/core/ingestion/type-extractors/typescript.js +52 -8
  30. package/dist/core/ingestion/utils.d.ts +25 -0
  31. package/dist/core/ingestion/utils.js +160 -1
  32. package/dist/core/ingestion/workers/parse-worker.d.ts +23 -7
  33. package/dist/core/ingestion/workers/parse-worker.js +73 -28
  34. package/dist/core/lbug/lbug-adapter.d.ts +2 -0
  35. package/dist/core/lbug/lbug-adapter.js +2 -0
  36. package/dist/core/lbug/schema.d.ts +1 -1
  37. package/dist/core/lbug/schema.js +1 -1
  38. package/dist/mcp/core/lbug-adapter.d.ts +22 -0
  39. package/dist/mcp/core/lbug-adapter.js +167 -23
  40. package/dist/mcp/local/local-backend.d.ts +1 -0
  41. package/dist/mcp/local/local-backend.js +25 -3
  42. package/dist/mcp/resources.js +11 -0
  43. package/dist/mcp/server.js +26 -4
  44. package/dist/mcp/tools.js +15 -5
  45. package/hooks/claude/gitnexus-hook.cjs +0 -0
  46. package/hooks/claude/pre-tool-use.sh +0 -0
  47. package/hooks/claude/session-start.sh +0 -0
  48. package/package.json +6 -5
  49. package/scripts/patch-tree-sitter-swift.cjs +0 -0
@@ -1,4 +1,4 @@
1
- import { extractSimpleTypeName, extractVarName, findChildByType, unwrapAwait, resolveIterableElementType, methodToTypeArgPosition } from './shared.js';
1
+ import { extractSimpleTypeName, extractVarName, findChildByType, unwrapAwait, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
2
2
  /** Known container property accessors that operate on the container itself (e.g., dict.Keys, dict.Values) */
3
3
  const KNOWN_CONTAINER_PROPS = new Set(['Keys', 'Values']);
4
4
  const DECLARATION_NODE_TYPES = new Set([
@@ -214,7 +214,7 @@ const findCSharpParamElementType = (iterableName, startNode, pos = 'last') => {
214
214
  };
215
215
  /** C#: foreach (User user in users) — extract loop variable binding.
216
216
  * Tier 1c: for `foreach (var user in users)`, resolves element type from iterable. */
217
- const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
217
+ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, returnTypeLookup }) => {
218
218
  const typeNode = node.childForFieldName('type');
219
219
  const nameNode = node.childForFieldName('left');
220
220
  if (!typeNode || !nameNode)
@@ -233,6 +233,7 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
233
233
  const rightNode = node.childForFieldName('right');
234
234
  let iterableName;
235
235
  let methodName;
236
+ let callExprElementType;
236
237
  if (rightNode?.type === 'identifier') {
237
238
  iterableName = rightNode.text;
238
239
  }
@@ -261,6 +262,7 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
261
262
  }
262
263
  else if (rightNode?.type === 'invocation_expression') {
263
264
  // C# method call: data.Select(...) → invocation_expression > member_access_expression
265
+ // Direct function call: GetUsers() → invocation_expression > identifier
264
266
  const fn = rightNode.firstNamedChild;
265
267
  if (fn?.type === 'member_access_expression') {
266
268
  const obj = fn.childForFieldName('expression');
@@ -270,12 +272,24 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
270
272
  if (prop?.type === 'identifier')
271
273
  methodName = prop.text;
272
274
  }
275
+ else if (fn?.type === 'identifier') {
276
+ // Direct function call: foreach (var u in GetUsers())
277
+ const rawReturn = returnTypeLookup.lookupRawReturnType(fn.text);
278
+ if (rawReturn)
279
+ callExprElementType = extractElementTypeFromString(rawReturn);
280
+ }
273
281
  }
274
- if (!iterableName)
282
+ if (!iterableName && !callExprElementType)
275
283
  return;
276
- const containerTypeName = scopeEnv.get(iterableName);
277
- const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
278
- const elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractCSharpElementTypeFromTypeNode, findCSharpParamElementType, typeArgPos);
284
+ let elementType;
285
+ if (callExprElementType) {
286
+ elementType = callExprElementType;
287
+ }
288
+ else {
289
+ const containerTypeName = scopeEnv.get(iterableName);
290
+ const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
291
+ elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractCSharpElementTypeFromTypeNode, findCSharpParamElementType, typeArgPos);
292
+ }
279
293
  if (elementType)
280
294
  scopeEnv.set(varName, elementType);
281
295
  };
@@ -351,7 +365,47 @@ const extractPendingAssignment = (node, scopeEnv) => {
351
365
  }
352
366
  const valueNode = evc?.firstNamedChild ?? child.namedChild(child.namedChildCount - 1);
353
367
  if (valueNode && valueNode !== nameNode && (valueNode.type === 'identifier' || valueNode.type === 'simple_identifier')) {
354
- return { lhs, rhs: valueNode.text };
368
+ return { kind: 'copy', lhs, rhs: valueNode.text };
369
+ }
370
+ // member_access_expression RHS → fieldAccess (a.Field)
371
+ if (valueNode?.type === 'member_access_expression') {
372
+ const expr = valueNode.childForFieldName('expression');
373
+ const name = valueNode.childForFieldName('name');
374
+ if (expr?.type === 'identifier' && name?.type === 'identifier') {
375
+ return { kind: 'fieldAccess', lhs, receiver: expr.text, field: name.text };
376
+ }
377
+ }
378
+ // invocation_expression RHS
379
+ if (valueNode?.type === 'invocation_expression') {
380
+ const funcNode = valueNode.firstNamedChild;
381
+ if (funcNode?.type === 'identifier_name' || funcNode?.type === 'identifier') {
382
+ return { kind: 'callResult', lhs, callee: funcNode.text };
383
+ }
384
+ // method call with receiver → methodCallResult: a.GetC()
385
+ if (funcNode?.type === 'member_access_expression') {
386
+ const expr = funcNode.childForFieldName('expression');
387
+ const name = funcNode.childForFieldName('name');
388
+ if (expr?.type === 'identifier' && name?.type === 'identifier') {
389
+ return { kind: 'methodCallResult', lhs, receiver: expr.text, method: name.text };
390
+ }
391
+ }
392
+ }
393
+ // await_expression → unwrap and check inner
394
+ if (valueNode?.type === 'await_expression') {
395
+ const inner = valueNode.firstNamedChild;
396
+ if (inner?.type === 'invocation_expression') {
397
+ const funcNode = inner.firstNamedChild;
398
+ if (funcNode?.type === 'identifier_name' || funcNode?.type === 'identifier') {
399
+ return { kind: 'callResult', lhs, callee: funcNode.text };
400
+ }
401
+ if (funcNode?.type === 'member_access_expression') {
402
+ const expr = funcNode.childForFieldName('expression');
403
+ const name = funcNode.childForFieldName('name');
404
+ if (expr?.type === 'identifier' && name?.type === 'identifier') {
405
+ return { kind: 'methodCallResult', lhs, receiver: expr.text, method: name.text };
406
+ }
407
+ }
408
+ }
355
409
  }
356
410
  }
357
411
  return undefined;
@@ -296,7 +296,7 @@ const findGoParamElementType = (iterableName, startNode, pos = 'last') => {
296
296
  * For `_, user := range users`, the loop variable is the second identifier in
297
297
  * the `left` expression_list (index is discarded, value is the element).
298
298
  */
299
- const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
299
+ const extractForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, returnTypeLookup }) => {
300
300
  if (node.type !== 'for_statement')
301
301
  return;
302
302
  // Find the range_clause child — this distinguishes range loops from other for forms.
@@ -313,6 +313,7 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
313
313
  // The iterable is the `right` field of the range_clause.
314
314
  const rightNode = rangeClause.childForFieldName('right');
315
315
  let iterableName;
316
+ let callExprElementType;
316
317
  if (rightNode?.type === 'identifier') {
317
318
  iterableName = rightNode.text;
318
319
  }
@@ -321,11 +322,35 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
321
322
  if (field)
322
323
  iterableName = field.text;
323
324
  }
324
- if (!iterableName)
325
+ else if (rightNode?.type === 'call_expression') {
326
+ // Range over a call result: `for _, v := range getItems()` or `for _, v := range repo.All()`
327
+ const funcNode = rightNode.childForFieldName('function');
328
+ let callee;
329
+ if (funcNode?.type === 'identifier') {
330
+ callee = funcNode.text;
331
+ }
332
+ else if (funcNode?.type === 'selector_expression') {
333
+ const field = funcNode.childForFieldName('field');
334
+ if (field)
335
+ callee = field.text;
336
+ }
337
+ if (callee) {
338
+ const rawReturn = returnTypeLookup.lookupRawReturnType(callee);
339
+ if (rawReturn)
340
+ callExprElementType = extractElementTypeFromString(rawReturn);
341
+ }
342
+ }
343
+ if (!iterableName && !callExprElementType)
325
344
  return;
326
- const containerTypeName = scopeEnv.get(iterableName);
327
- const typeArgPos = methodToTypeArgPosition(undefined, containerTypeName);
328
- const elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractGoElementTypeFromTypeNode, findGoParamElementType, typeArgPos);
345
+ let elementType;
346
+ if (callExprElementType) {
347
+ elementType = callExprElementType;
348
+ }
349
+ else {
350
+ const containerTypeName = scopeEnv.get(iterableName);
351
+ const typeArgPos = methodToTypeArgPosition(undefined, containerTypeName);
352
+ elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractGoElementTypeFromTypeNode, findGoParamElementType, typeArgPos);
353
+ }
329
354
  if (!elementType)
330
355
  return;
331
356
  // The loop variable(s) are in the `left` field.
@@ -343,8 +368,11 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
343
368
  loopVarNode = leftNode.namedChild(1);
344
369
  }
345
370
  else {
346
- // Single-var in expression_list — yields INDEX for slices/maps, ELEMENT for channels
347
- if (isChannelType(iterableName, scopeEnv, declarationTypeNodes, scope)) {
371
+ // Single-var in expression_list — yields INDEX for slices/maps, ELEMENT for channels.
372
+ // For call-expression iterables (iterableName undefined), conservative: treat as non-channel.
373
+ // Channels are rarely returned from function calls, and even if they were, skipping here
374
+ // just means we miss a binding rather than create an incorrect one.
375
+ if (iterableName && isChannelType(iterableName, scopeEnv, declarationTypeNodes, scope)) {
348
376
  loopVarNode = leftNode.namedChild(0);
349
377
  }
350
378
  else {
@@ -354,7 +382,10 @@ const extractForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
354
382
  }
355
383
  else {
356
384
  // Plain identifier (single-var form without expression_list)
357
- if (isChannelType(iterableName, scopeEnv, declarationTypeNodes, scope)) {
385
+ // For call-expression iterables (iterableName undefined), conservative: treat as non-channel.
386
+ // Channels are rarely returned from function calls, and even if they were, skipping here
387
+ // just means we miss a binding rather than create an incorrect one.
388
+ if (iterableName && isChannelType(iterableName, scopeEnv, declarationTypeNodes, scope)) {
358
389
  loopVarNode = leftNode;
359
390
  }
360
391
  else {
@@ -387,7 +418,30 @@ const extractPendingAssignment = (node, scopeEnv) => {
387
418
  if (scopeEnv.has(lhs))
388
419
  return undefined;
389
420
  if (rhsNode.type === 'identifier')
390
- return { lhs, rhs: rhsNode.text };
421
+ return { kind: 'copy', lhs, rhs: rhsNode.text };
422
+ // selector_expression RHS → fieldAccess (a.field)
423
+ if (rhsNode.type === 'selector_expression') {
424
+ const operand = rhsNode.childForFieldName('operand');
425
+ const field = rhsNode.childForFieldName('field');
426
+ if (operand?.type === 'identifier' && field) {
427
+ return { kind: 'fieldAccess', lhs, receiver: operand.text, field: field.text };
428
+ }
429
+ }
430
+ // call_expression RHS
431
+ if (rhsNode.type === 'call_expression') {
432
+ const funcNode = rhsNode.childForFieldName('function');
433
+ if (funcNode?.type === 'identifier') {
434
+ return { kind: 'callResult', lhs, callee: funcNode.text };
435
+ }
436
+ // method call with receiver: call_expression → function: selector_expression
437
+ if (funcNode?.type === 'selector_expression') {
438
+ const operand = funcNode.childForFieldName('operand');
439
+ const field = funcNode.childForFieldName('field');
440
+ if (operand?.type === 'identifier' && field) {
441
+ return { kind: 'methodCallResult', lhs, receiver: operand.text, method: field.text };
442
+ }
443
+ }
444
+ }
391
445
  return undefined;
392
446
  }
393
447
  if (node.type === 'var_spec' || node.type === 'var_declaration') {
@@ -420,7 +474,29 @@ const extractPendingAssignment = (node, scopeEnv) => {
420
474
  }
421
475
  const rhsNode = exprList?.firstNamedChild;
422
476
  if (rhsNode?.type === 'identifier')
423
- return { lhs, rhs: rhsNode.text };
477
+ return { kind: 'copy', lhs, rhs: rhsNode.text };
478
+ // selector_expression RHS → fieldAccess
479
+ if (rhsNode?.type === 'selector_expression') {
480
+ const operand = rhsNode.childForFieldName('operand');
481
+ const field = rhsNode.childForFieldName('field');
482
+ if (operand?.type === 'identifier' && field) {
483
+ return { kind: 'fieldAccess', lhs, receiver: operand.text, field: field.text };
484
+ }
485
+ }
486
+ // call_expression RHS
487
+ if (rhsNode?.type === 'call_expression') {
488
+ const funcNode = rhsNode.childForFieldName('function');
489
+ if (funcNode?.type === 'identifier') {
490
+ return { kind: 'callResult', lhs, callee: funcNode.text };
491
+ }
492
+ if (funcNode?.type === 'selector_expression') {
493
+ const operand = funcNode.childForFieldName('operand');
494
+ const field = funcNode.childForFieldName('field');
495
+ if (operand?.type === 'identifier' && field) {
496
+ return { kind: 'methodCallResult', lhs, receiver: operand.text, method: field.text };
497
+ }
498
+ }
499
+ }
424
500
  }
425
501
  }
426
502
  return undefined;
@@ -1,4 +1,4 @@
1
- import { extractSimpleTypeName, extractVarName, findChildByType, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition } from './shared.js';
1
+ import { extractSimpleTypeName, extractVarName, findChildByType, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
2
2
  // ── Java ──────────────────────────────────────────────────────────────────
3
3
  const JAVA_DECLARATION_NODE_TYPES = new Set([
4
4
  'local_variable_declaration',
@@ -139,7 +139,7 @@ const findJavaParamElementType = (iterableName, startNode, pos = 'last') => {
139
139
  };
140
140
  /** Java: for (User user : users) — extract loop variable binding.
141
141
  * Tier 1c: for `for (var user : users)`, resolves element type from iterable. */
142
- const extractJavaForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
142
+ const extractJavaForLoopBinding = (node, { scopeEnv, declarationTypeNodes, scope, returnTypeLookup }) => {
143
143
  const typeNode = node.childForFieldName('type');
144
144
  const nameNode = node.childForFieldName('name');
145
145
  if (!typeNode || !nameNode)
@@ -159,6 +159,7 @@ const extractJavaForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope)
159
159
  return;
160
160
  let iterableName;
161
161
  let methodName;
162
+ let callExprElementType;
162
163
  if (iterableNode.type === 'identifier') {
163
164
  iterableName = iterableNode.text;
164
165
  }
@@ -180,14 +181,26 @@ const extractJavaForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope)
180
181
  if (innerField)
181
182
  iterableName = innerField.text;
182
183
  }
184
+ else if (!obj && name) {
185
+ // Direct function call: for (var u : getUsers()) — no receiver object
186
+ const rawReturn = returnTypeLookup.lookupRawReturnType(name.text);
187
+ if (rawReturn)
188
+ callExprElementType = extractElementTypeFromString(rawReturn);
189
+ }
183
190
  if (name)
184
191
  methodName = name.text;
185
192
  }
186
- if (!iterableName)
193
+ if (!iterableName && !callExprElementType)
187
194
  return;
188
- const containerTypeName = scopeEnv.get(iterableName);
189
- const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
190
- const elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractJavaElementTypeFromTypeNode, findJavaParamElementType, typeArgPos);
195
+ let elementType;
196
+ if (callExprElementType) {
197
+ elementType = callExprElementType;
198
+ }
199
+ else {
200
+ const containerTypeName = scopeEnv.get(iterableName);
201
+ const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
202
+ elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractJavaElementTypeFromTypeNode, findJavaParamElementType, typeArgPos);
203
+ }
191
204
  if (elementType)
192
205
  scopeEnv.set(varName, elementType);
193
206
  };
@@ -205,7 +218,33 @@ const extractJavaPendingAssignment = (node, scopeEnv) => {
205
218
  if (scopeEnv.has(lhs))
206
219
  continue;
207
220
  if (valueNode.type === 'identifier' || valueNode.type === 'simple_identifier')
208
- return { lhs, rhs: valueNode.text };
221
+ return { kind: 'copy', lhs, rhs: valueNode.text };
222
+ // field_access RHS → fieldAccess (a.field)
223
+ if (valueNode.type === 'field_access') {
224
+ const obj = valueNode.childForFieldName('object');
225
+ const field = valueNode.childForFieldName('field');
226
+ if (obj?.type === 'identifier' && field) {
227
+ return { kind: 'fieldAccess', lhs, receiver: obj.text, field: field.text };
228
+ }
229
+ }
230
+ // method_invocation RHS
231
+ if (valueNode.type === 'method_invocation') {
232
+ const objField = valueNode.childForFieldName('object');
233
+ if (!objField) {
234
+ // No receiver → callResult
235
+ const nameField = valueNode.childForFieldName('name');
236
+ if (nameField?.type === 'identifier') {
237
+ return { kind: 'callResult', lhs, callee: nameField.text };
238
+ }
239
+ }
240
+ else if (objField.type === 'identifier') {
241
+ // With receiver → methodCallResult
242
+ const nameField = valueNode.childForFieldName('name');
243
+ if (nameField?.type === 'identifier') {
244
+ return { kind: 'methodCallResult', lhs, receiver: objField.text, method: nameField.text };
245
+ }
246
+ }
247
+ }
209
248
  }
210
249
  return undefined;
211
250
  };
@@ -459,7 +498,8 @@ const findKotlinParamElementType = (iterableName, startNode, pos = 'last') => {
459
498
  };
460
499
  /** Kotlin: for (user: User in users) — extract loop variable binding.
461
500
  * Tier 1c: for `for (user in users)` without annotation, resolves from iterable. */
462
- const extractKotlinForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope) => {
501
+ const extractKotlinForLoopBinding = (node, ctx) => {
502
+ const { scopeEnv, declarationTypeNodes, scope, returnTypeLookup } = ctx;
463
503
  const varDecl = findChildByType(node, 'variable_declaration');
464
504
  if (!varDecl)
465
505
  return;
@@ -483,6 +523,7 @@ const extractKotlinForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope
483
523
  let iterableName;
484
524
  let methodName;
485
525
  let fallbackIterableName;
526
+ let callExprElementType;
486
527
  let foundVarDecl = false;
487
528
  for (let i = 0; i < node.namedChildCount; i++) {
488
529
  const child = node.namedChild(i);
@@ -528,21 +569,33 @@ const extractKotlinForLoopBinding = (node, scopeEnv, declarationTypeNodes, scope
528
569
  methodName = prop.text;
529
570
  }
530
571
  }
572
+ else if (callee?.type === 'simple_identifier') {
573
+ // Direct function call: for (u in getUsers())
574
+ const rawReturn = returnTypeLookup.lookupRawReturnType(callee.text);
575
+ if (rawReturn)
576
+ callExprElementType = extractElementTypeFromString(rawReturn);
577
+ }
531
578
  break;
532
579
  }
533
580
  }
534
- if (!iterableName)
581
+ if (!iterableName && !callExprElementType)
535
582
  return;
536
- let containerTypeName = scopeEnv.get(iterableName);
537
- // Fallback: if object has no type in scope, try the property as the iterable name.
538
- // Handles patterns like this.users where the property itself is the iterable variable.
539
- if (!containerTypeName && fallbackIterableName) {
540
- iterableName = fallbackIterableName;
541
- methodName = undefined;
542
- containerTypeName = scopeEnv.get(iterableName);
583
+ let elementType;
584
+ if (callExprElementType) {
585
+ elementType = callExprElementType;
586
+ }
587
+ else {
588
+ let containerTypeName = scopeEnv.get(iterableName);
589
+ // Fallback: if object has no type in scope, try the property as the iterable name.
590
+ // Handles patterns like this.users where the property itself is the iterable variable.
591
+ if (!containerTypeName && fallbackIterableName) {
592
+ iterableName = fallbackIterableName;
593
+ methodName = undefined;
594
+ containerTypeName = scopeEnv.get(iterableName);
595
+ }
596
+ const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
597
+ elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractKotlinElementTypeFromTypeNode, findKotlinParamElementType, typeArgPos);
543
598
  }
544
- const typeArgPos = methodToTypeArgPosition(methodName, containerTypeName);
545
- const elementType = resolveIterableElementType(iterableName, node, scopeEnv, declarationTypeNodes, scope, extractKotlinElementTypeFromTypeNode, findKotlinParamElementType, typeArgPos);
546
599
  if (elementType)
547
600
  scopeEnv.set(varName, elementType);
548
601
  };
@@ -562,7 +615,7 @@ const extractKotlinPendingAssignment = (node, scopeEnv) => {
562
615
  const lhs = nameNode.text;
563
616
  if (scopeEnv.has(lhs))
564
617
  return undefined;
565
- // Find the RHS: a simple_identifier sibling after the "=" token
618
+ // Find the RHS after the "=" token
566
619
  let foundEq = false;
567
620
  for (let i = 0; i < node.childCount; i++) {
568
621
  const child = node.child(i);
@@ -573,7 +626,32 @@ const extractKotlinPendingAssignment = (node, scopeEnv) => {
573
626
  continue;
574
627
  }
575
628
  if (foundEq && child.type === 'simple_identifier') {
576
- return { lhs, rhs: child.text };
629
+ return { kind: 'copy', lhs, rhs: child.text };
630
+ }
631
+ // navigation_expression RHS → fieldAccess (a.field)
632
+ if (foundEq && child.type === 'navigation_expression') {
633
+ const recv = child.firstNamedChild;
634
+ const suffix = child.lastNamedChild;
635
+ const fieldNode = suffix?.type === 'navigation_suffix' ? suffix.lastNamedChild : suffix;
636
+ if (recv?.type === 'simple_identifier' && fieldNode?.type === 'simple_identifier') {
637
+ return { kind: 'fieldAccess', lhs, receiver: recv.text, field: fieldNode.text };
638
+ }
639
+ }
640
+ // call_expression RHS
641
+ if (foundEq && child.type === 'call_expression') {
642
+ const calleeNode = child.firstNamedChild;
643
+ if (calleeNode?.type === 'simple_identifier') {
644
+ return { kind: 'callResult', lhs, callee: calleeNode.text };
645
+ }
646
+ // navigation_expression callee → methodCallResult (a.method())
647
+ if (calleeNode?.type === 'navigation_expression') {
648
+ const recv = calleeNode.firstNamedChild;
649
+ const suffix = calleeNode.lastNamedChild;
650
+ const methodNode = suffix?.type === 'navigation_suffix' ? suffix.lastNamedChild : suffix;
651
+ if (recv?.type === 'simple_identifier' && methodNode?.type === 'simple_identifier') {
652
+ return { kind: 'methodCallResult', lhs, receiver: recv.text, method: methodNode.text };
653
+ }
654
+ }
577
655
  }
578
656
  }
579
657
  return undefined;
@@ -586,8 +664,7 @@ const extractKotlinPendingAssignment = (node, scopeEnv) => {
586
664
  const lhs = nameNode.text;
587
665
  if (scopeEnv.has(lhs))
588
666
  return undefined;
589
- // Look for RHS simple_identifier after "=" in the parent (property_declaration)
590
- // variable_declaration itself doesn't contain "=" — it's in the parent
667
+ // Look for RHS after "=" in the parent (property_declaration)
591
668
  const parent = node.parent;
592
669
  if (!parent)
593
670
  return undefined;
@@ -601,7 +678,29 @@ const extractKotlinPendingAssignment = (node, scopeEnv) => {
601
678
  continue;
602
679
  }
603
680
  if (foundEq && child.type === 'simple_identifier') {
604
- return { lhs, rhs: child.text };
681
+ return { kind: 'copy', lhs, rhs: child.text };
682
+ }
683
+ if (foundEq && child.type === 'navigation_expression') {
684
+ const recv = child.firstNamedChild;
685
+ const suffix = child.lastNamedChild;
686
+ const fieldNode = suffix?.type === 'navigation_suffix' ? suffix.lastNamedChild : suffix;
687
+ if (recv?.type === 'simple_identifier' && fieldNode?.type === 'simple_identifier') {
688
+ return { kind: 'fieldAccess', lhs, receiver: recv.text, field: fieldNode.text };
689
+ }
690
+ }
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
+ if (calleeNode?.type === 'navigation_expression') {
697
+ const recv = calleeNode.firstNamedChild;
698
+ const suffix = calleeNode.lastNamedChild;
699
+ const methodNode = suffix?.type === 'navigation_suffix' ? suffix.lastNamedChild : suffix;
700
+ if (recv?.type === 'simple_identifier' && methodNode?.type === 'simple_identifier') {
701
+ return { kind: 'methodCallResult', lhs, receiver: recv.text, method: methodNode.text };
702
+ }
703
+ }
605
704
  }
606
705
  }
607
706
  return undefined;