gitnexus 1.6.2-rc.14 → 1.6.2-rc.15

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 (47) hide show
  1. package/dist/core/ingestion/call-extractors/configs/c-cpp.d.ts +3 -0
  2. package/dist/core/ingestion/call-extractors/configs/c-cpp.js +8 -0
  3. package/dist/core/ingestion/call-extractors/configs/csharp.d.ts +2 -0
  4. package/dist/core/ingestion/call-extractors/configs/csharp.js +6 -0
  5. package/dist/core/ingestion/call-extractors/configs/dart.d.ts +2 -0
  6. package/dist/core/ingestion/call-extractors/configs/dart.js +5 -0
  7. package/dist/core/ingestion/call-extractors/configs/go.d.ts +2 -0
  8. package/dist/core/ingestion/call-extractors/configs/go.js +5 -0
  9. package/dist/core/ingestion/call-extractors/configs/jvm.d.ts +3 -0
  10. package/dist/core/ingestion/call-extractors/configs/jvm.js +51 -0
  11. package/dist/core/ingestion/call-extractors/configs/php.d.ts +2 -0
  12. package/dist/core/ingestion/call-extractors/configs/php.js +5 -0
  13. package/dist/core/ingestion/call-extractors/configs/python.d.ts +2 -0
  14. package/dist/core/ingestion/call-extractors/configs/python.js +5 -0
  15. package/dist/core/ingestion/call-extractors/configs/ruby.d.ts +2 -0
  16. package/dist/core/ingestion/call-extractors/configs/ruby.js +5 -0
  17. package/dist/core/ingestion/call-extractors/configs/rust.d.ts +2 -0
  18. package/dist/core/ingestion/call-extractors/configs/rust.js +5 -0
  19. package/dist/core/ingestion/call-extractors/configs/swift.d.ts +2 -0
  20. package/dist/core/ingestion/call-extractors/configs/swift.js +5 -0
  21. package/dist/core/ingestion/call-extractors/configs/typescript-javascript.d.ts +3 -0
  22. package/dist/core/ingestion/call-extractors/configs/typescript-javascript.js +8 -0
  23. package/dist/core/ingestion/call-extractors/generic.d.ts +5 -0
  24. package/dist/core/ingestion/call-extractors/generic.js +59 -0
  25. package/dist/core/ingestion/call-processor.js +48 -45
  26. package/dist/core/ingestion/call-types.d.ts +60 -0
  27. package/dist/core/ingestion/call-types.js +2 -0
  28. package/dist/core/ingestion/language-provider.d.ts +7 -0
  29. package/dist/core/ingestion/languages/c-cpp.js +4 -0
  30. package/dist/core/ingestion/languages/csharp.js +3 -0
  31. package/dist/core/ingestion/languages/dart.js +3 -0
  32. package/dist/core/ingestion/languages/go.js +3 -0
  33. package/dist/core/ingestion/languages/java.js +3 -0
  34. package/dist/core/ingestion/languages/kotlin.js +3 -0
  35. package/dist/core/ingestion/languages/php.js +3 -0
  36. package/dist/core/ingestion/languages/python.js +3 -0
  37. package/dist/core/ingestion/languages/ruby.js +3 -0
  38. package/dist/core/ingestion/languages/rust.js +3 -0
  39. package/dist/core/ingestion/languages/swift.js +3 -0
  40. package/dist/core/ingestion/languages/typescript.js +4 -0
  41. package/dist/core/ingestion/languages/vue.js +3 -0
  42. package/dist/core/ingestion/workers/parse-worker.js +162 -166
  43. package/package.json +1 -1
  44. package/dist/core/ingestion/call-sites/extract-language-call-site.d.ts +0 -10
  45. package/dist/core/ingestion/call-sites/extract-language-call-site.js +0 -22
  46. package/dist/core/ingestion/call-sites/java.d.ts +0 -9
  47. package/dist/core/ingestion/call-sites/java.js +0 -30
@@ -0,0 +1,3 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const cCallConfig: CallExtractionConfig;
3
+ export declare const cppCallConfig: CallExtractionConfig;
@@ -0,0 +1,8 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/c-cpp.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const cCallConfig = {
4
+ language: SupportedLanguages.C,
5
+ };
6
+ export const cppCallConfig = {
7
+ language: SupportedLanguages.CPlusPlus,
8
+ };
@@ -0,0 +1,2 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const csharpCallConfig: CallExtractionConfig;
@@ -0,0 +1,6 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/csharp.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const csharpCallConfig = {
4
+ language: SupportedLanguages.CSharp,
5
+ typeAsReceiverHeuristic: true,
6
+ };
@@ -0,0 +1,2 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const dartCallConfig: CallExtractionConfig;
@@ -0,0 +1,5 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/dart.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const dartCallConfig = {
4
+ language: SupportedLanguages.Dart,
5
+ };
@@ -0,0 +1,2 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const goCallConfig: CallExtractionConfig;
@@ -0,0 +1,5 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/go.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const goCallConfig = {
4
+ language: SupportedLanguages.Go,
5
+ };
@@ -0,0 +1,3 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const javaCallConfig: CallExtractionConfig;
3
+ export declare const kotlinCallConfig: CallExtractionConfig;
@@ -0,0 +1,51 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/jvm.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ // ---------------------------------------------------------------------------
4
+ // Java method_reference (::) parsing — absorbs call-sites/java.ts
5
+ // ---------------------------------------------------------------------------
6
+ /**
7
+ * Parse Java `method_reference` nodes (`expr::method`, `Type::new`,
8
+ * `this::m`, `super::m`).
9
+ */
10
+ function parseJavaMethodReference(callNode) {
11
+ if (callNode.type !== 'method_reference')
12
+ return null;
13
+ const recv = callNode.namedChild(0);
14
+ if (!recv)
15
+ return null;
16
+ // Type::new → constructor call
17
+ for (const c of callNode.children) {
18
+ if (c.type === 'new') {
19
+ if (recv.type !== 'identifier')
20
+ return null;
21
+ return { calledName: recv.text, callForm: 'constructor' };
22
+ }
23
+ }
24
+ // expr::method → member call with receiver
25
+ const rhs = callNode.child(callNode.childCount - 1);
26
+ if (!rhs || rhs.type !== 'identifier')
27
+ return null;
28
+ const methodName = rhs.text;
29
+ if (recv.type === 'identifier') {
30
+ return { calledName: methodName, callForm: 'member', receiverName: recv.text };
31
+ }
32
+ if (recv.type === 'this') {
33
+ return { calledName: methodName, callForm: 'member', receiverName: 'this' };
34
+ }
35
+ if (recv.type === 'super') {
36
+ return { calledName: methodName, callForm: 'member', receiverName: 'super' };
37
+ }
38
+ return null;
39
+ }
40
+ // ---------------------------------------------------------------------------
41
+ // Configs
42
+ // ---------------------------------------------------------------------------
43
+ export const javaCallConfig = {
44
+ language: SupportedLanguages.Java,
45
+ extractLanguageCallSite: parseJavaMethodReference,
46
+ typeAsReceiverHeuristic: true,
47
+ };
48
+ export const kotlinCallConfig = {
49
+ language: SupportedLanguages.Kotlin,
50
+ typeAsReceiverHeuristic: true,
51
+ };
@@ -0,0 +1,2 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const phpCallConfig: CallExtractionConfig;
@@ -0,0 +1,5 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/php.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const phpCallConfig = {
4
+ language: SupportedLanguages.PHP,
5
+ };
@@ -0,0 +1,2 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const pythonCallConfig: CallExtractionConfig;
@@ -0,0 +1,5 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/python.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const pythonCallConfig = {
4
+ language: SupportedLanguages.Python,
5
+ };
@@ -0,0 +1,2 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const rubyCallConfig: CallExtractionConfig;
@@ -0,0 +1,5 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/ruby.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const rubyCallConfig = {
4
+ language: SupportedLanguages.Ruby,
5
+ };
@@ -0,0 +1,2 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const rustCallConfig: CallExtractionConfig;
@@ -0,0 +1,5 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/rust.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const rustCallConfig = {
4
+ language: SupportedLanguages.Rust,
5
+ };
@@ -0,0 +1,2 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const swiftCallConfig: CallExtractionConfig;
@@ -0,0 +1,5 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/swift.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const swiftCallConfig = {
4
+ language: SupportedLanguages.Swift,
5
+ };
@@ -0,0 +1,3 @@
1
+ import type { CallExtractionConfig } from '../../call-types.js';
2
+ export declare const typescriptCallConfig: CallExtractionConfig;
3
+ export declare const javascriptCallConfig: CallExtractionConfig;
@@ -0,0 +1,8 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/configs/typescript-javascript.ts
2
+ import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ export const typescriptCallConfig = {
4
+ language: SupportedLanguages.TypeScript,
5
+ };
6
+ export const javascriptCallConfig = {
7
+ language: SupportedLanguages.JavaScript,
8
+ };
@@ -0,0 +1,5 @@
1
+ import type { CallExtractor, CallExtractionConfig } from '../call-types.js';
2
+ /**
3
+ * Create a CallExtractor from a declarative config.
4
+ */
5
+ export declare function createCallExtractor(config: CallExtractionConfig): CallExtractor;
@@ -0,0 +1,59 @@
1
+ // gitnexus/src/core/ingestion/call-extractors/generic.ts
2
+ import { inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, countCallArguments, } from '../utils/call-analysis.js';
3
+ /**
4
+ * Create a CallExtractor from a declarative config.
5
+ */
6
+ export function createCallExtractor(config) {
7
+ return {
8
+ language: config.language,
9
+ extract(callNode, callNameNode) {
10
+ // ── Path 1: Language-specific call site ──────────────────────────
11
+ // Non-standard call shapes (e.g. Java `::` method references) are
12
+ // handled entirely by the config hook. When it returns a result,
13
+ // the generic path is skipped — no argCount, no mixed chain.
14
+ //
15
+ // Note: `extractLanguageCallSite` is called on every `extract()`
16
+ // invocation — both `extract(callNode, undefined)` (parse-worker
17
+ // Path 1) and `extract(callNode, callNameNode)` (Path 2).
18
+ // Language hooks must therefore be idempotent and cheap (e.g. a
19
+ // single node-type check).
20
+ if (config.extractLanguageCallSite) {
21
+ const seed = config.extractLanguageCallSite(callNode);
22
+ if (seed) {
23
+ return {
24
+ ...seed,
25
+ ...(config.typeAsReceiverHeuristic ? { typeAsReceiverHeuristic: true } : {}),
26
+ };
27
+ }
28
+ }
29
+ // ── Path 2: Generic extraction via @call.name ────────────────────
30
+ if (!callNameNode)
31
+ return null;
32
+ const calledName = callNameNode.text;
33
+ const callForm = inferCallForm(callNode, callNameNode);
34
+ let receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
35
+ let receiverMixedChain;
36
+ // When the receiver is a complex expression (call chain, field chain,
37
+ // or mixed), extractReceiverName returns undefined. Walk the receiver
38
+ // node to build a unified mixed chain for deferred resolution.
39
+ if (callForm === 'member' && receiverName === undefined) {
40
+ const receiverNode = extractReceiverNode(callNameNode);
41
+ if (receiverNode) {
42
+ const extracted = extractMixedChain(receiverNode);
43
+ if (extracted && extracted.chain.length > 0) {
44
+ receiverMixedChain = extracted.chain;
45
+ receiverName = extracted.baseReceiverName;
46
+ }
47
+ }
48
+ }
49
+ return {
50
+ calledName,
51
+ ...(callForm !== undefined ? { callForm } : {}),
52
+ ...(receiverName !== undefined ? { receiverName } : {}),
53
+ argCount: countCallArguments(callNode),
54
+ ...(receiverMixedChain !== undefined ? { receiverMixedChain } : {}),
55
+ ...(config.typeAsReceiverHeuristic ? { typeAsReceiverHeuristic: true } : {}),
56
+ };
57
+ },
58
+ };
59
+ }
@@ -15,7 +15,6 @@ import { getTreeSitterBufferSize } from './constants.js';
15
15
  import { normalizeFetchURL, routeMatches } from './route-extractors/nextjs.js';
16
16
  import { extractTemplateComponents } from './vue-sfc-extractor.js';
17
17
  import { extractReturnTypeName, stripNullable } from './type-extractors/shared.js';
18
- import { extractParsedCallSite } from './call-sites/extract-language-call-site.js';
19
18
  import { lookupMethodByOwnerWithMRO } from './model/resolve.js';
20
19
  /**
21
20
  * Type labels treated as class-like **method-dispatch receivers** by the call
@@ -733,52 +732,56 @@ importedRawReturnTypesMap, heritageMap, bindingAccumulator) => {
733
732
  if (!captureMap['call'])
734
733
  return;
735
734
  const callNode = captureMap['call'];
736
- const languageSeed = extractParsedCallSite(language, callNode);
737
- if (languageSeed) {
738
- if (provider.isBuiltInName(languageSeed.calledName))
739
- return;
740
- const sourceId = findEnclosingFunction(callNode, file.path, ctx, provider) ||
741
- generateId('File', file.path);
742
- const receiverName = languageSeed.callForm === 'member' ? languageSeed.receiverName : undefined;
743
- let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
744
- if (receiverName !== undefined &&
745
- receiverTypeName === undefined &&
746
- languageSeed.callForm === 'member' &&
747
- (language === 'java' || language === 'csharp' || language === 'kotlin')) {
748
- const c0 = receiverName.charCodeAt(0);
749
- if (c0 >= 65 && c0 <= 90)
750
- receiverTypeName = receiverName;
751
- }
752
- const resolved = resolveCallTarget({
753
- calledName: languageSeed.calledName,
754
- callForm: languageSeed.callForm,
755
- ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
756
- ...(receiverName !== undefined ? { receiverName } : {}),
757
- }, file.path, ctx, undefined, widenCache, undefined, heritageMap);
758
- if (!resolved)
759
- return;
760
- graph.addRelationship({
761
- id: generateId('CALLS', `${sourceId}:${languageSeed.calledName}->${resolved.nodeId}`),
762
- sourceId,
763
- targetId: resolved.nodeId,
764
- type: 'CALLS',
765
- confidence: resolved.confidence,
766
- reason: resolved.reason,
767
- });
768
- if (heritageMap && languageSeed.callForm === 'member' && receiverTypeName) {
769
- const implTargets = findInterfaceDispatchTargets(languageSeed.calledName, receiverTypeName, file.path, ctx, heritageMap, resolved.nodeId);
770
- for (const impl of implTargets) {
771
- graph.addRelationship({
772
- id: generateId('CALLS', `${sourceId}:${languageSeed.calledName}->${impl.nodeId}`),
773
- sourceId,
774
- targetId: impl.nodeId,
775
- type: 'CALLS',
776
- confidence: impl.confidence,
777
- reason: impl.reason,
778
- });
735
+ const callExtractor = provider.callExtractor;
736
+ // ── Language-specific call site (e.g. Java :: method references) ──
737
+ if (callExtractor) {
738
+ const langCallSite = callExtractor.extract(callNode, undefined);
739
+ if (langCallSite) {
740
+ if (provider.isBuiltInName(langCallSite.calledName))
741
+ return;
742
+ const sourceId = findEnclosingFunction(callNode, file.path, ctx, provider) ||
743
+ generateId('File', file.path);
744
+ const receiverName = langCallSite.callForm === 'member' ? langCallSite.receiverName : undefined;
745
+ let receiverTypeName = receiverName && typeEnv ? typeEnv.lookup(receiverName, callNode) : undefined;
746
+ if (langCallSite.typeAsReceiverHeuristic &&
747
+ receiverName !== undefined &&
748
+ receiverTypeName === undefined &&
749
+ langCallSite.callForm === 'member') {
750
+ const c0 = receiverName.charCodeAt(0);
751
+ if (c0 >= 65 && c0 <= 90)
752
+ receiverTypeName = receiverName;
779
753
  }
754
+ const resolved = resolveCallTarget({
755
+ calledName: langCallSite.calledName,
756
+ callForm: langCallSite.callForm,
757
+ ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
758
+ ...(receiverName !== undefined ? { receiverName } : {}),
759
+ }, file.path, ctx, undefined, widenCache, undefined, heritageMap);
760
+ if (!resolved)
761
+ return;
762
+ graph.addRelationship({
763
+ id: generateId('CALLS', `${sourceId}:${langCallSite.calledName}->${resolved.nodeId}`),
764
+ sourceId,
765
+ targetId: resolved.nodeId,
766
+ type: 'CALLS',
767
+ confidence: resolved.confidence,
768
+ reason: resolved.reason,
769
+ });
770
+ if (heritageMap && langCallSite.callForm === 'member' && receiverTypeName) {
771
+ const implTargets = findInterfaceDispatchTargets(langCallSite.calledName, receiverTypeName, file.path, ctx, heritageMap, resolved.nodeId);
772
+ for (const impl of implTargets) {
773
+ graph.addRelationship({
774
+ id: generateId('CALLS', `${sourceId}:${langCallSite.calledName}->${impl.nodeId}`),
775
+ sourceId,
776
+ targetId: impl.nodeId,
777
+ type: 'CALLS',
778
+ confidence: impl.confidence,
779
+ reason: impl.reason,
780
+ });
781
+ }
782
+ }
783
+ return;
780
784
  }
781
- return;
782
785
  }
783
786
  const nameNode = captureMap['call.name'];
784
787
  if (!nameNode)
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Types for the language-agnostic call extraction pipeline.
3
+ *
4
+ * Mirrors method-types.ts / field-types.ts: defines the domain interfaces
5
+ * consumed by createCallExtractor() and the per-language configs.
6
+ */
7
+ import type { SupportedLanguages } from '../../_shared/index.js';
8
+ import type { SyntaxNode } from './utils/ast-helpers.js';
9
+ import type { MixedChainStep } from './utils/call-analysis.js';
10
+ /**
11
+ * Per-node call extraction result. The parse worker enriches this with
12
+ * file-level context (filePath, sourceId, TypeEnv lookups, arg types) to
13
+ * produce the final `ExtractedCall` that enters the resolution pipeline.
14
+ */
15
+ export interface ExtractedCallSite {
16
+ calledName: string;
17
+ callForm?: 'free' | 'member' | 'constructor';
18
+ receiverName?: string;
19
+ argCount?: number;
20
+ /** Unified mixed chain for complex receivers (field + call chains). */
21
+ receiverMixedChain?: MixedChainStep[];
22
+ /** When true, the type-as-receiver heuristic applies: if receiverName
23
+ * starts with an uppercase letter and has no TypeEnv binding, treat it
24
+ * as a type name (e.g. Java `User::getName`). */
25
+ typeAsReceiverHeuristic?: boolean;
26
+ }
27
+ export interface CallExtractor {
28
+ readonly language: SupportedLanguages;
29
+ /**
30
+ * Extract a call site from captured AST nodes.
31
+ *
32
+ * @param callNode The @call capture (call_expression, method_invocation, …)
33
+ * @param callNameNode The @call.name capture (identifier inside the call).
34
+ * May be undefined when the call shape has no name capture
35
+ * (e.g. Java method_reference via `::`).
36
+ * @returns Extracted call site, or null when no call can be derived.
37
+ */
38
+ extract(callNode: SyntaxNode, callNameNode: SyntaxNode | undefined): ExtractedCallSite | null;
39
+ }
40
+ export interface CallExtractionConfig {
41
+ language: SupportedLanguages;
42
+ /**
43
+ * Language-specific call site extraction. Called **before** the generic
44
+ * path. If it returns non-null, the generic `inferCallForm` /
45
+ * `extractReceiverName` path is skipped entirely.
46
+ *
47
+ * Use this for call shapes that don't follow the standard `@call` /
48
+ * `@call.name` pattern (e.g. Java `method_reference` via `::`).
49
+ */
50
+ extractLanguageCallSite?: (callNode: SyntaxNode) => ExtractedCallSite | null;
51
+ /**
52
+ * Whether the type-as-receiver heuristic applies for this language.
53
+ * When true and the receiver name starts with an uppercase letter,
54
+ * the receiver is treated as a type name when no TypeEnv binding exists.
55
+ *
56
+ * Applies to JVM and C# languages where `Type.method()` and `Type::method`
57
+ * are common patterns.
58
+ */
59
+ typeAsReceiverHeuristic?: boolean;
60
+ }
@@ -0,0 +1,2 @@
1
+ // gitnexus/src/core/ingestion/call-types.ts
2
+ export {};
@@ -11,6 +11,7 @@
11
11
  import type { SupportedLanguages, MroStrategy } from '../../_shared/index.js';
12
12
  import type { LanguageTypeConfig } from './type-extractors/types.js';
13
13
  import type { CallRouter } from './call-routing.js';
14
+ import type { CallExtractor } from './call-types.js';
14
15
  import type { ClassExtractor } from './class-types.js';
15
16
  import type { ExportChecker } from './export-detection.js';
16
17
  import type { FieldExtractor } from './field-extractor.js';
@@ -119,6 +120,12 @@ interface LanguageProviderConfig {
119
120
  /** MRO strategy for multiple inheritance resolution.
120
121
  * Default: 'first-wins'. */
121
122
  readonly mroStrategy?: MroStrategy;
123
+ /** Call extractor for extracting call site information (calledName, callForm,
124
+ * receiverName, argCount, mixed chains) from @call / @call.name captures.
125
+ * Produced by createCallExtractor() with a per-language CallExtractionConfig.
126
+ * Default: undefined — if unset, no calls are extracted for this language.
127
+ * All tree-sitter providers MUST supply this. */
128
+ readonly callExtractor?: CallExtractor;
122
129
  /** Field extractor for extracting field/property definitions from class/struct
123
130
  * declarations. Produces FieldInfo[] with name, type, visibility, static,
124
131
  * readonly metadata. Default: undefined (no field extraction). */
@@ -30,6 +30,8 @@ import { createFieldExtractor } from '../field-extractors/generic.js';
30
30
  import { cConfig as cFieldConfig, cppConfig as cppFieldConfig, } from '../field-extractors/configs/c-cpp.js';
31
31
  import { createMethodExtractor } from '../method-extractors/generic.js';
32
32
  import { cMethodConfig, cppMethodConfig } from '../method-extractors/configs/c-cpp.js';
33
+ import { createCallExtractor } from '../call-extractors/generic.js';
34
+ import { cCallConfig, cppCallConfig } from '../call-extractors/configs/c-cpp.js';
33
35
  const C_BUILT_INS = new Set([
34
36
  'printf',
35
37
  'fprintf',
@@ -288,6 +290,7 @@ export const cProvider = defineLanguage({
288
290
  exportChecker: cCppExportChecker,
289
291
  importResolver: resolveCImport,
290
292
  importSemantics: 'wildcard-transitive',
293
+ callExtractor: createCallExtractor(cCallConfig),
291
294
  fieldExtractor: createFieldExtractor(cFieldConfig),
292
295
  methodExtractor: createMethodExtractor({
293
296
  ...cMethodConfig,
@@ -306,6 +309,7 @@ export const cppProvider = defineLanguage({
306
309
  importResolver: resolveCppImport,
307
310
  importSemantics: 'wildcard-transitive',
308
311
  mroStrategy: 'leftmost-base',
312
+ callExtractor: createCallExtractor(cppCallConfig),
309
313
  fieldExtractor: createFieldExtractor(cppFieldConfig),
310
314
  methodExtractor: createMethodExtractor({
311
315
  ...cppMethodConfig,
@@ -14,6 +14,8 @@ import { csharpExportChecker } from '../export-detection.js';
14
14
  import { resolveCSharpImport } from '../import-resolvers/csharp.js';
15
15
  import { extractCSharpNamedBindings } from '../named-bindings/csharp.js';
16
16
  import { CSHARP_QUERIES } from '../tree-sitter-queries.js';
17
+ import { createCallExtractor } from '../call-extractors/generic.js';
18
+ import { csharpCallConfig } from '../call-extractors/configs/csharp.js';
17
19
  import { createFieldExtractor } from '../field-extractors/generic.js';
18
20
  import { csharpConfig as csharpFieldConfig } from '../field-extractors/configs/csharp.js';
19
21
  import { createMethodExtractor } from '../method-extractors/generic.js';
@@ -122,6 +124,7 @@ export const csharpProvider = defineLanguage({
122
124
  namedBindingExtractor: extractCSharpNamedBindings,
123
125
  interfaceNamePattern: /^I[A-Z]/,
124
126
  mroStrategy: 'implements-split',
127
+ callExtractor: createCallExtractor(csharpCallConfig),
125
128
  fieldExtractor: createFieldExtractor(csharpFieldConfig),
126
129
  methodExtractor: createMethodExtractor(csharpMethodConfig),
127
130
  classExtractor: createClassExtractor(csharpClassConfig),
@@ -22,6 +22,8 @@ import { createFieldExtractor } from '../field-extractors/generic.js';
22
22
  import { dartConfig as dartFieldConfig } from '../field-extractors/configs/dart.js';
23
23
  import { createMethodExtractor } from '../method-extractors/generic.js';
24
24
  import { dartMethodConfig } from '../method-extractors/configs/dart.js';
25
+ import { createCallExtractor } from '../call-extractors/generic.js';
26
+ import { dartCallConfig } from '../call-extractors/configs/dart.js';
25
27
  /**
26
28
  * Resolve the enclosing function from a `function_body` node by looking at its
27
29
  * previous sibling. In Dart's tree-sitter grammar, function_signature and
@@ -85,6 +87,7 @@ export const dartProvider = defineLanguage({
85
87
  exportChecker: dartExportChecker,
86
88
  importResolver: resolveDartImport,
87
89
  importSemantics: 'wildcard-leaf',
90
+ callExtractor: createCallExtractor(dartCallConfig),
88
91
  fieldExtractor: createFieldExtractor(dartFieldConfig),
89
92
  methodExtractor: createMethodExtractor(dartMethodConfig),
90
93
  classExtractor: createClassExtractor(dartClassConfig),
@@ -20,6 +20,8 @@ import { createFieldExtractor } from '../field-extractors/generic.js';
20
20
  import { goConfig as goFieldConfig } from '../field-extractors/configs/go.js';
21
21
  import { createMethodExtractor } from '../method-extractors/generic.js';
22
22
  import { goMethodConfig } from '../method-extractors/configs/go.js';
23
+ import { createCallExtractor } from '../call-extractors/generic.js';
24
+ import { goCallConfig } from '../call-extractors/configs/go.js';
23
25
  export const goProvider = defineLanguage({
24
26
  id: SupportedLanguages.Go,
25
27
  extensions: ['.go'],
@@ -28,6 +30,7 @@ export const goProvider = defineLanguage({
28
30
  exportChecker: goExportChecker,
29
31
  importResolver: resolveGoImport,
30
32
  importSemantics: 'wildcard-leaf',
33
+ callExtractor: createCallExtractor(goCallConfig),
31
34
  fieldExtractor: createFieldExtractor(goFieldConfig),
32
35
  methodExtractor: createMethodExtractor(goMethodConfig),
33
36
  classExtractor: createClassExtractor(goClassConfig),
@@ -15,6 +15,8 @@ import { javaExportChecker } from '../export-detection.js';
15
15
  import { resolveJavaImport } from '../import-resolvers/jvm.js';
16
16
  import { extractJavaNamedBindings } from '../named-bindings/java.js';
17
17
  import { JAVA_QUERIES } from '../tree-sitter-queries.js';
18
+ import { createCallExtractor } from '../call-extractors/generic.js';
19
+ import { javaCallConfig } from '../call-extractors/configs/jvm.js';
18
20
  import { createFieldExtractor } from '../field-extractors/generic.js';
19
21
  import { javaConfig } from '../field-extractors/configs/jvm.js';
20
22
  import { createMethodExtractor } from '../method-extractors/generic.js';
@@ -29,6 +31,7 @@ export const javaProvider = defineLanguage({
29
31
  namedBindingExtractor: extractJavaNamedBindings,
30
32
  interfaceNamePattern: /^I[A-Z]/,
31
33
  mroStrategy: 'implements-split',
34
+ callExtractor: createCallExtractor(javaCallConfig),
32
35
  fieldExtractor: createFieldExtractor(javaConfig),
33
36
  methodExtractor: createMethodExtractor(javaMethodConfig),
34
37
  classExtractor: createClassExtractor(javaClassConfig),
@@ -16,6 +16,8 @@ import { resolveKotlinImport } from '../import-resolvers/jvm.js';
16
16
  import { extractKotlinNamedBindings } from '../named-bindings/kotlin.js';
17
17
  import { appendKotlinWildcard } from '../import-resolvers/jvm.js';
18
18
  import { KOTLIN_QUERIES } from '../tree-sitter-queries.js';
19
+ import { createCallExtractor } from '../call-extractors/generic.js';
20
+ import { kotlinCallConfig } from '../call-extractors/configs/jvm.js';
19
21
  import { createFieldExtractor } from '../field-extractors/generic.js';
20
22
  import { kotlinConfig } from '../field-extractors/configs/jvm.js';
21
23
  import { createMethodExtractor } from '../method-extractors/generic.js';
@@ -100,6 +102,7 @@ export const kotlinProvider = defineLanguage({
100
102
  namedBindingExtractor: extractKotlinNamedBindings,
101
103
  importPathPreprocessor: appendKotlinWildcard,
102
104
  mroStrategy: 'implements-split',
105
+ callExtractor: createCallExtractor(kotlinCallConfig),
103
106
  fieldExtractor: createFieldExtractor(kotlinConfig),
104
107
  methodExtractor: createMethodExtractor(kotlinMethodConfig),
105
108
  classExtractor: createClassExtractor(kotlinClassConfig),
@@ -19,6 +19,8 @@ import { createFieldExtractor } from '../field-extractors/generic.js';
19
19
  import { phpConfig as phpFieldConfig } from '../field-extractors/configs/php.js';
20
20
  import { createMethodExtractor } from '../method-extractors/generic.js';
21
21
  import { phpMethodConfig } from '../method-extractors/configs/php.js';
22
+ import { createCallExtractor } from '../call-extractors/generic.js';
23
+ import { phpCallConfig } from '../call-extractors/configs/php.js';
22
24
  const BUILT_INS = new Set([
23
25
  'echo',
24
26
  'isset',
@@ -222,6 +224,7 @@ export const phpProvider = defineLanguage({
222
224
  exportChecker: phpExportChecker,
223
225
  importResolver: resolvePhpImport,
224
226
  namedBindingExtractor: extractPhpNamedBindings,
227
+ callExtractor: createCallExtractor(phpCallConfig),
225
228
  fieldExtractor: createFieldExtractor(phpFieldConfig),
226
229
  methodExtractor: createMethodExtractor(phpMethodConfig),
227
230
  classExtractor: createClassExtractor(phpClassConfig),
@@ -22,6 +22,8 @@ import { createFieldExtractor } from '../field-extractors/generic.js';
22
22
  import { pythonConfig as pythonFieldConfig } from '../field-extractors/configs/python.js';
23
23
  import { createMethodExtractor } from '../method-extractors/generic.js';
24
24
  import { pythonMethodConfig } from '../method-extractors/configs/python.js';
25
+ import { createCallExtractor } from '../call-extractors/generic.js';
26
+ import { pythonCallConfig } from '../call-extractors/configs/python.js';
25
27
  const BUILT_INS = new Set([
26
28
  'print',
27
29
  'len',
@@ -61,6 +63,7 @@ export const pythonProvider = defineLanguage({
61
63
  namedBindingExtractor: extractPythonNamedBindings,
62
64
  importSemantics: 'namespace',
63
65
  mroStrategy: 'c3',
66
+ callExtractor: createCallExtractor(pythonCallConfig),
64
67
  fieldExtractor: createFieldExtractor(pythonFieldConfig),
65
68
  methodExtractor: createMethodExtractor(pythonMethodConfig),
66
69
  classExtractor: createClassExtractor(pythonClassConfig),
@@ -19,6 +19,8 @@ import { createFieldExtractor } from '../field-extractors/generic.js';
19
19
  import { rubyConfig as rubyFieldConfig } from '../field-extractors/configs/ruby.js';
20
20
  import { createMethodExtractor } from '../method-extractors/generic.js';
21
21
  import { rubyMethodConfig } from '../method-extractors/configs/ruby.js';
22
+ import { createCallExtractor } from '../call-extractors/generic.js';
23
+ import { rubyCallConfig } from '../call-extractors/configs/ruby.js';
22
24
  /** Ruby method/singleton_method: extract name from 'name' field, label as Method. */
23
25
  const rubyExtractFunctionName = (node) => {
24
26
  if (node.type !== 'method' && node.type !== 'singleton_method')
@@ -101,6 +103,7 @@ export const rubyProvider = defineLanguage({
101
103
  importResolver: resolveRubyImport,
102
104
  callRouter: routeRubyCall,
103
105
  importSemantics: 'wildcard-leaf',
106
+ callExtractor: createCallExtractor(rubyCallConfig),
104
107
  resolveEnclosingOwner(node) {
105
108
  // Ruby singleton_class (class << self) should resolve to the enclosing
106
109
  // class or module for owner/container resolution (HAS_METHOD edges, class IDs).
@@ -22,6 +22,8 @@ import { createFieldExtractor } from '../field-extractors/generic.js';
22
22
  import { rustConfig as rustFieldConfig } from '../field-extractors/configs/rust.js';
23
23
  import { createMethodExtractor } from '../method-extractors/generic.js';
24
24
  import { rustMethodConfig } from '../method-extractors/configs/rust.js';
25
+ import { createCallExtractor } from '../call-extractors/generic.js';
26
+ import { rustCallConfig } from '../call-extractors/configs/rust.js';
25
27
  /** Rust impl_item: find the function_item child and extract its name as a Method. */
26
28
  const rustExtractFunctionName = (node) => {
27
29
  if (node.type !== 'impl_item')
@@ -113,6 +115,7 @@ export const rustProvider = defineLanguage({
113
115
  importResolver: resolveRustImport,
114
116
  namedBindingExtractor: extractRustNamedBindings,
115
117
  mroStrategy: 'qualified-syntax',
118
+ callExtractor: createCallExtractor(rustCallConfig),
116
119
  fieldExtractor: createFieldExtractor(rustFieldConfig),
117
120
  methodExtractor: createMethodExtractor({
118
121
  ...rustMethodConfig,
@@ -21,6 +21,8 @@ import { createFieldExtractor } from '../field-extractors/generic.js';
21
21
  import { swiftConfig as swiftFieldConfig } from '../field-extractors/configs/swift.js';
22
22
  import { createMethodExtractor } from '../method-extractors/generic.js';
23
23
  import { swiftMethodConfig } from '../method-extractors/configs/swift.js';
24
+ import { createCallExtractor } from '../call-extractors/generic.js';
25
+ import { swiftCallConfig } from '../call-extractors/configs/swift.js';
24
26
  /**
25
27
  * Group Swift files by SPM target for implicit module visibility.
26
28
  * If SwiftPackageConfig is available, use target -> directory mappings.
@@ -224,6 +226,7 @@ export const swiftProvider = defineLanguage({
224
226
  importResolver: resolveSwiftImport,
225
227
  importSemantics: 'wildcard-leaf',
226
228
  heritageDefaultEdge: 'IMPLEMENTS',
229
+ callExtractor: createCallExtractor(swiftCallConfig),
227
230
  fieldExtractor: createFieldExtractor(swiftFieldConfig),
228
231
  methodExtractor: createMethodExtractor({
229
232
  ...swiftMethodConfig,
@@ -20,6 +20,8 @@ import { createFieldExtractor } from '../field-extractors/generic.js';
20
20
  import { javascriptConfig } from '../field-extractors/configs/typescript-javascript.js';
21
21
  import { createMethodExtractor } from '../method-extractors/generic.js';
22
22
  import { typescriptMethodConfig, javascriptMethodConfig, } from '../method-extractors/configs/typescript-javascript.js';
23
+ import { createCallExtractor } from '../call-extractors/generic.js';
24
+ import { typescriptCallConfig, javascriptCallConfig, } from '../call-extractors/configs/typescript-javascript.js';
23
25
  /**
24
26
  * TypeScript/JavaScript: arrow_function and function_expression get their name
25
27
  * from the parent variable_declarator (e.g. `const foo = () => {}`).
@@ -146,6 +148,7 @@ export const typescriptProvider = defineLanguage({
146
148
  exportChecker: tsExportChecker,
147
149
  importResolver: resolveTypescriptImport,
148
150
  namedBindingExtractor: extractTsNamedBindings,
151
+ callExtractor: createCallExtractor(typescriptCallConfig),
149
152
  fieldExtractor: typescriptFieldExtractor,
150
153
  methodExtractor: createMethodExtractor({
151
154
  ...typescriptMethodConfig,
@@ -162,6 +165,7 @@ export const javascriptProvider = defineLanguage({
162
165
  exportChecker: tsExportChecker,
163
166
  importResolver: resolveJavascriptImport,
164
167
  namedBindingExtractor: extractTsNamedBindings,
168
+ callExtractor: createCallExtractor(javascriptCallConfig),
165
169
  fieldExtractor: createFieldExtractor(javascriptConfig),
166
170
  methodExtractor: createMethodExtractor({
167
171
  ...javascriptMethodConfig,
@@ -21,6 +21,8 @@ import { extractTsNamedBindings } from '../named-bindings/typescript.js';
21
21
  import { TYPESCRIPT_QUERIES } from '../tree-sitter-queries.js';
22
22
  import { typescriptFieldExtractor } from '../field-extractors/typescript.js';
23
23
  import { BUILT_INS as TS_BUILT_INS } from './typescript.js';
24
+ import { createCallExtractor } from '../call-extractors/generic.js';
25
+ import { typescriptCallConfig } from '../call-extractors/configs/typescript-javascript.js';
24
26
  const VUE_SPECIFIC_BUILT_INS = [
25
27
  'ref',
26
28
  'reactive',
@@ -62,6 +64,7 @@ export const vueProvider = defineLanguage({
62
64
  exportChecker: tsExportChecker,
63
65
  importResolver: resolveVueImport,
64
66
  namedBindingExtractor: extractTsNamedBindings,
67
+ callExtractor: createCallExtractor(typescriptCallConfig),
65
68
  fieldExtractor: typescriptFieldExtractor,
66
69
  classExtractor: vueClassExtractor,
67
70
  builtInNames: VUE_BUILT_INS,
@@ -36,8 +36,7 @@ try {
36
36
  catch { }
37
37
  import { getLanguageFromFilename } from '../../../_shared/index.js';
38
38
  import { FUNCTION_NODE_TYPES, getDefinitionNodeFromCaptures, findEnclosingClassInfo, getLabelFromCaptures, findDescendant, extractStringContent, genericFuncName, inferFunctionLabel, CLASS_CONTAINER_TYPES, } from '../utils/ast-helpers.js';
39
- import { countCallArguments, inferCallForm, extractReceiverName, extractReceiverNode, extractMixedChain, extractCallArgTypes, } from '../utils/call-analysis.js';
40
- import { extractParsedCallSite } from '../call-sites/extract-language-call-site.js';
39
+ import { extractCallArgTypes } from '../utils/call-analysis.js';
41
40
  import { buildTypeEnv } from '../type-env.js';
42
41
  import { detectFrameworkFromAST } from '../framework-detection.js';
43
42
  import { generateId } from '../../../lib/utils.js';
@@ -1248,98 +1247,125 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1248
1247
  }
1249
1248
  // Extract call sites
1250
1249
  if (captureMap['call']) {
1251
- const callNode0 = captureMap['call'];
1252
- const languageSeed = extractParsedCallSite(language, callNode0);
1253
- if (languageSeed) {
1254
- if (!provider.isBuiltInName(languageSeed.calledName)) {
1255
- const sourceId = findEnclosingFunctionId(callNode0, file.path, provider) ||
1256
- generateId('File', file.path);
1257
- const receiverName = languageSeed.callForm === 'member' ? languageSeed.receiverName : undefined;
1258
- let receiverTypeName = receiverName
1259
- ? typeEnv.lookup(receiverName, callNode0)
1260
- : undefined;
1261
- // Type-as-receiver (e.g. Java `User::getName`): no TypeEnv binding for the class name
1262
- if (receiverName !== undefined &&
1263
- receiverTypeName === undefined &&
1264
- languageSeed.callForm === 'member' &&
1265
- (language === SupportedLanguages.Java ||
1266
- language === SupportedLanguages.CSharp ||
1267
- language === SupportedLanguages.Kotlin)) {
1268
- const c0 = receiverName.charCodeAt(0);
1269
- if (c0 >= 65 && c0 <= 90)
1270
- receiverTypeName = receiverName;
1271
- }
1272
- result.calls.push({
1273
- filePath: file.path,
1274
- calledName: languageSeed.calledName,
1275
- sourceId,
1276
- callForm: languageSeed.callForm,
1277
- ...(receiverName !== undefined ? { receiverName } : {}),
1278
- ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
1279
- });
1280
- }
1281
- continue;
1282
- }
1250
+ const callNode = captureMap['call'];
1283
1251
  const callNameNode = captureMap['call.name'];
1284
- if (callNameNode) {
1285
- const calledName = callNameNode.text;
1286
- // Dispatch: route language-specific calls (heritage, properties, imports)
1287
- const routed = callRouter?.(calledName, captureMap['call']);
1288
- if (routed) {
1289
- if (routed.kind === 'skip')
1290
- continue;
1291
- if (routed.kind === 'import') {
1292
- result.imports.push({
1252
+ const callExtractor = provider.callExtractor;
1253
+ if (callExtractor) {
1254
+ // ── Path 1: Language-specific call site (bypasses routing) ────
1255
+ // Try language-specific extraction (e.g. Java `::` method references)
1256
+ // without callNameNode. If successful, skip routing and the generic
1257
+ // path entirely.
1258
+ const langCallSite = callExtractor.extract(callNode, undefined);
1259
+ if (langCallSite) {
1260
+ if (!provider.isBuiltInName(langCallSite.calledName)) {
1261
+ const sourceId = findEnclosingFunctionId(callNode, file.path, provider) ||
1262
+ generateId('File', file.path);
1263
+ const receiverName = langCallSite.callForm === 'member' ? langCallSite.receiverName : undefined;
1264
+ let receiverTypeName = receiverName
1265
+ ? typeEnv.lookup(receiverName, callNode)
1266
+ : undefined;
1267
+ // Type-as-receiver heuristic (e.g. Java `User::getName`)
1268
+ if (langCallSite.typeAsReceiverHeuristic &&
1269
+ receiverName !== undefined &&
1270
+ receiverTypeName === undefined &&
1271
+ langCallSite.callForm === 'member') {
1272
+ const c0 = receiverName.charCodeAt(0);
1273
+ if (c0 >= 65 && c0 <= 90)
1274
+ receiverTypeName = receiverName;
1275
+ }
1276
+ result.calls.push({
1293
1277
  filePath: file.path,
1294
- rawImportPath: routed.importPath,
1295
- language,
1278
+ calledName: langCallSite.calledName,
1279
+ sourceId,
1280
+ callForm: langCallSite.callForm,
1281
+ ...(receiverName !== undefined ? { receiverName } : {}),
1282
+ ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
1296
1283
  });
1297
- continue;
1298
1284
  }
1299
- if (routed.kind === 'heritage') {
1300
- for (const item of routed.items) {
1301
- result.heritage.push({
1285
+ continue;
1286
+ }
1287
+ // ── Path 2: Generic extraction via @call.name ────────────────
1288
+ if (callNameNode) {
1289
+ const calledName = callNameNode.text;
1290
+ // Dispatch: route language-specific calls (heritage, properties, imports)
1291
+ const routed = callRouter?.(calledName, captureMap['call']);
1292
+ if (routed) {
1293
+ if (routed.kind === 'skip')
1294
+ continue;
1295
+ if (routed.kind === 'import') {
1296
+ result.imports.push({
1302
1297
  filePath: file.path,
1303
- className: item.enclosingClass,
1304
- parentName: item.mixinName,
1305
- kind: item.heritageKind,
1298
+ rawImportPath: routed.importPath,
1299
+ language,
1306
1300
  });
1301
+ continue;
1307
1302
  }
1308
- continue;
1309
- }
1310
- if (routed.kind === 'properties') {
1311
- const propEnclosingInfo = cachedFindEnclosingClassInfo(captureMap['call'], file.path, provider.resolveEnclosingOwner);
1312
- const propEnclosingClassId = propEnclosingInfo?.classId ?? null;
1313
- // Enrich routed properties with FieldExtractor metadata
1314
- let routedFieldMap;
1315
- if (provider.fieldExtractor && typeEnv) {
1316
- const classNode = findEnclosingClassNode(captureMap['call']);
1317
- if (classNode) {
1318
- routedFieldMap = getFieldInfo(classNode, provider, {
1319
- typeEnv,
1320
- symbolTable: NOOP_SYMBOL_TABLE,
1303
+ if (routed.kind === 'heritage') {
1304
+ for (const item of routed.items) {
1305
+ result.heritage.push({
1321
1306
  filePath: file.path,
1322
- language,
1307
+ className: item.enclosingClass,
1308
+ parentName: item.mixinName,
1309
+ kind: item.heritageKind,
1323
1310
  });
1324
1311
  }
1312
+ continue;
1325
1313
  }
1326
- for (const item of routed.items) {
1327
- const routedFieldInfo = routedFieldMap?.get(item.propName);
1328
- const propQualifiedName = propEnclosingInfo
1329
- ? `${propEnclosingInfo.className}.${item.propName}`
1330
- : item.propName;
1331
- const nodeId = generateId('Property', `${file.path}:${propQualifiedName}`);
1332
- result.nodes.push({
1333
- id: nodeId,
1334
- label: 'Property',
1335
- properties: {
1336
- name: item.propName,
1314
+ if (routed.kind === 'properties') {
1315
+ const propEnclosingInfo = cachedFindEnclosingClassInfo(captureMap['call'], file.path, provider.resolveEnclosingOwner);
1316
+ const propEnclosingClassId = propEnclosingInfo?.classId ?? null;
1317
+ // Enrich routed properties with FieldExtractor metadata
1318
+ let routedFieldMap;
1319
+ if (provider.fieldExtractor && typeEnv) {
1320
+ const classNode = findEnclosingClassNode(captureMap['call']);
1321
+ if (classNode) {
1322
+ routedFieldMap = getFieldInfo(classNode, provider, {
1323
+ typeEnv,
1324
+ symbolTable: NOOP_SYMBOL_TABLE,
1325
+ filePath: file.path,
1326
+ language,
1327
+ });
1328
+ }
1329
+ }
1330
+ for (const item of routed.items) {
1331
+ const routedFieldInfo = routedFieldMap?.get(item.propName);
1332
+ const propQualifiedName = propEnclosingInfo
1333
+ ? `${propEnclosingInfo.className}.${item.propName}`
1334
+ : item.propName;
1335
+ const nodeId = generateId('Property', `${file.path}:${propQualifiedName}`);
1336
+ result.nodes.push({
1337
+ id: nodeId,
1338
+ label: 'Property',
1339
+ properties: {
1340
+ name: item.propName,
1341
+ filePath: file.path,
1342
+ startLine: item.startLine,
1343
+ endLine: item.endLine,
1344
+ language,
1345
+ isExported: true,
1346
+ description: item.accessorType,
1347
+ ...(item.declaredType
1348
+ ? { declaredType: item.declaredType }
1349
+ : routedFieldInfo?.type
1350
+ ? { declaredType: routedFieldInfo.type }
1351
+ : {}),
1352
+ ...(routedFieldInfo?.visibility !== undefined
1353
+ ? { visibility: routedFieldInfo.visibility }
1354
+ : {}),
1355
+ ...(routedFieldInfo?.isStatic !== undefined
1356
+ ? { isStatic: routedFieldInfo.isStatic }
1357
+ : {}),
1358
+ ...(routedFieldInfo?.isReadonly !== undefined
1359
+ ? { isReadonly: routedFieldInfo.isReadonly }
1360
+ : {}),
1361
+ },
1362
+ });
1363
+ result.symbols.push({
1337
1364
  filePath: file.path,
1338
- startLine: item.startLine,
1339
- endLine: item.endLine,
1340
- language,
1341
- isExported: true,
1342
- description: item.accessorType,
1365
+ name: item.propName,
1366
+ nodeId,
1367
+ type: 'Property',
1368
+ ...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
1343
1369
  ...(item.declaredType
1344
1370
  ? { declaredType: item.declaredType }
1345
1371
  : routedFieldInfo?.type
@@ -1354,101 +1380,71 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1354
1380
  ...(routedFieldInfo?.isReadonly !== undefined
1355
1381
  ? { isReadonly: routedFieldInfo.isReadonly }
1356
1382
  : {}),
1357
- },
1358
- });
1359
- result.symbols.push({
1360
- filePath: file.path,
1361
- name: item.propName,
1362
- nodeId,
1363
- type: 'Property',
1364
- ...(propEnclosingClassId ? { ownerId: propEnclosingClassId } : {}),
1365
- ...(item.declaredType
1366
- ? { declaredType: item.declaredType }
1367
- : routedFieldInfo?.type
1368
- ? { declaredType: routedFieldInfo.type }
1369
- : {}),
1370
- ...(routedFieldInfo?.visibility !== undefined
1371
- ? { visibility: routedFieldInfo.visibility }
1372
- : {}),
1373
- ...(routedFieldInfo?.isStatic !== undefined
1374
- ? { isStatic: routedFieldInfo.isStatic }
1375
- : {}),
1376
- ...(routedFieldInfo?.isReadonly !== undefined
1377
- ? { isReadonly: routedFieldInfo.isReadonly }
1378
- : {}),
1379
- });
1380
- const fileId = generateId('File', file.path);
1381
- const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
1382
- result.relationships.push({
1383
- id: relId,
1384
- sourceId: fileId,
1385
- targetId: nodeId,
1386
- type: 'DEFINES',
1387
- confidence: 1.0,
1388
- reason: '',
1389
- });
1390
- if (propEnclosingClassId) {
1383
+ });
1384
+ const fileId = generateId('File', file.path);
1385
+ const relId = generateId('DEFINES', `${fileId}->${nodeId}`);
1391
1386
  result.relationships.push({
1392
- id: generateId('HAS_PROPERTY', `${propEnclosingClassId}->${nodeId}`),
1393
- sourceId: propEnclosingClassId,
1387
+ id: relId,
1388
+ sourceId: fileId,
1394
1389
  targetId: nodeId,
1395
- type: 'HAS_PROPERTY',
1390
+ type: 'DEFINES',
1396
1391
  confidence: 1.0,
1397
1392
  reason: '',
1398
1393
  });
1394
+ if (propEnclosingClassId) {
1395
+ result.relationships.push({
1396
+ id: generateId('HAS_PROPERTY', `${propEnclosingClassId}->${nodeId}`),
1397
+ sourceId: propEnclosingClassId,
1398
+ targetId: nodeId,
1399
+ type: 'HAS_PROPERTY',
1400
+ confidence: 1.0,
1401
+ reason: '',
1402
+ });
1403
+ }
1399
1404
  }
1405
+ continue;
1400
1406
  }
1401
- continue;
1407
+ // kind === 'call' — fall through to normal call processing below
1402
1408
  }
1403
- // kind === 'call' — fall through to normal call processing below
1404
- }
1405
- if (!provider.isBuiltInName(calledName)) {
1406
- const callNode = captureMap['call'];
1407
- const sourceId = findEnclosingFunctionId(callNode, file.path, provider) ||
1408
- generateId('File', file.path);
1409
- const callForm = inferCallForm(callNode, callNameNode);
1410
- let receiverName = callForm === 'member' ? extractReceiverName(callNameNode) : undefined;
1411
- let receiverTypeName = receiverName
1412
- ? typeEnv.lookup(receiverName, callNode)
1413
- : undefined;
1414
- let receiverMixedChain;
1415
- // When the receiver is a complex expression (call chain, field chain, or mixed),
1416
- // extractReceiverName returns undefined. Walk the receiver node to build a unified
1417
- // mixed chain for deferred resolution in processCallsFromExtracted.
1418
- if (callForm === 'member' && receiverName === undefined && !receiverTypeName) {
1419
- const receiverNode = extractReceiverNode(callNameNode);
1420
- if (receiverNode) {
1421
- const extracted = extractMixedChain(receiverNode);
1422
- if (extracted && extracted.chain.length > 0) {
1423
- receiverMixedChain = extracted.chain;
1424
- receiverName = extracted.baseReceiverName;
1425
- // Try the type environment immediately for the base receiver
1426
- // (covers explicitly-typed locals and annotated parameters).
1427
- if (receiverName) {
1428
- receiverTypeName = typeEnv.lookup(receiverName, callNode);
1429
- }
1409
+ if (!provider.isBuiltInName(calledName)) {
1410
+ const callSite = callExtractor.extract(callNode, callNameNode);
1411
+ if (callSite) {
1412
+ const sourceId = findEnclosingFunctionId(callNode, file.path, provider) ||
1413
+ generateId('File', file.path);
1414
+ let receiverTypeName = callSite.receiverName
1415
+ ? typeEnv.lookup(callSite.receiverName, callNode)
1416
+ : undefined;
1417
+ // Type-as-receiver heuristic
1418
+ if (callSite.typeAsReceiverHeuristic &&
1419
+ callSite.receiverName !== undefined &&
1420
+ receiverTypeName === undefined &&
1421
+ callSite.callForm === 'member') {
1422
+ const c0 = callSite.receiverName.charCodeAt(0);
1423
+ if (c0 >= 65 && c0 <= 90)
1424
+ receiverTypeName = callSite.receiverName;
1430
1425
  }
1426
+ const inferLiteralType = provider.typeConfig?.inferLiteralType;
1427
+ // Skip when no arg list / zero args: nothing to infer for overload typing
1428
+ const argTypes = inferLiteralType && callSite.argCount !== undefined && callSite.argCount > 0
1429
+ ? extractCallArgTypes(callNode, inferLiteralType, (varName, cn) => typeEnv.lookup(varName, cn))
1430
+ : undefined;
1431
+ result.calls.push({
1432
+ filePath: file.path,
1433
+ calledName: callSite.calledName,
1434
+ sourceId,
1435
+ ...(callSite.argCount !== undefined ? { argCount: callSite.argCount } : {}),
1436
+ ...(callSite.callForm !== undefined ? { callForm: callSite.callForm } : {}),
1437
+ ...(callSite.receiverName !== undefined
1438
+ ? { receiverName: callSite.receiverName }
1439
+ : {}),
1440
+ ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
1441
+ ...(callSite.receiverMixedChain !== undefined
1442
+ ? { receiverMixedChain: callSite.receiverMixedChain }
1443
+ : {}),
1444
+ ...(argTypes !== undefined ? { argTypes } : {}),
1445
+ });
1431
1446
  }
1432
1447
  }
1433
- const inferLiteralType = provider.typeConfig?.inferLiteralType;
1434
- const argCountForOverloadHints = countCallArguments(callNode);
1435
- // Skip when no arg list / zero args: nothing to infer for overload typing; saves AST walks + payload size.
1436
- const argTypes = inferLiteralType &&
1437
- argCountForOverloadHints !== undefined &&
1438
- argCountForOverloadHints > 0
1439
- ? extractCallArgTypes(callNode, inferLiteralType, (varName, cn) => typeEnv.lookup(varName, cn))
1440
- : undefined;
1441
- result.calls.push({
1442
- filePath: file.path,
1443
- calledName,
1444
- sourceId,
1445
- argCount: countCallArguments(callNode),
1446
- ...(callForm !== undefined ? { callForm } : {}),
1447
- ...(receiverName !== undefined ? { receiverName } : {}),
1448
- ...(receiverTypeName !== undefined ? { receiverTypeName } : {}),
1449
- ...(receiverMixedChain !== undefined ? { receiverMixedChain } : {}),
1450
- ...(argTypes !== undefined ? { argTypes } : {}),
1451
- });
1452
1448
  }
1453
1449
  }
1454
1450
  continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.2-rc.14",
3
+ "version": "1.6.2-rc.15",
4
4
  "description": "Graph-powered code intelligence for AI agents. Index any codebase, query via MCP or CLI.",
5
5
  "author": "Abhigyan Patwari",
6
6
  "license": "PolyForm-Noncommercial-1.0.0",
@@ -1,10 +0,0 @@
1
- /** Non-generic @call shapes → { calledName, callForm, receiverName? } (used from call-processor / parse-worker). */
2
- import { SupportedLanguages } from '../../../config/supported-languages.js';
3
- import type { SyntaxNode } from '../utils/ast-helpers.js';
4
- export type ParsedCallSite = {
5
- calledName: string;
6
- callForm: 'free' | 'member' | 'constructor';
7
- receiverName?: string;
8
- };
9
- /** Non-null → seed replaces @call.name; null → use @call.name + inferCallForm / extractReceiverName. */
10
- export declare function extractParsedCallSite(language: SupportedLanguages, callNode: SyntaxNode): ParsedCallSite | null;
@@ -1,22 +0,0 @@
1
- /** Non-generic @call shapes → { calledName, callForm, receiverName? } (used from call-processor / parse-worker). */
2
- import { SupportedLanguages } from '../../../config/supported-languages.js';
3
- import { parseJavaMethodReference } from './java.js';
4
- /** Non-null → seed replaces @call.name; null → use @call.name + inferCallForm / extractReceiverName. */
5
- export function extractParsedCallSite(language, callNode) {
6
- switch (language) {
7
- case SupportedLanguages.Java:
8
- if (callNode.type === 'method_reference') {
9
- const parsed = parseJavaMethodReference(callNode);
10
- if (!parsed)
11
- return null;
12
- return {
13
- calledName: parsed.calledName,
14
- callForm: parsed.callForm,
15
- ...(parsed.receiverName !== undefined ? { receiverName: parsed.receiverName } : {}),
16
- };
17
- }
18
- return null;
19
- default:
20
- return null;
21
- }
22
- }
@@ -1,9 +0,0 @@
1
- /** Java `method_reference` (`::`) nodes (tree-sitter-java). `super::` still lacks TypeEnv receiver typing. */
2
- import type { SyntaxNode } from '../utils/ast-helpers.js';
3
- export type ParsedJavaMethodReference = {
4
- calledName: string;
5
- callForm: 'member' | 'constructor';
6
- receiverName?: string;
7
- };
8
- /** Parse `expr::method`, `Type::new`, `this::m`, `super::m`. */
9
- export declare const parseJavaMethodReference: (callNode: SyntaxNode) => ParsedJavaMethodReference | null;
@@ -1,30 +0,0 @@
1
- /** Java `method_reference` (`::`) nodes (tree-sitter-java). `super::` still lacks TypeEnv receiver typing. */
2
- /** Parse `expr::method`, `Type::new`, `this::m`, `super::m`. */
3
- export const parseJavaMethodReference = (callNode) => {
4
- if (callNode.type !== 'method_reference')
5
- return null;
6
- const recv = callNode.namedChild(0);
7
- if (!recv)
8
- return null;
9
- for (const c of callNode.children) {
10
- if (c.type === 'new') {
11
- if (recv.type !== 'identifier')
12
- return null;
13
- return { calledName: recv.text, callForm: 'constructor' };
14
- }
15
- }
16
- const rhs = callNode.child(callNode.childCount - 1);
17
- if (!rhs || rhs.type !== 'identifier')
18
- return null;
19
- const methodName = rhs.text;
20
- if (recv.type === 'identifier') {
21
- return { calledName: methodName, callForm: 'member', receiverName: recv.text };
22
- }
23
- if (recv.type === 'this') {
24
- return { calledName: methodName, callForm: 'member', receiverName: 'this' };
25
- }
26
- if (recv.type === 'super') {
27
- return { calledName: methodName, callForm: 'member', receiverName: 'super' };
28
- }
29
- return null;
30
- };