gitnexus 1.6.6-rc.97 → 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.
|
@@ -627,12 +627,19 @@ export const JAVA_HTTP_PLUGIN = {
|
|
|
627
627
|
confidence: 0.8,
|
|
628
628
|
});
|
|
629
629
|
}
|
|
630
|
-
// Native OpenFeign `@RequestLine("METHOD /path")`. Method-level only
|
|
631
|
-
//
|
|
632
|
-
//
|
|
630
|
+
// Native OpenFeign `@RequestLine("METHOD /path")`. Method-level only and
|
|
631
|
+
// always declared on an interface (Feign builds a proxy from the interface).
|
|
632
|
+
// We do NOT require an enclosing `@FeignClient`: `@RequestLine` is a core
|
|
633
|
+
// `feign.*` annotation used with `Feign.builder()`, whereas `@FeignClient`
|
|
634
|
+
// is the Spring Cloud variant that uses Spring MVC annotations instead — the
|
|
635
|
+
// two are effectively mutually exclusive, so requiring `@FeignClient` here
|
|
636
|
+
// would miss the annotation's primary use. The `RequestLine` name is itself
|
|
637
|
+
// a strong, framework-specific signal, so a structural interface check is
|
|
638
|
+
// enough to keep false positives away. A `@FeignClient(path=...)` prefix is
|
|
639
|
+
// still applied when present (rare, but harmless).
|
|
633
640
|
for (const requestLine of requestLines) {
|
|
634
641
|
const enclosingInterface = findEnclosingInterface(requestLine.methodNode);
|
|
635
|
-
if (!enclosingInterface
|
|
642
|
+
if (!enclosingInterface)
|
|
636
643
|
continue;
|
|
637
644
|
const prefix = feignPrefixByInterfaceId.get(enclosingInterface.id) ?? '';
|
|
638
645
|
out.push({
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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(
|
|
70
|
+
if (isRawMultiAssignTypeBinding(nodeMap))
|
|
53
71
|
continue;
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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