gitnexus 1.6.6-rc.82 → 1.6.6-rc.83

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.
@@ -3,8 +3,11 @@ import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-s
3
3
  /**
4
4
  * Java HTTP plugin. Handles:
5
5
  * - Spring `@RequestMapping` class prefixes + `@(Get|Post|...)Mapping` method annotations
6
- * - Spring `RestTemplate.getForObject/...`, `WebClient.method(HttpMethod.X, ...)`
6
+ * - Spring `RestTemplate.getForObject/...`, `exchange(...)`
7
+ * - Spring `WebClient.method(HttpMethod.X, ...)`, `WebClient.get().uri(...)`
7
8
  * - OkHttp `new Request.Builder().url("...")`
9
+ * - OpenFeign interfaces with Spring MVC method annotations
10
+ * - Java / Apache HttpClient literal request construction
8
11
  *
9
12
  * The plugin runs two pattern bundles: one to collect class-level
10
13
  * `@RequestMapping` prefixes keyed by the enclosing class node, and a
@@ -62,6 +65,52 @@ const SPRING_CLASS_PREFIX_PATTERNS = compilePatterns({
62
65
  },
63
66
  ],
64
67
  });
68
+ // ─── Consumer: OpenFeign interface-level prefixes ───────────────────
69
+ // Feign's `name`/`value` attributes identify a service, not an HTTP path,
70
+ // so only `path` is used as a URL prefix. `@RequestMapping` on a Feign
71
+ // interface is also common and does carry a path prefix.
72
+ const FEIGN_INTERFACE_PREFIX_PATTERNS = compilePatterns({
73
+ name: 'java-feign-interface-prefix',
74
+ language: Java,
75
+ patterns: [
76
+ {
77
+ meta: {},
78
+ query: `
79
+ (interface_declaration
80
+ (modifiers
81
+ (annotation
82
+ name: (identifier) @ann (#eq? @ann "FeignClient")
83
+ arguments: (annotation_argument_list
84
+ (element_value_pair
85
+ key: (identifier) @key (#eq? @key "path")
86
+ value: (string_literal) @prefix))))) @interface
87
+ `,
88
+ },
89
+ {
90
+ meta: {},
91
+ query: `
92
+ (interface_declaration
93
+ (modifiers
94
+ (annotation
95
+ name: (identifier) @ann (#eq? @ann "RequestMapping")
96
+ arguments: (annotation_argument_list (string_literal) @prefix)))) @interface
97
+ `,
98
+ },
99
+ {
100
+ meta: {},
101
+ query: `
102
+ (interface_declaration
103
+ (modifiers
104
+ (annotation
105
+ name: (identifier) @ann (#eq? @ann "RequestMapping")
106
+ arguments: (annotation_argument_list
107
+ (element_value_pair
108
+ key: (identifier) @key (#match? @key "^(path|value)$")
109
+ value: (string_literal) @prefix))))) @interface
110
+ `,
111
+ },
112
+ ],
113
+ });
65
114
  // ─── Provider: Spring @(Get|Post|...)Mapping method annotations ───────
66
115
  // Same dual-pattern approach: positional vs named argument. The named
67
116
  // pattern restricts the annotation member name to `path`/`value` to
@@ -104,6 +153,8 @@ const SPRING_METHOD_ROUTE_PATTERNS = compilePatterns({
104
153
  // RestTemplate.put → PUT
105
154
  // RestTemplate.delete → DELETE
106
155
  // RestTemplate.patchForObject → PATCH
156
+ // Source-scan only: receiver must be named exactly `restTemplate`.
157
+ // Fields, `this.restTemplate`, aliases, and other injection names are deferred.
107
158
  const REST_TEMPLATE_TO_HTTP = {
108
159
  getForObject: 'GET',
109
160
  getForEntity: 'GET',
@@ -128,22 +179,46 @@ const REST_TEMPLATE_PATTERNS = compilePatterns({
128
179
  },
129
180
  ],
130
181
  });
131
- // ─── Consumer: Spring WebClient — webClient.method(HttpMethod.X, "path") ─
132
- const WEB_CLIENT_PATTERNS = compilePatterns({
133
- name: 'java-web-client',
182
+ const REST_TEMPLATE_EXCHANGE_PATTERNS = compilePatterns({
183
+ name: 'java-rest-template-exchange',
134
184
  language: Java,
135
185
  patterns: [
136
186
  {
137
- meta: {},
187
+ meta: { framework: 'spring-rest-template' },
138
188
  query: `
139
189
  (method_invocation
140
- object: (identifier) @obj (#eq? @obj "webClient")
141
- name: (identifier) @method (#eq? @method "method")
190
+ object: (identifier) @obj (#eq? @obj "restTemplate")
191
+ name: (identifier) @method (#eq? @method "exchange")
142
192
  arguments: (argument_list
193
+ . (string_literal) @path
143
194
  (field_access
144
195
  object: (identifier) @httpMethodCls (#eq? @httpMethodCls "HttpMethod")
145
- field: (identifier) @http_method)
146
- (string_literal) @path))
196
+ field: (identifier) @http_method)))
197
+ `,
198
+ },
199
+ ],
200
+ });
201
+ const WEB_CLIENT_SHORT_TO_HTTP = {
202
+ get: 'GET',
203
+ post: 'POST',
204
+ put: 'PUT',
205
+ delete: 'DELETE',
206
+ patch: 'PATCH',
207
+ };
208
+ const WEB_CLIENT_SHORT_FORM_PATTERNS = compilePatterns({
209
+ name: 'java-web-client-short-form',
210
+ language: Java,
211
+ patterns: [
212
+ {
213
+ meta: {},
214
+ query: `
215
+ (method_invocation
216
+ object: (method_invocation
217
+ object: (identifier) @obj (#eq? @obj "webClient")
218
+ name: (identifier) @verb (#match? @verb "^(get|post|put|delete|patch)$")
219
+ arguments: (argument_list))
220
+ name: (identifier) @uri_method (#eq? @uri_method "uri")
221
+ arguments: (argument_list . (string_literal) @path))
147
222
  `,
148
223
  },
149
224
  ],
@@ -168,6 +243,51 @@ const OK_HTTP_PATTERNS = compilePatterns({
168
243
  },
169
244
  ],
170
245
  });
246
+ const JAVA_HTTP_CLIENT_PATTERNS = compilePatterns({
247
+ name: 'java-http-client',
248
+ language: Java,
249
+ patterns: [
250
+ {
251
+ meta: {},
252
+ query: `
253
+ (method_invocation
254
+ object: (method_invocation
255
+ object: (method_invocation
256
+ object: (identifier) @builderCls (#eq? @builderCls "HttpRequest")
257
+ name: (identifier) @newBuilder (#eq? @newBuilder "newBuilder")
258
+ arguments: (argument_list))
259
+ name: (identifier) @uri_method (#eq? @uri_method "uri")
260
+ arguments: (argument_list
261
+ (method_invocation
262
+ object: (identifier) @uriCls (#eq? @uriCls "URI")
263
+ name: (identifier) @create (#eq? @create "create")
264
+ arguments: (argument_list . (string_literal) @path))))
265
+ name: (identifier) @http_method (#match? @http_method "^(GET|POST|PUT|DELETE)$"))
266
+ `,
267
+ },
268
+ ],
269
+ });
270
+ const APACHE_HTTP_CLIENT_TO_HTTP = {
271
+ HttpGet: 'GET',
272
+ HttpPost: 'POST',
273
+ HttpPut: 'PUT',
274
+ HttpDelete: 'DELETE',
275
+ HttpPatch: 'PATCH',
276
+ };
277
+ const APACHE_HTTP_CLIENT_PATTERNS = compilePatterns({
278
+ name: 'java-apache-http-client',
279
+ language: Java,
280
+ patterns: [
281
+ {
282
+ meta: {},
283
+ query: `
284
+ (object_creation_expression
285
+ type: (type_identifier) @type (#match? @type "^Http(Get|Post|Put|Delete|Patch)$")
286
+ arguments: (argument_list . (string_literal) @path))
287
+ `,
288
+ },
289
+ ],
290
+ });
171
291
  /**
172
292
  * Find the nearest enclosing class_declaration ancestor for a node, or
173
293
  * null if the node is top-level. Tree-sitter's SyntaxNode.parent walks
@@ -182,6 +302,32 @@ function findEnclosingClass(node) {
182
302
  }
183
303
  return null;
184
304
  }
305
+ function findEnclosingInterface(node) {
306
+ let cur = node.parent;
307
+ while (cur) {
308
+ if (cur.type === 'interface_declaration')
309
+ return cur;
310
+ cur = cur.parent;
311
+ }
312
+ return null;
313
+ }
314
+ function hasAnnotation(node, annotationName) {
315
+ for (const child of node.namedChildren) {
316
+ if (child.type !== 'modifiers')
317
+ continue;
318
+ for (const modifier of child.namedChildren) {
319
+ if (modifier.type !== 'annotation')
320
+ continue;
321
+ const nameNode = modifier.childForFieldName('name');
322
+ if (!nameNode)
323
+ continue;
324
+ const simpleName = nameNode.text.split('.').pop();
325
+ if (nameNode.text === annotationName || simpleName === annotationName)
326
+ return true;
327
+ }
328
+ }
329
+ return false;
330
+ }
185
331
  /**
186
332
  * Join a class-level prefix and a method-level path into a single URL
187
333
  * path. Mirrors the semantics of the original regex implementation:
@@ -211,6 +357,16 @@ export const JAVA_HTTP_PLUGIN = {
211
357
  if (prefix !== null)
212
358
  prefixByClassId.set(classNode.id, prefix);
213
359
  }
360
+ const feignPrefixByInterfaceId = new Map();
361
+ for (const match of runCompiledPatterns(FEIGN_INTERFACE_PREFIX_PATTERNS, tree)) {
362
+ const prefixNode = match.captures.prefix;
363
+ const interfaceNode = match.captures.interface;
364
+ if (!prefixNode || !interfaceNode)
365
+ continue;
366
+ const prefix = unquoteLiteral(prefixNode.text);
367
+ if (prefix !== null && !feignPrefixByInterfaceId.has(interfaceNode.id))
368
+ feignPrefixByInterfaceId.set(interfaceNode.id, prefix);
369
+ }
214
370
  for (const match of runCompiledPatterns(SPRING_METHOD_ROUTE_PATTERNS, tree)) {
215
371
  const annNode = match.captures.ann;
216
372
  const pathNode = match.captures.path;
@@ -224,6 +380,20 @@ export const JAVA_HTTP_PLUGIN = {
224
380
  const rawPath = unquoteLiteral(pathNode.text);
225
381
  if (rawPath === null)
226
382
  continue;
383
+ const enclosingInterface = findEnclosingInterface(methodNode);
384
+ if (enclosingInterface && hasAnnotation(enclosingInterface, 'FeignClient')) {
385
+ const prefix = feignPrefixByInterfaceId.get(enclosingInterface.id) ?? '';
386
+ const fullPath = joinPath(prefix, rawPath);
387
+ out.push({
388
+ role: 'consumer',
389
+ framework: 'openfeign',
390
+ method: httpMethod,
391
+ path: fullPath,
392
+ name: nameNode?.text ?? null,
393
+ confidence: 0.7,
394
+ });
395
+ continue;
396
+ }
227
397
  const enclosingClass = findEnclosingClass(methodNode);
228
398
  const prefix = enclosingClass ? (prefixByClassId.get(enclosingClass.id) ?? '') : '';
229
399
  const fullPath = joinPath(prefix, rawPath);
@@ -257,8 +427,7 @@ export const JAVA_HTTP_PLUGIN = {
257
427
  confidence: 0.7,
258
428
  });
259
429
  }
260
- // ─── Consumers: WebClient.method(HttpMethod.X, "path") ──────────
261
- for (const match of runCompiledPatterns(WEB_CLIENT_PATTERNS, tree)) {
430
+ for (const match of runCompiledPatterns(REST_TEMPLATE_EXCHANGE_PATTERNS, tree)) {
262
431
  const httpMethodNode = match.captures.http_method;
263
432
  const pathNode = match.captures.path;
264
433
  if (!httpMethodNode || !pathNode)
@@ -268,13 +437,37 @@ export const JAVA_HTTP_PLUGIN = {
268
437
  continue;
269
438
  out.push({
270
439
  role: 'consumer',
271
- framework: 'spring-web-client',
440
+ framework: 'spring-rest-template',
272
441
  method: httpMethodNode.text.toUpperCase(),
273
442
  path,
274
443
  name: null,
275
444
  confidence: 0.7,
276
445
  });
277
446
  }
447
+ // ─── Consumers: WebClient.get().uri("path") short form ─────────
448
+ // Source-scan only: receiver must be named exactly `webClient`.
449
+ // The real long-form chain `webClient.method(HttpMethod.X).uri("/x")`
450
+ // needs multi-hop chain analysis and is intentionally deferred.
451
+ for (const match of runCompiledPatterns(WEB_CLIENT_SHORT_FORM_PATTERNS, tree)) {
452
+ const verbNode = match.captures.verb;
453
+ const pathNode = match.captures.path;
454
+ if (!verbNode || !pathNode)
455
+ continue;
456
+ const httpMethod = WEB_CLIENT_SHORT_TO_HTTP[verbNode.text];
457
+ if (!httpMethod)
458
+ continue;
459
+ const path = unquoteLiteral(pathNode.text);
460
+ if (path === null)
461
+ continue;
462
+ out.push({
463
+ role: 'consumer',
464
+ framework: 'spring-web-client',
465
+ method: httpMethod,
466
+ path,
467
+ name: null,
468
+ confidence: 0.7,
469
+ });
470
+ }
278
471
  // ─── Consumers: OkHttp Request.Builder().url("path") ────────────
279
472
  for (const match of runCompiledPatterns(OK_HTTP_PATTERNS, tree)) {
280
473
  const pathNode = match.captures.path;
@@ -292,6 +485,47 @@ export const JAVA_HTTP_PLUGIN = {
292
485
  confidence: 0.7,
293
486
  });
294
487
  }
488
+ // ─── Consumers: Java HttpClient request builder ─────────────────
489
+ // Java's builder exposes GET/POST/PUT/DELETE helpers. PATCH uses
490
+ // `.method("PATCH", body)`, which is intentionally deferred.
491
+ for (const match of runCompiledPatterns(JAVA_HTTP_CLIENT_PATTERNS, tree)) {
492
+ const httpMethodNode = match.captures.http_method;
493
+ const pathNode = match.captures.path;
494
+ if (!httpMethodNode || !pathNode)
495
+ continue;
496
+ const path = unquoteLiteral(pathNode.text);
497
+ if (path === null)
498
+ continue;
499
+ out.push({
500
+ role: 'consumer',
501
+ framework: 'java-http-client',
502
+ method: httpMethodNode.text.toUpperCase(),
503
+ path,
504
+ name: null,
505
+ confidence: 0.65,
506
+ });
507
+ }
508
+ // ─── Consumers: Apache HttpClient request constructors ──────────
509
+ for (const match of runCompiledPatterns(APACHE_HTTP_CLIENT_PATTERNS, tree)) {
510
+ const typeNode = match.captures.type;
511
+ const pathNode = match.captures.path;
512
+ if (!typeNode || !pathNode)
513
+ continue;
514
+ const httpMethod = APACHE_HTTP_CLIENT_TO_HTTP[typeNode.text];
515
+ if (!httpMethod)
516
+ continue;
517
+ const path = unquoteLiteral(pathNode.text);
518
+ if (path === null)
519
+ continue;
520
+ out.push({
521
+ role: 'consumer',
522
+ framework: 'apache-http-client',
523
+ method: httpMethod,
524
+ path,
525
+ name: null,
526
+ confidence: 0.65,
527
+ });
528
+ }
295
529
  return out;
296
530
  },
297
531
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gitnexus",
3
- "version": "1.6.6-rc.82",
3
+ "version": "1.6.6-rc.83",
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",