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.
- package/README.md +22 -1
- package/dist/cli/ai-context.d.ts +1 -1
- package/dist/cli/ai-context.js +1 -1
- package/dist/cli/analyze.d.ts +2 -0
- package/dist/cli/analyze.js +54 -21
- package/dist/cli/index.js +2 -1
- package/dist/cli/setup.js +78 -1
- package/dist/config/supported-languages.d.ts +30 -0
- package/dist/config/supported-languages.js +30 -0
- package/dist/core/embeddings/embedder.d.ts +6 -1
- package/dist/core/embeddings/embedder.js +65 -5
- package/dist/core/embeddings/embedding-pipeline.js +11 -9
- package/dist/core/embeddings/http-client.d.ts +31 -0
- package/dist/core/embeddings/http-client.js +179 -0
- package/dist/core/embeddings/index.d.ts +1 -0
- package/dist/core/embeddings/index.js +1 -0
- package/dist/core/embeddings/types.d.ts +1 -1
- package/dist/core/graph/types.d.ts +4 -3
- package/dist/core/ingestion/ast-helpers.d.ts +80 -0
- package/dist/core/ingestion/ast-helpers.js +738 -0
- package/dist/core/ingestion/call-analysis.d.ts +73 -0
- package/dist/core/ingestion/call-analysis.js +490 -0
- package/dist/core/ingestion/call-processor.d.ts +55 -2
- package/dist/core/ingestion/call-processor.js +673 -108
- package/dist/core/ingestion/call-routing.d.ts +23 -2
- package/dist/core/ingestion/call-routing.js +21 -0
- package/dist/core/ingestion/entry-point-scoring.js +36 -26
- package/dist/core/ingestion/framework-detection.d.ts +10 -2
- package/dist/core/ingestion/framework-detection.js +49 -12
- package/dist/core/ingestion/heritage-processor.js +47 -49
- package/dist/core/ingestion/import-processor.d.ts +1 -1
- package/dist/core/ingestion/import-processor.js +103 -194
- package/dist/core/ingestion/import-resolution.d.ts +101 -0
- package/dist/core/ingestion/import-resolution.js +251 -0
- package/dist/core/ingestion/language-config.d.ts +3 -0
- package/dist/core/ingestion/language-config.js +13 -0
- package/dist/core/ingestion/markdown-processor.d.ts +17 -0
- package/dist/core/ingestion/markdown-processor.js +124 -0
- package/dist/core/ingestion/mro-processor.js +8 -3
- package/dist/core/ingestion/named-binding-extraction.d.ts +9 -43
- package/dist/core/ingestion/named-binding-extraction.js +89 -79
- package/dist/core/ingestion/parsing-processor.d.ts +3 -2
- package/dist/core/ingestion/parsing-processor.js +27 -60
- package/dist/core/ingestion/pipeline.d.ts +10 -0
- package/dist/core/ingestion/pipeline.js +425 -4
- package/dist/core/ingestion/resolution-context.d.ts +5 -0
- package/dist/core/ingestion/resolution-context.js +7 -4
- package/dist/core/ingestion/resolvers/index.d.ts +1 -1
- package/dist/core/ingestion/resolvers/index.js +1 -1
- package/dist/core/ingestion/resolvers/jvm.d.ts +2 -1
- package/dist/core/ingestion/resolvers/jvm.js +25 -9
- package/dist/core/ingestion/resolvers/php.d.ts +14 -0
- package/dist/core/ingestion/resolvers/php.js +43 -3
- package/dist/core/ingestion/resolvers/utils.d.ts +5 -0
- package/dist/core/ingestion/resolvers/utils.js +16 -0
- package/dist/core/ingestion/symbol-table.d.ts +29 -3
- package/dist/core/ingestion/symbol-table.js +42 -9
- package/dist/core/ingestion/tree-sitter-queries.d.ts +12 -12
- package/dist/core/ingestion/tree-sitter-queries.js +243 -2
- package/dist/core/ingestion/type-env.d.ts +28 -1
- package/dist/core/ingestion/type-env.js +451 -72
- package/dist/core/ingestion/type-extractors/c-cpp.d.ts +5 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +146 -2
- package/dist/core/ingestion/type-extractors/csharp.js +189 -16
- package/dist/core/ingestion/type-extractors/go.js +45 -0
- package/dist/core/ingestion/type-extractors/index.d.ts +1 -1
- package/dist/core/ingestion/type-extractors/index.js +1 -1
- package/dist/core/ingestion/type-extractors/jvm.js +244 -69
- package/dist/core/ingestion/type-extractors/php.js +31 -4
- package/dist/core/ingestion/type-extractors/python.js +89 -17
- package/dist/core/ingestion/type-extractors/ruby.js +17 -2
- package/dist/core/ingestion/type-extractors/rust.js +72 -4
- package/dist/core/ingestion/type-extractors/shared.d.ts +12 -2
- package/dist/core/ingestion/type-extractors/shared.js +115 -13
- package/dist/core/ingestion/type-extractors/swift.js +7 -6
- package/dist/core/ingestion/type-extractors/types.d.ts +54 -11
- package/dist/core/ingestion/type-extractors/typescript.js +171 -9
- package/dist/core/ingestion/utils.d.ts +2 -95
- package/dist/core/ingestion/utils.js +3 -892
- package/dist/core/ingestion/workers/parse-worker.d.ts +36 -11
- package/dist/core/ingestion/workers/parse-worker.js +116 -95
- package/dist/core/lbug/csv-generator.js +18 -1
- package/dist/core/lbug/lbug-adapter.d.ts +12 -0
- package/dist/core/lbug/lbug-adapter.js +71 -4
- package/dist/core/lbug/schema.d.ts +6 -4
- package/dist/core/lbug/schema.js +27 -3
- package/dist/mcp/core/embedder.js +11 -3
- package/dist/mcp/core/lbug-adapter.d.ts +22 -0
- package/dist/mcp/core/lbug-adapter.js +178 -23
- package/dist/mcp/local/local-backend.d.ts +22 -0
- package/dist/mcp/local/local-backend.js +136 -32
- package/dist/mcp/resources.js +13 -0
- package/dist/mcp/server.js +26 -4
- package/dist/mcp/tools.js +17 -7
- package/dist/server/api.d.ts +19 -1
- package/dist/server/api.js +66 -6
- package/dist/storage/git.d.ts +12 -0
- package/dist/storage/git.js +21 -0
- package/package.json +12 -4
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { extractSimpleTypeName, extractVarName,
|
|
1
|
+
import { extractSimpleTypeName, extractVarName, extractGenericTypeArgs, resolveIterableElementType, methodToTypeArgPosition, extractElementTypeFromString } from './shared.js';
|
|
2
|
+
import { findChild } from '../resolvers/utils.js';
|
|
2
3
|
// ── Java ──────────────────────────────────────────────────────────────────
|
|
3
4
|
const JAVA_DECLARATION_NODE_TYPES = new Set([
|
|
4
5
|
'local_variable_declaration',
|
|
@@ -78,7 +79,7 @@ const scanJavaConstructorBinding = (node) => {
|
|
|
78
79
|
return undefined;
|
|
79
80
|
if (typeNode.text !== 'var')
|
|
80
81
|
return undefined;
|
|
81
|
-
const declarator =
|
|
82
|
+
const declarator = findChild(node, 'variable_declarator');
|
|
82
83
|
if (!declarator)
|
|
83
84
|
return undefined;
|
|
84
85
|
const nameNode = declarator.childForFieldName('name');
|
|
@@ -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 =
|
|
352
|
+
const varDecl = findChild(node, 'variable_declaration');
|
|
292
353
|
if (varDecl) {
|
|
293
|
-
const nameNode =
|
|
294
|
-
const typeNode =
|
|
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
|
-
??
|
|
367
|
+
?? findChild(node, 'simple_identifier');
|
|
306
368
|
const typeNode = node.childForFieldName('type')
|
|
307
|
-
??
|
|
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 =
|
|
318
|
-
const typeNode =
|
|
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
|
-
*
|
|
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 =
|
|
406
|
+
nameNode = findChild(node, 'simple_identifier');
|
|
345
407
|
if (!typeNode)
|
|
346
|
-
typeNode =
|
|
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
|
-
/**
|
|
355
|
-
*
|
|
356
|
-
|
|
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
|
-
??
|
|
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
|
-
?
|
|
379
|
-
:
|
|
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 =
|
|
466
|
+
const varDecl = findChild(node, 'variable_declaration');
|
|
391
467
|
if (!varDecl)
|
|
392
468
|
return undefined;
|
|
393
|
-
if (
|
|
469
|
+
if (findChild(varDecl, 'user_type'))
|
|
394
470
|
return undefined;
|
|
395
|
-
const callExpr =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
535
|
+
const nameNode = findChild(param, 'simple_identifier');
|
|
460
536
|
if (nameNode?.text !== iterableName)
|
|
461
537
|
continue;
|
|
462
|
-
const typeNode =
|
|
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 =
|
|
553
|
+
const varDecl = findChild(node, 'variable_declaration');
|
|
478
554
|
if (!varDecl)
|
|
479
555
|
return;
|
|
480
|
-
const nameNode =
|
|
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 =
|
|
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 =
|
|
518
|
-
const prop = suffix ?
|
|
519
|
-
const hasCallSuffix = suffix ?
|
|
593
|
+
const suffix = findChild(child, 'navigation_suffix');
|
|
594
|
+
const prop = suffix ? findChild(suffix, 'simple_identifier') : null;
|
|
595
|
+
const hasCallSuffix = suffix ? findChild(suffix, 'call_suffix') !== null : false;
|
|
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 =
|
|
615
|
+
const suffix = findChild(callee, 'navigation_suffix');
|
|
540
616
|
if (suffix) {
|
|
541
|
-
const prop =
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
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
|
-
|
|
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'
|
|
376
|
+
if (left.type !== 'variable_name')
|
|
377
377
|
return undefined;
|
|
378
378
|
const lhs = left.text;
|
|
379
|
-
|
|
380
|
-
if (!lhs || !rhs || scopeEnv.has(lhs))
|
|
379
|
+
if (!lhs || scopeEnv.has(lhs))
|
|
381
380
|
return undefined;
|
|
382
|
-
|
|
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',
|