gitnexus 1.6.5-rc.34 → 1.6.5-rc.35

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 (24) hide show
  1. package/dist/_shared/scope-resolution/symbol-definition.d.ts +2 -0
  2. package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
  3. package/dist/core/ingestion/class-extractors/configs/c-cpp.js +38 -0
  4. package/dist/core/ingestion/class-extractors/generic.js +8 -0
  5. package/dist/core/ingestion/class-types.d.ts +15 -0
  6. package/dist/core/ingestion/languages/cpp/interpret.d.ts +6 -1
  7. package/dist/core/ingestion/languages/cpp/interpret.js +6 -9
  8. package/dist/core/ingestion/languages/cpp/query.js +31 -0
  9. package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
  10. package/dist/core/ingestion/model/symbol-table.js +3 -0
  11. package/dist/core/ingestion/parsing-processor.js +40 -1
  12. package/dist/core/ingestion/scope-extractor.js +4 -0
  13. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.d.ts +1 -0
  14. package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +12 -0
  15. package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +11 -0
  16. package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +43 -1
  17. package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -1
  18. package/dist/core/ingestion/tree-sitter-queries.js +13 -0
  19. package/dist/core/ingestion/utils/ast-helpers.js +6 -1
  20. package/dist/core/ingestion/utils/template-arguments.d.ts +11 -0
  21. package/dist/core/ingestion/utils/template-arguments.js +65 -0
  22. package/dist/core/ingestion/workers/parse-worker.d.ts +1 -0
  23. package/dist/core/ingestion/workers/parse-worker.js +41 -1
  24. package/package.json +1 -1
@@ -28,6 +28,8 @@ export interface SymbolDefinition {
28
28
  returnType?: string;
29
29
  /** Declared type for non-callable symbols — fields/properties (e.g. 'Address', 'List<User>') */
30
30
  declaredType?: string;
31
+ /** Generic/template specialization arguments for class-like symbols (e.g. ['User'], ['T*']). */
32
+ templateArguments?: string[];
31
33
  /** Links Method/Constructor/Property to owning Class/Struct/Trait nodeId */
32
34
  ownerId?: string;
33
35
  }
@@ -1 +1 @@
1
- {"version":3,"file":"symbol-definition.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/symbol-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB;;+FAE2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;iHAC6G;IAC7G,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;uFACmF;IACnF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
1
+ {"version":3,"file":"symbol-definition.d.ts","sourceRoot":"","sources":["../../src/scope-resolution/symbol-definition.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEnD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,SAAS,CAAC;IAChB;;+FAE2F;IAC3F,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;iHAC6G;IAC7G,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC;uFACmF;IACnF,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,6EAA6E;IAC7E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gGAAgG;IAChG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,gGAAgG;IAChG,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB"}
@@ -1,5 +1,27 @@
1
1
  // gitnexus/src/core/ingestion/class-extractors/configs/c-cpp.ts
2
2
  import { SupportedLanguages } from '../../../../_shared/index.js';
3
+ import { extractTemplateArguments, stripTemplateArguments, } from '../../utils/template-arguments.js';
4
+ function shouldSkipCppTemplateDuplicateCapture(captureMap, definitionName, capturedName) {
5
+ if (captureMap['template-arguments'] !== undefined)
6
+ return false;
7
+ if (!definitionName)
8
+ return false;
9
+ const argsFromDefinitionName = extractTemplateArguments(definitionName);
10
+ if (argsFromDefinitionName === undefined)
11
+ return false;
12
+ const argsFromCaptureName = capturedName ? extractTemplateArguments(capturedName) : undefined;
13
+ // Generic class capture emits only `List`, while the specialization-aware
14
+ // capture emits `List` + `@declaration.template-arguments`. Skip the former
15
+ // when the declaration name itself is templated to avoid duplicate class defs.
16
+ return argsFromCaptureName === undefined;
17
+ }
18
+ function extractCppTemplateArgumentsWithFallback(captureMap, definitionName, capturedName) {
19
+ return ((captureMap['template-arguments']
20
+ ? extractTemplateArguments(captureMap['template-arguments'].text)
21
+ : undefined) ??
22
+ (definitionName ? extractTemplateArguments(definitionName) : undefined) ??
23
+ (capturedName ? extractTemplateArguments(capturedName) : undefined));
24
+ }
3
25
  export const cClassConfig = {
4
26
  language: SupportedLanguages.C,
5
27
  typeDeclarationNodes: ['struct_specifier', 'enum_specifier'],
@@ -8,4 +30,20 @@ export const cppClassConfig = {
8
30
  language: SupportedLanguages.CPlusPlus,
9
31
  typeDeclarationNodes: ['class_specifier', 'struct_specifier', 'enum_specifier'],
10
32
  ancestorScopeNodeTypes: ['namespace_definition', 'class_specifier', 'struct_specifier'],
33
+ extractName: (node) => {
34
+ const nameNode = node.childForFieldName?.('name');
35
+ if (!nameNode)
36
+ return undefined;
37
+ if (nameNode.type !== 'template_type')
38
+ return undefined;
39
+ return stripTemplateArguments(nameNode.text);
40
+ },
41
+ extractTemplateArguments: (node) => {
42
+ const nameNode = node.childForFieldName?.('name');
43
+ if (!nameNode || nameNode.type !== 'template_type')
44
+ return undefined;
45
+ return extractTemplateArguments(nameNode.text);
46
+ },
47
+ shouldSkipClassCapture: ({ captureMap, definitionNode, nameNode }) => shouldSkipCppTemplateDuplicateCapture(captureMap, definitionNode?.childForFieldName?.('name')?.text, nameNode?.text),
48
+ extractTemplateArgumentsFromCapture: ({ captureMap, definitionNode, nameNode }) => extractCppTemplateArgumentsWithFallback(captureMap, definitionNode?.childForFieldName?.('name')?.text, nameNode?.text),
11
49
  };
@@ -116,10 +116,12 @@ export function createClassExtractor(config) {
116
116
  (isClassLikeLabel(fallback?.type) ? fallback.type : undefined);
117
117
  if (!name || !type)
118
118
  return null;
119
+ const templateArguments = config.extractTemplateArguments?.(node);
119
120
  return {
120
121
  name,
121
122
  type,
122
123
  qualifiedName: buildQualifiedName(node, name) || name,
124
+ ...(templateArguments !== undefined ? { templateArguments } : {}),
123
125
  };
124
126
  };
125
127
  return {
@@ -131,5 +133,11 @@ export function createClassExtractor(config) {
131
133
  extractQualifiedName(node, simpleName) {
132
134
  return extract(node, { name: simpleName })?.qualifiedName ?? null;
133
135
  },
136
+ shouldSkipClassCapture(context) {
137
+ return config.shouldSkipClassCapture?.(context) ?? false;
138
+ },
139
+ extractTemplateArgumentsFromCapture(context) {
140
+ return config.extractTemplateArgumentsFromCapture?.(context);
141
+ },
134
142
  };
135
143
  }
@@ -5,6 +5,12 @@ export interface ExtractedClassSymbol {
5
5
  name: string;
6
6
  type: ClassLikeNodeLabel;
7
7
  qualifiedName: string;
8
+ templateArguments?: string[];
9
+ }
10
+ export interface ClassCaptureContext {
11
+ captureMap: Record<string, SyntaxNode>;
12
+ definitionNode: SyntaxNode | null;
13
+ nameNode: SyntaxNode | undefined;
8
14
  }
9
15
  /**
10
16
  * Cross-language qualified type names are normalized to dot-separated scope
@@ -21,6 +27,10 @@ export interface ClassExtractor {
21
27
  type?: NodeLabel | null;
22
28
  }): ExtractedClassSymbol | null;
23
29
  extractQualifiedName(node: SyntaxNode, simpleName: string): string | null;
30
+ shouldSkipClassCapture?(context: ClassCaptureContext & {
31
+ nodeLabel: ClassLikeNodeLabel;
32
+ }): boolean;
33
+ extractTemplateArgumentsFromCapture?(context: ClassCaptureContext): string[] | undefined;
24
34
  }
25
35
  export interface ClassExtractionConfig {
26
36
  language: SupportedLanguages;
@@ -31,4 +41,9 @@ export interface ClassExtractionConfig {
31
41
  extractName?: (node: SyntaxNode) => string | undefined;
32
42
  extractType?: (node: SyntaxNode) => ClassLikeNodeLabel | undefined;
33
43
  extractScopeSegments?: (node: SyntaxNode) => string[] | null | undefined;
44
+ extractTemplateArguments?: (node: SyntaxNode) => string[] | undefined;
45
+ shouldSkipClassCapture?(context: ClassCaptureContext & {
46
+ nodeLabel: ClassLikeNodeLabel;
47
+ }): boolean;
48
+ extractTemplateArgumentsFromCapture?(context: ClassCaptureContext): string[] | undefined;
34
49
  }
@@ -25,6 +25,11 @@ export declare function interpretCppImport(captures: CaptureMatch): ParsedImport
25
25
  export declare function interpretCppTypeBinding(captures: CaptureMatch): ParsedTypeBinding | null;
26
26
  /**
27
27
  * Normalize a C++ type name: strip pointer/array/reference syntax,
28
- * qualifiers, and template parameters (V1: generic-ignored).
28
+ * qualifiers, while preserving template arguments for specialization-aware
29
+ * receiver binding (`List<User>` vs `List<Order>`).
30
+ *
31
+ * Keeping template arguments here allows receiver-bound fallback to match
32
+ * specialization-specific class defs first; non-template behavior is preserved
33
+ * by base-name fallback in resolveClassBindingForName.
29
34
  */
30
35
  export declare function normalizeCppTypeName(text: string): string;
@@ -82,7 +82,12 @@ export function interpretCppTypeBinding(captures) {
82
82
  }
83
83
  /**
84
84
  * Normalize a C++ type name: strip pointer/array/reference syntax,
85
- * qualifiers, and template parameters (V1: generic-ignored).
85
+ * qualifiers, while preserving template arguments for specialization-aware
86
+ * receiver binding (`List<User>` vs `List<Order>`).
87
+ *
88
+ * Keeping template arguments here allows receiver-bound fallback to match
89
+ * specialization-specific class defs first; non-template behavior is preserved
90
+ * by base-name fallback in resolveClassBindingForName.
86
91
  */
87
92
  export function normalizeCppTypeName(text) {
88
93
  let t = text.trim();
@@ -90,14 +95,6 @@ export function normalizeCppTypeName(text) {
90
95
  t = t
91
96
  .replace(/\b(const|volatile|restrict|static|extern|inline|mutable|constexpr|consteval)\b/g, '')
92
97
  .trim();
93
- // Strip template parameters (loop handles nested: Map<List<int>> → Map)
94
- while (t.includes('<')) {
95
- const stripped = t.replace(/<[^<>]*>/g, '');
96
- if (stripped === t)
97
- break; // avoid infinite loop on malformed input
98
- t = stripped;
99
- }
100
- t = t.trim();
101
98
  // Strip pointer stars
102
99
  while (t.endsWith('*'))
103
100
  t = t.slice(0, -1).trim();
@@ -31,21 +31,47 @@ const CPP_SCOPE_QUERY = `
31
31
  name: (type_identifier) @declaration.name
32
32
  body: (field_declaration_list)) @declaration.class
33
33
 
34
+ (class_specifier
35
+ name: (template_type
36
+ (type_identifier) @declaration.name
37
+ (template_argument_list) @declaration.template-arguments)
38
+ body: (field_declaration_list)) @declaration.class
39
+
34
40
  (struct_specifier
35
41
  name: (type_identifier) @declaration.name
36
42
  body: (field_declaration_list)) @declaration.struct
37
43
 
44
+ (struct_specifier
45
+ name: (template_type
46
+ (type_identifier) @declaration.name
47
+ (template_argument_list) @declaration.template-arguments)
48
+ body: (field_declaration_list)) @declaration.struct
49
+
38
50
  ;; ─── Declarations — class / struct inside template_declaration ───────
39
51
  (template_declaration
40
52
  (class_specifier
41
53
  name: (type_identifier) @declaration.name
42
54
  body: (field_declaration_list)) @declaration.class)
43
55
 
56
+ (template_declaration
57
+ (class_specifier
58
+ name: (template_type
59
+ (type_identifier) @declaration.name
60
+ (template_argument_list) @declaration.template-arguments)
61
+ body: (field_declaration_list)) @declaration.class)
62
+
44
63
  (template_declaration
45
64
  (struct_specifier
46
65
  name: (type_identifier) @declaration.name
47
66
  body: (field_declaration_list)) @declaration.struct)
48
67
 
68
+ (template_declaration
69
+ (struct_specifier
70
+ name: (template_type
71
+ (type_identifier) @declaration.name
72
+ (template_argument_list) @declaration.template-arguments)
73
+ body: (field_declaration_list)) @declaration.struct)
74
+
49
75
  ;; ─── Declarations — enum ─────────────────────────────────────────────
50
76
  (enum_specifier
51
77
  name: (type_identifier) @declaration.name) @declaration.enum
@@ -222,6 +248,11 @@ const CPP_SCOPE_QUERY = `
222
248
  type: (type_identifier) @type-binding.type
223
249
  declarator: (identifier) @type-binding.name) @type-binding.annotation
224
250
 
251
+ ;; Covers: List<User> users;
252
+ (declaration
253
+ type: (template_type) @type-binding.type
254
+ declarator: (identifier) @type-binding.name) @type-binding.annotation
255
+
225
256
  ;; ─── Type bindings — pointer variable declaration ───────────────────
226
257
  ;; Covers: User* ptr = new User()
227
258
  (declaration
@@ -99,6 +99,7 @@ export interface AddMetadata {
99
99
  parameterTypes?: string[];
100
100
  returnType?: string;
101
101
  declaredType?: string;
102
+ templateArguments?: string[];
102
103
  ownerId?: string;
103
104
  qualifiedName?: string;
104
105
  }
@@ -130,6 +130,9 @@ export const createSymbolTable = () => {
130
130
  : {}),
131
131
  ...(metadata?.returnType !== undefined ? { returnType: metadata.returnType } : {}),
132
132
  ...(metadata?.declaredType !== undefined ? { declaredType: metadata.declaredType } : {}),
133
+ ...(metadata?.templateArguments !== undefined
134
+ ? { templateArguments: metadata.templateArguments }
135
+ : {}),
133
136
  ...(metadata?.ownerId !== undefined ? { ownerId: metadata.ownerId } : {}),
134
137
  };
135
138
  // A. File Index — unconditional.
@@ -11,6 +11,7 @@ import { getDefinitionNodeFromCaptures, findEnclosingClassInfo, getLabelFromCapt
11
11
  import { detectFrameworkFromAST } from './framework-detection.js';
12
12
  import { buildTypeEnv } from './type-env.js';
13
13
  import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from './utils/method-props.js';
14
+ import { extractTemplateArguments, templateArgumentsIdTag } from './utils/template-arguments.js';
14
15
  import { logger } from '../logger.js';
15
16
  import { getTreeSitterBufferSize, getTreeSitterContentByteLength, TREE_SITTER_MAX_BUFFER, } from './constants.js';
16
17
  // ============================================================================
@@ -58,6 +59,7 @@ export const mergeChunkResults = (graph, symbolTable, chunkResults) => {
58
59
  parameterTypes: sym.parameterTypes,
59
60
  returnType: sym.returnType,
60
61
  declaredType: sym.declaredType,
62
+ templateArguments: sym.templateArguments,
61
63
  ownerId: sym.ownerId,
62
64
  qualifiedName: sym.qualifiedName,
63
65
  });
@@ -365,6 +367,20 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
365
367
  })
366
368
  : null;
367
369
  const nodeLabel = extractedClassSymbol?.type ?? defaultNodeLabel;
370
+ const isClassLikeLabel = nodeLabel === 'Class' ||
371
+ nodeLabel === 'Struct' ||
372
+ nodeLabel === 'Interface' ||
373
+ nodeLabel === 'Enum' ||
374
+ nodeLabel === 'Record';
375
+ if (isClassLikeLabel &&
376
+ provider.classExtractor?.shouldSkipClassCapture?.({
377
+ captureMap,
378
+ definitionNode,
379
+ nameNode,
380
+ nodeLabel,
381
+ }) === true) {
382
+ return;
383
+ }
368
384
  // Synthesize name for constructors without explicit @name capture (e.g. Swift init)
369
385
  if (!nameNode && nodeLabel !== 'Constructor' && !extractedClassSymbol)
370
386
  return;
@@ -464,7 +480,26 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
464
480
  arityTag += typeTagForId(cached.map, nodeName, arityForId, seqDefMethodInfo, language, cached.groups);
465
481
  arityTag += constTagForId(cached.map, nodeName, arityForId, seqDefMethodInfo, cached.groups);
466
482
  }
467
- const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${arityTag}`);
483
+ const classTemplateArguments = extractedClassSymbol?.templateArguments ??
484
+ provider.classExtractor?.extractTemplateArgumentsFromCapture?.({
485
+ captureMap,
486
+ definitionNode,
487
+ nameNode,
488
+ }) ??
489
+ (captureMap['template-arguments']
490
+ ? extractTemplateArguments(captureMap['template-arguments'].text)
491
+ : undefined) ??
492
+ (nameNode && nameNode.text ? extractTemplateArguments(nameNode.text) : undefined);
493
+ const classTemplateTag = (nodeLabel === 'Class' ||
494
+ nodeLabel === 'Struct' ||
495
+ nodeLabel === 'Interface' ||
496
+ nodeLabel === 'Enum' ||
497
+ nodeLabel === 'Record') &&
498
+ classTemplateArguments !== undefined &&
499
+ classTemplateArguments.length > 0
500
+ ? templateArgumentsIdTag(classTemplateArguments)
501
+ : '';
502
+ const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${classTemplateTag}${arityTag}`);
468
503
  const classNodeForSymbol = definitionNodeForRange || definitionNode || nameNode;
469
504
  const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
470
505
  (classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
@@ -490,6 +525,9 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
490
525
  ? isVueSetupTopLevel(nameNode || definitionNodeForRange)
491
526
  : cachedExportCheck(provider.exportChecker, nameNode || definitionNodeForRange, nodeName),
492
527
  ...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
528
+ ...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
529
+ ? { templateArguments: classTemplateArguments }
530
+ : {}),
493
531
  ...(frameworkHint
494
532
  ? {
495
533
  astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
@@ -543,6 +581,7 @@ const processParsingSequential = async (graph, files, symbolTable, astCache, sco
543
581
  parameterTypes: methodProps.parameterTypes,
544
582
  returnType: methodProps.returnType,
545
583
  declaredType,
584
+ templateArguments: classTemplateArguments,
546
585
  ownerId: enclosingClassId ?? undefined,
547
586
  qualifiedName: qualifiedTypeName,
548
587
  });
@@ -59,6 +59,7 @@
59
59
  * - `ParsedFile.referenceSites` — pre-resolution usage facts.
60
60
  */
61
61
  import { buildPositionIndex, buildScopeTree, canParentScope, makeScopeId } from '../../_shared/index.js';
62
+ import { extractTemplateArguments } from './utils/template-arguments.js';
62
63
  // ─── Public entry point ─────────────────────────────────────────────────────
63
64
  /**
64
65
  * Drive the five extraction passes and return a `ParsedFile`.
@@ -362,6 +363,8 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
362
363
  return undefined;
363
364
  const qualifiedCap = match['@declaration.qualified_name'];
364
365
  const qualifiedName = qualifiedCap?.text;
366
+ const templateArguments = extractTemplateArguments(match['@declaration.template-arguments']?.text ?? '') ??
367
+ extractTemplateArguments(qualifiedName ?? nameCap.text);
365
368
  // Optional arity metadata — producers (e.g. Python emit-captures)
366
369
  // synthesize these on function/method declarations. Their absence is
367
370
  // the normal case for other producers; readers treat undefined as
@@ -381,6 +384,7 @@ function buildDefFromDeclarationMatch(match, anchor, filePath) {
381
384
  ...(parameterTypes !== undefined ? { parameterTypes } : {}),
382
385
  ...(declaredType !== undefined ? { declaredType } : {}),
383
386
  ...(returnType !== undefined ? { returnType } : {}),
387
+ ...(templateArguments !== undefined ? { templateArguments } : {}),
384
388
  };
385
389
  }
386
390
  function parseIntCapture(cap) {
@@ -40,6 +40,7 @@ export declare function resolveDefGraphId(filePath: string, def: {
40
40
  qualifiedName?: string;
41
41
  type?: NodeLabel;
42
42
  parameterTypes?: readonly string[];
43
+ templateArguments?: readonly string[];
43
44
  }, nodeLookup: GraphNodeLookup): string | undefined;
44
45
  /** Derive the simple (unqualified) name of a def from its `qualifiedName`. */
45
46
  export declare function simpleQualifiedName(def: SymbolDefinition): string | undefined;
@@ -79,6 +79,18 @@ export function resolveDefGraphId(filePath, def, nodeLookup) {
79
79
  if (pHit !== undefined)
80
80
  return pHit;
81
81
  }
82
+ if ((def.type === 'Class' ||
83
+ def.type === 'Struct' ||
84
+ def.type === 'Interface' ||
85
+ def.type === 'Enum' ||
86
+ def.type === 'Record') &&
87
+ def.templateArguments !== undefined &&
88
+ def.templateArguments.length > 0) {
89
+ const tKey = qualifiedKey(filePath, def.type, `${qn}~${def.templateArguments.join(',')}`);
90
+ const tHit = nodeLookup.get(tKey);
91
+ if (tHit !== undefined)
92
+ return tHit;
93
+ }
82
94
  const qualifiedHit = nodeLookup.get(qualifiedKey(filePath, def.type, qn));
83
95
  if (qualifiedHit !== undefined)
84
96
  return qualifiedHit;
@@ -86,6 +86,17 @@ export function buildGraphNodeLookup(graph) {
86
86
  // Each overload is unique — set unconditionally.
87
87
  lookup.set(pKey, node.id);
88
88
  }
89
+ if ((node.label === 'Class' ||
90
+ node.label === 'Struct' ||
91
+ node.label === 'Interface' ||
92
+ node.label === 'Enum' ||
93
+ node.label === 'Record') &&
94
+ props.templateArguments !== undefined &&
95
+ props.templateArguments.length > 0) {
96
+ const tKey = qualifiedKey(props.filePath, node.label, `${qualified}~${props.templateArguments.join(',')}`);
97
+ if (!lookup.has(tKey))
98
+ lookup.set(tKey, node.id);
99
+ }
89
100
  }
90
101
  // Fallback key: simple name. First-wins within a file — used when
91
102
  // the caller doesn't know the qualifier (unqualified free-call
@@ -37,6 +37,48 @@ import { tryEmitEdge } from '../graph-bridge/edges.js';
37
37
  import { resolveCompoundReceiverClass } from '../passes/compound-receiver.js';
38
38
  import { resolveDefGraphId } from '../graph-bridge/ids.js';
39
39
  import { narrowOverloadCandidates, isOverloadAmbiguousAfterNormalization, } from './overload-narrowing.js';
40
+ import { extractTemplateArguments, stripTemplateArguments, } from '../../utils/template-arguments.js';
41
+ function normalizeTemplateArgToken(value) {
42
+ return value.replace(/\s+/g, '');
43
+ }
44
+ function resolveClassBindingForName(scopeId, rawClassName, scopes) {
45
+ const direct = findClassBindingInScope(scopeId, rawClassName, scopes);
46
+ if (direct !== undefined)
47
+ return direct;
48
+ if (!rawClassName.includes('<'))
49
+ return undefined;
50
+ const baseName = stripTemplateArguments(rawClassName).replace(/\s+/g, '');
51
+ if (baseName.length === 0)
52
+ return undefined;
53
+ const wantedArgs = extractTemplateArguments(rawClassName)?.map(normalizeTemplateArgToken);
54
+ if (wantedArgs !== undefined && wantedArgs.length > 0) {
55
+ // qualifiedNames is a Map and may not contain the stripped base name at all
56
+ // (e.g., unresolved type binding or only template-qualified entries), so
57
+ // default to [] before checking `.length`.
58
+ const qnameIds = scopes.qualifiedNames.get(baseName) ?? [];
59
+ if (qnameIds.length === 0) {
60
+ return findClassBindingInScope(scopeId, baseName, scopes);
61
+ }
62
+ const matches = [];
63
+ for (const id of qnameIds) {
64
+ const def = scopes.defs.get(id);
65
+ if (def === undefined || !isClassLike(def.type))
66
+ continue;
67
+ const defArgs = def.templateArguments?.map(normalizeTemplateArgToken);
68
+ if (defArgs !== undefined &&
69
+ defArgs.length === wantedArgs.length &&
70
+ defArgs.every((value, i) => value === wantedArgs[i])) {
71
+ matches.push(def);
72
+ }
73
+ }
74
+ if (matches.length === 1)
75
+ return matches[0];
76
+ // Scope extractor only records class definitions with bodies in C++, so
77
+ // forward declarations are not expected here. Keep fallback behavior for
78
+ // safety in non-ODR or mixed-language edge cases.
79
+ }
80
+ return findClassBindingInScope(scopeId, baseName, scopes);
81
+ }
40
82
  export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, handledSites, provider, index, model) {
41
83
  let emitted = 0;
42
84
  // Per-pass dedup so the multiple cases don't double-emit if two of
@@ -322,7 +364,7 @@ export function emitReceiverBoundCalls(graph, scopes, parsedFiles, nodeLookup, h
322
364
  }
323
365
  // ── Case 4: simple typeBinding (`u: U`) ──────────────────────
324
366
  if (typeRef !== undefined && !typeRef.rawName.includes('.')) {
325
- let ownerDef = findClassBindingInScope(site.inScope, typeRef.rawName, scopes);
367
+ let ownerDef = resolveClassBindingForName(site.inScope, typeRef.rawName, scopes);
326
368
  // `findClassBindingInScope(..., typeRef.rawName)` only works when
327
369
  // rawName is itself a class symbol reachable through scope bindings.
328
370
  // For languages with namespace-style imports (Go), imported types
@@ -4,7 +4,7 @@ export declare const PYTHON_QUERIES = "\n(class_definition\n name: (identifier)
4
4
  export declare const JAVA_QUERIES = "\n; Classes, Interfaces, Enums, Annotations\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(enum_declaration name: (identifier) @name) @definition.enum\n(annotation_type_declaration name: (identifier) @name) @definition.annotation\n\n; Methods & Constructors\n(method_declaration name: (identifier) @name) @definition.method\n(constructor_declaration name: (identifier) @name) @definition.constructor\n\n; Fields \u2014 typed field declarations inside class bodies\n(field_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.property\n\n; Imports - capture any import declaration child as source\n(import_declaration (_) @import.source) @import\n\n; Calls\n(method_invocation name: (identifier) @call.name) @call\n(method_invocation object: (_) name: (identifier) @call.name) @call\n(method_reference) @call\n\n; Constructor calls: new Foo()\n(object_creation_expression type: (type_identifier) @call.name) @call\n\n; Local variable declarations inside method bodies\n(local_variable_declaration\n declarator: (variable_declarator\n name: (identifier) @name)) @definition.variable\n\n; Heritage - extends class\n(class_declaration name: (identifier) @heritage.class\n (superclass (type_identifier) @heritage.extends)) @heritage\n\n; Heritage - implements interfaces\n(class_declaration name: (identifier) @heritage.class\n (super_interfaces (type_list (type_identifier) @heritage.implements))) @heritage.impl\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_access\n object: (_) @assignment.receiver\n field: (identifier) @assignment.property)\n right: (_)) @assignment\n";
5
5
  export declare const C_QUERIES = "\n; Functions (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n\n; Functions returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n\n; Functions returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n\n; Structs, Unions, Enums, Typedefs\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(union_specifier name: (type_identifier) @name) @definition.union\n(enum_specifier name: (type_identifier) @name) @definition.enum\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n\n; Variable declarations: int x = 5; or int x;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n";
6
6
  export declare const GO_QUERIES = "\n; Functions & Methods\n(function_declaration name: (identifier) @name) @definition.function\n(method_declaration name: (field_identifier) @name) @definition.method\n\n; Types\n(type_declaration (type_spec name: (type_identifier) @name type: (struct_type))) @definition.struct\n(type_declaration (type_spec name: (type_identifier) @name type: (interface_type))) @definition.interface\n\n; Imports\n(import_declaration (import_spec path: (interpreted_string_literal) @import.source)) @import\n(import_declaration (import_spec_list (import_spec path: (interpreted_string_literal) @import.source))) @import\n\n; Struct fields \u2014 named field declarations inside struct types\n(field_declaration_list\n (field_declaration\n name: (field_identifier) @name) @definition.property)\n\n; Struct embedding (anonymous fields = inheritance)\n(type_declaration\n (type_spec\n name: (type_identifier) @heritage.class\n type: (struct_type\n (field_declaration_list\n (field_declaration\n type: (type_identifier) @heritage.extends))))) @definition.struct\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (selector_expression field: (field_identifier) @call.name)) @call\n\n; Const/var declarations\n(const_declaration (const_spec name: (identifier) @name)) @definition.const\n(var_declaration (var_spec name: (identifier) @name)) @definition.variable\n\n; Short variable declaration: x := 5\n(short_var_declaration left: (expression_list (identifier) @name)) @definition.variable\n\n; Struct literal construction: User{Name: \"Alice\"}\n(composite_literal type: (type_identifier) @call.name) @call\n\n; Write access: obj.field = value\n(assignment_statement\n left: (expression_list\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property))\n right: (_)) @assignment\n\n; Write access: obj.field++ / obj.field--\n(inc_statement\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)) @assignment\n(dec_statement\n (selector_expression\n operand: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)) @assignment\n";
7
- export declare const CPP_QUERIES = "\n; Classes, Structs, Namespaces\n(class_specifier name: (type_identifier) @name) @definition.class\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(namespace_definition name: (namespace_identifier) @name) @definition.namespace\n(enum_specifier name: (type_identifier) @name) @definition.enum\n\n; Typedefs and unions (common in C-style headers and mixed C/C++ code)\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n(union_specifier name: (type_identifier) @name) @definition.union\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Functions & Methods (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method\n\n; Functions/methods returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Functions/methods returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))))) @definition.method\n\n; Functions/methods returning references (reference_declarator wraps function_declarator)\n(function_definition declarator: (reference_declarator (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (reference_declarator (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Destructors (destructor_name is distinct from identifier in tree-sitter-cpp)\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (destructor_name) @name))) @definition.method\n\n; Function declarations / prototypes (common in headers)\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n\n; Class/struct data member fields (Address address; int count;)\n; Uses field_identifier to exclude method declarations (which use function_declarator)\n(field_declaration\n declarator: (field_identifier) @name) @definition.property\n\n; Pointer member fields (Address* address;)\n(field_declaration\n declarator: (pointer_declarator\n declarator: (field_identifier) @name)) @definition.property\n\n; Reference member fields (Address& address;)\n(field_declaration\n declarator: (reference_declarator\n (field_identifier) @name)) @definition.property\n\n; Inline class method declarations (inside class body, no body: void save();)\n; tree-sitter-cpp uses field_identifier (not identifier) for names inside class bodies\n(field_declaration declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name)) @definition.method\n\n; Inline class method declarations returning a pointer (User* lookup();)\n(field_declaration declarator: (pointer_declarator declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method declarations returning a reference (User& lookup();)\n(field_declaration declarator: (reference_declarator (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method definitions (inside class body, with body: void Foo() { ... })\n(field_declaration_list\n (function_definition\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name) (destructor_name)] @name)) @definition.method)\n\n; Inline class methods returning a pointer type (User* lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (pointer_declarator\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Inline class methods returning a reference type (User& lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (reference_declarator\n (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Templates\n(template_declaration (class_specifier name: (type_identifier) @name)) @definition.template\n(template_declaration (function_definition declarator: (function_declarator declarator: (identifier) @name))) @definition.template\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (qualified_identifier name: (identifier) @call.name)) @call\n(call_expression function: (template_function name: (identifier) @call.name)) @call\n\n; Constructor calls: new User()\n(new_expression type: (type_identifier) @call.name) @call\n\n; Variable declarations: int x = 5; or auto x = 5;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n\n; Heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (type_identifier) @heritage.extends)) @heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (access_specifier) (type_identifier) @heritage.extends)) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_expression\n argument: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n\n";
7
+ export declare const CPP_QUERIES = "\n; Classes, Structs, Namespaces\n(class_specifier name: (type_identifier) @name) @definition.class\n(class_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments)) @definition.class\n(struct_specifier name: (type_identifier) @name) @definition.struct\n(struct_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments)) @definition.struct\n(namespace_definition name: (namespace_identifier) @name) @definition.namespace\n(enum_specifier name: (type_identifier) @name) @definition.enum\n\n; Typedefs and unions (common in C-style headers and mixed C/C++ code)\n(type_definition declarator: (type_identifier) @name) @definition.typedef\n(union_specifier name: (type_identifier) @name) @definition.union\n\n; Macros\n(preproc_function_def name: (identifier) @name) @definition.macro\n(preproc_def name: (identifier) @name) @definition.macro\n\n; Functions & Methods (direct declarator)\n(function_definition declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))) @definition.method\n\n; Functions/methods returning pointers (pointer_declarator wraps function_declarator)\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Functions/methods returning double pointers (nested pointer_declarator)\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name)))) @definition.function\n(function_definition declarator: (pointer_declarator declarator: (pointer_declarator declarator: (function_declarator declarator: (qualified_identifier name: (identifier) @name))))) @definition.method\n\n; Functions/methods returning references (reference_declarator wraps function_declarator)\n(function_definition declarator: (reference_declarator (function_declarator declarator: (identifier) @name))) @definition.function\n(function_definition declarator: (reference_declarator (function_declarator declarator: (qualified_identifier name: (identifier) @name)))) @definition.method\n\n; Destructors (destructor_name is distinct from identifier in tree-sitter-cpp)\n(function_definition declarator: (function_declarator declarator: (qualified_identifier name: (destructor_name) @name))) @definition.method\n\n; Function declarations / prototypes (common in headers)\n(declaration declarator: (function_declarator declarator: (identifier) @name)) @definition.function\n(declaration declarator: (pointer_declarator declarator: (function_declarator declarator: (identifier) @name))) @definition.function\n\n; Class/struct data member fields (Address address; int count;)\n; Uses field_identifier to exclude method declarations (which use function_declarator)\n(field_declaration\n declarator: (field_identifier) @name) @definition.property\n\n; Pointer member fields (Address* address;)\n(field_declaration\n declarator: (pointer_declarator\n declarator: (field_identifier) @name)) @definition.property\n\n; Reference member fields (Address& address;)\n(field_declaration\n declarator: (reference_declarator\n (field_identifier) @name)) @definition.property\n\n; Inline class method declarations (inside class body, no body: void save();)\n; tree-sitter-cpp uses field_identifier (not identifier) for names inside class bodies\n(field_declaration declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name)) @definition.method\n\n; Inline class method declarations returning a pointer (User* lookup();)\n(field_declaration declarator: (pointer_declarator declarator: (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method declarations returning a reference (User& lookup();)\n(field_declaration declarator: (reference_declarator (function_declarator declarator: [(field_identifier) (identifier)] @name))) @definition.method\n\n; Inline class method definitions (inside class body, with body: void Foo() { ... })\n(field_declaration_list\n (function_definition\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name) (destructor_name)] @name)) @definition.method)\n\n; Inline class methods returning a pointer type (User* lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (pointer_declarator\n declarator: (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Inline class methods returning a reference type (User& lookup(int id) { ... })\n(field_declaration_list\n (function_definition\n declarator: (reference_declarator\n (function_declarator\n declarator: [(field_identifier) (identifier) (operator_name)] @name))) @definition.method)\n\n; Templates\n(template_declaration (class_specifier name: (type_identifier) @name)) @definition.template\n(template_declaration\n (class_specifier\n name: (template_type\n (type_identifier) @name\n (template_argument_list) @template-arguments))) @definition.template\n(template_declaration (function_definition declarator: (function_declarator declarator: (identifier) @name))) @definition.template\n\n; Includes\n(preproc_include path: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (qualified_identifier name: (identifier) @call.name)) @call\n(call_expression function: (template_function name: (identifier) @call.name)) @call\n\n; Constructor calls: new User()\n(new_expression type: (type_identifier) @call.name) @call\n\n; Variable declarations: int x = 5; or auto x = 5;\n(declaration\n declarator: (init_declarator\n declarator: (identifier) @name)) @definition.variable\n\n; Heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (type_identifier) @heritage.extends)) @heritage\n(class_specifier name: (type_identifier) @heritage.class\n (base_class_clause (access_specifier) (type_identifier) @heritage.extends)) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_expression\n argument: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n\n";
8
8
  export declare const CSHARP_QUERIES = "\n; Types\n(class_declaration name: (identifier) @name) @definition.class\n(interface_declaration name: (identifier) @name) @definition.interface\n(struct_declaration name: (identifier) @name) @definition.struct\n(enum_declaration name: (identifier) @name) @definition.enum\n(record_declaration name: (identifier) @name) @definition.record\n(delegate_declaration name: (identifier) @name) @definition.delegate\n\n; Namespaces (block form and C# 10+ file-scoped form)\n(namespace_declaration name: (identifier) @name) @definition.namespace\n(namespace_declaration name: (qualified_name) @name) @definition.namespace\n(file_scoped_namespace_declaration name: (identifier) @name) @definition.namespace\n(file_scoped_namespace_declaration name: (qualified_name) @name) @definition.namespace\n\n; Methods & Properties\n(method_declaration name: (identifier) @name) @definition.method\n(local_function_statement name: (identifier) @name) @definition.function\n(constructor_declaration name: (identifier) @name) @definition.constructor\n(property_declaration name: (identifier) @name) @definition.property\n\n; Primary constructors (C# 12): class User(string name, int age) { }\n(class_declaration name: (identifier) @name (parameter_list) @definition.constructor)\n(record_declaration name: (identifier) @name (parameter_list) @definition.constructor)\n\n; Using\n(using_directive (qualified_name) @import.source) @import\n(using_directive (identifier) @import.source) @import\n\n; Calls\n(invocation_expression function: (identifier) @call.name) @call\n(invocation_expression function: (member_access_expression name: (identifier) @call.name)) @call\n\n; Null-conditional method calls: user?.Save()\n; Parses as: invocation_expression \u2192 conditional_access_expression \u2192 member_binding_expression \u2192 identifier\n(invocation_expression\n function: (conditional_access_expression\n (member_binding_expression\n (identifier) @call.name))) @call\n\n; Constructor calls: new Foo() and new Foo { Props }\n(object_creation_expression type: (identifier) @call.name) @call\n\n; Target-typed new (C# 9): User u = new(\"x\", 5)\n(variable_declaration type: (identifier) @call.name (variable_declarator (implicit_object_creation_expression) @call))\n\n; Local variable declarations\n(local_declaration_statement\n (variable_declaration\n (variable_declarator\n (identifier) @name))) @definition.variable\n\n; Heritage\n(class_declaration name: (identifier) @heritage.class\n (base_list (identifier) @heritage.extends)) @heritage\n(class_declaration name: (identifier) @heritage.class\n (base_list (generic_name (identifier) @heritage.extends))) @heritage\n\n; Interface inheritance: interface IFoo : IBar / interface IFoo : IBar, IBaz\n; Without these patterns, interface-to-interface relationships are never\n; captured, so transitive \"class X implements IBar\" chains are broken.\n(interface_declaration name: (identifier) @heritage.class\n (base_list (identifier) @heritage.extends)) @heritage\n(interface_declaration name: (identifier) @heritage.class\n (base_list (generic_name (identifier) @heritage.extends))) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (member_access_expression\n expression: (_) @assignment.receiver\n name: (identifier) @assignment.property)\n right: (_)) @assignment\n";
9
9
  export declare const RUST_QUERIES = "\n; Functions & Items\n(function_item name: (identifier) @name) @definition.function\n(function_signature_item name: (identifier) @name) @definition.function\n(struct_item name: (type_identifier) @name) @definition.struct\n(enum_item name: (type_identifier) @name) @definition.enum\n(trait_item name: (type_identifier) @name) @definition.trait\n(impl_item type: (type_identifier) @name !trait) @definition.impl\n(impl_item type: (generic_type type: (type_identifier) @name) !trait) @definition.impl\n(mod_item name: (identifier) @name) @definition.module\n\n; Type aliases, const, static, macros\n(type_item name: (type_identifier) @name) @definition.type\n(const_item name: (identifier) @name) @definition.const\n(static_item name: (identifier) @name) @definition.static\n(macro_definition name: (identifier) @name) @definition.macro\n\n; Use statements\n(use_declaration argument: (_) @import.source) @import\n\n; Calls\n(call_expression function: (identifier) @call.name) @call\n(call_expression function: (field_expression field: (field_identifier) @call.name)) @call\n(call_expression function: (scoped_identifier name: (identifier) @call.name)) @call\n(call_expression function: (generic_function function: (identifier) @call.name)) @call\n\n; Struct literal construction: User { name: value }\n(struct_expression name: (type_identifier) @call.name) @call\n\n; Struct fields \u2014 named field declarations inside struct bodies\n(field_declaration_list\n (field_declaration\n name: (field_identifier) @name) @definition.property)\n\n; Heritage (trait implementation) \u2014 all combinations of concrete/generic trait \u00D7 concrete/generic type\n(impl_item trait: (type_identifier) @heritage.trait type: (type_identifier) @heritage.class) @heritage\n(impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (type_identifier) @heritage.class) @heritage\n(impl_item trait: (type_identifier) @heritage.trait type: (generic_type type: (type_identifier) @heritage.class)) @heritage\n(impl_item trait: (generic_type type: (type_identifier) @heritage.trait) type: (generic_type type: (type_identifier) @heritage.class)) @heritage\n\n; Write access: obj.field = value\n(assignment_expression\n left: (field_expression\n value: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n\n; Write access: obj.field += value (compound assignment)\n(compound_assignment_expr\n left: (field_expression\n value: (_) @assignment.receiver\n field: (field_identifier) @assignment.property)\n right: (_)) @assignment\n";
10
10
  export declare const PHP_QUERIES = "\n; \u2500\u2500 Namespace \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(namespace_definition\n name: (namespace_name) @name) @definition.namespace\n\n; \u2500\u2500 Classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @name) @definition.class\n\n; \u2500\u2500 Interfaces \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(interface_declaration\n name: (name) @name) @definition.interface\n\n; \u2500\u2500 Traits \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(trait_declaration\n name: (name) @name) @definition.trait\n\n; \u2500\u2500 Enums (PHP 8.1) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(enum_declaration\n name: (name) @name) @definition.enum\n\n; \u2500\u2500 Top-level functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(function_definition\n name: (name) @name) @definition.function\n\n; \u2500\u2500 Methods (including constructors) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(method_declaration\n name: (name) @name) @definition.method\n\n; \u2500\u2500 Class properties (including Eloquent $fillable, $casts, etc.) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(property_declaration\n (property_element\n (variable_name\n (name) @name))) @definition.property\n\n; Constructor property promotion (PHP 8.0+: public Address $address in __construct)\n(method_declaration\n parameters: (formal_parameters\n (property_promotion_parameter\n name: (variable_name\n (name) @name)))) @definition.property\n\n; \u2500\u2500 Imports: use statements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; Simple: use App\\Models\\User;\n(namespace_use_declaration\n (namespace_use_clause\n (qualified_name) @import.source)) @import\n\n; \u2500\u2500 Function/method calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; Regular function call: foo()\n(function_call_expression\n function: (name) @call.name) @call\n\n; Method call: $obj->method()\n(member_call_expression\n name: (name) @call.name) @call\n\n; Nullsafe method call: $obj?->method()\n(nullsafe_member_call_expression\n name: (name) @call.name) @call\n\n; Static call: Foo::bar() (php_only uses scoped_call_expression)\n(scoped_call_expression\n name: (name) @call.name) @call\n\n; Constructor call: new User()\n(object_creation_expression (name) @call.name) @call\n\n; Const declarations at class scope\n(const_declaration\n (const_element\n (name) @name)) @definition.const\n\n; \u2500\u2500 Heritage: extends \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n (base_clause\n [(name) (qualified_name)] @heritage.extends)) @heritage\n\n; \u2500\u2500 Heritage: implements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n (class_interface_clause\n [(name) (qualified_name)] @heritage.implements)) @heritage.impl\n\n; \u2500\u2500 Heritage: use trait (must capture enclosing class name) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n(class_declaration\n name: (name) @heritage.class\n body: (declaration_list\n (use_declaration\n [(name) (qualified_name)] @heritage.trait))) @heritage\n\n; \u2500\u2500 Heritage: trait uses another trait (transitive trait composition) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n; PHP allows a trait body to contain \"use OtherTrait;\". The trait-uses-trait\n; IMPLEMENTS edge is required by buildPhpMro to compute the full transitive\n; trait closure (depth 3+ chains).\n(trait_declaration\n name: (name) @heritage.class\n body: (declaration_list\n (use_declaration\n [(name) (qualified_name)] @heritage.trait))) @heritage\n\n; PHP HTTP consumers: file_get_contents('/path'), curl_init('/path')\n(function_call_expression\n function: (name) @_php_http (#match? @_php_http \"^(file_get_contents|curl_init)$\")\n arguments: (arguments\n (argument (string (string_content) @http_client.url)))) @http_client\n\n; Write access: $obj->field = value\n(assignment_expression\n left: (member_access_expression\n object: (_) @assignment.receiver\n name: (name) @assignment.property)\n right: (_)) @assignment\n\n; Write access: ClassName::$field = value (static property)\n(assignment_expression\n left: (scoped_property_access_expression\n scope: (_) @assignment.receiver\n name: (variable_name (name) @assignment.property))\n right: (_)) @assignment\n";
@@ -673,7 +673,15 @@ export const GO_QUERIES = `
673
673
  export const CPP_QUERIES = `
674
674
  ; Classes, Structs, Namespaces
675
675
  (class_specifier name: (type_identifier) @name) @definition.class
676
+ (class_specifier
677
+ name: (template_type
678
+ (type_identifier) @name
679
+ (template_argument_list) @template-arguments)) @definition.class
676
680
  (struct_specifier name: (type_identifier) @name) @definition.struct
681
+ (struct_specifier
682
+ name: (template_type
683
+ (type_identifier) @name
684
+ (template_argument_list) @template-arguments)) @definition.struct
677
685
  (namespace_definition name: (namespace_identifier) @name) @definition.namespace
678
686
  (enum_specifier name: (type_identifier) @name) @definition.enum
679
687
 
@@ -755,6 +763,11 @@ export const CPP_QUERIES = `
755
763
 
756
764
  ; Templates
757
765
  (template_declaration (class_specifier name: (type_identifier) @name)) @definition.template
766
+ (template_declaration
767
+ (class_specifier
768
+ name: (template_type
769
+ (type_identifier) @name
770
+ (template_argument_list) @template-arguments))) @definition.template
758
771
  (template_declaration (function_definition declarator: (function_declarator declarator: (identifier) @name))) @definition.template
759
772
 
760
773
  ; Includes
@@ -1,4 +1,5 @@
1
1
  import { generateId } from '../../../lib/utils.js';
2
+ import { extractTemplateArguments, stripTemplateArguments, templateArgumentsIdTag, } from './template-arguments.js';
2
3
  /**
3
4
  * Ordered list of definition capture keys for tree-sitter query matches.
4
5
  * Used to extract the definition node from a capture map.
@@ -370,8 +371,12 @@ export const findEnclosingClassInfo = (node, filePath, resolveEnclosingOwner) =>
370
371
  current.children?.some((c) => c.type === 'interface')) {
371
372
  label = 'Interface';
372
373
  }
374
+ const templateArguments = extractTemplateArguments(nameNode.text);
375
+ const classIdName = templateArguments !== undefined
376
+ ? `${stripTemplateArguments(nameNode.text)}${templateArgumentsIdTag(templateArguments)}`
377
+ : nameNode.text;
373
378
  return {
374
- classId: generateId(label, `${filePath}:${nameNode.text}`),
379
+ classId: generateId(label, `${filePath}:${classIdName}`),
375
380
  className: nameNode.text,
376
381
  };
377
382
  }
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Parse top-level generic/template arguments from a type-like string.
3
+ *
4
+ * Examples:
5
+ * - `List<int>` -> ['int']
6
+ * - `Map<string, vector<int>>` -> ['string', 'vector<int>']
7
+ * - `List<T*>` -> ['T*']
8
+ */
9
+ export declare function extractTemplateArguments(text: string): string[] | undefined;
10
+ export declare function stripTemplateArguments(text: string): string;
11
+ export declare function templateArgumentsIdTag(templateArguments?: readonly string[]): string;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Parse top-level generic/template arguments from a type-like string.
3
+ *
4
+ * Examples:
5
+ * - `List<int>` -> ['int']
6
+ * - `Map<string, vector<int>>` -> ['string', 'vector<int>']
7
+ * - `List<T*>` -> ['T*']
8
+ */
9
+ export function extractTemplateArguments(text) {
10
+ const start = text.indexOf('<');
11
+ if (start === -1)
12
+ return undefined;
13
+ let depth = 0;
14
+ let end = -1;
15
+ for (let i = start; i < text.length; i += 1) {
16
+ const ch = text[i];
17
+ if (ch === '<')
18
+ depth += 1;
19
+ else if (ch === '>') {
20
+ depth -= 1;
21
+ if (depth === 0) {
22
+ end = i;
23
+ break;
24
+ }
25
+ if (depth < 0)
26
+ return undefined;
27
+ }
28
+ }
29
+ if (end === -1)
30
+ return undefined;
31
+ const inner = text.slice(start + 1, end);
32
+ if (inner.trim().length === 0)
33
+ return undefined;
34
+ const args = [];
35
+ let tokenStart = 0;
36
+ let nested = 0;
37
+ for (let i = 0; i < inner.length; i += 1) {
38
+ const ch = inner[i];
39
+ if (ch === '<')
40
+ nested += 1;
41
+ else if (ch === '>')
42
+ nested -= 1;
43
+ else if (ch === ',' && nested === 0) {
44
+ const token = inner.slice(tokenStart, i).replace(/\s+/g, '');
45
+ if (token.length > 0)
46
+ args.push(token);
47
+ tokenStart = i + 1;
48
+ }
49
+ }
50
+ const last = inner.slice(tokenStart).replace(/\s+/g, '');
51
+ if (last.length > 0)
52
+ args.push(last);
53
+ return args.length > 0 ? args : undefined;
54
+ }
55
+ export function stripTemplateArguments(text) {
56
+ const start = text.indexOf('<');
57
+ if (start === -1)
58
+ return text;
59
+ return text.slice(0, start);
60
+ }
61
+ export function templateArgumentsIdTag(templateArguments) {
62
+ if (templateArguments === undefined || templateArguments.length === 0)
63
+ return '';
64
+ return `~${templateArguments.join(',')}`;
65
+ }
@@ -40,6 +40,7 @@ interface ParsedSymbol {
40
40
  parameterTypes?: string[];
41
41
  returnType?: string;
42
42
  declaredType?: string;
43
+ templateArguments?: string[];
43
44
  ownerId?: string;
44
45
  visibility?: string;
45
46
  isStatic?: boolean;
@@ -45,6 +45,7 @@ import { generateId } from '../../../lib/utils.js';
45
45
  import { preprocessImportPath } from '../import-processor.js';
46
46
  import { extractVueScript, extractTemplateComponents, isVueSetupTopLevel, } from '../vue-sfc-extractor.js';
47
47
  import { buildMethodProps, arityForIdFromInfo, typeTagForId, constTagForId, buildCollisionGroups, } from '../utils/method-props.js';
48
+ import { extractTemplateArguments, templateArgumentsIdTag } from '../utils/template-arguments.js';
48
49
  import { extractParsedFile } from '../scope-extractor-bridge.js';
49
50
  import { logger } from '../../logger.js';
50
51
  // ============================================================================
@@ -1545,6 +1546,20 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1545
1546
  })
1546
1547
  : null;
1547
1548
  const nodeLabel = extractedClassSymbol?.type ?? defaultNodeLabel;
1549
+ const isClassLikeLabel = nodeLabel === 'Class' ||
1550
+ nodeLabel === 'Struct' ||
1551
+ nodeLabel === 'Interface' ||
1552
+ nodeLabel === 'Enum' ||
1553
+ nodeLabel === 'Record';
1554
+ if (isClassLikeLabel &&
1555
+ provider.classExtractor?.shouldSkipClassCapture?.({
1556
+ captureMap,
1557
+ definitionNode,
1558
+ nameNode,
1559
+ nodeLabel,
1560
+ }) === true) {
1561
+ continue;
1562
+ }
1548
1563
  // Dedup: variable captures (Const/Static/Variable) may overlap with higher-priority
1549
1564
  // captures (e.g. `const fn = () => {}` matches both @definition.function and @definition.const).
1550
1565
  // Skip variable captures whose definition node was already processed.
@@ -1634,7 +1649,26 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1634
1649
  arityTag += typeTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, language, groups);
1635
1650
  arityTag += constTagForId(defMethodMap, nodeName, arityForId, defMethodInfo, groups);
1636
1651
  }
1637
- const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${arityTag}`);
1652
+ const classTemplateArguments = extractedClassSymbol?.templateArguments ??
1653
+ provider.classExtractor?.extractTemplateArgumentsFromCapture?.({
1654
+ captureMap,
1655
+ definitionNode,
1656
+ nameNode,
1657
+ }) ??
1658
+ (captureMap['template-arguments']
1659
+ ? extractTemplateArguments(captureMap['template-arguments'].text)
1660
+ : undefined) ??
1661
+ (nameNode && nameNode.text ? extractTemplateArguments(nameNode.text) : undefined);
1662
+ const classTemplateTag = (nodeLabel === 'Class' ||
1663
+ nodeLabel === 'Struct' ||
1664
+ nodeLabel === 'Interface' ||
1665
+ nodeLabel === 'Enum' ||
1666
+ nodeLabel === 'Record') &&
1667
+ classTemplateArguments !== undefined &&
1668
+ classTemplateArguments.length > 0
1669
+ ? templateArgumentsIdTag(classTemplateArguments)
1670
+ : '';
1671
+ const nodeId = generateId(nodeLabel, `${file.path}:${qualifiedName}${classTemplateTag}${arityTag}`);
1638
1672
  const classNodeForSymbol = definitionNode || nameNode;
1639
1673
  const qualifiedTypeName = extractedClassSymbol?.qualifiedName ??
1640
1674
  (classNodeForSymbol && provider.classExtractor?.isTypeDeclaration(classNodeForSymbol)
@@ -1743,6 +1777,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1743
1777
  ? isVueSetupTopLevel(nameNode || definitionNode)
1744
1778
  : cachedExportCheck(provider.exportChecker, nameNode || definitionNode, nodeName),
1745
1779
  ...(qualifiedTypeName !== undefined ? { qualifiedName: qualifiedTypeName } : {}),
1780
+ ...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
1781
+ ? { templateArguments: classTemplateArguments }
1782
+ : {}),
1746
1783
  ...(frameworkHint
1747
1784
  ? {
1748
1785
  astFrameworkMultiplier: frameworkHint.entryPointMultiplier,
@@ -1766,6 +1803,9 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
1766
1803
  parameterTypes: methodProps.parameterTypes,
1767
1804
  returnType: methodProps.returnType,
1768
1805
  ...(declaredType !== undefined ? { declaredType } : {}),
1806
+ ...(classTemplateArguments !== undefined && classTemplateArguments.length > 0
1807
+ ? { templateArguments: classTemplateArguments }
1808
+ : {}),
1769
1809
  ...(enclosingClassId ? { ownerId: enclosingClassId } : {}),
1770
1810
  visibility: methodProps.visibility,
1771
1811
  isStatic: methodProps.isStatic,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.5-rc.34",
3
+ "version": "1.6.5-rc.35",
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",