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
- * Mirrors the Java plugin for Spring `@RequestMapping` class prefixes
7
- * and `@(Get|Post|...)Mapping` method annotations on Kotlin Spring
8
- * Boot controllers. Both positional shorthand (`@GetMapping("/x")`)
9
- * and named annotation arguments (`@GetMapping(value = "/x")` and
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
- * Consumer detection (RestTemplate / WebClient / OkHttp) is intentionally
13
- * out of scope for this plugin — Kotlin call-site ASTs are sufficiently
14
- * different from Java's `method_invocation` shape that they warrant a
15
- * separate, focused follow-up.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.75",
3
+ "version": "1.6.6-rc.76",
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",