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.
- package/dist/cli/eval-server.js +13 -5
- package/dist/cli/index.js +0 -0
- package/dist/cli/tool.d.ts +3 -2
- package/dist/cli/tool.js +48 -13
- package/dist/core/graph/types.d.ts +2 -2
- package/dist/core/ingestion/call-processor.d.ts +7 -2
- package/dist/core/ingestion/call-processor.js +308 -235
- package/dist/core/ingestion/call-routing.d.ts +17 -2
- package/dist/core/ingestion/call-routing.js +21 -0
- package/dist/core/ingestion/parsing-processor.d.ts +2 -1
- package/dist/core/ingestion/parsing-processor.js +37 -8
- package/dist/core/ingestion/pipeline.js +5 -1
- package/dist/core/ingestion/symbol-table.d.ts +19 -3
- package/dist/core/ingestion/symbol-table.js +41 -2
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
- package/dist/core/ingestion/tree-sitter-queries.js +200 -0
- package/dist/core/ingestion/type-env.js +126 -18
- package/dist/core/ingestion/type-extractors/c-cpp.js +28 -3
- package/dist/core/ingestion/type-extractors/csharp.js +61 -7
- package/dist/core/ingestion/type-extractors/go.js +86 -10
- package/dist/core/ingestion/type-extractors/jvm.js +122 -23
- package/dist/core/ingestion/type-extractors/php.js +172 -7
- package/dist/core/ingestion/type-extractors/python.js +107 -21
- package/dist/core/ingestion/type-extractors/ruby.js +18 -3
- package/dist/core/ingestion/type-extractors/rust.js +61 -14
- package/dist/core/ingestion/type-extractors/shared.d.ts +13 -0
- package/dist/core/ingestion/type-extractors/shared.js +243 -4
- package/dist/core/ingestion/type-extractors/types.d.ts +57 -12
- package/dist/core/ingestion/type-extractors/typescript.js +52 -8
- package/dist/core/ingestion/utils.d.ts +25 -0
- package/dist/core/ingestion/utils.js +160 -1
- package/dist/core/ingestion/workers/parse-worker.d.ts +23 -7
- package/dist/core/ingestion/workers/parse-worker.js +73 -28
- package/dist/core/lbug/lbug-adapter.d.ts +2 -0
- package/dist/core/lbug/lbug-adapter.js +2 -0
- package/dist/core/lbug/schema.d.ts +1 -1
- package/dist/core/lbug/schema.js +1 -1
- package/dist/mcp/core/lbug-adapter.d.ts +22 -0
- package/dist/mcp/core/lbug-adapter.js +167 -23
- package/dist/mcp/local/local-backend.d.ts +1 -0
- package/dist/mcp/local/local-backend.js +25 -3
- package/dist/mcp/resources.js +11 -0
- package/dist/mcp/server.js +26 -4
- package/dist/mcp/tools.js +15 -5
- package/hooks/claude/gitnexus-hook.cjs +0 -0
- package/hooks/claude/pre-tool-use.sh +0 -0
- package/hooks/claude/session-start.sh +0 -0
- package/package.json +6 -5
- 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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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 (
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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,
|
|
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
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
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
|
|
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
|
|
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;
|