gitnexus 1.6.6-rc.98 → 1.6.6-rc.99

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.
@@ -1,4 +1,4 @@
1
- import { findNodeAtRange, nodeToCapture, syntheticCapture, } from '../../utils/ast-helpers.js';
1
+ import { nodeToCapture, syntheticCapture } from '../../utils/ast-helpers.js';
2
2
  import { getGoParser, getGoScopeQuery } from './query.js';
3
3
  import { recordGoCacheHit, recordGoCacheMiss } from './cache-stats.js';
4
4
  import { computeGoCallArity, computeGoDeclarationArity } from './arity-metadata.js';
@@ -22,39 +22,61 @@ export function emitGoScopeCaptures(sourceText, _filePath, cachedTree) {
22
22
  const out = [];
23
23
  for (const m of rawMatches) {
24
24
  const grouped = {};
25
+ // Parallel tag -> captured SyntaxNode map. The tree-sitter query already
26
+ // hands us the matched node as `c.node`; keeping it here lets us derive the
27
+ // anchor/relative node by walking LOCALLY (parent chain / own subtree)
28
+ // instead of re-walking from tree.rootNode (the O(matches x rootChildren)
29
+ // hotpath that made #1848's 250-struct DAO file take ~10s). The captured
30
+ // node either IS the node the old findNodeAtRange re-derived, or is a close
31
+ // relative reachable by a bounded local walk.
32
+ const nodeMap = {};
25
33
  for (const c of m.captures) {
26
34
  const tag = '@' + c.name;
27
35
  if (tag.startsWith('@_'))
28
36
  continue; // skip anonymous captures
29
37
  grouped[tag] = nodeToCapture(tag, c.node);
38
+ nodeMap[tag] = c.node;
30
39
  }
31
40
  if (Object.keys(grouped).length === 0)
32
41
  continue;
33
42
  if (grouped['@import.statement'] !== undefined) {
34
- const anchor = grouped['@import.statement'];
35
- const importNode = findNodeAtRange(tree.rootNode, anchor.range, 'import_declaration') ??
36
- findNodeAtRange(tree.rootNode, anchor.range, 'import_spec');
43
+ // The captured node is the `import_spec`; the original code preferred its
44
+ // enclosing `import_declaration` ONLY when that ancestor shares the exact
45
+ // same range (which never happens — the declaration always includes the
46
+ // `import` keyword prefix — so it falls back to the import_spec itself).
47
+ // Replicate that exactly via a local ancestor walk, never from root.
48
+ const importNode = resolveImportNode(nodeMap['@import.statement']);
37
49
  if (importNode !== null) {
38
50
  out.push(...splitGoImportStatement(importNode));
39
51
  continue;
40
52
  }
41
53
  }
42
54
  if (grouped['@scope.function'] !== undefined) {
43
- const scopeCap = grouped['@scope.function'];
44
- const fnNode = findNodeAtRange(tree.rootNode, scopeCap.range, 'function_declaration') ??
45
- findNodeAtRange(tree.rootNode, scopeCap.range, 'method_declaration');
55
+ // @scope.function captures function_declaration | method_declaration |
56
+ // func_literal. The original looked for a function_declaration or
57
+ // method_declaration at the captured range; the captured node IS that
58
+ // node for the first two, and a func_literal never coincides in range
59
+ // with either, so the lookup yields null for func_literal.
60
+ const scopeNode = nodeMap['@scope.function'];
61
+ const fnNode = scopeNode.type === 'function_declaration' || scopeNode.type === 'method_declaration'
62
+ ? scopeNode
63
+ : null;
46
64
  if (fnNode !== null) {
47
65
  const receiver = synthesizeGoReceiverBinding(fnNode);
48
66
  if (receiver !== null)
49
67
  out.push(receiver);
50
68
  }
51
69
  }
52
- if (isRawMultiAssignTypeBinding(tree.rootNode, grouped))
70
+ if (isRawMultiAssignTypeBinding(nodeMap))
53
71
  continue;
54
- const declAnchor = grouped['@declaration.function'] ?? grouped['@declaration.method'];
55
- if (declAnchor !== undefined) {
56
- const fnNode = findNodeAtRange(tree.rootNode, declAnchor.range, 'function_declaration') ??
57
- findNodeAtRange(tree.rootNode, declAnchor.range, 'method_declaration');
72
+ const declAnchorNode = nodeMap['@declaration.function'] ?? nodeMap['@declaration.method'];
73
+ if (declAnchorNode !== undefined) {
74
+ // @declaration.function / @declaration.method are captured directly on
75
+ // the function_declaration / method_declaration node.
76
+ const fnNode = declAnchorNode.type === 'function_declaration' ||
77
+ declAnchorNode.type === 'method_declaration'
78
+ ? declAnchorNode
79
+ : null;
58
80
  if (fnNode !== null) {
59
81
  const arity = computeGoDeclarationArity(fnNode);
60
82
  if (arity.parameterCount !== undefined) {
@@ -70,13 +92,14 @@ export function emitGoScopeCaptures(sourceText, _filePath, cachedTree) {
70
92
  out.push(grouped);
71
93
  continue;
72
94
  }
73
- const callAnchor = grouped['@reference.call.free'] ??
74
- grouped['@reference.call.member'] ??
75
- grouped['@reference.call.constructor'];
76
- if (callAnchor !== undefined && grouped['@reference.arity'] === undefined) {
77
- const callNode = findNodeAtRange(tree.rootNode, callAnchor.range, 'call_expression') ??
78
- findNodeAtRange(tree.rootNode, callAnchor.range, 'composite_literal');
79
- if (callNode !== null) {
95
+ // @reference.call.free / .member are captured on the call_expression;
96
+ // @reference.call.constructor on the composite_literal. The captured node
97
+ // IS the node the old findNodeAtRange re-derived for each, so use it.
98
+ const callNode = nodeMap['@reference.call.free'] ??
99
+ nodeMap['@reference.call.member'] ??
100
+ nodeMap['@reference.call.constructor'];
101
+ if (callNode !== undefined && grouped['@reference.arity'] === undefined) {
102
+ if (callNode.type === 'call_expression' || callNode.type === 'composite_literal') {
80
103
  grouped['@reference.arity'] = syntheticCapture('@reference.arity', callNode, String(computeGoCallArity(callNode)));
81
104
  }
82
105
  }
@@ -109,15 +132,54 @@ export function emitGoScopeCaptures(sourceText, _filePath, cachedTree) {
109
132
  }
110
133
  return out;
111
134
  }
112
- function isRawMultiAssignTypeBinding(rootNode, grouped) {
113
- const anchor = grouped['@type-binding.constructor'] ??
114
- grouped['@type-binding.call-return'] ??
115
- grouped['@type-binding.assertion'];
135
+ /**
136
+ * Resolve the node passed to `splitGoImportStatement` for an @import.statement
137
+ * match. The capture is on the `import_spec`; the original preferred an
138
+ * `import_declaration` at the SAME range, else the import_spec. An
139
+ * import_declaration always includes the `import` keyword and so never shares
140
+ * the spec's exact range — the only candidate is an ancestor, and it can only
141
+ * match when ranges coincide. Walk the parent chain (bounded, local) for an
142
+ * import_declaration whose range equals the spec's; otherwise return the spec.
143
+ */
144
+ function resolveImportNode(importSpec) {
145
+ let current = importSpec.parent;
146
+ while (current !== null) {
147
+ if (current.type === 'import_declaration') {
148
+ if (nodeRangeEquals(current, importSpec))
149
+ return current;
150
+ break;
151
+ }
152
+ // import_spec is nested at most under import_declaration ->
153
+ // import_spec_list -> import_spec; stop once we leave the import subtree.
154
+ if (current.type !== 'import_spec_list')
155
+ break;
156
+ current = current.parent;
157
+ }
158
+ return importSpec;
159
+ }
160
+ /** True iff two nodes occupy the exact same source range. */
161
+ function nodeRangeEquals(a, b) {
162
+ return (a.startPosition.row === b.startPosition.row &&
163
+ a.startPosition.column === b.startPosition.column &&
164
+ a.endPosition.row === b.endPosition.row &&
165
+ a.endPosition.column === b.endPosition.column);
166
+ }
167
+ function isRawMultiAssignTypeBinding(nodeMap) {
168
+ const anchor = nodeMap['@type-binding.constructor'] ??
169
+ nodeMap['@type-binding.call-return'] ??
170
+ nodeMap['@type-binding.assertion'];
116
171
  if (anchor === undefined)
117
172
  return false;
118
- const node = findNodeAtRange(rootNode, anchor.range, 'short_var_declaration');
119
- if (node === null)
173
+ // These tags are captured directly ON the short_var_declaration, so the
174
+ // captured node IS what the original findNodeAtRange(root, range,
175
+ // 'short_var_declaration') re-derived. The var_declaration (var-form)
176
+ // variants — @type-binding.assertion (`var x = e.(T)`) and
177
+ // @type-binding.call-return (`var x = Func()`) — anchor on a var_declaration
178
+ // instead; the old range+type lookup found no short_var_declaration at that
179
+ // range and returned null -> false, which this type guard reproduces exactly.
180
+ if (anchor.type !== 'short_var_declaration')
120
181
  return false;
182
+ const node = anchor;
121
183
  const lhs = node.childForFieldName('left');
122
184
  const rhs = node.childForFieldName('right');
123
185
  if (lhs === null)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.98",
3
+ "version": "1.6.6-rc.99",
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",