adl-toolkit 0.0.0-stage → 0.1.1
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/INSTALL_TOOLKIT.md +163 -0
- package/README.md +269 -2
- package/adl_drift/__init__.py +6 -0
- package/adl_drift/cli/__init__.py +1 -0
- package/adl_drift/cli/commands.py +993 -0
- package/adl_drift/cli/main.py +744 -0
- package/adl_drift/cortex/__init__.py +1 -0
- package/adl_drift/cortex/embeddings.py +96 -0
- package/adl_drift/cortex/memory.py +1749 -0
- package/adl_drift/cortex/patterns.py +89 -0
- package/adl_drift/cortex/telemetry.py +192 -0
- package/adl_drift/detectors/__init__.py +1 -0
- package/adl_drift/detectors/async_patterns.py +115 -0
- package/adl_drift/detectors/base.py +121 -0
- package/adl_drift/detectors/config.py +122 -0
- package/adl_drift/detectors/data_access.py +128 -0
- package/adl_drift/detectors/decorators.py +122 -0
- package/adl_drift/detectors/dynamic_patterns.py +260 -0
- package/adl_drift/detectors/errors.py +134 -0
- package/adl_drift/detectors/imports.py +132 -0
- package/adl_drift/detectors/logging_patterns.py +108 -0
- package/adl_drift/detectors/routes.py +257 -0
- package/adl_drift/detectors/security.py +236 -0
- package/adl_drift/detectors/taint.py +406 -0
- package/adl_drift/detectors/tests.py +95 -0
- package/adl_drift/detectors/type_conventions.py +81 -0
- package/adl_drift/graph/__init__.py +1 -0
- package/adl_drift/graph/builder.py +2670 -0
- package/adl_drift/graph/cfg.py +461 -0
- package/adl_drift/graph/cluster.py +40 -0
- package/adl_drift/graph/edges.py +86 -0
- package/adl_drift/graph/node.py +90 -0
- package/adl_drift/harness/__init__.py +10 -0
- package/adl_drift/harness/setup.py +832 -0
- package/adl_drift/harness/skills/drift-antigravity.md +19 -0
- package/adl_drift/harness/skills/drift-generic.md +19 -0
- package/adl_drift/harness/skills/drift.md +25 -0
- package/adl_drift/harness/watch.py +84 -0
- package/adl_drift/lang/__init__.py +8 -0
- package/adl_drift/lang/base.py +464 -0
- package/adl_drift/lang/java/__init__.py +6 -0
- package/adl_drift/lang/java/backend.py +97 -0
- package/adl_drift/lang/java/cfg.py +1424 -0
- package/adl_drift/lang/java/config.py +707 -0
- package/adl_drift/lang/java/detectors/__init__.py +29 -0
- package/adl_drift/lang/java/detectors/additional.py +445 -0
- package/adl_drift/lang/java/detectors/base.py +154 -0
- package/adl_drift/lang/java/detectors/data_access.py +148 -0
- package/adl_drift/lang/java/detectors/imports.py +53 -0
- package/adl_drift/lang/java/detectors/routes.py +159 -0
- package/adl_drift/lang/java/detectors/security.py +386 -0
- package/adl_drift/lang/java/detectors/tests.py +74 -0
- package/adl_drift/lang/java/extractor.py +582 -0
- package/adl_drift/lang/java/ids.py +99 -0
- package/adl_drift/lang/java/parser.py +49 -0
- package/adl_drift/lang/java/resolution/__init__.py +1 -0
- package/adl_drift/lang/java/resolution/constants.py +1619 -0
- package/adl_drift/lang/java/resolution/constructors.py +278 -0
- package/adl_drift/lang/java/resolution/context.py +305 -0
- package/adl_drift/lang/java/resolution/dispatch.py +540 -0
- package/adl_drift/lang/java/resolution/functional.py +1318 -0
- package/adl_drift/lang/java/resolution/generated.py +95 -0
- package/adl_drift/lang/java/resolution/model.py +153 -0
- package/adl_drift/lang/java/resolution/orchestration.py +866 -0
- package/adl_drift/lang/java/resolution/overloads.py +1275 -0
- package/adl_drift/lang/java/resolution/scopes.py +1111 -0
- package/adl_drift/lang/java/resolution/types.py +2947 -0
- package/adl_drift/lang/java/resolver.py +1401 -0
- package/adl_drift/lang/java/scopes.py +69 -0
- package/adl_drift/lang/java/taint.py +500 -0
- package/adl_drift/lang/java/type_provenance.py +342 -0
- package/adl_drift/lang/python/__init__.py +8 -0
- package/adl_drift/lang/python/backend.py +94 -0
- package/adl_drift/lang/python/cfg.py +537 -0
- package/adl_drift/lang/python/extractor.py +454 -0
- package/adl_drift/lang/python/parser.py +41 -0
- package/adl_drift/lang/python/resolver.py +1789 -0
- package/adl_drift/lang/python/taint.py +365 -0
- package/adl_drift/lang/registry.py +117 -0
- package/adl_drift/parser/__init__.py +1 -0
- package/adl_drift/parser/cache.py +38 -0
- package/adl_drift/parser/core.py +33 -0
- package/adl_drift/parser/extractor.py +17 -0
- package/adl_drift/parser/literal_eval.py +326 -0
- package/adl_drift/scanner/__init__.py +1 -0
- package/adl_drift/scanner/discovery.py +338 -0
- package/adl_drift/scanner/incremental.py +261 -0
- package/adl_drift/scanner/parallel.py +224 -0
- package/adl_drift/server/__init__.py +1 -0
- package/adl_drift/server/handlers.py +2849 -0
- package/adl_drift/server/mcp_server.py +127 -0
- package/adl_drift/server/mcp_stdio.py +335 -0
- package/adl_drift/server/protocol.py +71 -0
- package/adl_drift/server/static/index.html +696 -0
- package/adl_drift/server/web.py +350 -0
- package/adl_drift/utils/__init__.py +1 -0
- package/adl_drift/utils/config.py +77 -0
- package/adl_drift/utils/fswalk.py +52 -0
- package/adl_drift/utils/types.py +345 -0
- package/bin/adl-drift.mjs +35 -0
- package/bin/adl-toolkit.mjs +488 -0
- package/package.json +29 -4
- package/pyproject.toml +49 -0
- package/scripts/install_toolkit.py +3584 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""JDBC, JPA/Hibernate, Spring Data, and MyBatis detector."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Optional, Set
|
|
7
|
+
|
|
8
|
+
from tree_sitter import Node, Tree
|
|
9
|
+
|
|
10
|
+
from adl_drift.lang.java.detectors.base import DeclaredTypes, JavaDetector
|
|
11
|
+
from adl_drift.lang.java.scopes import TypeScopes
|
|
12
|
+
from adl_drift.utils.types import Pattern, PatternCategory
|
|
13
|
+
|
|
14
|
+
JDBC_TYPES = {"Statement", "PreparedStatement", "CallableStatement", "Connection"}
|
|
15
|
+
JDBC_STATEMENT_TYPES = {"Statement", "PreparedStatement", "CallableStatement"}
|
|
16
|
+
JPA_METHODS = {"createQuery", "createNativeQuery", "persist", "merge", "remove", "find", "save"}
|
|
17
|
+
MYBATIS_METHODS = {"selectOne", "selectList", "insert", "update", "delete"}
|
|
18
|
+
PERSISTENCE_PACKAGES = ("jakarta.persistence", "javax.persistence")
|
|
19
|
+
MYBATIS_PACKAGE = "org.apache.ibatis.annotations"
|
|
20
|
+
MYBATIS_SESSION_TYPES = {
|
|
21
|
+
"SqlSession": "org.apache.ibatis.session.SqlSession",
|
|
22
|
+
"SqlSessionTemplate": "org.mybatis.spring.SqlSessionTemplate",
|
|
23
|
+
}
|
|
24
|
+
JDBC_TYPE_NAMES = {name: f"java.sql.{name}" for name in JDBC_TYPES}
|
|
25
|
+
JPA_TYPE_NAMES = {
|
|
26
|
+
"EntityManager": ("jakarta.persistence.EntityManager", "javax.persistence.EntityManager"),
|
|
27
|
+
"Session": ("org.hibernate.Session",),
|
|
28
|
+
"StatelessSession": ("org.hibernate.StatelessSession",),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class JavaDataAccessDetector(JavaDetector):
|
|
33
|
+
@property
|
|
34
|
+
def category(self) -> PatternCategory:
|
|
35
|
+
return PatternCategory.DATA_ACCESS
|
|
36
|
+
|
|
37
|
+
def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
|
|
38
|
+
root = tree.root_node
|
|
39
|
+
imports = self._imports(root, source_bytes)
|
|
40
|
+
types = self._declared_types(root, source_bytes)
|
|
41
|
+
local_types = self._declared_type_names(root, source_bytes)
|
|
42
|
+
patterns: List[Pattern] = []
|
|
43
|
+
for node in self._walk(root):
|
|
44
|
+
if node.type == "method_invocation":
|
|
45
|
+
pattern = self._call(
|
|
46
|
+
node, source_bytes, file_path, types, imports, local_types
|
|
47
|
+
)
|
|
48
|
+
if pattern:
|
|
49
|
+
patterns.append(pattern)
|
|
50
|
+
elif node.type in ("class_declaration", "interface_declaration"):
|
|
51
|
+
patterns.extend(self._declaration(node, source_bytes, file_path, imports))
|
|
52
|
+
elif node.type == "method_declaration":
|
|
53
|
+
pattern = self._mybatis_annotation(node, source_bytes, file_path, imports)
|
|
54
|
+
if pattern:
|
|
55
|
+
patterns.append(pattern)
|
|
56
|
+
return patterns
|
|
57
|
+
|
|
58
|
+
def _call(
|
|
59
|
+
self, node: Node, source: bytes, path: Path,
|
|
60
|
+
types: DeclaredTypes, imports: Set[str], local_types: TypeScopes,
|
|
61
|
+
) -> Optional[Pattern]:
|
|
62
|
+
method = self._text(node.child_by_field_name("name"), source)
|
|
63
|
+
receiver = self._text(node.child_by_field_name("object"), source)
|
|
64
|
+
raw_declared = self._declared_type(receiver, types, node)
|
|
65
|
+
declared = self._simple_type(raw_declared)
|
|
66
|
+
jdbc_type = JDBC_TYPE_NAMES.get(declared)
|
|
67
|
+
jdbc_method = (
|
|
68
|
+
declared in JDBC_STATEMENT_TYPES and method.startswith("execute")
|
|
69
|
+
) or (declared == "Connection" and method == "prepareStatement")
|
|
70
|
+
if jdbc_type and jdbc_method and self._type_has_provenance(
|
|
71
|
+
raw_declared, imports, local_types, jdbc_type, node
|
|
72
|
+
):
|
|
73
|
+
return self._finding("jdbc_operation", "jdbc", method, node, path, 0.95,
|
|
74
|
+
"JDBC receiver type and operation match")
|
|
75
|
+
jpa_type_names = JPA_TYPE_NAMES.get(declared, ())
|
|
76
|
+
if method in JPA_METHODS and any(
|
|
77
|
+
self._type_has_provenance(raw_declared, imports, local_types, qualified, node)
|
|
78
|
+
for qualified in jpa_type_names
|
|
79
|
+
):
|
|
80
|
+
return self._finding("jpa_operation", "jpa", method, node, path, 0.93,
|
|
81
|
+
"JPA/Hibernate receiver type and operation match")
|
|
82
|
+
mybatis_type = MYBATIS_SESSION_TYPES.get(declared)
|
|
83
|
+
if mybatis_type and method in MYBATIS_METHODS and self._type_has_provenance(
|
|
84
|
+
raw_declared, imports, local_types, mybatis_type, node
|
|
85
|
+
):
|
|
86
|
+
return self._finding("mybatis_operation", "mybatis", method, node, path, 0.93,
|
|
87
|
+
"MyBatis session receiver type and operation match")
|
|
88
|
+
return None
|
|
89
|
+
|
|
90
|
+
def _declaration(
|
|
91
|
+
self, node: Node, source: bytes, path: Path, imports: Set[str]
|
|
92
|
+
) -> List[Pattern]:
|
|
93
|
+
patterns: List[Pattern] = []
|
|
94
|
+
for annotation in self._annotations(node):
|
|
95
|
+
if any(self._annotation_is(annotation, source, imports, f"{p}.Entity")
|
|
96
|
+
for p in PERSISTENCE_PACKAGES):
|
|
97
|
+
patterns.append(self._finding(
|
|
98
|
+
"jpa_entity", "jpa", "entity", node, path, 0.98,
|
|
99
|
+
"Class is annotated with the imported JPA @Entity annotation",
|
|
100
|
+
))
|
|
101
|
+
if self._annotation_is(annotation, source, imports, f"{MYBATIS_PACKAGE}.Mapper"):
|
|
102
|
+
patterns.append(self._finding(
|
|
103
|
+
"mybatis_mapper", "mybatis", "mapper", node, path, 0.98,
|
|
104
|
+
"Type is annotated with MyBatis @Mapper",
|
|
105
|
+
))
|
|
106
|
+
|
|
107
|
+
repository_types = {
|
|
108
|
+
"JpaRepository": "org.springframework.data.jpa.repository.JpaRepository",
|
|
109
|
+
"CrudRepository": "org.springframework.data.repository.CrudRepository",
|
|
110
|
+
"PagingAndSortingRepository": "org.springframework.data.repository.PagingAndSortingRepository",
|
|
111
|
+
}
|
|
112
|
+
if node.type == "interface_declaration" and any(
|
|
113
|
+
f"{marker}<" in self._text(node, source)
|
|
114
|
+
and self._has_import(imports, qualified)
|
|
115
|
+
for marker, qualified in repository_types.items()
|
|
116
|
+
):
|
|
117
|
+
patterns.append(self._finding(
|
|
118
|
+
"spring_data_repository", "spring-data", "repository", node, path, 0.96,
|
|
119
|
+
"Interface extends a Spring Data repository contract",
|
|
120
|
+
))
|
|
121
|
+
return patterns
|
|
122
|
+
|
|
123
|
+
def _mybatis_annotation(
|
|
124
|
+
self, node: Node, source: bytes, path: Path, imports: Set[str]
|
|
125
|
+
) -> Optional[Pattern]:
|
|
126
|
+
for annotation in self._annotations(node):
|
|
127
|
+
name = self._annotation_name(annotation, source).rsplit(".", 1)[-1]
|
|
128
|
+
if name in {"Select", "Insert", "Update", "Delete"} and self._annotation_is(
|
|
129
|
+
annotation, source, imports, f"{MYBATIS_PACKAGE}.{name}"
|
|
130
|
+
):
|
|
131
|
+
return self._finding(
|
|
132
|
+
"mybatis_statement", "mybatis", name.lower(), node, path, 0.98,
|
|
133
|
+
f"Method uses imported MyBatis @{name} SQL annotation",
|
|
134
|
+
)
|
|
135
|
+
return None
|
|
136
|
+
|
|
137
|
+
def _finding(
|
|
138
|
+
self, name: str, framework: str, operation: str, node: Node,
|
|
139
|
+
path: Path, confidence: float, reason: str,
|
|
140
|
+
) -> Pattern:
|
|
141
|
+
return self._make_pattern(
|
|
142
|
+
name=name,
|
|
143
|
+
description=f"{framework} data access: {operation}",
|
|
144
|
+
node=node,
|
|
145
|
+
file_path=path,
|
|
146
|
+
confidence=confidence,
|
|
147
|
+
metadata={"framework": framework, "operation": operation, "reason": reason},
|
|
148
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""Java import classification detector."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
from tree_sitter import Tree
|
|
10
|
+
|
|
11
|
+
from adl_drift.lang.java.detectors.base import JavaDetector
|
|
12
|
+
from adl_drift.utils.types import Pattern, PatternCategory
|
|
13
|
+
|
|
14
|
+
IMPORT_RE = re.compile(r"import\s+(static\s+)?([\w.]+)(\.\*)?\s*;")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class JavaImportDetector(JavaDetector):
|
|
18
|
+
@property
|
|
19
|
+
def category(self) -> PatternCategory:
|
|
20
|
+
return PatternCategory.IMPORTS
|
|
21
|
+
|
|
22
|
+
def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
|
|
23
|
+
patterns: List[Pattern] = []
|
|
24
|
+
for node in tree.root_node.named_children:
|
|
25
|
+
if node.type != "import_declaration":
|
|
26
|
+
continue
|
|
27
|
+
match = IMPORT_RE.fullmatch(self._text(node, source_bytes))
|
|
28
|
+
if not match:
|
|
29
|
+
continue
|
|
30
|
+
is_static = bool(match.group(1))
|
|
31
|
+
module = match.group(2)
|
|
32
|
+
is_wildcard = bool(match.group(3))
|
|
33
|
+
import_type = "jdk" if module.startswith(("java.", "javax.", "jakarta.")) else "third_party"
|
|
34
|
+
reason = (
|
|
35
|
+
"Import belongs to the JDK/Jakarta namespace"
|
|
36
|
+
if import_type == "jdk"
|
|
37
|
+
else "Import is outside the JDK/Jakarta namespace"
|
|
38
|
+
)
|
|
39
|
+
patterns.append(self._make_pattern(
|
|
40
|
+
name="wildcard_import" if is_wildcard else f"import:{import_type}",
|
|
41
|
+
description=f"Java {import_type} import: {module}",
|
|
42
|
+
node=node,
|
|
43
|
+
file_path=file_path,
|
|
44
|
+
confidence=0.99,
|
|
45
|
+
metadata={
|
|
46
|
+
"import_type": import_type,
|
|
47
|
+
"module": module,
|
|
48
|
+
"is_static": is_static,
|
|
49
|
+
"is_wildcard": is_wildcard,
|
|
50
|
+
"reason": reason,
|
|
51
|
+
},
|
|
52
|
+
))
|
|
53
|
+
return patterns
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
"""Spring MVC and JAX-RS route detector."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import List, Optional, Set, Tuple
|
|
8
|
+
|
|
9
|
+
from tree_sitter import Node, Tree
|
|
10
|
+
|
|
11
|
+
from adl_drift.lang.java.detectors.base import JavaDetector
|
|
12
|
+
from adl_drift.lang.java.extractor import JavaSymbolExtractor
|
|
13
|
+
from adl_drift.utils.types import Pattern, PatternCategory
|
|
14
|
+
|
|
15
|
+
SPRING_PACKAGE = "org.springframework.web.bind.annotation"
|
|
16
|
+
JAX_PACKAGES = ("jakarta.ws.rs", "javax.ws.rs")
|
|
17
|
+
SPRING_METHODS = {
|
|
18
|
+
"GetMapping": "GET",
|
|
19
|
+
"PostMapping": "POST",
|
|
20
|
+
"PutMapping": "PUT",
|
|
21
|
+
"PatchMapping": "PATCH",
|
|
22
|
+
"DeleteMapping": "DELETE",
|
|
23
|
+
}
|
|
24
|
+
JAX_METHODS = {name: name for name in ("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS")}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class JavaRouteDetector(JavaDetector):
|
|
28
|
+
@property
|
|
29
|
+
def category(self) -> PatternCategory:
|
|
30
|
+
return PatternCategory.ROUTES
|
|
31
|
+
|
|
32
|
+
def detect(self, tree: Tree, source_bytes: bytes, file_path: Path) -> List[Pattern]:
|
|
33
|
+
root = tree.root_node
|
|
34
|
+
imports = self._imports(root, source_bytes)
|
|
35
|
+
extractor = JavaSymbolExtractor(self.parser)
|
|
36
|
+
anon_index = extractor._index_anonymous_classes(root)
|
|
37
|
+
patterns: List[Pattern] = []
|
|
38
|
+
|
|
39
|
+
for method in self._walk(root):
|
|
40
|
+
if method.type != "method_declaration":
|
|
41
|
+
continue
|
|
42
|
+
owner = self._enclosing_class(method)
|
|
43
|
+
class_annotations = self._annotations(owner) if owner is not None else []
|
|
44
|
+
method_annotations = self._annotations(method)
|
|
45
|
+
|
|
46
|
+
routes = self._spring_routes(
|
|
47
|
+
class_annotations, method_annotations, source_bytes, imports
|
|
48
|
+
)
|
|
49
|
+
if not routes:
|
|
50
|
+
jax_route = self._jax_route(
|
|
51
|
+
class_annotations, method_annotations, source_bytes, imports
|
|
52
|
+
)
|
|
53
|
+
routes = [jax_route] if jax_route is not None else []
|
|
54
|
+
if not routes:
|
|
55
|
+
continue
|
|
56
|
+
|
|
57
|
+
method_symbol = extractor._extract_method(
|
|
58
|
+
method, source_bytes, file_path, anon_index
|
|
59
|
+
)
|
|
60
|
+
if method_symbol is None:
|
|
61
|
+
continue
|
|
62
|
+
handler_id = self._handler_id(
|
|
63
|
+
self._package(root, source_bytes), method_symbol.scope_chain,
|
|
64
|
+
method_symbol.name, method_symbol.signature_key or "()",
|
|
65
|
+
)
|
|
66
|
+
for framework, http_method, class_path, method_path in routes:
|
|
67
|
+
route_path = self._join_paths(class_path, method_path)
|
|
68
|
+
reason = f"{framework} route annotation on method {method_symbol.name}"
|
|
69
|
+
patterns.append(self._make_pattern(
|
|
70
|
+
name=f"route:{http_method}",
|
|
71
|
+
description=f"{framework} {http_method} route: {route_path}",
|
|
72
|
+
node=method,
|
|
73
|
+
file_path=file_path,
|
|
74
|
+
confidence=0.98,
|
|
75
|
+
metadata={
|
|
76
|
+
"framework": framework,
|
|
77
|
+
"http_method": http_method,
|
|
78
|
+
"route_path": route_path,
|
|
79
|
+
"class_path": class_path,
|
|
80
|
+
"method_path": method_path,
|
|
81
|
+
"handler": method_symbol.name,
|
|
82
|
+
"handler_id": handler_id,
|
|
83
|
+
"entry_point": True,
|
|
84
|
+
"reason": reason,
|
|
85
|
+
},
|
|
86
|
+
))
|
|
87
|
+
return patterns
|
|
88
|
+
|
|
89
|
+
def _spring_routes(
|
|
90
|
+
self, class_annotations: List[Node], method_annotations: List[Node],
|
|
91
|
+
source: bytes, imports: Set[str],
|
|
92
|
+
) -> List[Tuple[str, str, str, str]]:
|
|
93
|
+
class_path = ""
|
|
94
|
+
for annotation in class_annotations:
|
|
95
|
+
if self._annotation_is(annotation, source, imports, f"{SPRING_PACKAGE}.RequestMapping"):
|
|
96
|
+
class_path = self._path(annotation, source)
|
|
97
|
+
|
|
98
|
+
for annotation in method_annotations:
|
|
99
|
+
name = self._annotation_name(annotation, source).rsplit(".", 1)[-1]
|
|
100
|
+
if name in SPRING_METHODS and self._annotation_is(
|
|
101
|
+
annotation, source, imports, f"{SPRING_PACKAGE}.{name}"
|
|
102
|
+
):
|
|
103
|
+
return [("spring", SPRING_METHODS[name], class_path, self._path(annotation, source))]
|
|
104
|
+
if name == "RequestMapping" and self._annotation_is(
|
|
105
|
+
annotation, source, imports, f"{SPRING_PACKAGE}.RequestMapping"
|
|
106
|
+
):
|
|
107
|
+
methods = re.findall(
|
|
108
|
+
r"RequestMethod\.(GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS)",
|
|
109
|
+
self._text(annotation, source),
|
|
110
|
+
)
|
|
111
|
+
return [
|
|
112
|
+
("spring", method, class_path, self._path(annotation, source))
|
|
113
|
+
for method in methods or ["ANY"]
|
|
114
|
+
]
|
|
115
|
+
return []
|
|
116
|
+
|
|
117
|
+
def _jax_route(
|
|
118
|
+
self, class_annotations: List[Node], method_annotations: List[Node],
|
|
119
|
+
source: bytes, imports: Set[str],
|
|
120
|
+
) -> Optional[Tuple[str, str, str, str]]:
|
|
121
|
+
class_path = self._jax_path(class_annotations, source, imports)
|
|
122
|
+
method_path = self._jax_path(method_annotations, source, imports)
|
|
123
|
+
for annotation in method_annotations:
|
|
124
|
+
name = self._annotation_name(annotation, source).rsplit(".", 1)[-1]
|
|
125
|
+
for package in JAX_PACKAGES:
|
|
126
|
+
if name in JAX_METHODS and self._annotation_is(
|
|
127
|
+
annotation, source, imports, f"{package}.{name}"
|
|
128
|
+
):
|
|
129
|
+
return "jax-rs", JAX_METHODS[name], class_path, method_path
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def _jax_path(self, annotations: List[Node], source: bytes, imports: Set[str]) -> str:
|
|
133
|
+
for annotation in annotations:
|
|
134
|
+
if any(self._annotation_is(annotation, source, imports, f"{p}.Path") for p in JAX_PACKAGES):
|
|
135
|
+
return self._path(annotation, source)
|
|
136
|
+
return ""
|
|
137
|
+
|
|
138
|
+
def _path(self, annotation: Node, source: bytes) -> str:
|
|
139
|
+
strings = self._annotation_strings(annotation, source)
|
|
140
|
+
return strings[0] if strings else ""
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def _enclosing_class(node: Node) -> Optional[Node]:
|
|
144
|
+
parent = node.parent
|
|
145
|
+
while parent is not None and parent.type not in (
|
|
146
|
+
"class_declaration", "interface_declaration", "record_declaration"
|
|
147
|
+
):
|
|
148
|
+
parent = parent.parent
|
|
149
|
+
return parent
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def _join_paths(class_path: str, method_path: str) -> str:
|
|
153
|
+
parts = [part.strip("/") for part in (class_path, method_path) if part.strip("/")]
|
|
154
|
+
return "/" + "/".join(parts) if parts else "/"
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def _handler_id(package: str, scope: List[str], name: str, signature: str) -> str:
|
|
158
|
+
qualified = ".".join([*scope, f"{name}{signature}"])
|
|
159
|
+
return f"{package}:{qualified}"
|