osscodeiq 0.0.0__py3-none-any.whl
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.
- osscodeiq/__init__.py +0 -0
- osscodeiq/analyzer.py +467 -0
- osscodeiq/cache/__init__.py +0 -0
- osscodeiq/cache/hasher.py +23 -0
- osscodeiq/cache/store.py +300 -0
- osscodeiq/classifiers/__init__.py +0 -0
- osscodeiq/classifiers/layer_classifier.py +69 -0
- osscodeiq/cli.py +721 -0
- osscodeiq/config.py +113 -0
- osscodeiq/detectors/__init__.py +0 -0
- osscodeiq/detectors/auth/__init__.py +0 -0
- osscodeiq/detectors/auth/certificate_auth.py +139 -0
- osscodeiq/detectors/auth/ldap_auth.py +89 -0
- osscodeiq/detectors/auth/session_header_auth.py +120 -0
- osscodeiq/detectors/base.py +41 -0
- osscodeiq/detectors/config/__init__.py +0 -0
- osscodeiq/detectors/config/batch_structure.py +128 -0
- osscodeiq/detectors/config/cloudformation.py +183 -0
- osscodeiq/detectors/config/docker_compose.py +179 -0
- osscodeiq/detectors/config/github_actions.py +150 -0
- osscodeiq/detectors/config/gitlab_ci.py +216 -0
- osscodeiq/detectors/config/helm_chart.py +187 -0
- osscodeiq/detectors/config/ini_structure.py +101 -0
- osscodeiq/detectors/config/json_structure.py +72 -0
- osscodeiq/detectors/config/kubernetes.py +305 -0
- osscodeiq/detectors/config/kubernetes_rbac.py +212 -0
- osscodeiq/detectors/config/openapi.py +194 -0
- osscodeiq/detectors/config/package_json.py +99 -0
- osscodeiq/detectors/config/properties_detector.py +108 -0
- osscodeiq/detectors/config/pyproject_toml.py +169 -0
- osscodeiq/detectors/config/sql_structure.py +155 -0
- osscodeiq/detectors/config/toml_structure.py +93 -0
- osscodeiq/detectors/config/tsconfig_json.py +105 -0
- osscodeiq/detectors/config/yaml_structure.py +82 -0
- osscodeiq/detectors/cpp/__init__.py +0 -0
- osscodeiq/detectors/cpp/cpp_structures.py +192 -0
- osscodeiq/detectors/csharp/__init__.py +0 -0
- osscodeiq/detectors/csharp/csharp_efcore.py +184 -0
- osscodeiq/detectors/csharp/csharp_minimal_apis.py +156 -0
- osscodeiq/detectors/csharp/csharp_structures.py +317 -0
- osscodeiq/detectors/docs/__init__.py +0 -0
- osscodeiq/detectors/docs/markdown_structure.py +117 -0
- osscodeiq/detectors/frontend/__init__.py +0 -0
- osscodeiq/detectors/frontend/angular_components.py +177 -0
- osscodeiq/detectors/frontend/frontend_routes.py +259 -0
- osscodeiq/detectors/frontend/react_components.py +148 -0
- osscodeiq/detectors/frontend/svelte_components.py +84 -0
- osscodeiq/detectors/frontend/vue_components.py +150 -0
- osscodeiq/detectors/generic/__init__.py +1 -0
- osscodeiq/detectors/generic/imports_detector.py +413 -0
- osscodeiq/detectors/go/__init__.py +0 -0
- osscodeiq/detectors/go/go_orm.py +202 -0
- osscodeiq/detectors/go/go_structures.py +162 -0
- osscodeiq/detectors/go/go_web.py +157 -0
- osscodeiq/detectors/iac/__init__.py +0 -0
- osscodeiq/detectors/iac/bicep.py +135 -0
- osscodeiq/detectors/iac/dockerfile.py +182 -0
- osscodeiq/detectors/iac/terraform.py +188 -0
- osscodeiq/detectors/java/__init__.py +0 -0
- osscodeiq/detectors/java/azure_functions.py +424 -0
- osscodeiq/detectors/java/azure_messaging.py +350 -0
- osscodeiq/detectors/java/class_hierarchy.py +349 -0
- osscodeiq/detectors/java/config_def.py +82 -0
- osscodeiq/detectors/java/cosmos_db.py +105 -0
- osscodeiq/detectors/java/graphql_resolver.py +188 -0
- osscodeiq/detectors/java/grpc_service.py +142 -0
- osscodeiq/detectors/java/ibm_mq.py +178 -0
- osscodeiq/detectors/java/jaxrs.py +160 -0
- osscodeiq/detectors/java/jdbc.py +196 -0
- osscodeiq/detectors/java/jms.py +116 -0
- osscodeiq/detectors/java/jpa_entity.py +143 -0
- osscodeiq/detectors/java/kafka.py +113 -0
- osscodeiq/detectors/java/kafka_protocol.py +70 -0
- osscodeiq/detectors/java/micronaut.py +248 -0
- osscodeiq/detectors/java/module_deps.py +191 -0
- osscodeiq/detectors/java/public_api.py +206 -0
- osscodeiq/detectors/java/quarkus.py +176 -0
- osscodeiq/detectors/java/rabbitmq.py +150 -0
- osscodeiq/detectors/java/raw_sql.py +136 -0
- osscodeiq/detectors/java/repository.py +131 -0
- osscodeiq/detectors/java/rmi.py +129 -0
- osscodeiq/detectors/java/spring_events.py +117 -0
- osscodeiq/detectors/java/spring_rest.py +168 -0
- osscodeiq/detectors/java/spring_security.py +212 -0
- osscodeiq/detectors/java/tibco_ems.py +193 -0
- osscodeiq/detectors/java/websocket.py +188 -0
- osscodeiq/detectors/kotlin/__init__.py +0 -0
- osscodeiq/detectors/kotlin/kotlin_structures.py +124 -0
- osscodeiq/detectors/kotlin/ktor_routes.py +163 -0
- osscodeiq/detectors/proto/__init__.py +0 -0
- osscodeiq/detectors/proto/proto_structure.py +153 -0
- osscodeiq/detectors/python/__init__.py +0 -0
- osscodeiq/detectors/python/celery_tasks.py +88 -0
- osscodeiq/detectors/python/django_auth.py +132 -0
- osscodeiq/detectors/python/django_models.py +157 -0
- osscodeiq/detectors/python/django_views.py +74 -0
- osscodeiq/detectors/python/fastapi_auth.py +143 -0
- osscodeiq/detectors/python/fastapi_routes.py +68 -0
- osscodeiq/detectors/python/flask_routes.py +67 -0
- osscodeiq/detectors/python/kafka_python.py +175 -0
- osscodeiq/detectors/python/pydantic_models.py +115 -0
- osscodeiq/detectors/python/python_structures.py +234 -0
- osscodeiq/detectors/python/sqlalchemy_models.py +82 -0
- osscodeiq/detectors/registry.py +100 -0
- osscodeiq/detectors/rust/__init__.py +0 -0
- osscodeiq/detectors/rust/actix_web.py +234 -0
- osscodeiq/detectors/rust/rust_structures.py +174 -0
- osscodeiq/detectors/scala/__init__.py +0 -0
- osscodeiq/detectors/scala/scala_structures.py +128 -0
- osscodeiq/detectors/shell/__init__.py +0 -0
- osscodeiq/detectors/shell/bash_detector.py +127 -0
- osscodeiq/detectors/shell/powershell_detector.py +118 -0
- osscodeiq/detectors/typescript/__init__.py +0 -0
- osscodeiq/detectors/typescript/express_routes.py +55 -0
- osscodeiq/detectors/typescript/fastify_routes.py +156 -0
- osscodeiq/detectors/typescript/graphql_resolvers.py +100 -0
- osscodeiq/detectors/typescript/kafka_js.py +164 -0
- osscodeiq/detectors/typescript/mongoose_orm.py +151 -0
- osscodeiq/detectors/typescript/nestjs_controllers.py +99 -0
- osscodeiq/detectors/typescript/nestjs_guards.py +138 -0
- osscodeiq/detectors/typescript/passport_jwt.py +133 -0
- osscodeiq/detectors/typescript/prisma_orm.py +96 -0
- osscodeiq/detectors/typescript/remix_routes.py +160 -0
- osscodeiq/detectors/typescript/sequelize_orm.py +136 -0
- osscodeiq/detectors/typescript/typeorm_entities.py +86 -0
- osscodeiq/detectors/typescript/typescript_structures.py +185 -0
- osscodeiq/detectors/utils.py +49 -0
- osscodeiq/discovery/__init__.py +11 -0
- osscodeiq/discovery/change_detector.py +97 -0
- osscodeiq/discovery/file_discovery.py +342 -0
- osscodeiq/flow/__init__.py +0 -0
- osscodeiq/flow/engine.py +78 -0
- osscodeiq/flow/models.py +72 -0
- osscodeiq/flow/renderer.py +127 -0
- osscodeiq/flow/templates/interactive.html +252 -0
- osscodeiq/flow/vendor/cytoscape-dagre.min.js +8 -0
- osscodeiq/flow/vendor/cytoscape.min.js +32 -0
- osscodeiq/flow/vendor/dagre.min.js +3809 -0
- osscodeiq/flow/views.py +357 -0
- osscodeiq/graph/__init__.py +0 -0
- osscodeiq/graph/backend.py +52 -0
- osscodeiq/graph/backends/__init__.py +23 -0
- osscodeiq/graph/backends/kuzu.py +576 -0
- osscodeiq/graph/backends/networkx.py +135 -0
- osscodeiq/graph/backends/sqlite_backend.py +406 -0
- osscodeiq/graph/builder.py +297 -0
- osscodeiq/graph/query.py +228 -0
- osscodeiq/graph/store.py +183 -0
- osscodeiq/graph/views.py +231 -0
- osscodeiq/models/__init__.py +17 -0
- osscodeiq/models/graph.py +116 -0
- osscodeiq/output/__init__.py +0 -0
- osscodeiq/output/dot.py +171 -0
- osscodeiq/output/mermaid.py +160 -0
- osscodeiq/output/safety.py +58 -0
- osscodeiq/output/serializers.py +42 -0
- osscodeiq/parsing/__init__.py +5 -0
- osscodeiq/parsing/languages/__init__.py +0 -0
- osscodeiq/parsing/languages/base.py +23 -0
- osscodeiq/parsing/languages/java.py +68 -0
- osscodeiq/parsing/languages/python.py +57 -0
- osscodeiq/parsing/languages/typescript.py +95 -0
- osscodeiq/parsing/parser_manager.py +125 -0
- osscodeiq/parsing/structured/__init__.py +0 -0
- osscodeiq/parsing/structured/gradle_parser.py +78 -0
- osscodeiq/parsing/structured/json_parser.py +24 -0
- osscodeiq/parsing/structured/properties_parser.py +56 -0
- osscodeiq/parsing/structured/sql_parser.py +54 -0
- osscodeiq/parsing/structured/xml_parser.py +148 -0
- osscodeiq/parsing/structured/yaml_parser.py +38 -0
- osscodeiq/server/__init__.py +7 -0
- osscodeiq/server/app.py +53 -0
- osscodeiq/server/mcp_server.py +174 -0
- osscodeiq/server/middleware.py +16 -0
- osscodeiq/server/routes.py +184 -0
- osscodeiq/server/service.py +445 -0
- osscodeiq/server/templates/welcome.html +56 -0
- osscodeiq-0.0.0.dist-info/METADATA +30 -0
- osscodeiq-0.0.0.dist-info/RECORD +183 -0
- osscodeiq-0.0.0.dist-info/WHEEL +5 -0
- osscodeiq-0.0.0.dist-info/entry_points.txt +2 -0
- osscodeiq-0.0.0.dist-info/licenses/LICENSE +21 -0
- osscodeiq-0.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
"""Public API method detector for Java source files using tree-sitter AST."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.models.graph import (
|
|
9
|
+
EdgeKind,
|
|
10
|
+
GraphEdge,
|
|
11
|
+
GraphNode,
|
|
12
|
+
NodeKind,
|
|
13
|
+
SourceLocation,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
# Methods that are boilerplate / Object overrides -- skip them.
|
|
17
|
+
_SKIP_METHODS = frozenset({"toString", "hashCode", "equals", "clone", "finalize"})
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _is_trivial_accessor(name: str, param_count: int, body_lines: int) -> bool:
|
|
21
|
+
"""Return True for simple getters/setters (get*/set*/is* with <=1 param, <=1 body line)."""
|
|
22
|
+
if param_count > 1:
|
|
23
|
+
return False
|
|
24
|
+
if body_lines > 1:
|
|
25
|
+
return False
|
|
26
|
+
if name.startswith("get") or name.startswith("set") or name.startswith("is"):
|
|
27
|
+
return True
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _body_line_count(method_node) -> int: # type: ignore[no-untyped-def]
|
|
32
|
+
"""Count the number of non-empty lines inside the method body (excluding braces)."""
|
|
33
|
+
body = method_node.child_by_field_name("body")
|
|
34
|
+
if body is None:
|
|
35
|
+
return 0
|
|
36
|
+
start_line = body.start_point[0]
|
|
37
|
+
end_line = body.end_point[0]
|
|
38
|
+
# Subtract the lines with opening/closing braces themselves.
|
|
39
|
+
return max(0, end_line - start_line - 1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _param_types(params_node) -> list[str]: # type: ignore[no-untyped-def]
|
|
43
|
+
"""Extract simple type names from a formal_parameters node."""
|
|
44
|
+
types: list[str] = []
|
|
45
|
+
if params_node is None:
|
|
46
|
+
return types
|
|
47
|
+
for child in params_node.children:
|
|
48
|
+
if child.type == "formal_parameter":
|
|
49
|
+
type_node = child.child_by_field_name("type")
|
|
50
|
+
if type_node is not None:
|
|
51
|
+
types.append(type_node.text.decode("utf-8", errors="replace"))
|
|
52
|
+
return types
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _find_child_by_type(node, type_name: str): # type: ignore[no-untyped-def]
|
|
56
|
+
"""Find the first child of *node* with the given type (by iterating children)."""
|
|
57
|
+
for child in node.children:
|
|
58
|
+
if child.type == type_name:
|
|
59
|
+
return child
|
|
60
|
+
return None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _has_modifier(modifiers_node, modifier_text: str) -> bool: # type: ignore[no-untyped-def]
|
|
64
|
+
"""Check whether *modifiers_node* contains a specific keyword like 'public' or 'static'."""
|
|
65
|
+
if modifiers_node is None:
|
|
66
|
+
return False
|
|
67
|
+
for child in modifiers_node.children:
|
|
68
|
+
if child.text is not None and child.text.decode("utf-8", errors="replace") == modifier_text:
|
|
69
|
+
return True
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class PublicApiDetector:
|
|
74
|
+
"""Detects public and protected methods in Java classes and interfaces."""
|
|
75
|
+
|
|
76
|
+
name: str = "java.public_api"
|
|
77
|
+
supported_languages: tuple[str, ...] = ("java",)
|
|
78
|
+
|
|
79
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
80
|
+
result = DetectorResult()
|
|
81
|
+
|
|
82
|
+
if ctx.tree is None:
|
|
83
|
+
return result
|
|
84
|
+
|
|
85
|
+
root = ctx.tree.root_node
|
|
86
|
+
for child in root.children:
|
|
87
|
+
if child.type == "class_declaration":
|
|
88
|
+
self._process_class(child, ctx, result, is_interface=False)
|
|
89
|
+
elif child.type == "interface_declaration":
|
|
90
|
+
self._process_class(child, ctx, result, is_interface=True)
|
|
91
|
+
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
# ------------------------------------------------------------------
|
|
95
|
+
# Internal helpers
|
|
96
|
+
# ------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
def _process_class(
|
|
99
|
+
self,
|
|
100
|
+
class_node, # type: ignore[no-untyped-def]
|
|
101
|
+
ctx: DetectorContext,
|
|
102
|
+
result: DetectorResult,
|
|
103
|
+
*,
|
|
104
|
+
is_interface: bool,
|
|
105
|
+
) -> None:
|
|
106
|
+
name_node = class_node.child_by_field_name("name")
|
|
107
|
+
if name_node is None:
|
|
108
|
+
return
|
|
109
|
+
class_name: str = name_node.text.decode("utf-8", errors="replace")
|
|
110
|
+
class_node_id = f"{ctx.file_path}:{class_name}"
|
|
111
|
+
|
|
112
|
+
body_field = "interface_body" if is_interface else "class_body"
|
|
113
|
+
body = class_node.child_by_field_name("body")
|
|
114
|
+
if body is None:
|
|
115
|
+
return
|
|
116
|
+
|
|
117
|
+
for member in body.children:
|
|
118
|
+
if member.type == "method_declaration":
|
|
119
|
+
self._process_method(member, class_name, class_node_id, ctx, result, is_interface=is_interface)
|
|
120
|
+
|
|
121
|
+
def _process_method(
|
|
122
|
+
self,
|
|
123
|
+
method_node, # type: ignore[no-untyped-def]
|
|
124
|
+
class_name: str,
|
|
125
|
+
class_node_id: str,
|
|
126
|
+
ctx: DetectorContext,
|
|
127
|
+
result: DetectorResult,
|
|
128
|
+
*,
|
|
129
|
+
is_interface: bool,
|
|
130
|
+
) -> None:
|
|
131
|
+
# --- Method name ---
|
|
132
|
+
name_node = method_node.child_by_field_name("name")
|
|
133
|
+
if name_node is None:
|
|
134
|
+
return
|
|
135
|
+
method_name: str = name_node.text.decode("utf-8", errors="replace")
|
|
136
|
+
|
|
137
|
+
# Skip Object boilerplate
|
|
138
|
+
if method_name in _SKIP_METHODS:
|
|
139
|
+
return
|
|
140
|
+
|
|
141
|
+
# --- Modifiers ---
|
|
142
|
+
# NOTE: In tree-sitter-java, "modifiers" is a child node type, NOT a
|
|
143
|
+
# named field, so child_by_field_name("modifiers") returns None.
|
|
144
|
+
# We must locate it by iterating children.
|
|
145
|
+
modifiers_node = _find_child_by_type(method_node, "modifiers")
|
|
146
|
+
|
|
147
|
+
# In a class, only public / protected methods qualify.
|
|
148
|
+
# Interface methods are implicitly public.
|
|
149
|
+
if is_interface:
|
|
150
|
+
visibility = "public"
|
|
151
|
+
else:
|
|
152
|
+
if _has_modifier(modifiers_node, "public"):
|
|
153
|
+
visibility = "public"
|
|
154
|
+
elif _has_modifier(modifiers_node, "protected"):
|
|
155
|
+
visibility = "protected"
|
|
156
|
+
else:
|
|
157
|
+
# private or package-private -- skip
|
|
158
|
+
return
|
|
159
|
+
|
|
160
|
+
is_static = _has_modifier(modifiers_node, "static")
|
|
161
|
+
is_abstract = _has_modifier(modifiers_node, "abstract")
|
|
162
|
+
|
|
163
|
+
# --- Parameters ---
|
|
164
|
+
params_node = method_node.child_by_field_name("parameters")
|
|
165
|
+
ptypes = _param_types(params_node)
|
|
166
|
+
|
|
167
|
+
# Skip trivial getters / setters
|
|
168
|
+
body_lines = _body_line_count(method_node)
|
|
169
|
+
if _is_trivial_accessor(method_name, len(ptypes), body_lines):
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
# --- Return type ---
|
|
173
|
+
type_node = method_node.child_by_field_name("type")
|
|
174
|
+
return_type = type_node.text.decode("utf-8", errors="replace") if type_node is not None else "void"
|
|
175
|
+
|
|
176
|
+
# --- Build node ---
|
|
177
|
+
param_sig = ",".join(ptypes)
|
|
178
|
+
method_id = f"{ctx.file_path}:{class_name}:{method_name}({param_sig})"
|
|
179
|
+
line = method_node.start_point[0] + 1
|
|
180
|
+
|
|
181
|
+
properties: dict[str, Any] = {
|
|
182
|
+
"visibility": visibility,
|
|
183
|
+
"return_type": return_type,
|
|
184
|
+
"parameters": ptypes,
|
|
185
|
+
"is_static": is_static,
|
|
186
|
+
"is_abstract": is_abstract,
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
node = GraphNode(
|
|
190
|
+
id=method_id,
|
|
191
|
+
kind=NodeKind.METHOD,
|
|
192
|
+
label=f"{class_name}.{method_name}",
|
|
193
|
+
fqn=f"{class_name}.{method_name}({param_sig})",
|
|
194
|
+
module=ctx.module_name,
|
|
195
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=line),
|
|
196
|
+
properties=properties,
|
|
197
|
+
)
|
|
198
|
+
result.nodes.append(node)
|
|
199
|
+
|
|
200
|
+
edge = GraphEdge(
|
|
201
|
+
source=class_node_id,
|
|
202
|
+
target=method_id,
|
|
203
|
+
kind=EdgeKind.DEFINES,
|
|
204
|
+
label=f"{class_name} defines {method_name}",
|
|
205
|
+
)
|
|
206
|
+
result.edges.append(edge)
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Quarkus framework detector for Java source files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
9
|
+
from osscodeiq.detectors.utils import decode_text
|
|
10
|
+
from osscodeiq.models.graph import (
|
|
11
|
+
EdgeKind,
|
|
12
|
+
GraphEdge,
|
|
13
|
+
GraphNode,
|
|
14
|
+
NodeKind,
|
|
15
|
+
SourceLocation,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
_QUARKUS_TEST_RE = re.compile(r"@QuarkusTest\b")
|
|
19
|
+
_CONFIG_PROPERTY_RE = re.compile(r'@ConfigProperty\s*\(\s*name\s*=\s*"([^"]+)"')
|
|
20
|
+
_CDI_SCOPE_RE = re.compile(
|
|
21
|
+
r"@(Inject|Singleton|ApplicationScoped|RequestScoped)\b"
|
|
22
|
+
)
|
|
23
|
+
_SCHEDULED_RE = re.compile(r'@Scheduled\s*\(\s*(?:every|cron)\s*=\s*"([^"]+)"')
|
|
24
|
+
_TRANSACTIONAL_RE = re.compile(r"@Transactional\b")
|
|
25
|
+
_STARTUP_RE = re.compile(r"@Startup\b")
|
|
26
|
+
_TRANSACTIONAL = "@Transactional"
|
|
27
|
+
|
|
28
|
+
_CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class QuarkusDetector:
|
|
32
|
+
"""Detects Quarkus-specific patterns in Java source files."""
|
|
33
|
+
|
|
34
|
+
name: str = "quarkus"
|
|
35
|
+
supported_languages: tuple[str, ...] = ("java",)
|
|
36
|
+
|
|
37
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
38
|
+
result = DetectorResult()
|
|
39
|
+
text = decode_text(ctx)
|
|
40
|
+
|
|
41
|
+
# Fast bail: check for any Quarkus-related markers
|
|
42
|
+
if not any(
|
|
43
|
+
marker in text
|
|
44
|
+
for marker in (
|
|
45
|
+
"@QuarkusTest",
|
|
46
|
+
"@ConfigProperty",
|
|
47
|
+
"@Singleton",
|
|
48
|
+
"@ApplicationScoped",
|
|
49
|
+
"@RequestScoped",
|
|
50
|
+
"@Scheduled",
|
|
51
|
+
_TRANSACTIONAL,
|
|
52
|
+
"@Startup",
|
|
53
|
+
"io.quarkus",
|
|
54
|
+
)
|
|
55
|
+
):
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
lines = text.split("\n")
|
|
59
|
+
|
|
60
|
+
# Find class name
|
|
61
|
+
class_name: str | None = None
|
|
62
|
+
for line in lines:
|
|
63
|
+
cm = _CLASS_RE.search(line)
|
|
64
|
+
if cm:
|
|
65
|
+
class_name = cm.group(1)
|
|
66
|
+
break
|
|
67
|
+
|
|
68
|
+
for i, line in enumerate(lines):
|
|
69
|
+
lineno = i + 1
|
|
70
|
+
|
|
71
|
+
# @QuarkusTest annotation
|
|
72
|
+
m = _QUARKUS_TEST_RE.search(line)
|
|
73
|
+
if m:
|
|
74
|
+
node_id = f"quarkus:{ctx.file_path}:quarkus_test:{lineno}"
|
|
75
|
+
result.nodes.append(
|
|
76
|
+
GraphNode(
|
|
77
|
+
id=node_id,
|
|
78
|
+
kind=NodeKind.CLASS,
|
|
79
|
+
label=f"@QuarkusTest {class_name or 'unknown'}",
|
|
80
|
+
fqn=class_name,
|
|
81
|
+
module=ctx.module_name,
|
|
82
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
|
|
83
|
+
annotations=["@QuarkusTest"],
|
|
84
|
+
properties={"framework": "quarkus", "test": True},
|
|
85
|
+
)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
# @ConfigProperty
|
|
89
|
+
m = _CONFIG_PROPERTY_RE.search(line)
|
|
90
|
+
if m:
|
|
91
|
+
config_key = m.group(1)
|
|
92
|
+
node_id = f"quarkus:{ctx.file_path}:config_property:{lineno}"
|
|
93
|
+
result.nodes.append(
|
|
94
|
+
GraphNode(
|
|
95
|
+
id=node_id,
|
|
96
|
+
kind=NodeKind.CONFIG_KEY,
|
|
97
|
+
label=f"@ConfigProperty({config_key})",
|
|
98
|
+
fqn=config_key,
|
|
99
|
+
module=ctx.module_name,
|
|
100
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
|
|
101
|
+
annotations=["@ConfigProperty"],
|
|
102
|
+
properties={"framework": "quarkus", "config_key": config_key},
|
|
103
|
+
)
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# CDI scope annotations
|
|
107
|
+
m = _CDI_SCOPE_RE.search(line)
|
|
108
|
+
if m:
|
|
109
|
+
annotation = m.group(1)
|
|
110
|
+
node_id = f"quarkus:{ctx.file_path}:cdi_{annotation.lower()}:{lineno}"
|
|
111
|
+
result.nodes.append(
|
|
112
|
+
GraphNode(
|
|
113
|
+
id=node_id,
|
|
114
|
+
kind=NodeKind.MIDDLEWARE,
|
|
115
|
+
label=f"@{annotation} (CDI)",
|
|
116
|
+
fqn=f"{class_name}.{annotation}" if class_name else annotation,
|
|
117
|
+
module=ctx.module_name,
|
|
118
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
|
|
119
|
+
annotations=[f"@{annotation}"],
|
|
120
|
+
properties={"framework": "quarkus", "cdi_scope": annotation},
|
|
121
|
+
)
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
# @Scheduled
|
|
125
|
+
m = _SCHEDULED_RE.search(line)
|
|
126
|
+
if m:
|
|
127
|
+
schedule_expr = m.group(1)
|
|
128
|
+
node_id = f"quarkus:{ctx.file_path}:scheduled:{lineno}"
|
|
129
|
+
result.nodes.append(
|
|
130
|
+
GraphNode(
|
|
131
|
+
id=node_id,
|
|
132
|
+
kind=NodeKind.EVENT,
|
|
133
|
+
label=f"@Scheduled({schedule_expr})",
|
|
134
|
+
fqn=f"{class_name}.scheduled" if class_name else "scheduled",
|
|
135
|
+
module=ctx.module_name,
|
|
136
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
|
|
137
|
+
annotations=["@Scheduled"],
|
|
138
|
+
properties={"framework": "quarkus", "schedule": schedule_expr},
|
|
139
|
+
)
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# @Transactional
|
|
143
|
+
m = _TRANSACTIONAL_RE.search(line)
|
|
144
|
+
if m:
|
|
145
|
+
node_id = f"quarkus:{ctx.file_path}:transactional:{lineno}"
|
|
146
|
+
result.nodes.append(
|
|
147
|
+
GraphNode(
|
|
148
|
+
id=node_id,
|
|
149
|
+
kind=NodeKind.MIDDLEWARE,
|
|
150
|
+
label=_TRANSACTIONAL,
|
|
151
|
+
fqn=f"{class_name}.transactional" if class_name else "transactional",
|
|
152
|
+
module=ctx.module_name,
|
|
153
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
|
|
154
|
+
annotations=[_TRANSACTIONAL],
|
|
155
|
+
properties={"framework": "quarkus"},
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
# @Startup
|
|
160
|
+
m = _STARTUP_RE.search(line)
|
|
161
|
+
if m:
|
|
162
|
+
node_id = f"quarkus:{ctx.file_path}:startup:{lineno}"
|
|
163
|
+
result.nodes.append(
|
|
164
|
+
GraphNode(
|
|
165
|
+
id=node_id,
|
|
166
|
+
kind=NodeKind.MIDDLEWARE,
|
|
167
|
+
label=f"@Startup {class_name or 'unknown'}",
|
|
168
|
+
fqn=class_name,
|
|
169
|
+
module=ctx.module_name,
|
|
170
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
|
|
171
|
+
annotations=["@Startup"],
|
|
172
|
+
properties={"framework": "quarkus"},
|
|
173
|
+
)
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return result
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""RabbitMQ detector for Java source files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.detectors.utils import decode_text
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
EdgeKind,
|
|
11
|
+
GraphEdge,
|
|
12
|
+
GraphNode,
|
|
13
|
+
NodeKind,
|
|
14
|
+
SourceLocation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
|
|
18
|
+
|
|
19
|
+
# @RabbitListener
|
|
20
|
+
_RABBIT_LISTENER_RE = re.compile(
|
|
21
|
+
r'@RabbitListener\s*\(\s*(?:.*?queues?\s*=\s*)?[\{"]?\s*"([^"]+)"'
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# @RabbitHandler
|
|
25
|
+
_RABBIT_HANDLER_RE = re.compile(r"@RabbitHandler")
|
|
26
|
+
|
|
27
|
+
# RabbitTemplate.convertAndSend / send
|
|
28
|
+
_RABBIT_SEND_RE = re.compile(
|
|
29
|
+
r'(?:rabbitTemplate|RabbitTemplate)\s*\.(?:convertAndSend|send)\s*\(\s*"([^"]+)"'
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Exchange/queue/binding declarations
|
|
33
|
+
_EXCHANGE_RE = re.compile(
|
|
34
|
+
r'(?:DirectExchange|TopicExchange|FanoutExchange|HeadersExchange)\s*\(\s*"([^"]+)"'
|
|
35
|
+
)
|
|
36
|
+
_QUEUE_DECL_RE = re.compile(r'Queue\s*\(\s*"([^"]+)"')
|
|
37
|
+
_BINDING_RE = re.compile(
|
|
38
|
+
r'BindingBuilder\s*\.bind\s*\(\s*(\w+)\s*\)\s*\.to\s*\(\s*(\w+)\s*\)'
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
_ROUTING_KEY_RE = re.compile(r'routingKey\s*=\s*"([^"]+)"')
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RabbitmqDetector:
|
|
45
|
+
"""Detects RabbitMQ consumers and producers."""
|
|
46
|
+
|
|
47
|
+
name: str = "rabbitmq"
|
|
48
|
+
supported_languages: tuple[str, ...] = ("java",)
|
|
49
|
+
|
|
50
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
51
|
+
result = DetectorResult()
|
|
52
|
+
text = decode_text(ctx)
|
|
53
|
+
lines = text.split("\n")
|
|
54
|
+
|
|
55
|
+
if not any(kw in text for kw in (
|
|
56
|
+
"@RabbitListener", "RabbitTemplate", "rabbitTemplate",
|
|
57
|
+
"DirectExchange", "TopicExchange", "FanoutExchange",
|
|
58
|
+
)):
|
|
59
|
+
return result
|
|
60
|
+
|
|
61
|
+
# Find class name
|
|
62
|
+
class_name: str | None = None
|
|
63
|
+
for line in lines:
|
|
64
|
+
cm = _CLASS_RE.search(line)
|
|
65
|
+
if cm:
|
|
66
|
+
class_name = cm.group(1)
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
if not class_name:
|
|
70
|
+
return result
|
|
71
|
+
|
|
72
|
+
class_node_id = f"{ctx.file_path}:{class_name}"
|
|
73
|
+
seen_queues: set[str] = set()
|
|
74
|
+
|
|
75
|
+
def _ensure_queue_node(queue: str) -> str:
|
|
76
|
+
queue_id = f"rabbitmq:queue:{queue}"
|
|
77
|
+
if queue not in seen_queues:
|
|
78
|
+
seen_queues.add(queue)
|
|
79
|
+
result.nodes.append(GraphNode(
|
|
80
|
+
id=queue_id,
|
|
81
|
+
kind=NodeKind.QUEUE,
|
|
82
|
+
label=f"rabbitmq:{queue}",
|
|
83
|
+
properties={"broker": "rabbitmq", "queue": queue},
|
|
84
|
+
))
|
|
85
|
+
return queue_id
|
|
86
|
+
|
|
87
|
+
# Detect @RabbitListener consumers
|
|
88
|
+
for i, line in enumerate(lines):
|
|
89
|
+
m = _RABBIT_LISTENER_RE.search(line)
|
|
90
|
+
if not m:
|
|
91
|
+
continue
|
|
92
|
+
|
|
93
|
+
queue = m.group(1)
|
|
94
|
+
queue_id = _ensure_queue_node(queue)
|
|
95
|
+
|
|
96
|
+
result.edges.append(GraphEdge(
|
|
97
|
+
source=class_node_id,
|
|
98
|
+
target=queue_id,
|
|
99
|
+
kind=EdgeKind.CONSUMES,
|
|
100
|
+
label=f"{class_name} consumes from {queue}",
|
|
101
|
+
properties={"queue": queue},
|
|
102
|
+
))
|
|
103
|
+
|
|
104
|
+
# Detect RabbitTemplate sends
|
|
105
|
+
for i, line in enumerate(lines):
|
|
106
|
+
m = _RABBIT_SEND_RE.search(line)
|
|
107
|
+
if not m:
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
exchange_or_queue = m.group(1)
|
|
111
|
+
routing_key = _ROUTING_KEY_RE.search(line)
|
|
112
|
+
|
|
113
|
+
props: dict[str, str] = {"exchange": exchange_or_queue}
|
|
114
|
+
if routing_key:
|
|
115
|
+
props["routing_key"] = routing_key.group(1)
|
|
116
|
+
|
|
117
|
+
queue_id = f"rabbitmq:exchange:{exchange_or_queue}"
|
|
118
|
+
if exchange_or_queue not in seen_queues:
|
|
119
|
+
seen_queues.add(exchange_or_queue)
|
|
120
|
+
result.nodes.append(GraphNode(
|
|
121
|
+
id=queue_id,
|
|
122
|
+
kind=NodeKind.QUEUE,
|
|
123
|
+
label=f"rabbitmq:{exchange_or_queue}",
|
|
124
|
+
properties={"broker": "rabbitmq", "exchange": exchange_or_queue},
|
|
125
|
+
))
|
|
126
|
+
|
|
127
|
+
result.edges.append(GraphEdge(
|
|
128
|
+
source=class_node_id,
|
|
129
|
+
target=queue_id,
|
|
130
|
+
kind=EdgeKind.PRODUCES,
|
|
131
|
+
label=f"{class_name} produces to {exchange_or_queue}",
|
|
132
|
+
properties=props,
|
|
133
|
+
))
|
|
134
|
+
|
|
135
|
+
# Detect exchange declarations
|
|
136
|
+
for m in _EXCHANGE_RE.finditer(text):
|
|
137
|
+
exchange_name = m.group(1)
|
|
138
|
+
line_num = text[:m.start()].count("\n") + 1
|
|
139
|
+
exchange_id = f"rabbitmq:exchange:{exchange_name}"
|
|
140
|
+
if exchange_name not in seen_queues:
|
|
141
|
+
seen_queues.add(exchange_name)
|
|
142
|
+
result.nodes.append(GraphNode(
|
|
143
|
+
id=exchange_id,
|
|
144
|
+
kind=NodeKind.QUEUE,
|
|
145
|
+
label=f"rabbitmq:exchange:{exchange_name}",
|
|
146
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=line_num),
|
|
147
|
+
properties={"broker": "rabbitmq", "exchange": exchange_name},
|
|
148
|
+
))
|
|
149
|
+
|
|
150
|
+
return result
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""Raw SQL query detector for Java source files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.detectors.utils import decode_text
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
GraphNode,
|
|
11
|
+
NodeKind,
|
|
12
|
+
SourceLocation,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
_CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
|
|
16
|
+
|
|
17
|
+
# @Query annotations with SQL/JPQL
|
|
18
|
+
_QUERY_ANNO_RE = re.compile(
|
|
19
|
+
r'@Query\s*\(\s*(?:value\s*=\s*)?"((?:[^"\\]|\\.)+)"', re.DOTALL
|
|
20
|
+
)
|
|
21
|
+
_NATIVE_QUERY_RE = re.compile(r"nativeQuery\s*=\s*true")
|
|
22
|
+
|
|
23
|
+
# JdbcTemplate patterns
|
|
24
|
+
_JDBC_TEMPLATE_RE = re.compile(
|
|
25
|
+
r'(?:jdbcTemplate|namedParameterJdbcTemplate|JdbcTemplate)\s*\.'
|
|
26
|
+
r'(?:query|queryForObject|queryForList|queryForMap|update|execute|batchUpdate)'
|
|
27
|
+
r'\s*\(\s*"((?:[^"\\]|\\.)+)"',
|
|
28
|
+
re.DOTALL,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# EntityManager.createNativeQuery / createQuery
|
|
32
|
+
_EM_QUERY_RE = re.compile(
|
|
33
|
+
r'(?:entityManager|em)\s*\.(?:createNativeQuery|createQuery)\s*\(\s*"((?:[^"\\]|\\.)+)"',
|
|
34
|
+
re.DOTALL,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
# SQL table references
|
|
38
|
+
_TABLE_REF_RE = re.compile(
|
|
39
|
+
r'\b(?:FROM|JOIN|INTO|UPDATE|TABLE)\s+(\w+)', re.IGNORECASE
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class RawSqlDetector:
|
|
44
|
+
"""Detects raw SQL queries in @Query annotations and JdbcTemplate calls."""
|
|
45
|
+
|
|
46
|
+
name: str = "raw_sql"
|
|
47
|
+
supported_languages: tuple[str, ...] = ("java",)
|
|
48
|
+
|
|
49
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
50
|
+
result = DetectorResult()
|
|
51
|
+
text = decode_text(ctx)
|
|
52
|
+
lines = text.split("\n")
|
|
53
|
+
|
|
54
|
+
if "@Query" not in text and "jdbcTemplate" not in text and "JdbcTemplate" not in text and "createNativeQuery" not in text and "createQuery" not in text:
|
|
55
|
+
return result
|
|
56
|
+
|
|
57
|
+
# Find class name
|
|
58
|
+
class_name: str | None = None
|
|
59
|
+
for line in lines:
|
|
60
|
+
cm = _CLASS_RE.search(line)
|
|
61
|
+
if cm:
|
|
62
|
+
class_name = cm.group(1)
|
|
63
|
+
break
|
|
64
|
+
|
|
65
|
+
class_name = class_name or "Unknown"
|
|
66
|
+
|
|
67
|
+
# Detect @Query annotations
|
|
68
|
+
for m in _QUERY_ANNO_RE.finditer(text):
|
|
69
|
+
query_str = m.group(1)
|
|
70
|
+
line_num = text[:m.start()].count("\n") + 1
|
|
71
|
+
is_native = bool(_NATIVE_QUERY_RE.search(text[m.start():m.end() + 50]))
|
|
72
|
+
|
|
73
|
+
tables = _TABLE_REF_RE.findall(query_str)
|
|
74
|
+
|
|
75
|
+
query_id = f"{ctx.file_path}:{class_name}:query:L{line_num}"
|
|
76
|
+
result.nodes.append(GraphNode(
|
|
77
|
+
id=query_id,
|
|
78
|
+
kind=NodeKind.QUERY,
|
|
79
|
+
label=query_str[:80] + ("..." if len(query_str) > 80 else ""),
|
|
80
|
+
fqn=f"{class_name}.query@L{line_num}",
|
|
81
|
+
module=ctx.module_name,
|
|
82
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=line_num),
|
|
83
|
+
annotations=["@Query"],
|
|
84
|
+
properties={
|
|
85
|
+
"query": query_str,
|
|
86
|
+
"native": is_native,
|
|
87
|
+
"source": "annotation",
|
|
88
|
+
"tables": tables,
|
|
89
|
+
},
|
|
90
|
+
))
|
|
91
|
+
|
|
92
|
+
# Detect JdbcTemplate queries
|
|
93
|
+
for m in _JDBC_TEMPLATE_RE.finditer(text):
|
|
94
|
+
query_str = m.group(1)
|
|
95
|
+
line_num = text[:m.start()].count("\n") + 1
|
|
96
|
+
tables = _TABLE_REF_RE.findall(query_str)
|
|
97
|
+
|
|
98
|
+
query_id = f"{ctx.file_path}:{class_name}:jdbc:L{line_num}"
|
|
99
|
+
result.nodes.append(GraphNode(
|
|
100
|
+
id=query_id,
|
|
101
|
+
kind=NodeKind.QUERY,
|
|
102
|
+
label=query_str[:80] + ("..." if len(query_str) > 80 else ""),
|
|
103
|
+
fqn=f"{class_name}.jdbc@L{line_num}",
|
|
104
|
+
module=ctx.module_name,
|
|
105
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=line_num),
|
|
106
|
+
properties={
|
|
107
|
+
"query": query_str,
|
|
108
|
+
"native": True,
|
|
109
|
+
"source": "jdbc_template",
|
|
110
|
+
"tables": tables,
|
|
111
|
+
},
|
|
112
|
+
))
|
|
113
|
+
|
|
114
|
+
# Detect EntityManager queries
|
|
115
|
+
for m in _EM_QUERY_RE.finditer(text):
|
|
116
|
+
query_str = m.group(1)
|
|
117
|
+
line_num = text[:m.start()].count("\n") + 1
|
|
118
|
+
tables = _TABLE_REF_RE.findall(query_str)
|
|
119
|
+
|
|
120
|
+
query_id = f"{ctx.file_path}:{class_name}:em:L{line_num}"
|
|
121
|
+
result.nodes.append(GraphNode(
|
|
122
|
+
id=query_id,
|
|
123
|
+
kind=NodeKind.QUERY,
|
|
124
|
+
label=query_str[:80] + ("..." if len(query_str) > 80 else ""),
|
|
125
|
+
fqn=f"{class_name}.em@L{line_num}",
|
|
126
|
+
module=ctx.module_name,
|
|
127
|
+
location=SourceLocation(file_path=ctx.file_path, line_start=line_num),
|
|
128
|
+
properties={
|
|
129
|
+
"query": query_str,
|
|
130
|
+
"native": "createNativeQuery" in text[max(0, m.start() - 30):m.start() + 20],
|
|
131
|
+
"source": "entity_manager",
|
|
132
|
+
"tables": tables,
|
|
133
|
+
},
|
|
134
|
+
))
|
|
135
|
+
|
|
136
|
+
return result
|