gitnexus 1.6.8-rc.4 → 1.6.8-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.js +3 -50
- package/dist/core/ingestion/language-provider.d.ts +13 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +50 -0
- package/dist/core/ingestion/route-extractors/spring-shared.js +80 -0
- package/dist/core/ingestion/route-extractors/spring.d.ts +35 -0
- package/dist/core/ingestion/route-extractors/spring.js +136 -0
- package/dist/core/ingestion/workers/parse-worker.js +10 -0
- package/package.json +1 -1
|
@@ -1,31 +1,6 @@
|
|
|
1
1
|
import Java from 'tree-sitter-java';
|
|
2
2
|
import { compilePatterns, runCompiledPatterns, unquoteLiteral, } from '../tree-sitter-scanner.js';
|
|
3
|
-
|
|
4
|
-
* Java HTTP plugin. Handles:
|
|
5
|
-
* - Spring `@RequestMapping` class prefixes + `@(Get|Post|...)Mapping` method annotations
|
|
6
|
-
* - Spring `RestTemplate.getForObject/...`, `exchange(...)`
|
|
7
|
-
* - Spring `WebClient.method(HttpMethod.X, ...)`, `WebClient.get().uri(...)`
|
|
8
|
-
* - OkHttp `new Request.Builder().url("...")`
|
|
9
|
-
* - OpenFeign interfaces with Spring MVC method annotations or
|
|
10
|
-
* native `@RequestLine("METHOD /path")` annotations
|
|
11
|
-
* - Java / Apache HttpClient literal request construction
|
|
12
|
-
*
|
|
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.
|
|
21
|
-
*/
|
|
22
|
-
const METHOD_ANNOTATION_TO_HTTP = {
|
|
23
|
-
GetMapping: 'GET',
|
|
24
|
-
PostMapping: 'POST',
|
|
25
|
-
PutMapping: 'PUT',
|
|
26
|
-
DeleteMapping: 'DELETE',
|
|
27
|
-
PatchMapping: 'PATCH',
|
|
28
|
-
};
|
|
3
|
+
import { METHOD_ANNOTATION_TO_HTTP, isRouteMemberKey, findEnclosingClass, } from '../../../ingestion/route-extractors/spring-shared.js';
|
|
29
4
|
// ─── Route-defining annotations (one generic query, one pass) ─────────
|
|
30
5
|
// Every Java route-mapper annotation shares one shape: an annotation carrying a
|
|
31
6
|
// single string argument — positional `"..."` or named `key = "..."` — on a
|
|
@@ -300,19 +275,9 @@ const APACHE_HTTP_CLIENT_PATTERNS = compilePatterns({
|
|
|
300
275
|
],
|
|
301
276
|
});
|
|
302
277
|
/**
|
|
303
|
-
* Find the nearest enclosing
|
|
304
|
-
*
|
|
305
|
-
* SyntaxNode.parent walks one level at a time.
|
|
278
|
+
* Find the nearest enclosing interface declaration ancestor for a node, or
|
|
279
|
+
* null if the node is top-level.
|
|
306
280
|
*/
|
|
307
|
-
function findEnclosingClass(node) {
|
|
308
|
-
let cur = node.parent;
|
|
309
|
-
while (cur) {
|
|
310
|
-
if (cur.type === 'class_declaration')
|
|
311
|
-
return cur;
|
|
312
|
-
cur = cur.parent;
|
|
313
|
-
}
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
281
|
function findEnclosingInterface(node) {
|
|
317
282
|
let cur = node.parent;
|
|
318
283
|
while (cur) {
|
|
@@ -369,18 +334,6 @@ function hasAnnotation(node, names) {
|
|
|
369
334
|
}
|
|
370
335
|
return false;
|
|
371
336
|
}
|
|
372
|
-
/**
|
|
373
|
-
* A named annotation argument contributes a route only when its member key is
|
|
374
|
-
* `path` or `value`; a positional argument (no key node) always qualifies.
|
|
375
|
-
* This is the JS-side replacement for the in-query `^(path|value)$` filter and
|
|
376
|
-
* drops Spring's non-route string attributes (`produces`, `consumes`,
|
|
377
|
-
* `headers`, `name`, `params`) that would otherwise be mis-read as routes.
|
|
378
|
-
*/
|
|
379
|
-
function isRouteMemberKey(keyNode) {
|
|
380
|
-
if (!keyNode)
|
|
381
|
-
return true;
|
|
382
|
-
return keyNode.text === 'path' || keyNode.text === 'value';
|
|
383
|
-
}
|
|
384
337
|
/**
|
|
385
338
|
* Resolve every Java route-defining annotation in a single tree-sitter pass.
|
|
386
339
|
*
|
|
@@ -20,6 +20,8 @@ import type { VariableExtractor } from './variable-types.js';
|
|
|
20
20
|
import type { ImportResolverFn } from './import-resolvers/types.js';
|
|
21
21
|
import type { SyntaxNode } from './utils/ast-helpers.js';
|
|
22
22
|
import type { NodeLabel } from '../../_shared/index.js';
|
|
23
|
+
import type Parser from 'tree-sitter';
|
|
24
|
+
import type { ExtractedDecoratorRoute } from './workers/parse-worker.js';
|
|
23
25
|
/** Tree-sitter query captures: capture name → AST node (or undefined if not captured). */
|
|
24
26
|
export type CaptureMap = Record<string, SyntaxNode | undefined>;
|
|
25
27
|
/** Configuration for AST-based framework detection patterns. */
|
|
@@ -185,6 +187,17 @@ interface LanguageProviderConfig {
|
|
|
185
187
|
* When true, the worker extracts routes via the language's route extraction logic.
|
|
186
188
|
* Default: undefined (no route files). */
|
|
187
189
|
readonly isRouteFile?: (filePath: string) => boolean;
|
|
190
|
+
/**
|
|
191
|
+
* Extract decorator-style route annotations from a parsed file.
|
|
192
|
+
*
|
|
193
|
+
* When defined, the parse worker calls this after per-file capture processing
|
|
194
|
+
* to extract framework route definitions that require AST-level analysis beyond
|
|
195
|
+
* generic `@decorator` captures (e.g., Java Spring class-level prefix joining,
|
|
196
|
+
* multi-class handling). The returned routes are appended to `decoratorRoutes`.
|
|
197
|
+
*
|
|
198
|
+
* Default: undefined (no language-specific decorator route extraction).
|
|
199
|
+
*/
|
|
200
|
+
readonly extractDecoratorRoutes?: (tree: Parser.Tree, filePath: string, lineOffset: number) => ExtractedDecoratorRoute[];
|
|
188
201
|
/** Built-in/stdlib names that should be filtered from the call graph for this language.
|
|
189
202
|
* Default: undefined (no language-specific filtering). */
|
|
190
203
|
readonly builtInNames?: ReadonlySet<string>;
|
|
@@ -11,6 +11,7 @@ import { createClassExtractor } from '../class-extractors/generic.js';
|
|
|
11
11
|
import { javaClassConfig } from '../class-extractors/configs/jvm.js';
|
|
12
12
|
import { defineLanguage } from '../language-provider.js';
|
|
13
13
|
import { javaTypeConfig } from '../type-extractors/jvm.js';
|
|
14
|
+
import { extractSpringRoutes } from '../route-extractors/spring.js';
|
|
14
15
|
import { javaExportChecker } from '../export-detection.js';
|
|
15
16
|
import { createImportResolver } from '../import-resolvers/resolver-factory.js';
|
|
16
17
|
import { javaImportConfig } from '../import-resolvers/configs/jvm.js';
|
|
@@ -103,4 +104,6 @@ export const javaProvider = defineLanguage({
|
|
|
103
104
|
arityCompatibility: javaArityCompatibility,
|
|
104
105
|
resolveImportTarget: resolveJavaImportTarget,
|
|
105
106
|
orderSameNameTypeCandidates: orderJavaSameNameTypeCandidates,
|
|
107
|
+
// ── Route extraction ──
|
|
108
|
+
extractDecoratorRoutes: extractSpringRoutes,
|
|
106
109
|
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Spring route-annotation primitives.
|
|
3
|
+
*
|
|
4
|
+
* These are the low-level building blocks the two Spring route extractors —
|
|
5
|
+
* the ingestion-layer `route-extractors/spring.ts` (produces graph `Route`
|
|
6
|
+
* nodes) and the group-layer `group/extractors/http-patterns/java.ts`
|
|
7
|
+
* (produces cross-repo HTTP contracts) — would otherwise each maintain
|
|
8
|
+
* independently. Centralising the annotation→method map, the enclosing-class
|
|
9
|
+
* lookup, and the route-key filter keeps those semantics in one place so the
|
|
10
|
+
* two extractors can't drift apart.
|
|
11
|
+
*
|
|
12
|
+
* This module lives in `ingestion/` (the lower layer); the group layer imports
|
|
13
|
+
* from it, matching the existing `group → ingestion` dependency direction
|
|
14
|
+
* (e.g. `group/extractors/include-extractor.ts` already imports
|
|
15
|
+
* `ingestion/import-resolvers/utils.ts`). It MUST NOT import anything from
|
|
16
|
+
* `group/` to avoid a dependency cycle.
|
|
17
|
+
*/
|
|
18
|
+
import type Parser from 'tree-sitter';
|
|
19
|
+
/**
|
|
20
|
+
* Spring shortcut method-annotation → HTTP verb.
|
|
21
|
+
*
|
|
22
|
+
* `@RequestMapping` is intentionally absent: on a method it carries no implicit
|
|
23
|
+
* verb (the verb lives in its `method = RequestMethod.X` attribute), and on a
|
|
24
|
+
* class it is a URL prefix rather than a route. Callers handle `@RequestMapping`
|
|
25
|
+
* separately.
|
|
26
|
+
*/
|
|
27
|
+
export declare const METHOD_ANNOTATION_TO_HTTP: Record<string, string>;
|
|
28
|
+
/**
|
|
29
|
+
* A named annotation argument contributes a route only when its member key is
|
|
30
|
+
* `path` or `value`; a positional argument (no key node) always qualifies.
|
|
31
|
+
* Drops Spring's non-route string attributes (`produces`, `consumes`,
|
|
32
|
+
* `headers`, `name`, `params`) that would otherwise be mis-read as routes.
|
|
33
|
+
*/
|
|
34
|
+
export declare function isRouteMemberKey(keyNode: Parser.SyntaxNode | undefined): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Find the nearest enclosing `class_declaration` ancestor for a node, or null
|
|
37
|
+
* if the node is top-level. Tree-sitter's `SyntaxNode.parent` walks one level
|
|
38
|
+
* at a time.
|
|
39
|
+
*/
|
|
40
|
+
export declare function findEnclosingClass(node: Parser.SyntaxNode): Parser.SyntaxNode | null;
|
|
41
|
+
/**
|
|
42
|
+
* Strip enclosing quotes from a tree-sitter string-literal node's text.
|
|
43
|
+
* Handles single / double / template (backtick) quotes and triple-quoted
|
|
44
|
+
* strings. Mirrors the safer semantics of the group layer's `unquoteLiteral`:
|
|
45
|
+
* returns `null` for empty / nullish input so callers can uniformly skip
|
|
46
|
+
* captures whose value is missing, and returns the text unchanged when it
|
|
47
|
+
* carries no recognisable surrounding quotes (some grammars expose string
|
|
48
|
+
* content without quotes already).
|
|
49
|
+
*/
|
|
50
|
+
export declare function unquoteSpringLiteral(raw: string): string | null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared Spring route-annotation primitives.
|
|
3
|
+
*
|
|
4
|
+
* These are the low-level building blocks the two Spring route extractors —
|
|
5
|
+
* the ingestion-layer `route-extractors/spring.ts` (produces graph `Route`
|
|
6
|
+
* nodes) and the group-layer `group/extractors/http-patterns/java.ts`
|
|
7
|
+
* (produces cross-repo HTTP contracts) — would otherwise each maintain
|
|
8
|
+
* independently. Centralising the annotation→method map, the enclosing-class
|
|
9
|
+
* lookup, and the route-key filter keeps those semantics in one place so the
|
|
10
|
+
* two extractors can't drift apart.
|
|
11
|
+
*
|
|
12
|
+
* This module lives in `ingestion/` (the lower layer); the group layer imports
|
|
13
|
+
* from it, matching the existing `group → ingestion` dependency direction
|
|
14
|
+
* (e.g. `group/extractors/include-extractor.ts` already imports
|
|
15
|
+
* `ingestion/import-resolvers/utils.ts`). It MUST NOT import anything from
|
|
16
|
+
* `group/` to avoid a dependency cycle.
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* Spring shortcut method-annotation → HTTP verb.
|
|
20
|
+
*
|
|
21
|
+
* `@RequestMapping` is intentionally absent: on a method it carries no implicit
|
|
22
|
+
* verb (the verb lives in its `method = RequestMethod.X` attribute), and on a
|
|
23
|
+
* class it is a URL prefix rather than a route. Callers handle `@RequestMapping`
|
|
24
|
+
* separately.
|
|
25
|
+
*/
|
|
26
|
+
export const METHOD_ANNOTATION_TO_HTTP = {
|
|
27
|
+
GetMapping: 'GET',
|
|
28
|
+
PostMapping: 'POST',
|
|
29
|
+
PutMapping: 'PUT',
|
|
30
|
+
DeleteMapping: 'DELETE',
|
|
31
|
+
PatchMapping: 'PATCH',
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* A named annotation argument contributes a route only when its member key is
|
|
35
|
+
* `path` or `value`; a positional argument (no key node) always qualifies.
|
|
36
|
+
* Drops Spring's non-route string attributes (`produces`, `consumes`,
|
|
37
|
+
* `headers`, `name`, `params`) that would otherwise be mis-read as routes.
|
|
38
|
+
*/
|
|
39
|
+
export function isRouteMemberKey(keyNode) {
|
|
40
|
+
if (!keyNode)
|
|
41
|
+
return true;
|
|
42
|
+
return keyNode.text === 'path' || keyNode.text === 'value';
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Find the nearest enclosing `class_declaration` ancestor for a node, or null
|
|
46
|
+
* if the node is top-level. Tree-sitter's `SyntaxNode.parent` walks one level
|
|
47
|
+
* at a time.
|
|
48
|
+
*/
|
|
49
|
+
export function findEnclosingClass(node) {
|
|
50
|
+
let cur = node.parent;
|
|
51
|
+
while (cur) {
|
|
52
|
+
if (cur.type === 'class_declaration')
|
|
53
|
+
return cur;
|
|
54
|
+
cur = cur.parent;
|
|
55
|
+
}
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Strip enclosing quotes from a tree-sitter string-literal node's text.
|
|
60
|
+
* Handles single / double / template (backtick) quotes and triple-quoted
|
|
61
|
+
* strings. Mirrors the safer semantics of the group layer's `unquoteLiteral`:
|
|
62
|
+
* returns `null` for empty / nullish input so callers can uniformly skip
|
|
63
|
+
* captures whose value is missing, and returns the text unchanged when it
|
|
64
|
+
* carries no recognisable surrounding quotes (some grammars expose string
|
|
65
|
+
* content without quotes already).
|
|
66
|
+
*/
|
|
67
|
+
export function unquoteSpringLiteral(raw) {
|
|
68
|
+
if (!raw)
|
|
69
|
+
return null;
|
|
70
|
+
if ((raw.startsWith('"""') && raw.endsWith('"""')) ||
|
|
71
|
+
(raw.startsWith("'''") && raw.endsWith("'''"))) {
|
|
72
|
+
return raw.slice(3, -3);
|
|
73
|
+
}
|
|
74
|
+
const first = raw[0];
|
|
75
|
+
const last = raw[raw.length - 1];
|
|
76
|
+
if ((first === '"' || first === "'" || first === '`') && last === first && raw.length >= 2) {
|
|
77
|
+
return raw.slice(1, -1);
|
|
78
|
+
}
|
|
79
|
+
return raw;
|
|
80
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spring route annotation extractor for the ingestion pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Extracts `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`,
|
|
5
|
+
* `@PatchMapping`, and `@RequestMapping` annotations from Java source files
|
|
6
|
+
* and returns `ExtractedDecoratorRoute[]` with class-level `@RequestMapping`
|
|
7
|
+
* prefixes already resolved per-class.
|
|
8
|
+
*
|
|
9
|
+
* This module is the ingestion-layer counterpart of
|
|
10
|
+
* `group/extractors/http-patterns/java.ts` (which extracts HTTP contracts
|
|
11
|
+
* for cross-repo matching). It uses the same tree-sitter capture approach:
|
|
12
|
+
* a single predicate-free query matches all route annotations generically,
|
|
13
|
+
* then a for-loop discriminates class-level prefixes from method-level routes
|
|
14
|
+
* by reading `@node.type` and the annotation name.
|
|
15
|
+
*
|
|
16
|
+
* The query is predicate-free to avoid the tree-sitter 0.21.x hazard where
|
|
17
|
+
* `#match?` / `#eq?` predicates in a top-level `[...]` alternation silently
|
|
18
|
+
* drop sibling-branch matches (see group-layer `JAVA_ROUTE_ANNOTATION_PATTERNS`
|
|
19
|
+
* header comment for details).
|
|
20
|
+
*/
|
|
21
|
+
import Parser from 'tree-sitter';
|
|
22
|
+
import type { ExtractedDecoratorRoute } from '../workers/parse-worker.js';
|
|
23
|
+
/**
|
|
24
|
+
* Extract Spring route annotations from a parsed Java file.
|
|
25
|
+
*
|
|
26
|
+
* Uses a single tree-sitter query pass to capture all annotations, then
|
|
27
|
+
* discriminates class-level prefixes from method-level routes in a loop.
|
|
28
|
+
* Handles multiple classes per file, each with its own prefix.
|
|
29
|
+
*
|
|
30
|
+
* @param tree - tree-sitter parse tree
|
|
31
|
+
* @param filePath - relative file path (for `ExtractedDecoratorRoute.filePath`)
|
|
32
|
+
* @param lineOffset - line offset for pre-processing (usually 0)
|
|
33
|
+
* @returns Decorator routes with prefix already set per-class
|
|
34
|
+
*/
|
|
35
|
+
export declare function extractSpringRoutes(tree: Parser.Tree, filePath: string, lineOffset?: number): ExtractedDecoratorRoute[];
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spring route annotation extractor for the ingestion pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Extracts `@GetMapping`, `@PostMapping`, `@PutMapping`, `@DeleteMapping`,
|
|
5
|
+
* `@PatchMapping`, and `@RequestMapping` annotations from Java source files
|
|
6
|
+
* and returns `ExtractedDecoratorRoute[]` with class-level `@RequestMapping`
|
|
7
|
+
* prefixes already resolved per-class.
|
|
8
|
+
*
|
|
9
|
+
* This module is the ingestion-layer counterpart of
|
|
10
|
+
* `group/extractors/http-patterns/java.ts` (which extracts HTTP contracts
|
|
11
|
+
* for cross-repo matching). It uses the same tree-sitter capture approach:
|
|
12
|
+
* a single predicate-free query matches all route annotations generically,
|
|
13
|
+
* then a for-loop discriminates class-level prefixes from method-level routes
|
|
14
|
+
* by reading `@node.type` and the annotation name.
|
|
15
|
+
*
|
|
16
|
+
* The query is predicate-free to avoid the tree-sitter 0.21.x hazard where
|
|
17
|
+
* `#match?` / `#eq?` predicates in a top-level `[...]` alternation silently
|
|
18
|
+
* drop sibling-branch matches (see group-layer `JAVA_ROUTE_ANNOTATION_PATTERNS`
|
|
19
|
+
* header comment for details).
|
|
20
|
+
*/
|
|
21
|
+
import Parser from 'tree-sitter';
|
|
22
|
+
import Java from 'tree-sitter-java';
|
|
23
|
+
import { METHOD_ANNOTATION_TO_HTTP, isRouteMemberKey, findEnclosingClass, unquoteSpringLiteral, } from './spring-shared.js';
|
|
24
|
+
/**
|
|
25
|
+
* Single predicate-free tree-sitter query that captures all route annotations
|
|
26
|
+
* on classes and methods. Discrimination by annotation name and node type
|
|
27
|
+
* happens in the loop below.
|
|
28
|
+
*
|
|
29
|
+
* Captures:
|
|
30
|
+
* @ann → annotation name identifier (RequestMapping, GetMapping, etc.)
|
|
31
|
+
* @node → enclosing declaration (class_declaration | method_declaration)
|
|
32
|
+
* @value → the string-literal argument
|
|
33
|
+
* @key → the named-argument member key (absent for positional form)
|
|
34
|
+
*/
|
|
35
|
+
const ROUTE_ANNOTATION_QUERY = new Parser.Query(Java, `
|
|
36
|
+
[
|
|
37
|
+
(class_declaration
|
|
38
|
+
(modifiers
|
|
39
|
+
(annotation
|
|
40
|
+
name: (identifier) @ann
|
|
41
|
+
arguments: (annotation_argument_list (string_literal) @value)))) @node
|
|
42
|
+
(class_declaration
|
|
43
|
+
(modifiers
|
|
44
|
+
(annotation
|
|
45
|
+
name: (identifier) @ann
|
|
46
|
+
arguments: (annotation_argument_list
|
|
47
|
+
(element_value_pair
|
|
48
|
+
key: (identifier) @key
|
|
49
|
+
value: (string_literal) @value))))) @node
|
|
50
|
+
(method_declaration
|
|
51
|
+
(modifiers
|
|
52
|
+
(annotation
|
|
53
|
+
name: (identifier) @ann
|
|
54
|
+
arguments: (annotation_argument_list (string_literal) @value)))) @node
|
|
55
|
+
(method_declaration
|
|
56
|
+
(modifiers
|
|
57
|
+
(annotation
|
|
58
|
+
name: (identifier) @ann
|
|
59
|
+
arguments: (annotation_argument_list
|
|
60
|
+
(element_value_pair
|
|
61
|
+
key: (identifier) @key
|
|
62
|
+
value: (string_literal) @value))))) @node
|
|
63
|
+
]
|
|
64
|
+
`);
|
|
65
|
+
/**
|
|
66
|
+
* Extract Spring route annotations from a parsed Java file.
|
|
67
|
+
*
|
|
68
|
+
* Uses a single tree-sitter query pass to capture all annotations, then
|
|
69
|
+
* discriminates class-level prefixes from method-level routes in a loop.
|
|
70
|
+
* Handles multiple classes per file, each with its own prefix.
|
|
71
|
+
*
|
|
72
|
+
* @param tree - tree-sitter parse tree
|
|
73
|
+
* @param filePath - relative file path (for `ExtractedDecoratorRoute.filePath`)
|
|
74
|
+
* @param lineOffset - line offset for pre-processing (usually 0)
|
|
75
|
+
* @returns Decorator routes with prefix already set per-class
|
|
76
|
+
*/
|
|
77
|
+
export function extractSpringRoutes(tree, filePath, lineOffset = 0) {
|
|
78
|
+
const matches = ROUTE_ANNOTATION_QUERY.matches(tree.rootNode);
|
|
79
|
+
// Phase 1: collect class-level @RequestMapping prefixes keyed by node id
|
|
80
|
+
const prefixByClassId = new Map();
|
|
81
|
+
for (const match of matches) {
|
|
82
|
+
const caps = {};
|
|
83
|
+
for (const { name, node } of match.captures) {
|
|
84
|
+
caps[name] = node;
|
|
85
|
+
}
|
|
86
|
+
const annNode = caps['ann'];
|
|
87
|
+
const node = caps['node'];
|
|
88
|
+
const valueNode = caps['value'];
|
|
89
|
+
const keyNode = caps['key'];
|
|
90
|
+
if (!annNode || !node || !valueNode)
|
|
91
|
+
continue;
|
|
92
|
+
if (node.type === 'class_declaration' && annNode.text === 'RequestMapping') {
|
|
93
|
+
if (!isRouteMemberKey(keyNode))
|
|
94
|
+
continue;
|
|
95
|
+
const prefix = unquoteSpringLiteral(valueNode.text);
|
|
96
|
+
if (prefix !== null)
|
|
97
|
+
prefixByClassId.set(node.id, prefix);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Phase 2: collect method-level routes and resolve their class prefix
|
|
101
|
+
const routes = [];
|
|
102
|
+
for (const match of matches) {
|
|
103
|
+
const caps = {};
|
|
104
|
+
for (const { name, node } of match.captures) {
|
|
105
|
+
caps[name] = node;
|
|
106
|
+
}
|
|
107
|
+
const annNode = caps['ann'];
|
|
108
|
+
const node = caps['node'];
|
|
109
|
+
const valueNode = caps['value'];
|
|
110
|
+
const keyNode = caps['key'];
|
|
111
|
+
if (!annNode || !node || !valueNode)
|
|
112
|
+
continue;
|
|
113
|
+
if (node.type !== 'method_declaration')
|
|
114
|
+
continue;
|
|
115
|
+
const ann = annNode.text;
|
|
116
|
+
const httpMethod = METHOD_ANNOTATION_TO_HTTP[ann];
|
|
117
|
+
if (!httpMethod)
|
|
118
|
+
continue; // skip @RequestMapping on methods (ambiguous verb)
|
|
119
|
+
if (!isRouteMemberKey(keyNode))
|
|
120
|
+
continue;
|
|
121
|
+
const routePath = unquoteSpringLiteral(valueNode.text);
|
|
122
|
+
if (routePath === null)
|
|
123
|
+
continue;
|
|
124
|
+
const enclosingClass = findEnclosingClass(node);
|
|
125
|
+
const classPrefix = enclosingClass ? (prefixByClassId.get(enclosingClass.id) ?? '') : '';
|
|
126
|
+
routes.push({
|
|
127
|
+
filePath,
|
|
128
|
+
routePath,
|
|
129
|
+
httpMethod,
|
|
130
|
+
decoratorName: ann,
|
|
131
|
+
lineNumber: annNode.startPosition.row + lineOffset,
|
|
132
|
+
...(classPrefix ? { prefix: classPrefix } : {}),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return routes;
|
|
136
|
+
}
|
|
@@ -678,6 +678,7 @@ const ROUTE_DECORATOR_NAMES = new Set([
|
|
|
678
678
|
'PostMapping',
|
|
679
679
|
'PutMapping',
|
|
680
680
|
'DeleteMapping',
|
|
681
|
+
'PatchMapping',
|
|
681
682
|
]);
|
|
682
683
|
// ============================================================================
|
|
683
684
|
// ORM Query Detection (Prisma + Supabase)
|
|
@@ -1743,6 +1744,15 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1743
1744
|
if (language === SupportedLanguages.Python) {
|
|
1744
1745
|
extractFastAPIRouterBindings(file.path, parseContent, result.routerIncludes, result.routerImports, (result.routerModuleAliases ??= []));
|
|
1745
1746
|
}
|
|
1747
|
+
// Language-specific decorator route extraction via provider hook.
|
|
1748
|
+
// The provider's extractDecoratorRoutes walks the AST for framework-specific
|
|
1749
|
+
// route patterns (e.g., Java Spring class-level prefix joining). Routes are
|
|
1750
|
+
// appended to decoratorRoutes for the routes phase to emit as Route nodes.
|
|
1751
|
+
if (provider.extractDecoratorRoutes) {
|
|
1752
|
+
const frameworkRoutes = provider.extractDecoratorRoutes(tree, file.path, lineOffset);
|
|
1753
|
+
for (const r of frameworkRoutes)
|
|
1754
|
+
result.decoratorRoutes.push(r);
|
|
1755
|
+
}
|
|
1746
1756
|
// Vue: emit CALLS edges for components used in <template>
|
|
1747
1757
|
if (language === SupportedLanguages.Vue) {
|
|
1748
1758
|
const templateComponents = extractTemplateComponents(file.content);
|
package/package.json
CHANGED