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,212 @@
1
+ """Spring Security auth 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 GraphNode, NodeKind, SourceLocation
10
+
11
+ # @Secured("ROLE_ADMIN") or @Secured({"ROLE_ADMIN", "ROLE_USER"})
12
+ _SECURED_RE = re.compile(
13
+ r'@Secured\(\s*(?:\{([^}]*)\}|"([^"]*)")\s*\)'
14
+ )
15
+
16
+ # @PreAuthorize("hasRole('ADMIN')") and similar SpEL expressions
17
+ _PRE_AUTHORIZE_RE = re.compile(
18
+ r'@PreAuthorize\(\s*"([^"]*)"\s*\)'
19
+ )
20
+
21
+ # @RolesAllowed({"ROLE_ADMIN", "ROLE_USER"}) or @RolesAllowed("ROLE_ADMIN")
22
+ _ROLES_ALLOWED_RE = re.compile(
23
+ r'@RolesAllowed\(\s*(?:\{([^}]*)\}|"([^"]*)")\s*\)'
24
+ )
25
+
26
+ # @EnableWebSecurity
27
+ _ENABLE_WEB_SECURITY_RE = re.compile(r'@EnableWebSecurity\b')
28
+
29
+ # @EnableMethodSecurity
30
+ _ENABLE_METHOD_SECURITY_RE = re.compile(r'@EnableMethodSecurity\b')
31
+
32
+ # SecurityFilterChain method declaration
33
+ _SECURITY_FILTER_CHAIN_RE = re.compile(
34
+ r'(?:public\s+)?SecurityFilterChain\s+(\w+)\s*\('
35
+ )
36
+
37
+ # .authorizeHttpRequests() fluent call
38
+ _AUTHORIZE_HTTP_REQUESTS_RE = re.compile(
39
+ r'\.authorizeHttpRequests\s*\('
40
+ )
41
+
42
+ # Helper to extract quoted role strings from annotation values
43
+ _ROLE_STR_RE = re.compile(r'"([^"]*)"')
44
+
45
+ # Extract roles from hasRole/hasAnyRole SpEL expressions
46
+ _HAS_ROLE_RE = re.compile(r"hasRole\(\s*'([^']*)'\s*\)")
47
+ _HAS_ANY_ROLE_RE = re.compile(r"hasAnyRole\(\s*([^)]+)\)")
48
+ _SINGLE_QUOTED_RE = re.compile(r"'([^']*)'")
49
+
50
+
51
+ def _extract_roles_from_annotation(groups: tuple[str | None, str | None]) -> list[str]:
52
+ """Extract role names from a @Secured or @RolesAllowed annotation match groups."""
53
+ multi, single = groups
54
+ if single is not None:
55
+ return [single]
56
+ if multi is not None:
57
+ return [m.group(1) for m in _ROLE_STR_RE.finditer(multi)]
58
+ return []
59
+
60
+
61
+ def _extract_roles_from_spel(expr: str) -> list[str]:
62
+ """Extract role names from a SpEL expression in @PreAuthorize."""
63
+ roles: list[str] = []
64
+ for m in _HAS_ROLE_RE.finditer(expr):
65
+ roles.append(m.group(1))
66
+ for m in _HAS_ANY_ROLE_RE.finditer(expr):
67
+ inner = m.group(1)
68
+ for q in _SINGLE_QUOTED_RE.finditer(inner):
69
+ roles.append(q.group(1))
70
+ return roles
71
+
72
+
73
+ def _line_number(text: str, pos: int) -> int:
74
+ """Return 1-based line number for a character offset."""
75
+ return text[:pos].count("\n") + 1
76
+
77
+
78
+ class SpringSecurityDetector:
79
+ """Detects Spring Security auth patterns in Java source files."""
80
+
81
+ name: str = "spring_security"
82
+ supported_languages: tuple[str, ...] = ("java",)
83
+
84
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
85
+ result = DetectorResult()
86
+ text = decode_text(ctx)
87
+
88
+ # @Secured annotations
89
+ for m in _SECURED_RE.finditer(text):
90
+ line = _line_number(text, m.start())
91
+ roles = _extract_roles_from_annotation((m.group(1), m.group(2)))
92
+ result.nodes.append(GraphNode(
93
+ id=f"auth:{ctx.file_path}:Secured:{line}",
94
+ kind=NodeKind.GUARD,
95
+ label="@Secured",
96
+ module=ctx.module_name,
97
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
98
+ annotations=["@Secured"],
99
+ properties={
100
+ "auth_type": "spring_security",
101
+ "roles": roles,
102
+ "auth_required": True,
103
+ },
104
+ ))
105
+
106
+ # @PreAuthorize annotations
107
+ for m in _PRE_AUTHORIZE_RE.finditer(text):
108
+ line = _line_number(text, m.start())
109
+ expr = m.group(1)
110
+ roles = _extract_roles_from_spel(expr)
111
+ result.nodes.append(GraphNode(
112
+ id=f"auth:{ctx.file_path}:PreAuthorize:{line}",
113
+ kind=NodeKind.GUARD,
114
+ label="@PreAuthorize",
115
+ module=ctx.module_name,
116
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
117
+ annotations=["@PreAuthorize"],
118
+ properties={
119
+ "auth_type": "spring_security",
120
+ "roles": roles,
121
+ "expression": expr,
122
+ "auth_required": True,
123
+ },
124
+ ))
125
+
126
+ # @RolesAllowed annotations
127
+ for m in _ROLES_ALLOWED_RE.finditer(text):
128
+ line = _line_number(text, m.start())
129
+ roles = _extract_roles_from_annotation((m.group(1), m.group(2)))
130
+ result.nodes.append(GraphNode(
131
+ id=f"auth:{ctx.file_path}:RolesAllowed:{line}",
132
+ kind=NodeKind.GUARD,
133
+ label="@RolesAllowed",
134
+ module=ctx.module_name,
135
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
136
+ annotations=["@RolesAllowed"],
137
+ properties={
138
+ "auth_type": "spring_security",
139
+ "roles": roles,
140
+ "auth_required": True,
141
+ },
142
+ ))
143
+
144
+ # @EnableWebSecurity
145
+ for m in _ENABLE_WEB_SECURITY_RE.finditer(text):
146
+ line = _line_number(text, m.start())
147
+ result.nodes.append(GraphNode(
148
+ id=f"auth:{ctx.file_path}:EnableWebSecurity:{line}",
149
+ kind=NodeKind.GUARD,
150
+ label="@EnableWebSecurity",
151
+ module=ctx.module_name,
152
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
153
+ annotations=["@EnableWebSecurity"],
154
+ properties={
155
+ "auth_type": "spring_security",
156
+ "roles": [],
157
+ "auth_required": True,
158
+ },
159
+ ))
160
+
161
+ # @EnableMethodSecurity
162
+ for m in _ENABLE_METHOD_SECURITY_RE.finditer(text):
163
+ line = _line_number(text, m.start())
164
+ result.nodes.append(GraphNode(
165
+ id=f"auth:{ctx.file_path}:EnableMethodSecurity:{line}",
166
+ kind=NodeKind.GUARD,
167
+ label="@EnableMethodSecurity",
168
+ module=ctx.module_name,
169
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
170
+ annotations=["@EnableMethodSecurity"],
171
+ properties={
172
+ "auth_type": "spring_security",
173
+ "roles": [],
174
+ "auth_required": True,
175
+ },
176
+ ))
177
+
178
+ # SecurityFilterChain method declarations
179
+ for m in _SECURITY_FILTER_CHAIN_RE.finditer(text):
180
+ line = _line_number(text, m.start())
181
+ method_name = m.group(1)
182
+ result.nodes.append(GraphNode(
183
+ id=f"auth:{ctx.file_path}:SecurityFilterChain:{line}",
184
+ kind=NodeKind.GUARD,
185
+ label=f"SecurityFilterChain:{method_name}",
186
+ module=ctx.module_name,
187
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
188
+ properties={
189
+ "auth_type": "spring_security",
190
+ "roles": [],
191
+ "method_name": method_name,
192
+ "auth_required": True,
193
+ },
194
+ ))
195
+
196
+ # .authorizeHttpRequests() calls
197
+ for m in _AUTHORIZE_HTTP_REQUESTS_RE.finditer(text):
198
+ line = _line_number(text, m.start())
199
+ result.nodes.append(GraphNode(
200
+ id=f"auth:{ctx.file_path}:authorizeHttpRequests:{line}",
201
+ kind=NodeKind.GUARD,
202
+ label=".authorizeHttpRequests()",
203
+ module=ctx.module_name,
204
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
205
+ properties={
206
+ "auth_type": "spring_security",
207
+ "roles": [],
208
+ "auth_required": True,
209
+ },
210
+ ))
211
+
212
+ return result
@@ -0,0 +1,193 @@
1
+ """TIBCO EMS 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
+ # TIBCO EMS connection factories
20
+ _TIBJMS_FACTORY_RE = re.compile(
21
+ r'\b(TibjmsConnectionFactory|TibjmsQueueConnectionFactory|TibjmsTopicConnectionFactory)\b'
22
+ )
23
+
24
+ # Server URL patterns (e.g. "tcp://ems-server:7222")
25
+ _SERVER_URL_RE = re.compile(r'"(tcp://[^"]+)"')
26
+
27
+ # createQueue / createTopic
28
+ _CREATE_QUEUE_RE = re.compile(r'createQueue\s*\(\s*"([^"]+)"')
29
+ _CREATE_TOPIC_RE = re.compile(r'createTopic\s*\(\s*"([^"]+)"')
30
+
31
+ # send / publish patterns
32
+ _SEND_RE = re.compile(r'\bsend\s*\(')
33
+ _PUBLISH_RE = re.compile(r'\bpublish\s*\(')
34
+
35
+ # receive / onMessage patterns
36
+ _RECEIVE_RE = re.compile(r'\breceive\s*\(')
37
+ _ON_MESSAGE_RE = re.compile(r'\bonMessage\s*\(')
38
+
39
+ # MessageProducer / MessageConsumer declarations
40
+ _PRODUCER_RE = re.compile(r'\bMessageProducer\b')
41
+ _CONSUMER_RE = re.compile(r'\bMessageConsumer\b')
42
+
43
+ # Tibjms-specific queue/topic classes
44
+ _TIBJMS_QUEUE_RE = re.compile(r'new\s+TibjmsQueue\s*\(\s*"([^"]+)"')
45
+ _TIBJMS_TOPIC_RE = re.compile(r'new\s+TibjmsTopic\s*\(\s*"([^"]+)"')
46
+
47
+
48
+ class TibcoEmsDetector:
49
+ """Detects TIBCO EMS queue and topic usage in Java source files."""
50
+
51
+ name: str = "tibco_ems"
52
+ supported_languages: tuple[str, ...] = ("java",)
53
+
54
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
55
+ result = DetectorResult()
56
+ text = decode_text(ctx)
57
+ lines = text.split("\n")
58
+
59
+ if "tibjms" not in text and "TibjmsConnectionFactory" not in text and "com.tibco" not in text and "TIBJMS" not in text:
60
+ return result
61
+
62
+ # Find class name
63
+ class_name: str | None = None
64
+ for line in lines:
65
+ cm = _CLASS_RE.search(line)
66
+ if cm:
67
+ class_name = cm.group(1)
68
+ break
69
+
70
+ if not class_name:
71
+ return result
72
+
73
+ class_node_id = f"{ctx.file_path}:{class_name}"
74
+ seen_queues: set[str] = set()
75
+ seen_topics: set[str] = set()
76
+
77
+ def _ensure_queue_node(queue_name: str) -> str:
78
+ queue_id = f"ems:queue:{queue_name}"
79
+ if queue_name not in seen_queues:
80
+ seen_queues.add(queue_name)
81
+ result.nodes.append(GraphNode(
82
+ id=queue_id,
83
+ kind=NodeKind.QUEUE,
84
+ label=f"ems:queue:{queue_name}",
85
+ properties={"broker": "tibco_ems", "queue": queue_name},
86
+ ))
87
+ return queue_id
88
+
89
+ def _ensure_topic_node(topic_name: str) -> str:
90
+ topic_id = f"ems:topic:{topic_name}"
91
+ if topic_name not in seen_topics:
92
+ seen_topics.add(topic_name)
93
+ result.nodes.append(GraphNode(
94
+ id=topic_id,
95
+ kind=NodeKind.TOPIC,
96
+ label=f"ems:topic:{topic_name}",
97
+ properties={"broker": "tibco_ems", "topic": topic_name},
98
+ ))
99
+ return topic_id
100
+
101
+ # Detect whether this class is a producer or consumer
102
+ is_producer = bool(_SEND_RE.search(text) or _PUBLISH_RE.search(text) or _PRODUCER_RE.search(text))
103
+ is_consumer = bool(_RECEIVE_RE.search(text) or _ON_MESSAGE_RE.search(text) or _CONSUMER_RE.search(text))
104
+
105
+ # Detect connection factory — create a node for the EMS server
106
+ server_urls: list[str] = []
107
+ for i, line in enumerate(lines):
108
+ m = _TIBJMS_FACTORY_RE.search(line)
109
+ if m:
110
+ factory_type = m.group(1)
111
+ # Look for server URL on same line or next few lines
112
+ for j in range(max(0, i - 1), min(len(lines), i + 4)):
113
+ url_m = _SERVER_URL_RE.search(lines[j])
114
+ if url_m:
115
+ server_urls.append(url_m.group(1))
116
+
117
+ # Create an EMS connection node
118
+ node_id = f"ems:server:{factory_type}"
119
+ result.nodes.append(GraphNode(
120
+ id=node_id,
121
+ kind=NodeKind.MESSAGE_QUEUE,
122
+ label=f"ems:{factory_type}",
123
+ properties={
124
+ "broker": "tibco_ems",
125
+ "factory_type": factory_type,
126
+ **({"server_url": server_urls[0]} if server_urls else {}),
127
+ },
128
+ ))
129
+ result.edges.append(GraphEdge(
130
+ source=class_node_id,
131
+ target=node_id,
132
+ kind=EdgeKind.CONNECTS_TO,
133
+ label=f"{class_name} connects to EMS via {factory_type}",
134
+ properties={"factory_type": factory_type},
135
+ ))
136
+
137
+ # Detect createQueue / createTopic
138
+ for i, line in enumerate(lines):
139
+ m = _CREATE_QUEUE_RE.search(line)
140
+ if m:
141
+ queue_name = m.group(1)
142
+ queue_id = _ensure_queue_node(queue_name)
143
+ if is_producer:
144
+ result.edges.append(GraphEdge(
145
+ source=class_node_id,
146
+ target=queue_id,
147
+ kind=EdgeKind.SENDS_TO,
148
+ label=f"{class_name} sends to {queue_name}",
149
+ properties={"queue": queue_name},
150
+ ))
151
+ if is_consumer:
152
+ result.edges.append(GraphEdge(
153
+ source=class_node_id,
154
+ target=queue_id,
155
+ kind=EdgeKind.RECEIVES_FROM,
156
+ label=f"{class_name} receives from {queue_name}",
157
+ properties={"queue": queue_name},
158
+ ))
159
+
160
+ m = _CREATE_TOPIC_RE.search(line)
161
+ if m:
162
+ topic_name = m.group(1)
163
+ topic_id = _ensure_topic_node(topic_name)
164
+ if is_producer:
165
+ result.edges.append(GraphEdge(
166
+ source=class_node_id,
167
+ target=topic_id,
168
+ kind=EdgeKind.SENDS_TO,
169
+ label=f"{class_name} sends to {topic_name}",
170
+ properties={"topic": topic_name},
171
+ ))
172
+ if is_consumer:
173
+ result.edges.append(GraphEdge(
174
+ source=class_node_id,
175
+ target=topic_id,
176
+ kind=EdgeKind.RECEIVES_FROM,
177
+ label=f"{class_name} receives from {topic_name}",
178
+ properties={"topic": topic_name},
179
+ ))
180
+
181
+ # Detect TibjmsQueue / TibjmsTopic direct instantiation
182
+ for i, line in enumerate(lines):
183
+ m = _TIBJMS_QUEUE_RE.search(line)
184
+ if m:
185
+ queue_name = m.group(1)
186
+ _ensure_queue_node(queue_name)
187
+
188
+ m = _TIBJMS_TOPIC_RE.search(line)
189
+ if m:
190
+ topic_name = m.group(1)
191
+ _ensure_topic_node(topic_name)
192
+
193
+ return result
@@ -0,0 +1,188 @@
1
+ """WebSocket 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
+ # JSR 356 @ServerEndpoint
20
+ _SERVER_ENDPOINT_RE = re.compile(r'@ServerEndpoint\s*\(\s*(?:value\s*=\s*)?"([^"]+)"')
21
+
22
+ # Spring WebSocket
23
+ _MESSAGE_MAPPING_RE = re.compile(r'@MessageMapping\s*\(\s*"([^"]+)"')
24
+ _SEND_TO_RE = re.compile(r'@SendTo\s*\(\s*"([^"]+)"')
25
+ _SEND_TO_USER_RE = re.compile(r'@SendToUser\s*\(\s*"([^"]+)"')
26
+
27
+ # WebSocket handler registration (in config classes)
28
+ _REGISTER_HANDLER_RE = re.compile(
29
+ r'\.addHandler\s*\(\s*\w+\s*,\s*"([^"]+)"'
30
+ )
31
+ _STOMP_ENDPOINT_RE = re.compile(
32
+ r'registerStompEndpoints.*?\.addEndpoint\s*\(\s*"([^"]+)"',
33
+ re.DOTALL,
34
+ )
35
+ _APP_DEST_PREFIX_RE = re.compile(
36
+ r'setApplicationDestinationPrefixes\s*\(\s*"([^"]+)"'
37
+ )
38
+
39
+ # SimpMessagingTemplate for sending
40
+ _MESSAGING_TEMPLATE_RE = re.compile(
41
+ r'(?:simpMessagingTemplate|messagingTemplate)\s*\.(?:convertAndSend|convertAndSendToUser)\s*\(\s*"([^"]+)"'
42
+ )
43
+
44
+ _METHOD_RE = re.compile(r"(?:public|protected|private)?\s*(?:[\w<>\[\],?\s]+)\s+(\w+)\s*\(")
45
+
46
+
47
+ class WebSocketDetector:
48
+ """Detects WebSocket endpoints and message handlers."""
49
+
50
+ name: str = "websocket"
51
+ supported_languages: tuple[str, ...] = ("java",)
52
+
53
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
54
+ result = DetectorResult()
55
+ text = decode_text(ctx)
56
+ lines = text.split("\n")
57
+
58
+ if not any(kw in text for kw in (
59
+ "@ServerEndpoint", "@MessageMapping", "WebSocketHandler",
60
+ "registerStompEndpoints", "SimpMessagingTemplate",
61
+ "simpMessagingTemplate", "messagingTemplate",
62
+ "@SendTo", "@SendToUser",
63
+ )):
64
+ return result
65
+
66
+ # Find class name
67
+ class_name: str | None = None
68
+ class_line: int = 0
69
+ for i, line in enumerate(lines):
70
+ cm = _CLASS_RE.search(line)
71
+ if cm:
72
+ class_name = cm.group(1)
73
+ class_line = i + 1
74
+ break
75
+
76
+ if not class_name:
77
+ return result
78
+
79
+ class_node_id = f"{ctx.file_path}:{class_name}"
80
+
81
+ # Detect @ServerEndpoint (JSR 356)
82
+ for m in _SERVER_ENDPOINT_RE.finditer(text):
83
+ path = m.group(1)
84
+ line_num = text[:m.start()].count("\n") + 1
85
+ ws_id = f"ws:endpoint:{path}"
86
+
87
+ result.nodes.append(GraphNode(
88
+ id=ws_id,
89
+ kind=NodeKind.WEBSOCKET_ENDPOINT,
90
+ label=f"WS {path}",
91
+ fqn=f"{class_name}:{path}",
92
+ module=ctx.module_name,
93
+ location=SourceLocation(file_path=ctx.file_path, line_start=line_num),
94
+ annotations=["@ServerEndpoint"],
95
+ properties={"path": path, "protocol": "websocket", "type": "jsr356"},
96
+ ))
97
+
98
+ result.edges.append(GraphEdge(
99
+ source=class_node_id,
100
+ target=ws_id,
101
+ kind=EdgeKind.EXPOSES,
102
+ label=f"{class_name} exposes WS {path}",
103
+ ))
104
+
105
+ # Detect @MessageMapping (Spring STOMP)
106
+ for i, line in enumerate(lines):
107
+ m = _MESSAGE_MAPPING_RE.search(line)
108
+ if not m:
109
+ continue
110
+
111
+ destination = m.group(1)
112
+ method_name = None
113
+ for k in range(i + 1, min(i + 5, len(lines))):
114
+ mm = _METHOD_RE.search(lines[k])
115
+ if mm:
116
+ method_name = mm.group(1)
117
+ break
118
+
119
+ ws_id = f"ws:message:{destination}"
120
+ result.nodes.append(GraphNode(
121
+ id=ws_id,
122
+ kind=NodeKind.WEBSOCKET_ENDPOINT,
123
+ label=f"WS MSG {destination}",
124
+ fqn=f"{class_name}.{method_name or 'unknown'}",
125
+ module=ctx.module_name,
126
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
127
+ annotations=["@MessageMapping"],
128
+ properties={
129
+ "destination": destination,
130
+ "protocol": "websocket",
131
+ "type": "stomp",
132
+ },
133
+ ))
134
+
135
+ result.edges.append(GraphEdge(
136
+ source=class_node_id,
137
+ target=ws_id,
138
+ kind=EdgeKind.EXPOSES,
139
+ label=f"{class_name} handles WS {destination}",
140
+ ))
141
+
142
+ # Check for @SendTo on next lines
143
+ for k in range(i + 1, min(i + 5, len(lines))):
144
+ st = _SEND_TO_RE.search(lines[k]) or _SEND_TO_USER_RE.search(lines[k])
145
+ if st:
146
+ send_dest = st.group(1)
147
+ send_id = f"ws:topic:{send_dest}"
148
+ result.nodes.append(GraphNode(
149
+ id=send_id,
150
+ kind=NodeKind.WEBSOCKET_ENDPOINT,
151
+ label=f"WS TOPIC {send_dest}",
152
+ properties={"destination": send_dest, "protocol": "websocket"},
153
+ ))
154
+ result.edges.append(GraphEdge(
155
+ source=ws_id,
156
+ target=send_id,
157
+ kind=EdgeKind.PRODUCES,
158
+ label=f"{destination} sends to {send_dest}",
159
+ ))
160
+
161
+ # Detect STOMP endpoint registration
162
+ for m in _STOMP_ENDPOINT_RE.finditer(text):
163
+ path = m.group(1)
164
+ ws_id = f"ws:stomp:{path}"
165
+ line_num = text[:m.start()].count("\n") + 1
166
+ result.nodes.append(GraphNode(
167
+ id=ws_id,
168
+ kind=NodeKind.WEBSOCKET_ENDPOINT,
169
+ label=f"STOMP {path}",
170
+ module=ctx.module_name,
171
+ location=SourceLocation(file_path=ctx.file_path, line_start=line_num),
172
+ properties={"path": path, "protocol": "stomp", "type": "stomp_endpoint"},
173
+ ))
174
+
175
+ # Detect SimpMessagingTemplate sends
176
+ for m in _MESSAGING_TEMPLATE_RE.finditer(text):
177
+ destination = m.group(1)
178
+ line_num = text[:m.start()].count("\n") + 1
179
+
180
+ result.edges.append(GraphEdge(
181
+ source=class_node_id,
182
+ target=f"ws:topic:{destination}",
183
+ kind=EdgeKind.PRODUCES,
184
+ label=f"{class_name} sends to {destination}",
185
+ properties={"destination": destination},
186
+ ))
187
+
188
+ return result
File without changes