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,234 @@
1
+ """Actix-web and Axum web framework detector for Rust 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
+ # Actix attribute macros: #[get("/path")], #[post("/path")], etc.
18
+ _ACTIX_ATTR_RE = re.compile(r'#\[(get|post|put|delete)\s*\(\s*"([^"]*)"\s*\)\s*\]')
19
+
20
+ # HttpServer::new(
21
+ _HTTP_SERVER_RE = re.compile(r"HttpServer::new\s*\(")
22
+
23
+ # .route("/path", web::get().to(handler))
24
+ _ROUTE_RE = re.compile(r'\.route\s*\(\s*"([^"]*)"\s*,\s*web::(get|post|put|delete)\s*\(\s*\)\s*\.to\s*\(\s*(\w+)')
25
+
26
+ # .service(web::resource("/path"))
27
+ _SERVICE_RESOURCE_RE = re.compile(r'\.service\s*\(\s*web::resource\s*\(\s*"([^"]*)"')
28
+
29
+ # Axum: Router::new().route("/path", get(handler))
30
+ _AXUM_ROUTE_RE = re.compile(r'\.route\s*\(\s*"([^"]*)"\s*,\s*(get|post|put|delete)\s*\(\s*(\w+)\s*\)')
31
+
32
+ # Axum: .layer(middleware)
33
+ _AXUM_LAYER_RE = re.compile(r"\.layer\s*\(\s*(\w+)")
34
+
35
+ # #[actix_web::main] or #[tokio::main]
36
+ _MAIN_ATTR_RE = re.compile(r"#\[(actix_web::main|tokio::main)\]")
37
+
38
+ # fn function_name
39
+ _FN_RE = re.compile(r"(?:pub\s+)?(?:async\s+)?fn\s+(\w+)")
40
+
41
+
42
+ class ActixWebDetector:
43
+ """Detects Actix-web and Axum web framework patterns in Rust source files."""
44
+
45
+ name: str = "actix_web"
46
+ supported_languages: tuple[str, ...] = ("rust",)
47
+
48
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
49
+ result = DetectorResult()
50
+ text = decode_text(ctx)
51
+
52
+ # Fast bail
53
+ if not any(
54
+ marker in text
55
+ for marker in (
56
+ "#[get",
57
+ "#[post",
58
+ "#[put",
59
+ "#[delete",
60
+ "HttpServer::new",
61
+ "web::get",
62
+ "web::post",
63
+ "web::resource",
64
+ "Router::new",
65
+ ".layer(",
66
+ "actix_web::main",
67
+ "tokio::main",
68
+ "actix_web",
69
+ "axum",
70
+ )
71
+ ):
72
+ return result
73
+
74
+ lines = text.split("\n")
75
+
76
+ for i, line in enumerate(lines):
77
+ lineno = i + 1
78
+
79
+ # Actix attribute macros
80
+ m = _ACTIX_ATTR_RE.search(line)
81
+ if m:
82
+ method = m.group(1).upper()
83
+ path = m.group(2)
84
+
85
+ # Find the function name on subsequent lines
86
+ fn_name: str | None = None
87
+ for k in range(i + 1, min(i + 5, len(lines))):
88
+ fm = _FN_RE.search(lines[k])
89
+ if fm:
90
+ fn_name = fm.group(1)
91
+ break
92
+
93
+ node_id = f"rust_web:{ctx.file_path}:{method}:{path}:{lineno}"
94
+ result.nodes.append(
95
+ GraphNode(
96
+ id=node_id,
97
+ kind=NodeKind.ENDPOINT,
98
+ label=f"{method} {path}",
99
+ fqn=fn_name,
100
+ module=ctx.module_name,
101
+ location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
102
+ annotations=[f"#[{m.group(1)}]"],
103
+ properties={
104
+ "framework": "actix_web",
105
+ "http_method": method,
106
+ "path": path,
107
+ },
108
+ )
109
+ )
110
+
111
+ # HttpServer::new
112
+ m = _HTTP_SERVER_RE.search(line)
113
+ if m:
114
+ node_id = f"rust_web:{ctx.file_path}:http_server:{lineno}"
115
+ result.nodes.append(
116
+ GraphNode(
117
+ id=node_id,
118
+ kind=NodeKind.MODULE,
119
+ label="HttpServer",
120
+ fqn="HttpServer",
121
+ module=ctx.module_name,
122
+ location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
123
+ annotations=[],
124
+ properties={"framework": "actix_web"},
125
+ )
126
+ )
127
+
128
+ # .route("/path", web::get().to(handler))
129
+ m = _ROUTE_RE.search(line)
130
+ if m:
131
+ path = m.group(1)
132
+ method = m.group(2).upper()
133
+ handler = m.group(3)
134
+ node_id = f"rust_web:{ctx.file_path}:{method}:{path}:{lineno}"
135
+ result.nodes.append(
136
+ GraphNode(
137
+ id=node_id,
138
+ kind=NodeKind.ENDPOINT,
139
+ label=f"{method} {path}",
140
+ fqn=handler,
141
+ module=ctx.module_name,
142
+ location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
143
+ annotations=[],
144
+ properties={
145
+ "framework": "actix_web",
146
+ "http_method": method,
147
+ "path": path,
148
+ "handler": handler,
149
+ },
150
+ )
151
+ )
152
+
153
+ # .service(web::resource("/path"))
154
+ m = _SERVICE_RESOURCE_RE.search(line)
155
+ if m:
156
+ path = m.group(1)
157
+ node_id = f"rust_web:{ctx.file_path}:resource:{path}:{lineno}"
158
+ result.nodes.append(
159
+ GraphNode(
160
+ id=node_id,
161
+ kind=NodeKind.ENDPOINT,
162
+ label=f"resource {path}",
163
+ fqn=path,
164
+ module=ctx.module_name,
165
+ location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
166
+ annotations=[],
167
+ properties={"framework": "actix_web", "path": path},
168
+ )
169
+ )
170
+
171
+ # Axum Router::new().route("/path", get(handler))
172
+ m = _AXUM_ROUTE_RE.search(line)
173
+ if m:
174
+ path = m.group(1)
175
+ method = m.group(2).upper()
176
+ handler = m.group(3)
177
+ node_id = f"rust_web:{ctx.file_path}:{method}:{path}:{lineno}"
178
+ result.nodes.append(
179
+ GraphNode(
180
+ id=node_id,
181
+ kind=NodeKind.ENDPOINT,
182
+ label=f"{method} {path}",
183
+ fqn=handler,
184
+ module=ctx.module_name,
185
+ location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
186
+ annotations=[],
187
+ properties={
188
+ "framework": "axum",
189
+ "http_method": method,
190
+ "path": path,
191
+ "handler": handler,
192
+ },
193
+ )
194
+ )
195
+
196
+ # Axum .layer(middleware)
197
+ m = _AXUM_LAYER_RE.search(line)
198
+ if m:
199
+ middleware_name = m.group(1)
200
+ node_id = f"rust_web:{ctx.file_path}:layer:{middleware_name}:{lineno}"
201
+ result.nodes.append(
202
+ GraphNode(
203
+ id=node_id,
204
+ kind=NodeKind.MIDDLEWARE,
205
+ label=f"layer({middleware_name})",
206
+ fqn=middleware_name,
207
+ module=ctx.module_name,
208
+ location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
209
+ annotations=[],
210
+ properties={"framework": "axum", "middleware": middleware_name},
211
+ )
212
+ )
213
+
214
+ # #[actix_web::main] / #[tokio::main]
215
+ m = _MAIN_ATTR_RE.search(line)
216
+ if m:
217
+ attr = m.group(1)
218
+ node_id = f"rust_web:{ctx.file_path}:main:{lineno}"
219
+ result.nodes.append(
220
+ GraphNode(
221
+ id=node_id,
222
+ kind=NodeKind.MODULE,
223
+ label=f"#[{attr}]",
224
+ fqn="main",
225
+ module=ctx.module_name,
226
+ location=SourceLocation(file_path=ctx.file_path, line_start=lineno),
227
+ annotations=[f"#[{attr}]"],
228
+ properties={
229
+ "framework": "actix_web" if "actix" in attr else "tokio",
230
+ },
231
+ )
232
+ )
233
+
234
+ return result
@@ -0,0 +1,174 @@
1
+ """Regex-based Rust structures detector for Rust 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, find_line_number
9
+ from osscodeiq.models.graph import (
10
+ EdgeKind,
11
+ GraphEdge,
12
+ GraphNode,
13
+ NodeKind,
14
+ SourceLocation,
15
+ )
16
+
17
+ _RUST_USE_RE = re.compile(r'^\s*use\s+([\w:]+)', re.MULTILINE)
18
+ _RUST_STRUCT_RE = re.compile(r'^\s*(?:pub\s+)?struct\s+(\w+)', re.MULTILINE)
19
+ _RUST_TRAIT_RE = re.compile(r'^\s*(?:pub\s+)?trait\s+(\w+)', re.MULTILINE)
20
+ # FIX: Match both trait impls (`impl X for Y`) and inherent impls (`impl Foo`).
21
+ # Group 1 = first type, Group 2 = second type (None for inherent impls).
22
+ _RUST_IMPL_RE = re.compile(
23
+ r'^\s*impl(?:<[^>]*>)?\s+(\w+)(?:\s+for\s+(\w+))?\s*\{',
24
+ re.MULTILINE,
25
+ )
26
+ # FIX: Match async fn, pub async fn, pub(crate) fn, etc.
27
+ _RUST_FN_RE = re.compile(
28
+ r'^\s*(?:pub(?:\([^)]*\))?\s+)?(?:async\s+)?(?:unsafe\s+)?fn\s+(\w+)\s*\(',
29
+ re.MULTILINE,
30
+ )
31
+ _RUST_MOD_RE = re.compile(r'^\s*(?:pub\s+)?mod\s+(\w+)', re.MULTILINE)
32
+ _RUST_ENUM_RE = re.compile(r'^\s*(?:pub\s+)?enum\s+(\w+)', re.MULTILINE)
33
+ # FIX: Add macro_rules! detection.
34
+ _RUST_MACRO_RE = re.compile(r'^\s*macro_rules!\s+(\w+)', re.MULTILINE)
35
+
36
+
37
+
38
+ class RustStructuresDetector:
39
+ """Detects Rust imports, structs, traits, impls, functions, enums, modules, and macros."""
40
+
41
+ name: str = "rust_structures"
42
+ supported_languages: tuple[str, ...] = ("rust",)
43
+
44
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
45
+ result = DetectorResult()
46
+ text = decode_text(ctx)
47
+ file_node_id = ctx.file_path
48
+
49
+ for m in _RUST_USE_RE.finditer(text):
50
+ target = m.group(1)
51
+ result.edges.append(GraphEdge(
52
+ source=file_node_id,
53
+ target=target,
54
+ kind=EdgeKind.IMPORTS,
55
+ label=f"{ctx.file_path} imports {target}",
56
+ ))
57
+
58
+ for m in _RUST_MOD_RE.finditer(text):
59
+ mod_name = m.group(1)
60
+ result.nodes.append(GraphNode(
61
+ id=f"{ctx.file_path}:mod:{mod_name}",
62
+ kind=NodeKind.MODULE,
63
+ label=mod_name,
64
+ fqn=mod_name,
65
+ module=ctx.module_name,
66
+ location=SourceLocation(
67
+ file_path=ctx.file_path,
68
+ line_start=find_line_number(text, m.start()),
69
+ ),
70
+ ))
71
+
72
+ for m in _RUST_STRUCT_RE.finditer(text):
73
+ struct_name = m.group(1)
74
+ result.nodes.append(GraphNode(
75
+ id=f"{ctx.file_path}:{struct_name}",
76
+ kind=NodeKind.CLASS,
77
+ label=struct_name,
78
+ fqn=struct_name,
79
+ module=ctx.module_name,
80
+ location=SourceLocation(
81
+ file_path=ctx.file_path,
82
+ line_start=find_line_number(text, m.start()),
83
+ ),
84
+ properties={"type": "struct"},
85
+ ))
86
+
87
+ for m in _RUST_TRAIT_RE.finditer(text):
88
+ trait_name = m.group(1)
89
+ result.nodes.append(GraphNode(
90
+ id=f"{ctx.file_path}:{trait_name}",
91
+ kind=NodeKind.INTERFACE,
92
+ label=trait_name,
93
+ fqn=trait_name,
94
+ module=ctx.module_name,
95
+ location=SourceLocation(
96
+ file_path=ctx.file_path,
97
+ line_start=find_line_number(text, m.start()),
98
+ ),
99
+ properties={"type": "trait"},
100
+ ))
101
+
102
+ for m in _RUST_ENUM_RE.finditer(text):
103
+ enum_name = m.group(1)
104
+ result.nodes.append(GraphNode(
105
+ id=f"{ctx.file_path}:{enum_name}",
106
+ kind=NodeKind.ENUM,
107
+ label=enum_name,
108
+ fqn=enum_name,
109
+ module=ctx.module_name,
110
+ location=SourceLocation(
111
+ file_path=ctx.file_path,
112
+ line_start=find_line_number(text, m.start()),
113
+ ),
114
+ ))
115
+
116
+ # FIX: Handle both trait impls and inherent impls.
117
+ for m in _RUST_IMPL_RE.finditer(text):
118
+ first = m.group(1)
119
+ second = m.group(2)
120
+ if second:
121
+ # Trait impl: `impl Trait for Struct`
122
+ trait_name = first
123
+ struct_name = second
124
+ struct_node_id = f"{ctx.file_path}:{struct_name}"
125
+ trait_node_id = f"{ctx.file_path}:{trait_name}"
126
+ result.edges.append(GraphEdge(
127
+ source=struct_node_id,
128
+ target=trait_node_id,
129
+ kind=EdgeKind.IMPLEMENTS,
130
+ label=f"{struct_name} implements {trait_name}",
131
+ ))
132
+ else:
133
+ # Inherent impl: `impl Foo {`
134
+ struct_name = first
135
+ struct_node_id = f"{ctx.file_path}:{struct_name}"
136
+ result.edges.append(GraphEdge(
137
+ source=struct_node_id,
138
+ target=struct_node_id,
139
+ kind=EdgeKind.DEFINES,
140
+ label=f"{struct_name} inherent impl",
141
+ ))
142
+
143
+ for m in _RUST_FN_RE.finditer(text):
144
+ fn_name = m.group(1)
145
+ result.nodes.append(GraphNode(
146
+ id=f"{ctx.file_path}:{fn_name}",
147
+ kind=NodeKind.METHOD,
148
+ label=fn_name,
149
+ fqn=fn_name,
150
+ module=ctx.module_name,
151
+ location=SourceLocation(
152
+ file_path=ctx.file_path,
153
+ line_start=find_line_number(text, m.start()),
154
+ ),
155
+ properties={"type": "function"},
156
+ ))
157
+
158
+ # FIX: Detect macro_rules! definitions.
159
+ for m in _RUST_MACRO_RE.finditer(text):
160
+ macro_name = m.group(1)
161
+ result.nodes.append(GraphNode(
162
+ id=f"{ctx.file_path}:macro:{macro_name}",
163
+ kind=NodeKind.METHOD,
164
+ label=f"{macro_name}!",
165
+ fqn=f"{macro_name}!",
166
+ module=ctx.module_name,
167
+ location=SourceLocation(
168
+ file_path=ctx.file_path,
169
+ line_start=find_line_number(text, m.start()),
170
+ ),
171
+ properties={"type": "macro"},
172
+ ))
173
+
174
+ return result
File without changes
@@ -0,0 +1,128 @@
1
+ """Regex-based Scala structures detector for Scala 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, find_line_number
9
+ from osscodeiq.models.graph import (
10
+ EdgeKind,
11
+ GraphEdge,
12
+ GraphNode,
13
+ NodeKind,
14
+ SourceLocation,
15
+ )
16
+
17
+ _SCALA_IMPORT_RE = re.compile(r'^\s*import\s+([\w.]+)', re.MULTILINE)
18
+ _SCALA_CLASS_RE = re.compile(
19
+ r'^\s*(?:case\s+)?class\s+(\w+)'
20
+ r'(?:\s+extends\s+(\w+))?'
21
+ r'(?:\s+with\s+([\w\s,]+))?',
22
+ re.MULTILINE,
23
+ )
24
+ _SCALA_TRAIT_RE = re.compile(r'^\s*trait\s+(\w+)', re.MULTILINE)
25
+ _SCALA_OBJECT_RE = re.compile(r'^\s*object\s+(\w+)', re.MULTILINE)
26
+ _SCALA_DEF_RE = re.compile(r'^\s*def\s+(\w+)\s*[\[(]', re.MULTILINE)
27
+
28
+
29
+
30
+ class ScalaStructuresDetector:
31
+ """Detects Scala imports, classes, traits, objects, and methods."""
32
+
33
+ name: str = "scala_structures"
34
+ supported_languages: tuple[str, ...] = ("scala",)
35
+
36
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
37
+ result = DetectorResult()
38
+ text = decode_text(ctx)
39
+ file_node_id = ctx.file_path
40
+
41
+ for m in _SCALA_IMPORT_RE.finditer(text):
42
+ target = m.group(1)
43
+ result.edges.append(GraphEdge(
44
+ source=file_node_id,
45
+ target=target,
46
+ kind=EdgeKind.IMPORTS,
47
+ label=f"{ctx.file_path} imports {target}",
48
+ ))
49
+
50
+ for m in _SCALA_CLASS_RE.finditer(text):
51
+ class_name = m.group(1)
52
+ base_class = m.group(2)
53
+ traits_str = m.group(3)
54
+ node_id = f"{ctx.file_path}:{class_name}"
55
+ result.nodes.append(GraphNode(
56
+ id=node_id,
57
+ kind=NodeKind.CLASS,
58
+ label=class_name,
59
+ fqn=class_name,
60
+ module=ctx.module_name,
61
+ location=SourceLocation(
62
+ file_path=ctx.file_path,
63
+ line_start=find_line_number(text, m.start()),
64
+ ),
65
+ ))
66
+ if base_class:
67
+ result.edges.append(GraphEdge(
68
+ source=node_id,
69
+ target=base_class,
70
+ kind=EdgeKind.EXTENDS,
71
+ label=f"{class_name} extends {base_class}",
72
+ ))
73
+ if traits_str:
74
+ for trait in traits_str.split(","):
75
+ trait = trait.strip()
76
+ if trait:
77
+ result.edges.append(GraphEdge(
78
+ source=node_id,
79
+ target=trait,
80
+ kind=EdgeKind.IMPLEMENTS,
81
+ label=f"{class_name} implements {trait}",
82
+ ))
83
+
84
+ for m in _SCALA_TRAIT_RE.finditer(text):
85
+ trait_name = m.group(1)
86
+ result.nodes.append(GraphNode(
87
+ id=f"{ctx.file_path}:{trait_name}",
88
+ kind=NodeKind.INTERFACE,
89
+ label=trait_name,
90
+ fqn=trait_name,
91
+ module=ctx.module_name,
92
+ location=SourceLocation(
93
+ file_path=ctx.file_path,
94
+ line_start=find_line_number(text, m.start()),
95
+ ),
96
+ properties={"type": "trait"},
97
+ ))
98
+
99
+ for m in _SCALA_OBJECT_RE.finditer(text):
100
+ obj_name = m.group(1)
101
+ result.nodes.append(GraphNode(
102
+ id=f"{ctx.file_path}:{obj_name}",
103
+ kind=NodeKind.CLASS,
104
+ label=obj_name,
105
+ fqn=obj_name,
106
+ module=ctx.module_name,
107
+ location=SourceLocation(
108
+ file_path=ctx.file_path,
109
+ line_start=find_line_number(text, m.start()),
110
+ ),
111
+ properties={"type": "object"},
112
+ ))
113
+
114
+ for m in _SCALA_DEF_RE.finditer(text):
115
+ method_name = m.group(1)
116
+ result.nodes.append(GraphNode(
117
+ id=f"{ctx.file_path}:{method_name}",
118
+ kind=NodeKind.METHOD,
119
+ label=method_name,
120
+ fqn=method_name,
121
+ module=ctx.module_name,
122
+ location=SourceLocation(
123
+ file_path=ctx.file_path,
124
+ line_start=find_line_number(text, m.start()),
125
+ ),
126
+ ))
127
+
128
+ return result
File without changes
@@ -0,0 +1,127 @@
1
+ """Bash/Shell script detector for functions, sourced files, exports, and tool usage."""
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
+ _FUNC_RE = re.compile(r'(?:function\s+(\w+)|(\w+)\s*\(\s*\))\s*\{')
18
+ _SOURCE_RE = re.compile(r'(?:source|\.) (?:")?([^\s"]+)')
19
+ _SHEBANG_RE = re.compile(r'^#!\s*/(?:usr/)?(?:bin/)?(?:env\s+)?(\w+)')
20
+ _EXPORT_RE = re.compile(r'export\s+(\w+)=')
21
+
22
+ _INFRA_TOOLS = {"docker", "kubectl", "terraform", "aws", "az", "gcloud"}
23
+ _TOOL_RE = re.compile(
24
+ r'\b(' + '|'.join(re.escape(t) for t in sorted(_INFRA_TOOLS)) + r')\b'
25
+ )
26
+
27
+
28
+ class BashDetector:
29
+ """Detects Bash/Shell script structures: functions, sourced files, exports, and tool usage."""
30
+
31
+ name: str = "bash"
32
+ supported_languages: tuple[str, ...] = ("bash",)
33
+
34
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
35
+ result = DetectorResult()
36
+ text = decode_text(ctx)
37
+ lines = text.split("\n")
38
+
39
+ # Detect shebang
40
+ if lines:
41
+ m = _SHEBANG_RE.match(lines[0])
42
+ if m:
43
+ shell = m.group(1)
44
+ result.nodes.append(GraphNode(
45
+ id=ctx.file_path,
46
+ kind=NodeKind.MODULE,
47
+ label=ctx.file_path,
48
+ fqn=ctx.file_path,
49
+ module=ctx.module_name,
50
+ location=SourceLocation(
51
+ file_path=ctx.file_path,
52
+ line_start=1,
53
+ ),
54
+ properties={"shell": shell},
55
+ ))
56
+
57
+ # Detect function definitions
58
+ for i, line in enumerate(lines):
59
+ m = _FUNC_RE.search(line)
60
+ if not m:
61
+ continue
62
+ func_name = m.group(1) or m.group(2)
63
+
64
+ result.nodes.append(GraphNode(
65
+ id=f"{ctx.file_path}:{func_name}",
66
+ kind=NodeKind.METHOD,
67
+ label=func_name,
68
+ fqn=func_name,
69
+ module=ctx.module_name,
70
+ location=SourceLocation(
71
+ file_path=ctx.file_path,
72
+ line_start=i + 1,
73
+ ),
74
+ ))
75
+
76
+ # Detect source/dot-source imports
77
+ for i, line in enumerate(lines):
78
+ m = _SOURCE_RE.search(line)
79
+ if not m:
80
+ continue
81
+ sourced_file = m.group(1)
82
+ result.edges.append(GraphEdge(
83
+ source=ctx.file_path,
84
+ target=sourced_file,
85
+ kind=EdgeKind.IMPORTS,
86
+ label=f"{ctx.file_path} sources {sourced_file}",
87
+ ))
88
+
89
+ # Detect exports
90
+ for i, line in enumerate(lines):
91
+ m = _EXPORT_RE.search(line)
92
+ if not m:
93
+ continue
94
+ var_name = m.group(1)
95
+
96
+ result.nodes.append(GraphNode(
97
+ id=f"{ctx.file_path}:export:{var_name}",
98
+ kind=NodeKind.CONFIG_DEFINITION,
99
+ label=f"export {var_name}",
100
+ fqn=var_name,
101
+ module=ctx.module_name,
102
+ location=SourceLocation(
103
+ file_path=ctx.file_path,
104
+ line_start=i + 1,
105
+ ),
106
+ ))
107
+
108
+ # Detect infrastructure tool usage
109
+ tools_seen: set[str] = set()
110
+ for i, line in enumerate(lines):
111
+ # Skip comments
112
+ stripped = line.lstrip()
113
+ if stripped.startswith('#'):
114
+ continue
115
+ for m in _TOOL_RE.finditer(line):
116
+ tool = m.group(1)
117
+ if tool not in tools_seen:
118
+ tools_seen.add(tool)
119
+ result.edges.append(GraphEdge(
120
+ source=ctx.file_path,
121
+ target=tool,
122
+ kind=EdgeKind.CALLS,
123
+ label=f"{ctx.file_path} uses {tool}",
124
+ properties={"tool": tool},
125
+ ))
126
+
127
+ return result