gitnexus 1.6.9-rc.3 → 1.6.9-rc.5
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.
- package/dist/core/group/extractors/http-patterns/java-static-path.d.ts +39 -0
- package/dist/core/group/extractors/http-patterns/java-static-path.js +248 -0
- package/dist/core/group/extractors/http-patterns/java.js +140 -50
- package/dist/core/group/extractors/http-patterns/kotlin.js +143 -26
- package/dist/core/group/extractors/http-patterns/python.js +221 -1
- package/dist/core/group/matching.js +23 -7
- package/dist/core/ingestion/language-provider.d.ts +23 -2
- package/dist/core/ingestion/languages/python.js +9 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +19 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +125 -1
- package/dist/core/ingestion/pipeline-phases/routes.js +5 -0
- package/dist/core/ingestion/route-extractors/django-root-discovery.d.ts +25 -0
- package/dist/core/ingestion/route-extractors/django-root-discovery.js +217 -0
- package/dist/core/ingestion/route-extractors/django.d.ts +4 -0
- package/dist/core/ingestion/route-extractors/django.js +425 -0
- package/package.json +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import Parser from 'tree-sitter';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve a statically-derivable path argument to a literal path: a bare
|
|
4
|
+
* string literal, a `URI.create("/x")` call, or a `UriComponentsBuilder`
|
|
5
|
+
* fluent chain. Genuinely dynamic arguments → null.
|
|
6
|
+
*/
|
|
7
|
+
export declare function extractStaticPathExpression(node: Parser.SyntaxNode): string | null;
|
|
8
|
+
/**
|
|
9
|
+
* Infer an OkHttp request verb by scanning the builder chain around the matched
|
|
10
|
+
* `.url(...)` call: a `.get()/.head()/.post()/.put()/.delete()/.patch()` helper
|
|
11
|
+
* (before or after `.url()`), or a `.method("VERB", …)` literal, wins. Returns
|
|
12
|
+
* `'GET'` only when the chain has NO verb call at all (a bare `.url(...).build()`
|
|
13
|
+
* — OkHttp's real default). Returns `null` for an explicit `.method(verb, …)`
|
|
14
|
+
* whose verb is a non-literal: the verb is set but not statically resolvable, so
|
|
15
|
+
* the caller skips the call rather than asserting a wrong GET (parity with the
|
|
16
|
+
* WebClient long-form variable-bound-verb behavior, which also skips).
|
|
17
|
+
*/
|
|
18
|
+
export declare function inferOkHttpMethod(urlCall: Parser.SyntaxNode): string | null;
|
|
19
|
+
/**
|
|
20
|
+
* Infer a Java-`HttpClient` request verb by walking UP the builder chain from the
|
|
21
|
+
* matched `.uri(URI.create("..."))` call: the first `.GET()/.POST()/.PUT()/`
|
|
22
|
+
* `.DELETE()/.HEAD()` verb-helper, or a `.method("VERB", body)` literal, wins.
|
|
23
|
+
* Returns `'GET'` only when the chain has no verb call (a bare `.build()` — the
|
|
24
|
+
* builder's real default). Returns `null` for an explicit `.method(verb, …)` with
|
|
25
|
+
* a non-literal verb (skip, not a guessed GET). The scan is transparent to
|
|
26
|
+
* neutral calls anywhere in the chain (`.header()`/`.timeout()`/`.version()`),
|
|
27
|
+
* before or after `.uri()`, so neither a header/timeout hop nor a verb call's
|
|
28
|
+
* position drops the contract.
|
|
29
|
+
*/
|
|
30
|
+
export declare function inferHttpClientMethod(uriCall: Parser.SyntaxNode): string | null;
|
|
31
|
+
/** True when `urlCall`'s receiver chain bottoms out on a `new Request.Builder()`
|
|
32
|
+
* object-creation (descending the `.object` chain past any intervening calls). */
|
|
33
|
+
export declare function okHttpUrlRootsAtBuilder(urlCall: Parser.SyntaxNode): boolean;
|
|
34
|
+
/** True when `uriCall`'s receiver chain includes a `HttpRequest.newBuilder()`. */
|
|
35
|
+
export declare function httpClientUriRootsAtNewBuilder(uriCall: Parser.SyntaxNode): boolean;
|
|
36
|
+
/** True when a `HttpRequest.newBuilder(URI.create(...))` chain ALSO calls `.uri(...)`
|
|
37
|
+
* later — a later `.uri()` overrides the constructor URI at runtime, so the
|
|
38
|
+
* constructor-arg path must NOT be emitted (the `.uri()` query emits the override). */
|
|
39
|
+
export declare function httpClientChainHasUriCall(newBuilderCall: Parser.SyntaxNode): boolean;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { unquoteLiteral } from '../tree-sitter-scanner.js';
|
|
2
|
+
// ─── Statically-resolvable consumer path + builder verb-walk helpers ──────
|
|
3
|
+
// RestTemplate calls increasingly pass a non-literal path argument that is
|
|
4
|
+
// still statically derivable — `URI.create("/x")` or a `UriComponentsBuilder`
|
|
5
|
+
// fluent chain. These helpers resolve those shapes to a literal path; a
|
|
6
|
+
// genuinely dynamic argument (a variable, a non-`URI`/`UriComponentsBuilder`
|
|
7
|
+
// call) resolves to null and the call site is skipped. This module also owns
|
|
8
|
+
// the OkHttp / Java-HttpClient builder verb-walks (`inferOkHttpMethod` /
|
|
9
|
+
// `inferHttpClientMethod`), which recover the request verb by walking UP the
|
|
10
|
+
// fluent chain from the matched `.url(...)` / `.uri(...)` call. Extracted from
|
|
11
|
+
// java.ts (#2268) so that plugin stays under ~1000 lines; the `methodInvocation*`
|
|
12
|
+
// primitives + `firstLiteralArgument` are module-internal (shared by the path
|
|
13
|
+
// resolvers and the verb-walks), while the path resolver and the two verb-walks
|
|
14
|
+
// are java.ts's only entry points here.
|
|
15
|
+
function methodInvocationName(node) {
|
|
16
|
+
return node.type === 'method_invocation' ? (node.childForFieldName('name')?.text ?? null) : null;
|
|
17
|
+
}
|
|
18
|
+
function methodInvocationObject(node) {
|
|
19
|
+
return node.type === 'method_invocation' ? node.childForFieldName('object') : null;
|
|
20
|
+
}
|
|
21
|
+
function methodInvocationArguments(node) {
|
|
22
|
+
const argsNode = node.type === 'method_invocation' ? node.childForFieldName('arguments') : null;
|
|
23
|
+
return argsNode?.namedChildren ?? [];
|
|
24
|
+
}
|
|
25
|
+
function firstLiteralArgument(node) {
|
|
26
|
+
const first = methodInvocationArguments(node)[0];
|
|
27
|
+
return first?.type === 'string_literal' ? unquoteLiteral(first.text) : null;
|
|
28
|
+
}
|
|
29
|
+
/** Resolve a `URI.create("/path")` call to its literal path; null otherwise. */
|
|
30
|
+
function extractUriCreatePath(node) {
|
|
31
|
+
if (node.type !== 'method_invocation')
|
|
32
|
+
return null;
|
|
33
|
+
if (methodInvocationObject(node)?.text !== 'URI' || methodInvocationName(node) !== 'create')
|
|
34
|
+
return null;
|
|
35
|
+
return firstLiteralArgument(node);
|
|
36
|
+
}
|
|
37
|
+
// Join a builder base with a sub-path using exactly one separating slash. This
|
|
38
|
+
// is intentionally NOT the shared `joinPath`: `joinPath` force-prepends `/`,
|
|
39
|
+
// whereas `appendPath` must preserve an absolute/host base (`fromHttpUrl`
|
|
40
|
+
// "https://host/api") so the host survives until `normalizeConsumerPath` strips
|
|
41
|
+
// it downstream. Do not unify the two.
|
|
42
|
+
function appendPath(base, subPath) {
|
|
43
|
+
if (!base)
|
|
44
|
+
return subPath.startsWith('/') ? subPath : `/${subPath}`;
|
|
45
|
+
if (!subPath)
|
|
46
|
+
return base;
|
|
47
|
+
return `${base.replace(/\/+$/, '')}/${subPath.replace(/^\/+/, '')}`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve a `UriComponentsBuilder` fluent chain to its literal path. Seed
|
|
51
|
+
* methods (`fromPath`/`fromUriString`/`fromHttpUrl`) return the literal arg
|
|
52
|
+
* VERBATIM — a `fromHttpUrl("https://host/api")` seed keeps its host, which the
|
|
53
|
+
* shared `normalizeConsumerPath` later reduces to the path (the same single
|
|
54
|
+
* normalization point every other consumer path goes through). `path` and
|
|
55
|
+
* `pathSegment` append literal segments; `build`/`toUriString`/`toUri`/`encode`
|
|
56
|
+
* and the `query*` family pass through (query attributes do not change the
|
|
57
|
+
* path). Any non-literal segment or unknown call → null.
|
|
58
|
+
*/
|
|
59
|
+
// A UriComponentsBuilder chain deeper than this is not realistic source; cap the
|
|
60
|
+
// recursion so a pathological / machine-generated chain returns null instead of
|
|
61
|
+
// overflowing the stack (mirrors the project's other AST-depth guards).
|
|
62
|
+
const MAX_BUILDER_DEPTH = 100;
|
|
63
|
+
function extractUriComponentsBuilderPath(node, depth = 0) {
|
|
64
|
+
if (depth > MAX_BUILDER_DEPTH)
|
|
65
|
+
return null;
|
|
66
|
+
if (node.type !== 'method_invocation')
|
|
67
|
+
return null;
|
|
68
|
+
const name = methodInvocationName(node);
|
|
69
|
+
const objectNode = methodInvocationObject(node);
|
|
70
|
+
if ((name === 'fromPath' || name === 'fromUriString' || name === 'fromHttpUrl') &&
|
|
71
|
+
objectNode?.text === 'UriComponentsBuilder') {
|
|
72
|
+
// Strip any `?query` baked into the seed literal so a later `.path()` appends
|
|
73
|
+
// to a clean base; otherwise the sub-path is glued after the query
|
|
74
|
+
// (`/base?x=1/sub`) and normalizeHttpPath truncates the whole tail at `?`.
|
|
75
|
+
// A host prefix (`https://h/api`) is preserved and stripped downstream by
|
|
76
|
+
// normalizeConsumerPath.
|
|
77
|
+
const seed = firstLiteralArgument(node);
|
|
78
|
+
return seed === null ? null : seed.split('?')[0];
|
|
79
|
+
}
|
|
80
|
+
if (!objectNode)
|
|
81
|
+
return null;
|
|
82
|
+
if (name === 'path') {
|
|
83
|
+
const base = extractUriComponentsBuilderPath(objectNode, depth + 1);
|
|
84
|
+
const subPath = firstLiteralArgument(node);
|
|
85
|
+
if (base === null || subPath === null)
|
|
86
|
+
return null;
|
|
87
|
+
// Spring `UriComponentsBuilder.path(p)` appends `p` VERBATIM (no slash
|
|
88
|
+
// inserted — unlike `pathSegment`, which slash-joins), then normalizes the
|
|
89
|
+
// full path to collapse duplicate slashes. So `fromPath("/api").path("users")`
|
|
90
|
+
// → `/apiusers`, while `.path("/users")` → `/api/users`, and a trailing-slash
|
|
91
|
+
// base collapses (`/api/` + `/users` → `/api/users`). The `(?<!:)` keeps a
|
|
92
|
+
// scheme `://` in a host seed intact; the downstream normalizer is the other
|
|
93
|
+
// slash-collapser for consumer paths (see KTD2 — do not remove it).
|
|
94
|
+
return (base + subPath).replace(/(?<!:)\/{2,}/g, '/');
|
|
95
|
+
}
|
|
96
|
+
if (name === 'pathSegment') {
|
|
97
|
+
const base = extractUriComponentsBuilderPath(objectNode, depth + 1);
|
|
98
|
+
if (base === null)
|
|
99
|
+
return null;
|
|
100
|
+
const args = methodInvocationArguments(node);
|
|
101
|
+
const segments = args
|
|
102
|
+
.map((arg) => (arg.type === 'string_literal' ? unquoteLiteral(arg.text) : null))
|
|
103
|
+
.filter((segment) => segment !== null);
|
|
104
|
+
if (segments.length !== args.length)
|
|
105
|
+
return null; // a non-literal segment defeats static resolution
|
|
106
|
+
return segments.reduce((acc, segment) => appendPath(acc, segment), base);
|
|
107
|
+
}
|
|
108
|
+
if (name === 'build' ||
|
|
109
|
+
name === 'toUriString' ||
|
|
110
|
+
name === 'toUri' ||
|
|
111
|
+
name === 'encode' ||
|
|
112
|
+
name === 'query' ||
|
|
113
|
+
name === 'queryParam' ||
|
|
114
|
+
name === 'queryParams' ||
|
|
115
|
+
name === 'replaceQuery' ||
|
|
116
|
+
name === 'replaceQueryParam' ||
|
|
117
|
+
name === 'replaceQueryParams')
|
|
118
|
+
return extractUriComponentsBuilderPath(objectNode, depth + 1);
|
|
119
|
+
return null;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Resolve a statically-derivable path argument to a literal path: a bare
|
|
123
|
+
* string literal, a `URI.create("/x")` call, or a `UriComponentsBuilder`
|
|
124
|
+
* fluent chain. Genuinely dynamic arguments → null.
|
|
125
|
+
*/
|
|
126
|
+
export function extractStaticPathExpression(node) {
|
|
127
|
+
if (node.type === 'string_literal')
|
|
128
|
+
return unquoteLiteral(node.text);
|
|
129
|
+
return extractUriCreatePath(node) ?? extractUriComponentsBuilderPath(node);
|
|
130
|
+
}
|
|
131
|
+
// ─── Builder verb-walks ───────────────────────────────────────────────
|
|
132
|
+
// OkHttp / Java-HttpClient encode the request verb on a SIBLING call elsewhere in
|
|
133
|
+
// the fluent chain, not on the matched path call. Both queries capture the path
|
|
134
|
+
// call (`.url(...)` / `.uri(...)`); these helpers recover the verb by scanning
|
|
135
|
+
// the WHOLE chain — transparent to neutral calls (`.addHeader()`/`.header()`/
|
|
136
|
+
// `.timeout()`/`.version()`) wherever they sit relative to the path call, so the
|
|
137
|
+
// verb resolves whether it precedes or follows the path call.
|
|
138
|
+
/** Every `method_invocation` in the fluent chain `pathCall` belongs to, ordered
|
|
139
|
+
* innermost (next to the construction) → outermost (terminal). Lets the verb-walk
|
|
140
|
+
* find the verb wherever it sits, and lets the root gates inspect the chain base. */
|
|
141
|
+
function builderChainCalls(pathCall) {
|
|
142
|
+
let innermost = pathCall;
|
|
143
|
+
let obj = methodInvocationObject(innermost);
|
|
144
|
+
while (obj?.type === 'method_invocation') {
|
|
145
|
+
innermost = obj;
|
|
146
|
+
obj = methodInvocationObject(innermost);
|
|
147
|
+
}
|
|
148
|
+
const calls = [innermost];
|
|
149
|
+
let cur = innermost;
|
|
150
|
+
let parent = cur.parent;
|
|
151
|
+
while (parent?.type === 'method_invocation' && methodInvocationObject(parent)?.id === cur.id) {
|
|
152
|
+
calls.push(parent);
|
|
153
|
+
cur = parent;
|
|
154
|
+
parent = parent.parent;
|
|
155
|
+
}
|
|
156
|
+
return calls;
|
|
157
|
+
}
|
|
158
|
+
/** Scan the chain for the verb a `name`-matching helper or a `.method("LITERAL")`
|
|
159
|
+
* call sets, resolving the LAST such call — each verb-setter overwrites the
|
|
160
|
+
* previous at runtime, so on the (non-idiomatic) chain that sets two verbs the
|
|
161
|
+
* one nearest the terminal wins. `null` means "verb is set but not statically
|
|
162
|
+
* resolvable" (a non-literal/empty `.method(verb)`) → the caller skips rather
|
|
163
|
+
* than guessing. `defaultVerb` is returned only when the chain has no verb call
|
|
164
|
+
* at all (a bare build). `verbHelpers` are matched on the method NAME (OkHttp
|
|
165
|
+
* helpers are lowercase, HttpClient helpers uppercase). */
|
|
166
|
+
function inferBuilderVerb(pathCall, verbHelpers, defaultVerb) {
|
|
167
|
+
let lastVerbCall = null;
|
|
168
|
+
for (const call of builderChainCalls(pathCall)) {
|
|
169
|
+
if (call.id === pathCall.id)
|
|
170
|
+
continue; // the path call itself is never the verb
|
|
171
|
+
const name = methodInvocationName(call);
|
|
172
|
+
if (name === 'method' || (name !== null && verbHelpers.includes(name)))
|
|
173
|
+
lastVerbCall = call;
|
|
174
|
+
}
|
|
175
|
+
if (lastVerbCall === null)
|
|
176
|
+
return defaultVerb; // no verb call → builder default
|
|
177
|
+
const name = methodInvocationName(lastVerbCall);
|
|
178
|
+
// Explicit `.method(...)`: a non-empty string-literal verb resolves; a
|
|
179
|
+
// non-literal (variable-bound) OR empty-string verb is unresolvable → null
|
|
180
|
+
// (skip), NOT a guessed default or a malformed empty-method contract.
|
|
181
|
+
if (name === 'method') {
|
|
182
|
+
const verb = firstLiteralArgument(lastVerbCall);
|
|
183
|
+
return verb ? verb.toUpperCase() : null;
|
|
184
|
+
}
|
|
185
|
+
return name === null ? defaultVerb : name.toUpperCase(); // a verb-helper name
|
|
186
|
+
}
|
|
187
|
+
const OK_HTTP_VERB_HELPERS = ['get', 'head', 'post', 'put', 'delete', 'patch'];
|
|
188
|
+
const HTTP_CLIENT_VERB_HELPERS = ['GET', 'POST', 'PUT', 'DELETE', 'HEAD'];
|
|
189
|
+
/**
|
|
190
|
+
* Infer an OkHttp request verb by scanning the builder chain around the matched
|
|
191
|
+
* `.url(...)` call: a `.get()/.head()/.post()/.put()/.delete()/.patch()` helper
|
|
192
|
+
* (before or after `.url()`), or a `.method("VERB", …)` literal, wins. Returns
|
|
193
|
+
* `'GET'` only when the chain has NO verb call at all (a bare `.url(...).build()`
|
|
194
|
+
* — OkHttp's real default). Returns `null` for an explicit `.method(verb, …)`
|
|
195
|
+
* whose verb is a non-literal: the verb is set but not statically resolvable, so
|
|
196
|
+
* the caller skips the call rather than asserting a wrong GET (parity with the
|
|
197
|
+
* WebClient long-form variable-bound-verb behavior, which also skips).
|
|
198
|
+
*/
|
|
199
|
+
export function inferOkHttpMethod(urlCall) {
|
|
200
|
+
return inferBuilderVerb(urlCall, OK_HTTP_VERB_HELPERS, 'GET');
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Infer a Java-`HttpClient` request verb by walking UP the builder chain from the
|
|
204
|
+
* matched `.uri(URI.create("..."))` call: the first `.GET()/.POST()/.PUT()/`
|
|
205
|
+
* `.DELETE()/.HEAD()` verb-helper, or a `.method("VERB", body)` literal, wins.
|
|
206
|
+
* Returns `'GET'` only when the chain has no verb call (a bare `.build()` — the
|
|
207
|
+
* builder's real default). Returns `null` for an explicit `.method(verb, …)` with
|
|
208
|
+
* a non-literal verb (skip, not a guessed GET). The scan is transparent to
|
|
209
|
+
* neutral calls anywhere in the chain (`.header()`/`.timeout()`/`.version()`),
|
|
210
|
+
* before or after `.uri()`, so neither a header/timeout hop nor a verb call's
|
|
211
|
+
* position drops the contract.
|
|
212
|
+
*/
|
|
213
|
+
export function inferHttpClientMethod(uriCall) {
|
|
214
|
+
return inferBuilderVerb(uriCall, HTTP_CLIENT_VERB_HELPERS, 'GET');
|
|
215
|
+
}
|
|
216
|
+
// ─── Builder-chain root gates (anti-overreach) ────────────────────────
|
|
217
|
+
// The path queries match a bare `.url(...)` / `.uri(URI.create(...))` call on ANY
|
|
218
|
+
// receiver so that a builder call BEFORE the path call (`new Request.Builder()`
|
|
219
|
+
// `.addHeader(...).url(...)`, `HttpRequest.newBuilder().version(v).uri(...)`) is
|
|
220
|
+
// still captured. These gates re-impose the framework anchor in JS — only a chain
|
|
221
|
+
// that bottoms out on the right construction emits, so a `.url(...)`/`.uri(...)`
|
|
222
|
+
// on an unrelated object does not.
|
|
223
|
+
/** True when `urlCall`'s receiver chain bottoms out on a `new Request.Builder()`
|
|
224
|
+
* object-creation (descending the `.object` chain past any intervening calls). */
|
|
225
|
+
export function okHttpUrlRootsAtBuilder(urlCall) {
|
|
226
|
+
let obj = methodInvocationObject(urlCall);
|
|
227
|
+
while (obj?.type === 'method_invocation')
|
|
228
|
+
obj = methodInvocationObject(obj);
|
|
229
|
+
return (obj?.type === 'object_creation_expression' &&
|
|
230
|
+
obj.childForFieldName('type')?.text === 'Request.Builder');
|
|
231
|
+
}
|
|
232
|
+
/** True when `uriCall`'s receiver chain includes a `HttpRequest.newBuilder()`. */
|
|
233
|
+
export function httpClientUriRootsAtNewBuilder(uriCall) {
|
|
234
|
+
let obj = methodInvocationObject(uriCall);
|
|
235
|
+
while (obj?.type === 'method_invocation') {
|
|
236
|
+
if (methodInvocationName(obj) === 'newBuilder' &&
|
|
237
|
+
methodInvocationObject(obj)?.text === 'HttpRequest')
|
|
238
|
+
return true;
|
|
239
|
+
obj = methodInvocationObject(obj);
|
|
240
|
+
}
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
/** True when a `HttpRequest.newBuilder(URI.create(...))` chain ALSO calls `.uri(...)`
|
|
244
|
+
* later — a later `.uri()` overrides the constructor URI at runtime, so the
|
|
245
|
+
* constructor-arg path must NOT be emitted (the `.uri()` query emits the override). */
|
|
246
|
+
export function httpClientChainHasUriCall(newBuilderCall) {
|
|
247
|
+
return builderChainCalls(newBuilderCall).some((call) => call.id !== newBuilderCall.id && methodInvocationName(call) === 'uri');
|
|
248
|
+
}
|
|
@@ -2,6 +2,7 @@ import Java from 'tree-sitter-java';
|
|
|
2
2
|
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
3
|
import { METHOD_ANNOTATION_TO_HTTP, isRouteMemberKey, findEnclosingClass, } from '../../../ingestion/route-extractors/spring-shared.js';
|
|
4
4
|
import { REST_TEMPLATE_TO_HTTP, WEB_CLIENT_SHORT_TO_HTTP, WEB_CLIENT_LONG_VERB_RE, EXCHANGE_ANNOTATION_TO_HTTP, parseRequestLine, pushPrefix, joinPath, scanSpringInheritanceProject, OPENFEIGN_FRAMEWORK, HTTP_INTERFACE_FRAMEWORK, FEIGN_CONFIDENCE, REQUEST_LINE_CONFIDENCE, EXCHANGE_CONFIDENCE, } from './spring-consumer-shared.js';
|
|
5
|
+
import { extractStaticPathExpression, inferOkHttpMethod, inferHttpClientMethod, okHttpUrlRootsAtBuilder, httpClientUriRootsAtNewBuilder, httpClientChainHasUriCall, } from './java-static-path.js';
|
|
5
6
|
/**
|
|
6
7
|
* Java HTTP plugin. Handles:
|
|
7
8
|
* - Spring `@RequestMapping` class prefixes + `@(Get|Post|...)Mapping` method annotations
|
|
@@ -64,17 +65,15 @@ import { REST_TEMPLATE_TO_HTTP, WEB_CLIENT_SHORT_TO_HTTP, WEB_CLIENT_LONG_VERB_R
|
|
|
64
65
|
// sidesteps that hazard entirely; all name/key discrimination lives in the
|
|
65
66
|
// for-loop, where it reads as straight-line code.
|
|
66
67
|
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// GetMapping("/x")`)
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
// `scoped_identifier` too; that is deferred to avoid re-keying existing Java
|
|
77
|
-
// contracts via the predicate hazard above. Pinned by an anti-overreach test.
|
|
68
|
+
// FULLY-QUALIFIED route annotations ARE matched. `@ann` binds either an
|
|
69
|
+
// `identifier` (simple name) or a `scoped_identifier` (a FQN annotation such as
|
|
70
|
+
// `@org.springframework…GetMapping("/x")`); the for-loop normalizes the name to
|
|
71
|
+
// its trailing segment via `simpleName` before discriminating. This is a node-
|
|
72
|
+
// type widening only — the query stays predicate-free, so it does NOT reintroduce
|
|
73
|
+
// the bucket hazard above, and a simple name maps to itself so existing Java
|
|
74
|
+
// contracts are unchanged (only previously-unmatched FQN annotations gain
|
|
75
|
+
// routes). This brings Java to parity with the Kotlin plugin, whose grammar
|
|
76
|
+
// already matches FQN route annotations.
|
|
78
77
|
const JAVA_ROUTE_ANNOTATION_PATTERNS = compilePatterns({
|
|
79
78
|
name: 'java-route-annotation',
|
|
80
79
|
language: Java,
|
|
@@ -86,17 +85,17 @@ const JAVA_ROUTE_ANNOTATION_PATTERNS = compilePatterns({
|
|
|
86
85
|
(class_declaration
|
|
87
86
|
(modifiers
|
|
88
87
|
(annotation
|
|
89
|
-
name: (identifier) @ann
|
|
88
|
+
name: [(identifier) (scoped_identifier)] @ann
|
|
90
89
|
arguments: (annotation_argument_list [(string_literal) @value (element_value_array_initializer (string_literal) @value)])))) @node
|
|
91
90
|
(interface_declaration
|
|
92
91
|
(modifiers
|
|
93
92
|
(annotation
|
|
94
|
-
name: (identifier) @ann
|
|
93
|
+
name: [(identifier) (scoped_identifier)] @ann
|
|
95
94
|
arguments: (annotation_argument_list [(string_literal) @value (element_value_array_initializer (string_literal) @value)])))) @node
|
|
96
95
|
(class_declaration
|
|
97
96
|
(modifiers
|
|
98
97
|
(annotation
|
|
99
|
-
name: (identifier) @ann
|
|
98
|
+
name: [(identifier) (scoped_identifier)] @ann
|
|
100
99
|
arguments: (annotation_argument_list
|
|
101
100
|
(element_value_pair
|
|
102
101
|
key: (identifier) @key
|
|
@@ -104,7 +103,7 @@ const JAVA_ROUTE_ANNOTATION_PATTERNS = compilePatterns({
|
|
|
104
103
|
(interface_declaration
|
|
105
104
|
(modifiers
|
|
106
105
|
(annotation
|
|
107
|
-
name: (identifier) @ann
|
|
106
|
+
name: [(identifier) (scoped_identifier)] @ann
|
|
108
107
|
arguments: (annotation_argument_list
|
|
109
108
|
(element_value_pair
|
|
110
109
|
key: (identifier) @key
|
|
@@ -112,13 +111,13 @@ const JAVA_ROUTE_ANNOTATION_PATTERNS = compilePatterns({
|
|
|
112
111
|
(method_declaration
|
|
113
112
|
(modifiers
|
|
114
113
|
(annotation
|
|
115
|
-
name: (identifier) @ann
|
|
114
|
+
name: [(identifier) (scoped_identifier)] @ann
|
|
116
115
|
arguments: (annotation_argument_list [(string_literal) @value (element_value_array_initializer (string_literal) @value)])))
|
|
117
116
|
name: (identifier) @member) @node
|
|
118
117
|
(method_declaration
|
|
119
118
|
(modifiers
|
|
120
119
|
(annotation
|
|
121
|
-
name: (identifier) @ann
|
|
120
|
+
name: [(identifier) (scoped_identifier)] @ann
|
|
122
121
|
arguments: (annotation_argument_list
|
|
123
122
|
(element_value_pair
|
|
124
123
|
key: (identifier) @key
|
|
@@ -154,7 +153,7 @@ const REST_TEMPLATE_PATTERNS = compilePatterns({
|
|
|
154
153
|
(method_invocation
|
|
155
154
|
object: (identifier) @obj (#eq? @obj "restTemplate")
|
|
156
155
|
name: (identifier) @method
|
|
157
|
-
arguments: (argument_list . (
|
|
156
|
+
arguments: (argument_list . (_) @path))
|
|
158
157
|
`,
|
|
159
158
|
},
|
|
160
159
|
],
|
|
@@ -170,7 +169,7 @@ const REST_TEMPLATE_EXCHANGE_PATTERNS = compilePatterns({
|
|
|
170
169
|
object: (identifier) @obj (#eq? @obj "restTemplate")
|
|
171
170
|
name: (identifier) @method (#eq? @method "exchange")
|
|
172
171
|
arguments: (argument_list
|
|
173
|
-
. (
|
|
172
|
+
. (_) @path
|
|
174
173
|
(field_access
|
|
175
174
|
object: (identifier) @httpMethodCls (#eq? @httpMethodCls "HttpMethod")
|
|
176
175
|
field: (identifier) @http_method)))
|
|
@@ -226,10 +225,14 @@ const WEB_CLIENT_LONG_FORM_PATTERNS = compilePatterns({
|
|
|
226
225
|
},
|
|
227
226
|
],
|
|
228
227
|
});
|
|
229
|
-
// ─── Consumer: OkHttp `
|
|
230
|
-
//
|
|
231
|
-
//
|
|
232
|
-
//
|
|
228
|
+
// ─── Consumer: OkHttp `Request.Builder()…url("path")` ─────────────────
|
|
229
|
+
// Match a bare `.url("literal")` call on ANY receiver, capturing it as `@call`.
|
|
230
|
+
// `okHttpUrlRootsAtBuilder` (JS) then verifies the receiver chain bottoms out on
|
|
231
|
+
// `new Request.Builder()` — re-imposing the framework anchor while allowing
|
|
232
|
+
// builder calls BEFORE `.url()` (`new Request.Builder().addHeader(...).url("/x")`)
|
|
233
|
+
// that the old object-direct query dropped, and rejecting a `.url(...)` on an
|
|
234
|
+
// unrelated object. The verb is recovered by `inferOkHttpMethod` scanning the
|
|
235
|
+
// whole chain (java-static-path.ts).
|
|
233
236
|
const OK_HTTP_PATTERNS = compilePatterns({
|
|
234
237
|
name: 'java-okhttp',
|
|
235
238
|
language: Java,
|
|
@@ -238,14 +241,21 @@ const OK_HTTP_PATTERNS = compilePatterns({
|
|
|
238
241
|
meta: {},
|
|
239
242
|
query: `
|
|
240
243
|
(method_invocation
|
|
241
|
-
object: (object_creation_expression
|
|
242
|
-
type: (scoped_type_identifier) @type (#eq? @type "Request.Builder"))
|
|
243
244
|
name: (identifier) @method (#eq? @method "url")
|
|
244
|
-
arguments: (argument_list . (string_literal) @path))
|
|
245
|
+
arguments: (argument_list . (string_literal) @path)) @call
|
|
245
246
|
`,
|
|
246
247
|
},
|
|
247
248
|
],
|
|
248
249
|
});
|
|
250
|
+
// Match a bare `.uri(URI.create("literal"))` call on ANY receiver, capturing it
|
|
251
|
+
// as `@call`. `httpClientUriRootsAtNewBuilder` (JS) verifies the chain includes
|
|
252
|
+
// `HttpRequest.newBuilder()` — allowing calls BEFORE `.uri()` (`.version(v)`
|
|
253
|
+
// `.uri(...)`) that the old object-direct query dropped, and rejecting a `.uri(...)`
|
|
254
|
+
// on an unrelated object (e.g. WebClient). The verb (a `.GET()/.POST()/.PUT()/`
|
|
255
|
+
// `.DELETE()/.HEAD()` helper, a `.method("VERB", body)` literal, or the bare-build
|
|
256
|
+
// default) is recovered by `inferHttpClientMethod` scanning the whole chain.
|
|
257
|
+
// Matching `.uri(...)` regardless of a trailing `.build()` mirrors the accepted
|
|
258
|
+
// OkHttp over-match posture.
|
|
249
259
|
const JAVA_HTTP_CLIENT_PATTERNS = compilePatterns({
|
|
250
260
|
name: 'java-http-client',
|
|
251
261
|
language: Java,
|
|
@@ -254,18 +264,35 @@ const JAVA_HTTP_CLIENT_PATTERNS = compilePatterns({
|
|
|
254
264
|
meta: {},
|
|
255
265
|
query: `
|
|
256
266
|
(method_invocation
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
267
|
+
name: (identifier) @uri_method (#eq? @uri_method "uri")
|
|
268
|
+
arguments: (argument_list
|
|
269
|
+
(method_invocation
|
|
270
|
+
object: (identifier) @uriCls (#eq? @uriCls "URI")
|
|
271
|
+
name: (identifier) @create (#eq? @create "create")
|
|
272
|
+
arguments: (argument_list . (string_literal) @path)))) @call
|
|
273
|
+
`,
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
});
|
|
277
|
+
// Constructor-arg form: `HttpRequest.newBuilder(URI.create("..."))` — the path is
|
|
278
|
+
// the `newBuilder(...)` argument, with no `.uri()` call. Captures the `newBuilder`
|
|
279
|
+
// call as `@call`; the emission skips it when a later `.uri(...)` overrides the
|
|
280
|
+
// constructor URI (`httpClientChainHasUriCall`), so the `.uri()` query owns that case.
|
|
281
|
+
const JAVA_HTTP_CLIENT_CTOR_PATTERNS = compilePatterns({
|
|
282
|
+
name: 'java-http-client-ctor',
|
|
283
|
+
language: Java,
|
|
284
|
+
patterns: [
|
|
285
|
+
{
|
|
286
|
+
meta: {},
|
|
287
|
+
query: `
|
|
288
|
+
(method_invocation
|
|
289
|
+
object: (identifier) @builderCls (#eq? @builderCls "HttpRequest")
|
|
290
|
+
name: (identifier) @newBuilder (#eq? @newBuilder "newBuilder")
|
|
291
|
+
arguments: (argument_list
|
|
292
|
+
(method_invocation
|
|
293
|
+
object: (identifier) @uriCls (#eq? @uriCls "URI")
|
|
294
|
+
name: (identifier) @create (#eq? @create "create")
|
|
295
|
+
arguments: (argument_list . (string_literal) @path)))) @call
|
|
269
296
|
`,
|
|
270
297
|
},
|
|
271
298
|
],
|
|
@@ -307,6 +334,17 @@ function findEnclosingInterface(node) {
|
|
|
307
334
|
function getNodeName(node) {
|
|
308
335
|
return node.childForFieldName('name')?.text ?? null;
|
|
309
336
|
}
|
|
337
|
+
/**
|
|
338
|
+
* Trailing segment of a possibly fully-qualified annotation name
|
|
339
|
+
* (`org.springframework.web.bind.annotation.GetMapping` → `GetMapping`). The
|
|
340
|
+
* route query binds `@ann` to either an `identifier` (simple) or a
|
|
341
|
+
* `scoped_identifier` (FQN); normalizing here lets the one for-loop discriminate
|
|
342
|
+
* on the simple name in both cases. A simple name maps to itself, so this never
|
|
343
|
+
* changes how a non-FQN annotation is classified.
|
|
344
|
+
*/
|
|
345
|
+
function simpleName(text) {
|
|
346
|
+
return text.split('.').pop() ?? text;
|
|
347
|
+
}
|
|
310
348
|
function hasAnnotation(node, names) {
|
|
311
349
|
const modifiers = node.namedChildren.find((child) => child.type === 'modifiers');
|
|
312
350
|
if (!modifiers)
|
|
@@ -316,9 +354,8 @@ function hasAnnotation(node, names) {
|
|
|
316
354
|
while (stack.length > 0) {
|
|
317
355
|
const cur = stack.pop();
|
|
318
356
|
const annotationName = cur.childForFieldName('name')?.text ?? '';
|
|
319
|
-
const simpleName = annotationName.split('.').pop() ?? annotationName;
|
|
320
357
|
if ((cur.type === 'annotation' || cur.type === 'marker_annotation') &&
|
|
321
|
-
(allowed.has(annotationName) || allowed.has(simpleName))) {
|
|
358
|
+
(allowed.has(annotationName) || allowed.has(simpleName(annotationName)))) {
|
|
322
359
|
return true;
|
|
323
360
|
}
|
|
324
361
|
stack.push(...cur.namedChildren);
|
|
@@ -359,7 +396,13 @@ function scanRouteAnnotations(tree) {
|
|
|
359
396
|
const valueNode = captures.value;
|
|
360
397
|
if (!annNode || !node || !valueNode)
|
|
361
398
|
continue;
|
|
362
|
-
|
|
399
|
+
// Discrimination is on the trailing segment only (`simpleName`), so a
|
|
400
|
+
// non-Spring annotation whose last segment collides with a route annotation
|
|
401
|
+
// (e.g. `@com.evil.GetMapping("/x")`) is treated as a route. This is the
|
|
402
|
+
// same accepted trailing-segment trade-off `hasAnnotation` already makes and
|
|
403
|
+
// the intended parity with the Kotlin plugin — package-origin gating would
|
|
404
|
+
// break that parity and is deliberately not done.
|
|
405
|
+
const ann = simpleName(annNode.text);
|
|
363
406
|
const keyNode = captures.key; // undefined for the positional shape
|
|
364
407
|
if (node.type === 'method_declaration') {
|
|
365
408
|
// Method-level: a Spring `@(Get|...)Mapping` route, or native `@RequestLine`.
|
|
@@ -630,7 +673,7 @@ export const JAVA_HTTP_PLUGIN = {
|
|
|
630
673
|
const httpMethod = REST_TEMPLATE_TO_HTTP[methodNode.text];
|
|
631
674
|
if (!httpMethod)
|
|
632
675
|
continue;
|
|
633
|
-
const path =
|
|
676
|
+
const path = extractStaticPathExpression(pathNode);
|
|
634
677
|
if (path === null)
|
|
635
678
|
continue;
|
|
636
679
|
out.push({
|
|
@@ -647,7 +690,7 @@ export const JAVA_HTTP_PLUGIN = {
|
|
|
647
690
|
const pathNode = match.captures.path;
|
|
648
691
|
if (!httpMethodNode || !pathNode)
|
|
649
692
|
continue;
|
|
650
|
-
const path =
|
|
693
|
+
const path = extractStaticPathExpression(pathNode);
|
|
651
694
|
if (path === null)
|
|
652
695
|
continue;
|
|
653
696
|
out.push({
|
|
@@ -709,37 +752,84 @@ export const JAVA_HTTP_PLUGIN = {
|
|
|
709
752
|
});
|
|
710
753
|
}
|
|
711
754
|
// ─── Consumers: OkHttp Request.Builder().url("path") ────────────
|
|
755
|
+
// Match any `.url("literal")`, gate to chains rooting at `new Request.Builder()`
|
|
756
|
+
// (so a call before `.url()` is captured but an unrelated `.url()` is not), then
|
|
757
|
+
// recover the verb (`.post()`/`.method("X")`) by scanning the builder chain.
|
|
712
758
|
for (const match of runCompiledPatterns(OK_HTTP_PATTERNS, tree)) {
|
|
759
|
+
const callNode = match.captures.call;
|
|
713
760
|
const pathNode = match.captures.path;
|
|
714
|
-
if (!pathNode)
|
|
761
|
+
if (!callNode || !pathNode)
|
|
762
|
+
continue;
|
|
763
|
+
if (!okHttpUrlRootsAtBuilder(callNode))
|
|
715
764
|
continue;
|
|
716
765
|
const path = unquoteLiteral(pathNode.text);
|
|
717
766
|
if (path === null)
|
|
718
767
|
continue;
|
|
768
|
+
const method = inferOkHttpMethod(callNode);
|
|
769
|
+
// An explicit `.method(verb, …)` with a non-literal or empty verb is
|
|
770
|
+
// unresolvable (null) — emit nothing rather than a wrong GET or an empty
|
|
771
|
+
// `http::::/path` contract.
|
|
772
|
+
if (!method)
|
|
773
|
+
continue;
|
|
719
774
|
out.push({
|
|
720
775
|
role: 'consumer',
|
|
721
776
|
framework: 'okhttp',
|
|
722
|
-
method
|
|
777
|
+
method,
|
|
723
778
|
path,
|
|
724
779
|
name: null,
|
|
725
780
|
confidence: 0.7,
|
|
726
781
|
});
|
|
727
782
|
}
|
|
728
783
|
// ─── Consumers: Java HttpClient request builder ─────────────────
|
|
729
|
-
//
|
|
730
|
-
// `.
|
|
784
|
+
// Match any `.uri(URI.create("..."))`, gate to chains including `HttpRequest`
|
|
785
|
+
// `.newBuilder()` (so a call before `.uri()` is captured but an unrelated
|
|
786
|
+
// `.uri()` is not); `inferHttpClientMethod` scans the chain for the verb — a
|
|
787
|
+
// `.GET()/.POST()/.PUT()/.DELETE()/.HEAD()` helper, a `.method("VERB", body)`
|
|
788
|
+
// literal, or the bare-build default GET. A variable-bound `.method(verb, …)`
|
|
789
|
+
// is unresolvable → emit nothing. Mirrors the OkHttp loop above. The
|
|
790
|
+
// constructor-arg form (`newBuilder(URI.create(...))`, no `.uri()`) follows.
|
|
731
791
|
for (const match of runCompiledPatterns(JAVA_HTTP_CLIENT_PATTERNS, tree)) {
|
|
732
|
-
const
|
|
792
|
+
const callNode = match.captures.call;
|
|
733
793
|
const pathNode = match.captures.path;
|
|
734
|
-
if (!
|
|
794
|
+
if (!callNode || !pathNode)
|
|
795
|
+
continue;
|
|
796
|
+
if (!httpClientUriRootsAtNewBuilder(callNode))
|
|
735
797
|
continue;
|
|
736
798
|
const path = unquoteLiteral(pathNode.text);
|
|
737
799
|
if (path === null)
|
|
738
800
|
continue;
|
|
801
|
+
const method = inferHttpClientMethod(callNode);
|
|
802
|
+
if (!method)
|
|
803
|
+
continue;
|
|
739
804
|
out.push({
|
|
740
805
|
role: 'consumer',
|
|
741
806
|
framework: 'java-http-client',
|
|
742
|
-
method
|
|
807
|
+
method,
|
|
808
|
+
path,
|
|
809
|
+
name: null,
|
|
810
|
+
confidence: 0.65,
|
|
811
|
+
});
|
|
812
|
+
}
|
|
813
|
+
// Constructor-arg form: `HttpRequest.newBuilder(URI.create("..."))` with the
|
|
814
|
+
// path in the constructor and no overriding `.uri(...)` later in the chain
|
|
815
|
+
// (a later `.uri()` wins at runtime, so the loop above owns that case).
|
|
816
|
+
for (const match of runCompiledPatterns(JAVA_HTTP_CLIENT_CTOR_PATTERNS, tree)) {
|
|
817
|
+
const callNode = match.captures.call;
|
|
818
|
+
const pathNode = match.captures.path;
|
|
819
|
+
if (!callNode || !pathNode)
|
|
820
|
+
continue;
|
|
821
|
+
if (httpClientChainHasUriCall(callNode))
|
|
822
|
+
continue;
|
|
823
|
+
const path = unquoteLiteral(pathNode.text);
|
|
824
|
+
if (path === null)
|
|
825
|
+
continue;
|
|
826
|
+
const method = inferHttpClientMethod(callNode);
|
|
827
|
+
if (!method)
|
|
828
|
+
continue;
|
|
829
|
+
out.push({
|
|
830
|
+
role: 'consumer',
|
|
831
|
+
framework: 'java-http-client',
|
|
832
|
+
method,
|
|
743
833
|
path,
|
|
744
834
|
name: null,
|
|
745
835
|
confidence: 0.65,
|