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.
Files changed (183) hide show
  1. osscodeiq/__init__.py +0 -0
  2. osscodeiq/analyzer.py +467 -0
  3. osscodeiq/cache/__init__.py +0 -0
  4. osscodeiq/cache/hasher.py +23 -0
  5. osscodeiq/cache/store.py +300 -0
  6. osscodeiq/classifiers/__init__.py +0 -0
  7. osscodeiq/classifiers/layer_classifier.py +69 -0
  8. osscodeiq/cli.py +721 -0
  9. osscodeiq/config.py +113 -0
  10. osscodeiq/detectors/__init__.py +0 -0
  11. osscodeiq/detectors/auth/__init__.py +0 -0
  12. osscodeiq/detectors/auth/certificate_auth.py +139 -0
  13. osscodeiq/detectors/auth/ldap_auth.py +89 -0
  14. osscodeiq/detectors/auth/session_header_auth.py +120 -0
  15. osscodeiq/detectors/base.py +41 -0
  16. osscodeiq/detectors/config/__init__.py +0 -0
  17. osscodeiq/detectors/config/batch_structure.py +128 -0
  18. osscodeiq/detectors/config/cloudformation.py +183 -0
  19. osscodeiq/detectors/config/docker_compose.py +179 -0
  20. osscodeiq/detectors/config/github_actions.py +150 -0
  21. osscodeiq/detectors/config/gitlab_ci.py +216 -0
  22. osscodeiq/detectors/config/helm_chart.py +187 -0
  23. osscodeiq/detectors/config/ini_structure.py +101 -0
  24. osscodeiq/detectors/config/json_structure.py +72 -0
  25. osscodeiq/detectors/config/kubernetes.py +305 -0
  26. osscodeiq/detectors/config/kubernetes_rbac.py +212 -0
  27. osscodeiq/detectors/config/openapi.py +194 -0
  28. osscodeiq/detectors/config/package_json.py +99 -0
  29. osscodeiq/detectors/config/properties_detector.py +108 -0
  30. osscodeiq/detectors/config/pyproject_toml.py +169 -0
  31. osscodeiq/detectors/config/sql_structure.py +155 -0
  32. osscodeiq/detectors/config/toml_structure.py +93 -0
  33. osscodeiq/detectors/config/tsconfig_json.py +105 -0
  34. osscodeiq/detectors/config/yaml_structure.py +82 -0
  35. osscodeiq/detectors/cpp/__init__.py +0 -0
  36. osscodeiq/detectors/cpp/cpp_structures.py +192 -0
  37. osscodeiq/detectors/csharp/__init__.py +0 -0
  38. osscodeiq/detectors/csharp/csharp_efcore.py +184 -0
  39. osscodeiq/detectors/csharp/csharp_minimal_apis.py +156 -0
  40. osscodeiq/detectors/csharp/csharp_structures.py +317 -0
  41. osscodeiq/detectors/docs/__init__.py +0 -0
  42. osscodeiq/detectors/docs/markdown_structure.py +117 -0
  43. osscodeiq/detectors/frontend/__init__.py +0 -0
  44. osscodeiq/detectors/frontend/angular_components.py +177 -0
  45. osscodeiq/detectors/frontend/frontend_routes.py +259 -0
  46. osscodeiq/detectors/frontend/react_components.py +148 -0
  47. osscodeiq/detectors/frontend/svelte_components.py +84 -0
  48. osscodeiq/detectors/frontend/vue_components.py +150 -0
  49. osscodeiq/detectors/generic/__init__.py +1 -0
  50. osscodeiq/detectors/generic/imports_detector.py +413 -0
  51. osscodeiq/detectors/go/__init__.py +0 -0
  52. osscodeiq/detectors/go/go_orm.py +202 -0
  53. osscodeiq/detectors/go/go_structures.py +162 -0
  54. osscodeiq/detectors/go/go_web.py +157 -0
  55. osscodeiq/detectors/iac/__init__.py +0 -0
  56. osscodeiq/detectors/iac/bicep.py +135 -0
  57. osscodeiq/detectors/iac/dockerfile.py +182 -0
  58. osscodeiq/detectors/iac/terraform.py +188 -0
  59. osscodeiq/detectors/java/__init__.py +0 -0
  60. osscodeiq/detectors/java/azure_functions.py +424 -0
  61. osscodeiq/detectors/java/azure_messaging.py +350 -0
  62. osscodeiq/detectors/java/class_hierarchy.py +349 -0
  63. osscodeiq/detectors/java/config_def.py +82 -0
  64. osscodeiq/detectors/java/cosmos_db.py +105 -0
  65. osscodeiq/detectors/java/graphql_resolver.py +188 -0
  66. osscodeiq/detectors/java/grpc_service.py +142 -0
  67. osscodeiq/detectors/java/ibm_mq.py +178 -0
  68. osscodeiq/detectors/java/jaxrs.py +160 -0
  69. osscodeiq/detectors/java/jdbc.py +196 -0
  70. osscodeiq/detectors/java/jms.py +116 -0
  71. osscodeiq/detectors/java/jpa_entity.py +143 -0
  72. osscodeiq/detectors/java/kafka.py +113 -0
  73. osscodeiq/detectors/java/kafka_protocol.py +70 -0
  74. osscodeiq/detectors/java/micronaut.py +248 -0
  75. osscodeiq/detectors/java/module_deps.py +191 -0
  76. osscodeiq/detectors/java/public_api.py +206 -0
  77. osscodeiq/detectors/java/quarkus.py +176 -0
  78. osscodeiq/detectors/java/rabbitmq.py +150 -0
  79. osscodeiq/detectors/java/raw_sql.py +136 -0
  80. osscodeiq/detectors/java/repository.py +131 -0
  81. osscodeiq/detectors/java/rmi.py +129 -0
  82. osscodeiq/detectors/java/spring_events.py +117 -0
  83. osscodeiq/detectors/java/spring_rest.py +168 -0
  84. osscodeiq/detectors/java/spring_security.py +212 -0
  85. osscodeiq/detectors/java/tibco_ems.py +193 -0
  86. osscodeiq/detectors/java/websocket.py +188 -0
  87. osscodeiq/detectors/kotlin/__init__.py +0 -0
  88. osscodeiq/detectors/kotlin/kotlin_structures.py +124 -0
  89. osscodeiq/detectors/kotlin/ktor_routes.py +163 -0
  90. osscodeiq/detectors/proto/__init__.py +0 -0
  91. osscodeiq/detectors/proto/proto_structure.py +153 -0
  92. osscodeiq/detectors/python/__init__.py +0 -0
  93. osscodeiq/detectors/python/celery_tasks.py +88 -0
  94. osscodeiq/detectors/python/django_auth.py +132 -0
  95. osscodeiq/detectors/python/django_models.py +157 -0
  96. osscodeiq/detectors/python/django_views.py +74 -0
  97. osscodeiq/detectors/python/fastapi_auth.py +143 -0
  98. osscodeiq/detectors/python/fastapi_routes.py +68 -0
  99. osscodeiq/detectors/python/flask_routes.py +67 -0
  100. osscodeiq/detectors/python/kafka_python.py +175 -0
  101. osscodeiq/detectors/python/pydantic_models.py +115 -0
  102. osscodeiq/detectors/python/python_structures.py +234 -0
  103. osscodeiq/detectors/python/sqlalchemy_models.py +82 -0
  104. osscodeiq/detectors/registry.py +100 -0
  105. osscodeiq/detectors/rust/__init__.py +0 -0
  106. osscodeiq/detectors/rust/actix_web.py +234 -0
  107. osscodeiq/detectors/rust/rust_structures.py +174 -0
  108. osscodeiq/detectors/scala/__init__.py +0 -0
  109. osscodeiq/detectors/scala/scala_structures.py +128 -0
  110. osscodeiq/detectors/shell/__init__.py +0 -0
  111. osscodeiq/detectors/shell/bash_detector.py +127 -0
  112. osscodeiq/detectors/shell/powershell_detector.py +118 -0
  113. osscodeiq/detectors/typescript/__init__.py +0 -0
  114. osscodeiq/detectors/typescript/express_routes.py +55 -0
  115. osscodeiq/detectors/typescript/fastify_routes.py +156 -0
  116. osscodeiq/detectors/typescript/graphql_resolvers.py +100 -0
  117. osscodeiq/detectors/typescript/kafka_js.py +164 -0
  118. osscodeiq/detectors/typescript/mongoose_orm.py +151 -0
  119. osscodeiq/detectors/typescript/nestjs_controllers.py +99 -0
  120. osscodeiq/detectors/typescript/nestjs_guards.py +138 -0
  121. osscodeiq/detectors/typescript/passport_jwt.py +133 -0
  122. osscodeiq/detectors/typescript/prisma_orm.py +96 -0
  123. osscodeiq/detectors/typescript/remix_routes.py +160 -0
  124. osscodeiq/detectors/typescript/sequelize_orm.py +136 -0
  125. osscodeiq/detectors/typescript/typeorm_entities.py +86 -0
  126. osscodeiq/detectors/typescript/typescript_structures.py +185 -0
  127. osscodeiq/detectors/utils.py +49 -0
  128. osscodeiq/discovery/__init__.py +11 -0
  129. osscodeiq/discovery/change_detector.py +97 -0
  130. osscodeiq/discovery/file_discovery.py +342 -0
  131. osscodeiq/flow/__init__.py +0 -0
  132. osscodeiq/flow/engine.py +78 -0
  133. osscodeiq/flow/models.py +72 -0
  134. osscodeiq/flow/renderer.py +127 -0
  135. osscodeiq/flow/templates/interactive.html +252 -0
  136. osscodeiq/flow/vendor/cytoscape-dagre.min.js +8 -0
  137. osscodeiq/flow/vendor/cytoscape.min.js +32 -0
  138. osscodeiq/flow/vendor/dagre.min.js +3809 -0
  139. osscodeiq/flow/views.py +357 -0
  140. osscodeiq/graph/__init__.py +0 -0
  141. osscodeiq/graph/backend.py +52 -0
  142. osscodeiq/graph/backends/__init__.py +23 -0
  143. osscodeiq/graph/backends/kuzu.py +576 -0
  144. osscodeiq/graph/backends/networkx.py +135 -0
  145. osscodeiq/graph/backends/sqlite_backend.py +406 -0
  146. osscodeiq/graph/builder.py +297 -0
  147. osscodeiq/graph/query.py +228 -0
  148. osscodeiq/graph/store.py +183 -0
  149. osscodeiq/graph/views.py +231 -0
  150. osscodeiq/models/__init__.py +17 -0
  151. osscodeiq/models/graph.py +116 -0
  152. osscodeiq/output/__init__.py +0 -0
  153. osscodeiq/output/dot.py +171 -0
  154. osscodeiq/output/mermaid.py +160 -0
  155. osscodeiq/output/safety.py +58 -0
  156. osscodeiq/output/serializers.py +42 -0
  157. osscodeiq/parsing/__init__.py +5 -0
  158. osscodeiq/parsing/languages/__init__.py +0 -0
  159. osscodeiq/parsing/languages/base.py +23 -0
  160. osscodeiq/parsing/languages/java.py +68 -0
  161. osscodeiq/parsing/languages/python.py +57 -0
  162. osscodeiq/parsing/languages/typescript.py +95 -0
  163. osscodeiq/parsing/parser_manager.py +125 -0
  164. osscodeiq/parsing/structured/__init__.py +0 -0
  165. osscodeiq/parsing/structured/gradle_parser.py +78 -0
  166. osscodeiq/parsing/structured/json_parser.py +24 -0
  167. osscodeiq/parsing/structured/properties_parser.py +56 -0
  168. osscodeiq/parsing/structured/sql_parser.py +54 -0
  169. osscodeiq/parsing/structured/xml_parser.py +148 -0
  170. osscodeiq/parsing/structured/yaml_parser.py +38 -0
  171. osscodeiq/server/__init__.py +7 -0
  172. osscodeiq/server/app.py +53 -0
  173. osscodeiq/server/mcp_server.py +174 -0
  174. osscodeiq/server/middleware.py +16 -0
  175. osscodeiq/server/routes.py +184 -0
  176. osscodeiq/server/service.py +445 -0
  177. osscodeiq/server/templates/welcome.html +56 -0
  178. osscodeiq-0.0.0.dist-info/METADATA +30 -0
  179. osscodeiq-0.0.0.dist-info/RECORD +183 -0
  180. osscodeiq-0.0.0.dist-info/WHEEL +5 -0
  181. osscodeiq-0.0.0.dist-info/entry_points.txt +2 -0
  182. osscodeiq-0.0.0.dist-info/licenses/LICENSE +21 -0
  183. 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