gitnexus 1.6.6-rc.93 → 1.6.6-rc.95
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.
|
@@ -69,21 +69,38 @@ const NEW_QUALIFIED_CTOR_SPEC = {
|
|
|
69
69
|
// proto loader). Matches either a bare call or an `obj.loadPackageDefinition(...)`
|
|
70
70
|
// call. Plugin gates the qualified-constructor consumer on this —
|
|
71
71
|
// structural check avoids materializing `tree.rootNode.text` for every file.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
//
|
|
73
|
+
// These are TWO separate specs, NOT one `function: [ (identifier) ... (member_expression) ... ]`
|
|
74
|
+
// alternation. Under the pinned tree-sitter@0.21.1 binding a top-level alternation
|
|
75
|
+
// whose branches reuse the same capture name (`@fn`) collapses to one pattern with
|
|
76
|
+
// a shared predicate bucket; the second branch's `@fn` is left unbound and its
|
|
77
|
+
// `#eq?` is never enforced, so the member-expression branch would match EVERY
|
|
78
|
+
// `obj.method(...)` call (e.g. `console.log(...)`) — turning this gate always-on
|
|
79
|
+
// and emitting spurious qualified-constructor consumers. Two specs compile to two
|
|
80
|
+
// queries with independent predicate buckets; `runCompiledPatterns` concatenates
|
|
81
|
+
// their matches, so the `.length > 0` gate still means "either form is present".
|
|
82
|
+
const LOAD_PACKAGE_DEFINITION_SPECS = [
|
|
83
|
+
{
|
|
84
|
+
meta: {},
|
|
85
|
+
query: `
|
|
86
|
+
(call_expression
|
|
87
|
+
function: (identifier) @fn (#eq? @fn "loadPackageDefinition"))
|
|
88
|
+
`,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
meta: {},
|
|
92
|
+
query: `
|
|
93
|
+
(call_expression
|
|
94
|
+
function: (member_expression
|
|
95
|
+
property: (property_identifier) @fn (#eq? @fn "loadPackageDefinition")))
|
|
96
|
+
`,
|
|
97
|
+
},
|
|
98
|
+
];
|
|
82
99
|
function compileBundle(language, name) {
|
|
83
100
|
const mk = (spec, suffix) => compilePatterns({
|
|
84
101
|
name: `${name}-${suffix}`,
|
|
85
102
|
language,
|
|
86
|
-
patterns: [spec],
|
|
103
|
+
patterns: Array.isArray(spec) ? spec : [spec],
|
|
87
104
|
});
|
|
88
105
|
return {
|
|
89
106
|
grpcMethod: mk(GRPC_METHOD_SPEC, 'grpc-method'),
|
|
@@ -91,7 +108,7 @@ function compileBundle(language, name) {
|
|
|
91
108
|
getService: mk(GET_SERVICE_SPEC, 'get-service'),
|
|
92
109
|
newSimpleCtor: mk(NEW_SIMPLE_CTOR_SPEC, 'new-simple-ctor'),
|
|
93
110
|
newQualifiedCtor: mk(NEW_QUALIFIED_CTOR_SPEC, 'new-qualified-ctor'),
|
|
94
|
-
loadPackageDefinition: mk(
|
|
111
|
+
loadPackageDefinition: mk(LOAD_PACKAGE_DEFINITION_SPECS, 'load-package-definition'),
|
|
95
112
|
};
|
|
96
113
|
}
|
|
97
114
|
const JAVASCRIPT_BUNDLE = compileBundle(JavaScript, 'javascript-grpc');
|
|
@@ -6,14 +6,18 @@ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-s
|
|
|
6
6
|
* - Spring `RestTemplate.getForObject/...`, `exchange(...)`
|
|
7
7
|
* - Spring `WebClient.method(HttpMethod.X, ...)`, `WebClient.get().uri(...)`
|
|
8
8
|
* - OkHttp `new Request.Builder().url("...")`
|
|
9
|
-
* - OpenFeign interfaces with Spring MVC method annotations
|
|
9
|
+
* - OpenFeign interfaces with Spring MVC method annotations or
|
|
10
|
+
* native `@RequestLine("METHOD /path")` annotations
|
|
10
11
|
* - Java / Apache HttpClient literal request construction
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
* `@
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* Every route-defining annotation (class/interface `@RequestMapping`
|
|
14
|
+
* prefixes, `@FeignClient(path)` prefixes, `@(Get|...)Mapping` method
|
|
15
|
+
* routes and native `@RequestLine`s) is matched by a single consolidated
|
|
16
|
+
* query (`JAVA_ROUTE_ANNOTATION_PATTERNS`) in one pass via
|
|
17
|
+
* `scanRouteAnnotations`. The `scan` function then walks up from each
|
|
18
|
+
* matched method to its enclosing class/interface to combine the prefix
|
|
19
|
+
* with the method path. Call-site consumers (RestTemplate, WebClient,
|
|
20
|
+
* OkHttp, Java/Apache HttpClient) keep their own focused queries.
|
|
17
21
|
*/
|
|
18
22
|
const METHOD_ANNOTATION_TO_HTTP = {
|
|
19
23
|
GetMapping: 'GET',
|
|
@@ -22,9 +26,34 @@ const METHOD_ANNOTATION_TO_HTTP = {
|
|
|
22
26
|
DeleteMapping: 'DELETE',
|
|
23
27
|
PatchMapping: 'PATCH',
|
|
24
28
|
};
|
|
25
|
-
// ───
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
// ─── Route-defining annotations (one generic query, one pass) ─────────
|
|
30
|
+
// Every Java route-mapper annotation shares one shape: an annotation carrying a
|
|
31
|
+
// single string argument — positional `"..."` or named `key = "..."` — on a
|
|
32
|
+
// class, interface, or method. This SINGLE query matches that shape generically;
|
|
33
|
+
// `scanRouteAnnotations` then reads the annotation NAME (`@ann`) and declaration
|
|
34
|
+
// kind (`@node.type`) in its for-loop to decide what each match means. Adding a
|
|
35
|
+
// new framework annotation that follows this single-string-argument shape is a
|
|
36
|
+
// change to that loop (and the lookup maps), not to this query. Annotations with
|
|
37
|
+
// a different argument shape — e.g. an array value `@RequestMapping({"/a","/b"})`
|
|
38
|
+
// — are out of scope here (as they were for the prior queries) and would need a
|
|
39
|
+
// new branch.
|
|
40
|
+
//
|
|
41
|
+
// Captures (shared across all branches; intentionally framework-agnostic):
|
|
42
|
+
// @ann → the annotation name identifier (RequestMapping, GetMapping, RequestLine, …)
|
|
43
|
+
// @node → the enclosing declaration (class_declaration | interface_declaration | method_declaration)
|
|
44
|
+
// @value → the string-literal argument
|
|
45
|
+
// @key → the named-argument member key (absent for the positional shape)
|
|
46
|
+
// @member → the method name (method_declaration branches only)
|
|
47
|
+
//
|
|
48
|
+
// The query carries NO `#eq?` / `#match?` predicates. Under the pinned
|
|
49
|
+
// tree-sitter 0.21.x binding a top-level `[ ... ]` alternation compiles to one
|
|
50
|
+
// pattern whose text predicates share a single bucket keyed by capture name, and
|
|
51
|
+
// a `#match?` against a capture absent from the matched branch evaluates FALSE —
|
|
52
|
+
// silently dropping sibling-branch matches. Keeping the query predicate-free
|
|
53
|
+
// sidesteps that hazard entirely; all name/key discrimination lives in the
|
|
54
|
+
// for-loop, where it reads as straight-line code.
|
|
55
|
+
const JAVA_ROUTE_ANNOTATION_PATTERNS = compilePatterns({
|
|
56
|
+
name: 'java-route-annotation',
|
|
28
57
|
language: Java,
|
|
29
58
|
patterns: [
|
|
30
59
|
{
|
|
@@ -34,36 +63,44 @@ const SPRING_TYPE_PREFIX_PATTERNS = compilePatterns({
|
|
|
34
63
|
(class_declaration
|
|
35
64
|
(modifiers
|
|
36
65
|
(annotation
|
|
37
|
-
name: (identifier) @ann
|
|
38
|
-
arguments: (annotation_argument_list (string_literal) @
|
|
66
|
+
name: (identifier) @ann
|
|
67
|
+
arguments: (annotation_argument_list (string_literal) @value)))) @node
|
|
39
68
|
(interface_declaration
|
|
40
69
|
(modifiers
|
|
41
70
|
(annotation
|
|
42
|
-
name: (identifier) @ann
|
|
43
|
-
arguments: (annotation_argument_list (string_literal) @
|
|
44
|
-
]
|
|
45
|
-
`,
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
meta: {},
|
|
49
|
-
query: `
|
|
50
|
-
[
|
|
71
|
+
name: (identifier) @ann
|
|
72
|
+
arguments: (annotation_argument_list (string_literal) @value)))) @node
|
|
51
73
|
(class_declaration
|
|
52
74
|
(modifiers
|
|
53
75
|
(annotation
|
|
54
|
-
name: (identifier) @ann
|
|
76
|
+
name: (identifier) @ann
|
|
55
77
|
arguments: (annotation_argument_list
|
|
56
78
|
(element_value_pair
|
|
57
|
-
key: (identifier) @key
|
|
58
|
-
value: (string_literal) @
|
|
79
|
+
key: (identifier) @key
|
|
80
|
+
value: (string_literal) @value))))) @node
|
|
59
81
|
(interface_declaration
|
|
60
82
|
(modifiers
|
|
61
83
|
(annotation
|
|
62
|
-
name: (identifier) @ann
|
|
84
|
+
name: (identifier) @ann
|
|
63
85
|
arguments: (annotation_argument_list
|
|
64
86
|
(element_value_pair
|
|
65
|
-
key: (identifier) @key
|
|
66
|
-
value: (string_literal) @
|
|
87
|
+
key: (identifier) @key
|
|
88
|
+
value: (string_literal) @value))))) @node
|
|
89
|
+
(method_declaration
|
|
90
|
+
(modifiers
|
|
91
|
+
(annotation
|
|
92
|
+
name: (identifier) @ann
|
|
93
|
+
arguments: (annotation_argument_list (string_literal) @value)))
|
|
94
|
+
name: (identifier) @member) @node
|
|
95
|
+
(method_declaration
|
|
96
|
+
(modifiers
|
|
97
|
+
(annotation
|
|
98
|
+
name: (identifier) @ann
|
|
99
|
+
arguments: (annotation_argument_list
|
|
100
|
+
(element_value_pair
|
|
101
|
+
key: (identifier) @key
|
|
102
|
+
value: (string_literal) @value))))
|
|
103
|
+
name: (identifier) @member) @node
|
|
67
104
|
]
|
|
68
105
|
`,
|
|
69
106
|
},
|
|
@@ -84,88 +121,43 @@ const SPRING_TYPE_DECLARATION_PATTERNS = compilePatterns({
|
|
|
84
121
|
},
|
|
85
122
|
],
|
|
86
123
|
});
|
|
87
|
-
// ─── Consumer: OpenFeign
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
name: (identifier) @ann (#eq? @ann "RequestMapping")
|
|
125
|
-
arguments: (annotation_argument_list
|
|
126
|
-
(element_value_pair
|
|
127
|
-
key: (identifier) @key (#match? @key "^(path|value)$")
|
|
128
|
-
value: (string_literal) @prefix))))) @interface
|
|
129
|
-
`,
|
|
130
|
-
},
|
|
131
|
-
],
|
|
132
|
-
});
|
|
133
|
-
// ─── Provider: Spring @(Get|Post|...)Mapping method annotations ───────
|
|
134
|
-
// Same dual-pattern approach: positional vs named argument. The named
|
|
135
|
-
// pattern restricts the annotation member name to `path`/`value` to
|
|
136
|
-
// avoid capturing unrelated string-valued attributes
|
|
137
|
-
// (`produces`, `consumes`, `headers`, `name`, `params`, ...).
|
|
138
|
-
const SPRING_METHOD_ROUTE_PATTERNS = compilePatterns({
|
|
139
|
-
name: 'java-spring-method-route',
|
|
140
|
-
language: Java,
|
|
141
|
-
patterns: [
|
|
142
|
-
{
|
|
143
|
-
meta: {},
|
|
144
|
-
query: `
|
|
145
|
-
(method_declaration
|
|
146
|
-
(modifiers
|
|
147
|
-
(annotation
|
|
148
|
-
name: (identifier) @ann (#match? @ann "^(Get|Post|Put|Delete|Patch)Mapping$")
|
|
149
|
-
arguments: (annotation_argument_list (string_literal) @path)))
|
|
150
|
-
name: (identifier) @method_name) @method
|
|
151
|
-
`,
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
meta: {},
|
|
155
|
-
query: `
|
|
156
|
-
(method_declaration
|
|
157
|
-
(modifiers
|
|
158
|
-
(annotation
|
|
159
|
-
name: (identifier) @ann (#match? @ann "^(Get|Post|Put|Delete|Patch)Mapping$")
|
|
160
|
-
arguments: (annotation_argument_list
|
|
161
|
-
(element_value_pair
|
|
162
|
-
key: (identifier) @key (#match? @key "^(path|value)$")
|
|
163
|
-
value: (string_literal) @path))))
|
|
164
|
-
name: (identifier) @method_name) @method
|
|
165
|
-
`,
|
|
166
|
-
},
|
|
167
|
-
],
|
|
168
|
-
});
|
|
124
|
+
// ─── Consumer: OpenFeign `@RequestLine("METHOD /path")` parsing ───────
|
|
125
|
+
// OpenFeign's native annotation pairs an HTTP method and path in a single
|
|
126
|
+
// string literal — see https://github.com/OpenFeign/feign#interface-annotations.
|
|
127
|
+
// It is method-level only and is mutually exclusive with Spring MVC
|
|
128
|
+
// `@GetMapping` / `@PostMapping` etc. on the same method (mixing them
|
|
129
|
+
// requires a different Feign Contract — they are not combined). The match
|
|
130
|
+
// itself comes from `JAVA_ROUTE_ANNOTATION_PATTERNS`; this regex splits the
|
|
131
|
+
// verb from the path of the captured literal.
|
|
132
|
+
//
|
|
133
|
+
// Examples:
|
|
134
|
+
// @RequestLine("GET /users/{id}")
|
|
135
|
+
// @RequestLine("POST /users?status=active")
|
|
136
|
+
const REQUEST_LINE_VERB_RE = /^\s*(GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s+(\S.*?)\s*$/i;
|
|
137
|
+
/**
|
|
138
|
+
* Parse a Feign `@RequestLine` value into a method + path pair.
|
|
139
|
+
*
|
|
140
|
+
* `@RequestLine("METHOD /path[?query]")` packs both fields in one string;
|
|
141
|
+
* the query portion is dropped because contract IDs are method+path only
|
|
142
|
+
* (consistent with how other consumers like RestTemplate/WebClient drop
|
|
143
|
+
* query strings when their values are inline literals).
|
|
144
|
+
*
|
|
145
|
+
* Returns null if the value is not a recognized HTTP verb followed by a
|
|
146
|
+
* path beginning with `/`.
|
|
147
|
+
*/
|
|
148
|
+
function parseRequestLine(raw) {
|
|
149
|
+
const match = REQUEST_LINE_VERB_RE.exec(raw);
|
|
150
|
+
if (!match)
|
|
151
|
+
return null;
|
|
152
|
+
const [, verb, rest] = match;
|
|
153
|
+
if (typeof verb !== 'string' || typeof rest !== 'string')
|
|
154
|
+
return null;
|
|
155
|
+
const queryIdx = rest.indexOf('?');
|
|
156
|
+
const pathOnly = (queryIdx >= 0 ? rest.slice(0, queryIdx) : rest).trim();
|
|
157
|
+
if (!pathOnly.startsWith('/'))
|
|
158
|
+
return null;
|
|
159
|
+
return { method: verb.toUpperCase(), path: pathOnly };
|
|
160
|
+
}
|
|
169
161
|
// ─── Consumer: Spring RestTemplate (object-named + method-named) ──────
|
|
170
162
|
// RestTemplate.getForObject / getForEntity → GET
|
|
171
163
|
// RestTemplate.postForObject / postForEntity → POST
|
|
@@ -364,38 +356,110 @@ function hasAnnotation(node, names) {
|
|
|
364
356
|
}
|
|
365
357
|
return false;
|
|
366
358
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return prefixByTypeId;
|
|
359
|
+
/**
|
|
360
|
+
* A named annotation argument contributes a route only when its member key is
|
|
361
|
+
* `path` or `value`; a positional argument (no key node) always qualifies.
|
|
362
|
+
* This is the JS-side replacement for the in-query `^(path|value)$` filter and
|
|
363
|
+
* drops Spring's non-route string attributes (`produces`, `consumes`,
|
|
364
|
+
* `headers`, `name`, `params`) that would otherwise be mis-read as routes.
|
|
365
|
+
*/
|
|
366
|
+
function isRouteMemberKey(keyNode) {
|
|
367
|
+
if (!keyNode)
|
|
368
|
+
return true;
|
|
369
|
+
return keyNode.text === 'path' || keyNode.text === 'value';
|
|
379
370
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Resolve every Java route-defining annotation in a single tree-sitter pass.
|
|
373
|
+
*
|
|
374
|
+
* The generic `JAVA_ROUTE_ANNOTATION_PATTERNS` query yields one match per
|
|
375
|
+
* annotation-carrying-a-string-argument on any class / interface / method. This
|
|
376
|
+
* loop reads the annotation name and declaration kind to decide what each match
|
|
377
|
+
* means, ignoring annotations it does not recognise. The HTTP verb map
|
|
378
|
+
* (`METHOD_ANNOTATION_TO_HTTP`) and the `path`/`value` key filter
|
|
379
|
+
* (`isRouteMemberKey`) live here rather than in the query (see its header).
|
|
380
|
+
*/
|
|
381
|
+
function scanRouteAnnotations(tree) {
|
|
382
|
+
const matches = runCompiledPatterns(JAVA_ROUTE_ANNOTATION_PATTERNS, tree);
|
|
383
|
+
// The two prefix maps intentionally diverge for the same interface node:
|
|
384
|
+
// `prefixByTypeId` feeds the Spring *provider* path (class prefix +
|
|
385
|
+
// collectSpringTypes cross-file inheritance), while `feignPrefixByInterfaceId`
|
|
386
|
+
// feeds the OpenFeign *consumer* path in scan(). An interface carrying both
|
|
387
|
+
// `@RequestMapping` and `@FeignClient(path)` lands a different value in each.
|
|
388
|
+
const prefixByTypeId = new Map();
|
|
389
|
+
const feignPrefixByInterfaceId = new Map();
|
|
390
|
+
const methodRoutes = [];
|
|
391
|
+
const requestLines = [];
|
|
392
|
+
// Interface `@RequestMapping` prefixes rank below `@FeignClient(path)`;
|
|
393
|
+
// collect them and apply only after the FeignClient pass below.
|
|
394
|
+
const interfaceRequestMappingPrefixes = [];
|
|
395
|
+
for (const { captures } of matches) {
|
|
396
|
+
const annNode = captures.ann;
|
|
397
|
+
const node = captures.node;
|
|
398
|
+
const valueNode = captures.value;
|
|
399
|
+
if (!annNode || !node || !valueNode)
|
|
390
400
|
continue;
|
|
391
|
-
const
|
|
392
|
-
|
|
401
|
+
const ann = annNode.text;
|
|
402
|
+
const keyNode = captures.key; // undefined for the positional shape
|
|
403
|
+
if (node.type === 'method_declaration') {
|
|
404
|
+
// Method-level: a Spring `@(Get|...)Mapping` route, or native `@RequestLine`.
|
|
405
|
+
const httpMethod = METHOD_ANNOTATION_TO_HTTP[ann];
|
|
406
|
+
if (httpMethod) {
|
|
407
|
+
if (!isRouteMemberKey(keyNode))
|
|
408
|
+
continue;
|
|
409
|
+
const rawPath = unquoteLiteral(valueNode.text);
|
|
410
|
+
if (rawPath !== null) {
|
|
411
|
+
methodRoutes.push({
|
|
412
|
+
methodNode: node,
|
|
413
|
+
methodName: captures.member?.text ?? null,
|
|
414
|
+
httpMethod,
|
|
415
|
+
rawPath,
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
else if (ann === 'RequestLine') {
|
|
420
|
+
// Feign packs verb + path in one literal; its only named argument is `value`.
|
|
421
|
+
if (keyNode && keyNode.text !== 'value')
|
|
422
|
+
continue;
|
|
423
|
+
const raw = unquoteLiteral(valueNode.text);
|
|
424
|
+
const parsed = raw !== null ? parseRequestLine(raw) : null;
|
|
425
|
+
if (parsed) {
|
|
426
|
+
requestLines.push({
|
|
427
|
+
methodNode: node,
|
|
428
|
+
methodName: captures.member?.text ?? null,
|
|
429
|
+
parsed,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
}
|
|
393
433
|
continue;
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
434
|
+
}
|
|
435
|
+
// Type-level (class or interface): a Spring `@RequestMapping` URL prefix, or
|
|
436
|
+
// — on an interface — an OpenFeign `@FeignClient(path = "...")` prefix.
|
|
437
|
+
if (ann === 'RequestMapping') {
|
|
438
|
+
if (!isRouteMemberKey(keyNode))
|
|
439
|
+
continue;
|
|
440
|
+
const prefix = unquoteLiteral(valueNode.text);
|
|
441
|
+
if (prefix !== null) {
|
|
442
|
+
prefixByTypeId.set(node.id, prefix);
|
|
443
|
+
if (node.type === 'interface_declaration') {
|
|
444
|
+
interfaceRequestMappingPrefixes.push({ id: node.id, prefix });
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
else if (ann === 'FeignClient' && node.type === 'interface_declaration') {
|
|
449
|
+
// Feign's `name`/`value` identify a service, not a path — only `path` is a prefix.
|
|
450
|
+
if (!keyNode || keyNode.text !== 'path')
|
|
451
|
+
continue;
|
|
452
|
+
const prefix = unquoteLiteral(valueNode.text);
|
|
453
|
+
if (prefix !== null && !feignPrefixByInterfaceId.has(node.id)) {
|
|
454
|
+
feignPrefixByInterfaceId.set(node.id, prefix);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
for (const { id, prefix } of interfaceRequestMappingPrefixes) {
|
|
459
|
+
if (!feignPrefixByInterfaceId.has(id))
|
|
460
|
+
feignPrefixByInterfaceId.set(id, prefix);
|
|
397
461
|
}
|
|
398
|
-
return
|
|
462
|
+
return { prefixByTypeId, feignPrefixByInterfaceId, methodRoutes, requestLines };
|
|
399
463
|
}
|
|
400
464
|
function collectDirectMethods(typeNode) {
|
|
401
465
|
const out = [];
|
|
@@ -432,8 +496,13 @@ function collectImplementedInterfaces(typeNode) {
|
|
|
432
496
|
return out;
|
|
433
497
|
}
|
|
434
498
|
function collectSpringTypes(filePath, tree) {
|
|
435
|
-
const prefixByTypeId =
|
|
436
|
-
const routesByMethodId =
|
|
499
|
+
const { prefixByTypeId, methodRoutes } = scanRouteAnnotations(tree);
|
|
500
|
+
const routesByMethodId = new Map();
|
|
501
|
+
for (const route of methodRoutes) {
|
|
502
|
+
const routes = routesByMethodId.get(route.methodNode.id) ?? [];
|
|
503
|
+
routes.push({ method: route.httpMethod, path: route.rawPath });
|
|
504
|
+
routesByMethodId.set(route.methodNode.id, routes);
|
|
505
|
+
}
|
|
437
506
|
const out = [];
|
|
438
507
|
for (const match of runCompiledPatterns(SPRING_TYPE_DECLARATION_PATTERNS, tree)) {
|
|
439
508
|
const typeNode = match.captures.type;
|
|
@@ -521,59 +590,60 @@ export const JAVA_HTTP_PLUGIN = {
|
|
|
521
590
|
language: Java,
|
|
522
591
|
scan(tree) {
|
|
523
592
|
const out = [];
|
|
524
|
-
// ───
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
for (const match of runCompiledPatterns(SPRING_METHOD_ROUTE_PATTERNS, tree)) {
|
|
537
|
-
const annNode = match.captures.ann;
|
|
538
|
-
const pathNode = match.captures.path;
|
|
539
|
-
const nameNode = match.captures.method_name;
|
|
540
|
-
const methodNode = match.captures.method;
|
|
541
|
-
if (!annNode || !pathNode || !methodNode)
|
|
542
|
-
continue;
|
|
543
|
-
const httpMethod = METHOD_ANNOTATION_TO_HTTP[annNode.text];
|
|
544
|
-
if (!httpMethod)
|
|
545
|
-
continue;
|
|
546
|
-
const rawPath = unquoteLiteral(pathNode.text);
|
|
547
|
-
if (rawPath === null)
|
|
548
|
-
continue;
|
|
549
|
-
const enclosingInterface = findEnclosingInterface(methodNode);
|
|
593
|
+
// ─── Spring providers + OpenFeign consumers (one query pass) ────
|
|
594
|
+
// `scanRouteAnnotations` resolves every route-defining annotation —
|
|
595
|
+
// class/interface prefixes, method `@(Get|...)Mapping`s and native
|
|
596
|
+
// `@RequestLine`s — from a single `matches()` pass over the tree.
|
|
597
|
+
const { prefixByTypeId, feignPrefixByInterfaceId, methodRoutes, requestLines } = scanRouteAnnotations(tree);
|
|
598
|
+
// A `@(Get|...)Mapping` inside a `@FeignClient` interface is an OpenFeign
|
|
599
|
+
// *consumer* (it describes a remote call); the same annotation inside a
|
|
600
|
+
// class is a Spring *provider*. A mapping on a non-Feign interface has no
|
|
601
|
+
// enclosing class and is dropped here — interface→controller inheritance is
|
|
602
|
+
// handled by `scanProject`.
|
|
603
|
+
for (const route of methodRoutes) {
|
|
604
|
+
const enclosingInterface = findEnclosingInterface(route.methodNode);
|
|
550
605
|
if (enclosingInterface && hasAnnotation(enclosingInterface, 'FeignClient')) {
|
|
551
606
|
const prefix = feignPrefixByInterfaceId.get(enclosingInterface.id) ?? '';
|
|
552
|
-
const fullPath = joinPath(prefix, rawPath);
|
|
553
607
|
out.push({
|
|
554
608
|
role: 'consumer',
|
|
555
609
|
framework: 'openfeign',
|
|
556
|
-
method: httpMethod,
|
|
557
|
-
path:
|
|
558
|
-
name:
|
|
610
|
+
method: route.httpMethod,
|
|
611
|
+
path: joinPath(prefix, route.rawPath),
|
|
612
|
+
name: route.methodName,
|
|
559
613
|
confidence: 0.7,
|
|
560
614
|
});
|
|
561
615
|
continue;
|
|
562
616
|
}
|
|
563
|
-
const enclosingClass = findEnclosingClass(methodNode);
|
|
617
|
+
const enclosingClass = findEnclosingClass(route.methodNode);
|
|
564
618
|
if (!enclosingClass)
|
|
565
619
|
continue;
|
|
566
620
|
const prefix = prefixByTypeId.get(enclosingClass.id) ?? '';
|
|
567
|
-
const fullPath = joinPath(prefix, rawPath);
|
|
568
621
|
out.push({
|
|
569
622
|
role: 'provider',
|
|
570
623
|
framework: 'spring',
|
|
571
|
-
method: httpMethod,
|
|
572
|
-
path:
|
|
573
|
-
name:
|
|
624
|
+
method: route.httpMethod,
|
|
625
|
+
path: joinPath(prefix, route.rawPath),
|
|
626
|
+
name: route.methodName,
|
|
574
627
|
confidence: 0.8,
|
|
575
628
|
});
|
|
576
629
|
}
|
|
630
|
+
// Native OpenFeign `@RequestLine("METHOD /path")`. Method-level only; the
|
|
631
|
+
// enclosing interface MUST carry `@FeignClient`, otherwise the same
|
|
632
|
+
// annotation name in unrelated libraries would be a false positive.
|
|
633
|
+
for (const requestLine of requestLines) {
|
|
634
|
+
const enclosingInterface = findEnclosingInterface(requestLine.methodNode);
|
|
635
|
+
if (!enclosingInterface || !hasAnnotation(enclosingInterface, 'FeignClient'))
|
|
636
|
+
continue;
|
|
637
|
+
const prefix = feignPrefixByInterfaceId.get(enclosingInterface.id) ?? '';
|
|
638
|
+
out.push({
|
|
639
|
+
role: 'consumer',
|
|
640
|
+
framework: 'openfeign',
|
|
641
|
+
method: requestLine.parsed.method,
|
|
642
|
+
path: joinPath(prefix, requestLine.parsed.path),
|
|
643
|
+
name: requestLine.methodName,
|
|
644
|
+
confidence: 0.75,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
577
647
|
// ─── Consumers: RestTemplate ────────────────────────────────────
|
|
578
648
|
for (const match of runCompiledPatterns(REST_TEMPLATE_PATTERNS, tree)) {
|
|
579
649
|
const methodNode = match.captures.method;
|
package/package.json
CHANGED