gitnexus 1.6.6-rc.75 → 1.6.6-rc.76
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,18 +1,26 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
3
|
/**
|
|
4
|
-
* Kotlin HTTP plugin (Spring providers).
|
|
4
|
+
* Kotlin HTTP plugin (Spring providers + consumers).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* **Providers** (#1849) — Spring `@RequestMapping` class prefixes and
|
|
7
|
+
* `@(Get|Post|...)Mapping` method annotations on Kotlin Spring Boot
|
|
8
|
+
* controllers. Both positional shorthand (`@GetMapping("/x")`) and
|
|
9
|
+
* named annotation arguments (`@GetMapping(value = "/x")` and
|
|
10
10
|
* `@GetMapping(path = "/x")`) are supported.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* **Consumers** (this PR) — three call-site patterns common in Kotlin
|
|
13
|
+
* Spring projects:
|
|
14
|
+
*
|
|
15
|
+
* 1. `restTemplate.getForObject("/x", ...)` and friends
|
|
16
|
+
* 2. `webClient.get().uri("/x")` (short form, 1 verb hop + 1 uri hop)
|
|
17
|
+
* 3. `Request.Builder().url("/x")` (OkHttp)
|
|
18
|
+
*
|
|
19
|
+
* The long-form `webClient.method(HttpMethod.X).uri("/y")` chain is
|
|
20
|
+
* intentionally deferred to a follow-up: it requires walk-up logic
|
|
21
|
+
* to recover the verb from a sibling `call_expression`, and we can
|
|
22
|
+
* land 80% of real-world Kotlin Spring consumer coverage with the
|
|
23
|
+
* three simpler patterns above.
|
|
16
24
|
*
|
|
17
25
|
* tree-sitter-kotlin (fwcd) AST shapes used here:
|
|
18
26
|
* class_declaration
|
|
@@ -26,6 +34,20 @@ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-s
|
|
|
26
34
|
* string_literal
|
|
27
35
|
* type_identifier ← class name
|
|
28
36
|
*
|
|
37
|
+
* Consumer call shape (Kotlin chains everything via `navigation_expression`):
|
|
38
|
+
* call_expression ← outer `.uri("/x")` or `.url("/x")`
|
|
39
|
+
* navigation_expression
|
|
40
|
+
* call_expression ← inner `.get()` / `Request.Builder()` / `restTemplate.x`
|
|
41
|
+
* navigation_expression
|
|
42
|
+
* simple_identifier ← receiver: `webClient` / `Request` / `restTemplate`
|
|
43
|
+
* navigation_suffix ← `.method` / `.Builder` / `.getForObject`
|
|
44
|
+
* call_suffix (value_arguments)
|
|
45
|
+
* navigation_suffix ← `.uri` / `.url`
|
|
46
|
+
* call_suffix
|
|
47
|
+
* value_arguments
|
|
48
|
+
* value_argument
|
|
49
|
+
* string_literal ← the path
|
|
50
|
+
*
|
|
29
51
|
* tree-sitter-kotlin is an optional npm dependency — when its native
|
|
30
52
|
* binding is unavailable the plugin gracefully exports `null` and
|
|
31
53
|
* `http-patterns/index.ts` skips registration for `.kt`/`.kts` files.
|
|
@@ -46,6 +68,34 @@ const METHOD_ANNOTATION_TO_HTTP = {
|
|
|
46
68
|
DeleteMapping: 'DELETE',
|
|
47
69
|
PatchMapping: 'PATCH',
|
|
48
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* RestTemplate method-name → HTTP verb. Mirrors the Java plugin's
|
|
73
|
+
* `REST_TEMPLATE_TO_HTTP` (java.ts) so a polyglot repo emits the
|
|
74
|
+
* same contract IDs from .java and .kt sources.
|
|
75
|
+
*/
|
|
76
|
+
const REST_TEMPLATE_TO_HTTP = {
|
|
77
|
+
getForObject: 'GET',
|
|
78
|
+
getForEntity: 'GET',
|
|
79
|
+
postForObject: 'POST',
|
|
80
|
+
postForEntity: 'POST',
|
|
81
|
+
put: 'PUT',
|
|
82
|
+
delete: 'DELETE',
|
|
83
|
+
patchForObject: 'PATCH',
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* WebClient short-form verb → HTTP verb. The reactive WebClient API
|
|
87
|
+
* exposes `.get()`, `.post()`, `.put()`, `.delete()`, `.patch()` as
|
|
88
|
+
* one-liners that return a `RequestHeadersUriSpec` whose `.uri(...)`
|
|
89
|
+
* carries the path. We capture both pieces in a single query (see
|
|
90
|
+
* `WEB_CLIENT_SHORT_PATTERNS` below) and translate the verb here.
|
|
91
|
+
*/
|
|
92
|
+
const WEB_CLIENT_SHORT_TO_HTTP = {
|
|
93
|
+
get: 'GET',
|
|
94
|
+
post: 'POST',
|
|
95
|
+
put: 'PUT',
|
|
96
|
+
delete: 'DELETE',
|
|
97
|
+
patch: 'PATCH',
|
|
98
|
+
};
|
|
49
99
|
/**
|
|
50
100
|
* Build the plugin only if the Kotlin grammar is available. Compiling
|
|
51
101
|
* the queries against a null grammar would throw at module load time
|
|
@@ -144,6 +194,127 @@ function buildKotlinPlugin(language) {
|
|
|
144
194
|
},
|
|
145
195
|
],
|
|
146
196
|
});
|
|
197
|
+
// ─── Consumer: Spring RestTemplate ────────────────────────────────────
|
|
198
|
+
// Kotlin call-site shape mirrors the Java plugin's
|
|
199
|
+
// `REST_TEMPLATE_PATTERNS`, but goes through tree-sitter-kotlin's
|
|
200
|
+
// `navigation_expression` instead of Java's `method_invocation`:
|
|
201
|
+
//
|
|
202
|
+
// restTemplate.getForObject("/x", User::class.java)
|
|
203
|
+
//
|
|
204
|
+
// becomes
|
|
205
|
+
//
|
|
206
|
+
// call_expression
|
|
207
|
+
// navigation_expression
|
|
208
|
+
// simple_identifier "restTemplate"
|
|
209
|
+
// navigation_suffix → simple_identifier "getForObject"
|
|
210
|
+
// call_suffix
|
|
211
|
+
// value_arguments
|
|
212
|
+
// value_argument . string_literal "/x" ← captured
|
|
213
|
+
// value_argument User::class.java
|
|
214
|
+
//
|
|
215
|
+
// The receiver name is constrained to `restTemplate` (#eq? @obj),
|
|
216
|
+
// matching the Java plugin's heuristic. This means a non-conventional
|
|
217
|
+
// field name (e.g. `userServiceTemplate`) will not be picked up;
|
|
218
|
+
// that's the same trade-off already accepted on the Java side.
|
|
219
|
+
const REST_TEMPLATE_PATTERNS = compilePatterns({
|
|
220
|
+
name: 'kotlin-rest-template',
|
|
221
|
+
language,
|
|
222
|
+
patterns: [
|
|
223
|
+
{
|
|
224
|
+
meta: {},
|
|
225
|
+
query: `
|
|
226
|
+
(call_expression
|
|
227
|
+
(navigation_expression
|
|
228
|
+
(simple_identifier) @obj (#eq? @obj "restTemplate")
|
|
229
|
+
(navigation_suffix (simple_identifier) @method))
|
|
230
|
+
(call_suffix
|
|
231
|
+
(value_arguments . (value_argument . (string_literal) @path))))
|
|
232
|
+
`,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
});
|
|
236
|
+
// ─── Consumer: Spring WebClient (short form) ──────────────────────────
|
|
237
|
+
// Reactive WebClient exposes one-liner verb helpers:
|
|
238
|
+
//
|
|
239
|
+
// webClient.get().uri("/x").retrieve().awaitBody<T>()
|
|
240
|
+
// webClient.post().uri("/x")...
|
|
241
|
+
//
|
|
242
|
+
// The chain `webClient.get().uri("/x")` parses as two nested
|
|
243
|
+
// `call_expression` nodes — the OUTER call is `.uri("/x")` and the
|
|
244
|
+
// INNER call is `webClient.get()`. We anchor on the outer call and
|
|
245
|
+
// require:
|
|
246
|
+
// - inner receiver is `webClient`
|
|
247
|
+
// - inner suffix is one of the HTTP verbs (#match?)
|
|
248
|
+
// - outer suffix is exactly `uri`
|
|
249
|
+
// - outer call's first value_argument is a string literal
|
|
250
|
+
//
|
|
251
|
+
// The long-form `webClient.method(HttpMethod.GET).uri("/x")` chain
|
|
252
|
+
// uses an extra navigation hop and an enum field access — it's
|
|
253
|
+
// intentionally out of scope here (see file header).
|
|
254
|
+
const WEB_CLIENT_SHORT_PATTERNS = compilePatterns({
|
|
255
|
+
name: 'kotlin-web-client-short',
|
|
256
|
+
language,
|
|
257
|
+
patterns: [
|
|
258
|
+
{
|
|
259
|
+
meta: {},
|
|
260
|
+
query: `
|
|
261
|
+
(call_expression
|
|
262
|
+
(navigation_expression
|
|
263
|
+
(call_expression
|
|
264
|
+
(navigation_expression
|
|
265
|
+
(simple_identifier) @obj (#eq? @obj "webClient")
|
|
266
|
+
(navigation_suffix
|
|
267
|
+
(simple_identifier) @verb (#match? @verb "^(get|post|put|delete|patch)$")))
|
|
268
|
+
(call_suffix (value_arguments)))
|
|
269
|
+
(navigation_suffix (simple_identifier) @uri (#eq? @uri "uri")))
|
|
270
|
+
(call_suffix
|
|
271
|
+
(value_arguments . (value_argument . (string_literal) @path))))
|
|
272
|
+
`,
|
|
273
|
+
},
|
|
274
|
+
],
|
|
275
|
+
});
|
|
276
|
+
// ─── Consumer: OkHttp Request.Builder().url("/x") ─────────────────────
|
|
277
|
+
// Kotlin parses `Request.Builder()` as a `call_expression` whose
|
|
278
|
+
// callee is a `navigation_expression` (Request → .Builder), NOT as
|
|
279
|
+
// Java's `object_creation_expression`. The chain `.url("/x")` then
|
|
280
|
+
// wraps that in another `call_expression`. The query mirrors Java's
|
|
281
|
+
// `OK_HTTP_PATTERNS` (java.ts) but adapts the node types.
|
|
282
|
+
//
|
|
283
|
+
// Receiver `Request` is constrained by name (#eq? @cls); a project
|
|
284
|
+
// that imports OkHttp's `Request` under an alias (`import okhttp3.Request as OkRequest`)
|
|
285
|
+
// would not be picked up — this matches the Java plugin's heuristic.
|
|
286
|
+
//
|
|
287
|
+
// **Known limitation — verb defaults to GET.** OkHttp encodes the
|
|
288
|
+
// verb on a *sibling* call further down the builder chain (e.g.
|
|
289
|
+
// `.post(body)` / `.get()` / `.delete()`), not on `.url(...)` itself.
|
|
290
|
+
// This query intentionally does not walk the chain to recover the
|
|
291
|
+
// verb — it emits `method: 'GET'` for every match, mirroring
|
|
292
|
+
// `java.ts:OK_HTTP_PATTERNS`. So a `Request.Builder().url("/x").post(body).build()`
|
|
293
|
+
// call becomes `http::GET::/x`, not `http::POST::/x`. This is the
|
|
294
|
+
// same trade-off Java has accepted; pinned by an anti-overreach
|
|
295
|
+
// test in `http-route-extractor.test.ts` so a future verb-walk
|
|
296
|
+
// implementation has to update this comment in lockstep.
|
|
297
|
+
const OK_HTTP_PATTERNS = compilePatterns({
|
|
298
|
+
name: 'kotlin-okhttp',
|
|
299
|
+
language,
|
|
300
|
+
patterns: [
|
|
301
|
+
{
|
|
302
|
+
meta: {},
|
|
303
|
+
query: `
|
|
304
|
+
(call_expression
|
|
305
|
+
(navigation_expression
|
|
306
|
+
(call_expression
|
|
307
|
+
(navigation_expression
|
|
308
|
+
(simple_identifier) @cls (#eq? @cls "Request")
|
|
309
|
+
(navigation_suffix (simple_identifier) @builder (#eq? @builder "Builder")))
|
|
310
|
+
(call_suffix (value_arguments)))
|
|
311
|
+
(navigation_suffix (simple_identifier) @method (#eq? @method "url")))
|
|
312
|
+
(call_suffix
|
|
313
|
+
(value_arguments . (value_argument . (string_literal) @path))))
|
|
314
|
+
`,
|
|
315
|
+
},
|
|
316
|
+
],
|
|
317
|
+
});
|
|
147
318
|
/**
|
|
148
319
|
* Find the nearest enclosing class_declaration ancestor for a node, or
|
|
149
320
|
* null if the node is top-level. Mirrors the Java plugin's helper.
|
|
@@ -212,6 +383,65 @@ function buildKotlinPlugin(language) {
|
|
|
212
383
|
confidence: 0.8,
|
|
213
384
|
});
|
|
214
385
|
}
|
|
386
|
+
// ─── Consumers: RestTemplate ────────────────────────────────────
|
|
387
|
+
for (const match of runCompiledPatterns(REST_TEMPLATE_PATTERNS, tree)) {
|
|
388
|
+
const methodNode = match.captures.method;
|
|
389
|
+
const pathNode = match.captures.path;
|
|
390
|
+
if (!methodNode || !pathNode)
|
|
391
|
+
continue;
|
|
392
|
+
const httpMethod = REST_TEMPLATE_TO_HTTP[methodNode.text];
|
|
393
|
+
if (!httpMethod)
|
|
394
|
+
continue;
|
|
395
|
+
const path = unquoteLiteral(pathNode.text);
|
|
396
|
+
if (path === null)
|
|
397
|
+
continue;
|
|
398
|
+
out.push({
|
|
399
|
+
role: 'consumer',
|
|
400
|
+
framework: 'spring-rest-template',
|
|
401
|
+
method: httpMethod,
|
|
402
|
+
path,
|
|
403
|
+
name: null,
|
|
404
|
+
confidence: 0.7,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
// ─── Consumers: WebClient short form (.get()/.post()/etc → .uri) ─
|
|
408
|
+
for (const match of runCompiledPatterns(WEB_CLIENT_SHORT_PATTERNS, tree)) {
|
|
409
|
+
const verbNode = match.captures.verb;
|
|
410
|
+
const pathNode = match.captures.path;
|
|
411
|
+
if (!verbNode || !pathNode)
|
|
412
|
+
continue;
|
|
413
|
+
const httpMethod = WEB_CLIENT_SHORT_TO_HTTP[verbNode.text];
|
|
414
|
+
if (!httpMethod)
|
|
415
|
+
continue;
|
|
416
|
+
const path = unquoteLiteral(pathNode.text);
|
|
417
|
+
if (path === null)
|
|
418
|
+
continue;
|
|
419
|
+
out.push({
|
|
420
|
+
role: 'consumer',
|
|
421
|
+
framework: 'spring-web-client',
|
|
422
|
+
method: httpMethod,
|
|
423
|
+
path,
|
|
424
|
+
name: null,
|
|
425
|
+
confidence: 0.7,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
// ─── Consumers: OkHttp Request.Builder().url("path") ────────────
|
|
429
|
+
for (const match of runCompiledPatterns(OK_HTTP_PATTERNS, tree)) {
|
|
430
|
+
const pathNode = match.captures.path;
|
|
431
|
+
if (!pathNode)
|
|
432
|
+
continue;
|
|
433
|
+
const path = unquoteLiteral(pathNode.text);
|
|
434
|
+
if (path === null)
|
|
435
|
+
continue;
|
|
436
|
+
out.push({
|
|
437
|
+
role: 'consumer',
|
|
438
|
+
framework: 'okhttp',
|
|
439
|
+
method: 'GET',
|
|
440
|
+
path,
|
|
441
|
+
name: null,
|
|
442
|
+
confidence: 0.7,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
215
445
|
return out;
|
|
216
446
|
},
|
|
217
447
|
};
|
package/package.json
CHANGED