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,413 @@
1
+ """Generic multi-language import and structure detector.
2
+
3
+ Catches import/dependency patterns and structural definitions across
4
+ languages that don't have dedicated detectors.
5
+
6
+ Note: Kotlin, Rust, and Scala have been moved to dedicated detectors in
7
+ detectors/kotlin/, detectors/rust/, and detectors/scala/ respectively.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import re
13
+ from typing import Callable
14
+
15
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
16
+ from osscodeiq.detectors.utils import decode_text, find_line_number
17
+ from osscodeiq.models.graph import (
18
+ EdgeKind,
19
+ GraphEdge,
20
+ GraphNode,
21
+ NodeKind,
22
+ SourceLocation,
23
+ )
24
+
25
+
26
+
27
+ # ---------------------------------------------------------------------------
28
+ # Ruby patterns
29
+ # ---------------------------------------------------------------------------
30
+ _RUBY_REQUIRE_RE = re.compile(r"^(?:require|require_relative)\s+'([^']+)'", re.MULTILINE)
31
+ _RUBY_MODULE_RE = re.compile(r'^\s*module\s+(\w+)', re.MULTILINE)
32
+ _RUBY_CLASS_RE = re.compile(r'^\s*class\s+(\w+)(?:\s*<\s*(\w+))?', re.MULTILINE)
33
+ _RUBY_DEF_RE = re.compile(r'^\s*def\s+(\w+)', re.MULTILINE)
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Swift patterns
37
+ # ---------------------------------------------------------------------------
38
+ _SWIFT_IMPORT_RE = re.compile(r'^\s*import\s+(\w+)', re.MULTILINE)
39
+ _SWIFT_CLASS_RE = re.compile(r'^\s*class\s+(\w+)(?:\s*:\s*([\w\s,]+))?', re.MULTILINE)
40
+ _SWIFT_PROTOCOL_RE = re.compile(r'^\s*protocol\s+(\w+)', re.MULTILINE)
41
+ _SWIFT_STRUCT_RE = re.compile(r'^\s*struct\s+(\w+)', re.MULTILINE)
42
+ _SWIFT_ENUM_RE = re.compile(r'^\s*enum\s+(\w+)', re.MULTILINE)
43
+ _SWIFT_FUNC_RE = re.compile(r'^\s*(?:override\s+)?func\s+(\w+)\s*\(', re.MULTILINE)
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Perl patterns
47
+ # ---------------------------------------------------------------------------
48
+ _PERL_PACKAGE_RE = re.compile(r'^\s*package\s+([\w:]+)\s*;', re.MULTILINE)
49
+ _PERL_SUB_RE = re.compile(r'^\s*sub\s+(\w+)', re.MULTILINE)
50
+ _PERL_USE_RE = re.compile(r'^\s*use\s+([\w:]+)', re.MULTILINE)
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Lua patterns
54
+ # ---------------------------------------------------------------------------
55
+ _LUA_REQUIRE_RE = re.compile(r"""require\s*\(\s*['"]([^'"]+)['"]\s*\)""", re.MULTILINE)
56
+ _LUA_FUNCTION_RE = re.compile(
57
+ r'^\s*(?:local\s+)?function\s+(?:[\w.]+[.:])?(\w+)\s*\(',
58
+ re.MULTILINE,
59
+ )
60
+
61
+ # ---------------------------------------------------------------------------
62
+ # Dart patterns
63
+ # ---------------------------------------------------------------------------
64
+ _DART_IMPORT_RE = re.compile(r"""^\s*import\s+['"]([^'"]+)['"]""", re.MULTILINE)
65
+ _DART_CLASS_RE = re.compile(
66
+ r'^\s*(?:abstract\s+)?class\s+(\w+)'
67
+ r'(?:\s+extends\s+(\w+))?'
68
+ r'(?:\s+implements\s+([\w\s,]+))?',
69
+ re.MULTILINE,
70
+ )
71
+ _DART_FUNC_RE = re.compile(r'^\s*(?:\w+\s+)?(\w+)\s*\(', re.MULTILINE)
72
+
73
+ # ---------------------------------------------------------------------------
74
+ # R patterns
75
+ # ---------------------------------------------------------------------------
76
+ _R_LIBRARY_RE = re.compile(r'(?:library|require)\s*\(\s*(\w+)\s*\)', re.MULTILINE)
77
+ _R_FUNCTION_RE = re.compile(r'^\s*(\w+)\s*<-\s*function\s*\(', re.MULTILINE)
78
+
79
+
80
+ # ---------------------------------------------------------------------------
81
+ # Language-specific detect functions
82
+ # ---------------------------------------------------------------------------
83
+
84
+ def _detect_ruby(ctx: DetectorContext, text: str, result: DetectorResult) -> None:
85
+ file_node_id = ctx.file_path
86
+
87
+ for m in _RUBY_REQUIRE_RE.finditer(text):
88
+ target = m.group(1)
89
+ result.edges.append(GraphEdge(
90
+ source=file_node_id,
91
+ target=target,
92
+ kind=EdgeKind.IMPORTS,
93
+ label=f"{ctx.file_path} imports {target}",
94
+ ))
95
+
96
+ for m in _RUBY_MODULE_RE.finditer(text):
97
+ mod_name = m.group(1)
98
+ result.nodes.append(GraphNode(
99
+ id=f"{ctx.file_path}:{mod_name}",
100
+ kind=NodeKind.MODULE,
101
+ label=mod_name,
102
+ fqn=mod_name,
103
+ module=ctx.module_name,
104
+ location=SourceLocation(
105
+ file_path=ctx.file_path,
106
+ line_start=find_line_number(text, m.start()),
107
+ ),
108
+ ))
109
+
110
+ for m in _RUBY_CLASS_RE.finditer(text):
111
+ class_name = m.group(1)
112
+ base_class = m.group(2)
113
+ node_id = f"{ctx.file_path}:{class_name}"
114
+ result.nodes.append(GraphNode(
115
+ id=node_id,
116
+ kind=NodeKind.CLASS,
117
+ label=class_name,
118
+ fqn=class_name,
119
+ module=ctx.module_name,
120
+ location=SourceLocation(
121
+ file_path=ctx.file_path,
122
+ line_start=find_line_number(text, m.start()),
123
+ ),
124
+ properties={"base_class": base_class} if base_class else {},
125
+ ))
126
+ if base_class:
127
+ result.edges.append(GraphEdge(
128
+ source=node_id,
129
+ target=base_class,
130
+ kind=EdgeKind.EXTENDS,
131
+ label=f"{class_name} extends {base_class}",
132
+ ))
133
+
134
+ for m in _RUBY_DEF_RE.finditer(text):
135
+ method_name = m.group(1)
136
+ result.nodes.append(GraphNode(
137
+ id=f"{ctx.file_path}:{method_name}",
138
+ kind=NodeKind.METHOD,
139
+ label=method_name,
140
+ fqn=method_name,
141
+ module=ctx.module_name,
142
+ location=SourceLocation(
143
+ file_path=ctx.file_path,
144
+ line_start=find_line_number(text, m.start()),
145
+ ),
146
+ ))
147
+
148
+
149
+ def _detect_swift(ctx: DetectorContext, text: str, result: DetectorResult) -> None:
150
+ file_node_id = ctx.file_path
151
+
152
+ for m in _SWIFT_IMPORT_RE.finditer(text):
153
+ target = m.group(1)
154
+ result.edges.append(GraphEdge(
155
+ source=file_node_id,
156
+ target=target,
157
+ kind=EdgeKind.IMPORTS,
158
+ label=f"{ctx.file_path} imports {target}",
159
+ ))
160
+
161
+ for m in _SWIFT_CLASS_RE.finditer(text):
162
+ class_name = m.group(1)
163
+ supertypes_str = m.group(2)
164
+ node_id = f"{ctx.file_path}:{class_name}"
165
+ result.nodes.append(GraphNode(
166
+ id=node_id,
167
+ kind=NodeKind.CLASS,
168
+ label=class_name,
169
+ fqn=class_name,
170
+ module=ctx.module_name,
171
+ location=SourceLocation(
172
+ file_path=ctx.file_path,
173
+ line_start=find_line_number(text, m.start()),
174
+ ),
175
+ ))
176
+ if supertypes_str:
177
+ for st in supertypes_str.split(","):
178
+ st = st.strip()
179
+ if st:
180
+ result.edges.append(GraphEdge(
181
+ source=node_id,
182
+ target=st,
183
+ kind=EdgeKind.EXTENDS,
184
+ label=f"{class_name} extends {st}",
185
+ ))
186
+
187
+ for m in _SWIFT_PROTOCOL_RE.finditer(text):
188
+ proto_name = m.group(1)
189
+ result.nodes.append(GraphNode(
190
+ id=f"{ctx.file_path}:{proto_name}",
191
+ kind=NodeKind.INTERFACE,
192
+ label=proto_name,
193
+ fqn=proto_name,
194
+ module=ctx.module_name,
195
+ location=SourceLocation(
196
+ file_path=ctx.file_path,
197
+ line_start=find_line_number(text, m.start()),
198
+ ),
199
+ properties={"type": "protocol"},
200
+ ))
201
+
202
+ for m in _SWIFT_STRUCT_RE.finditer(text):
203
+ struct_name = m.group(1)
204
+ result.nodes.append(GraphNode(
205
+ id=f"{ctx.file_path}:{struct_name}",
206
+ kind=NodeKind.CLASS,
207
+ label=struct_name,
208
+ fqn=struct_name,
209
+ module=ctx.module_name,
210
+ location=SourceLocation(
211
+ file_path=ctx.file_path,
212
+ line_start=find_line_number(text, m.start()),
213
+ ),
214
+ properties={"type": "struct"},
215
+ ))
216
+
217
+ for m in _SWIFT_ENUM_RE.finditer(text):
218
+ enum_name = m.group(1)
219
+ result.nodes.append(GraphNode(
220
+ id=f"{ctx.file_path}:{enum_name}",
221
+ kind=NodeKind.ENUM,
222
+ label=enum_name,
223
+ fqn=enum_name,
224
+ module=ctx.module_name,
225
+ location=SourceLocation(
226
+ file_path=ctx.file_path,
227
+ line_start=find_line_number(text, m.start()),
228
+ ),
229
+ ))
230
+
231
+ for m in _SWIFT_FUNC_RE.finditer(text):
232
+ fn_name = m.group(1)
233
+ result.nodes.append(GraphNode(
234
+ id=f"{ctx.file_path}:{fn_name}",
235
+ kind=NodeKind.METHOD,
236
+ label=fn_name,
237
+ fqn=fn_name,
238
+ module=ctx.module_name,
239
+ location=SourceLocation(
240
+ file_path=ctx.file_path,
241
+ line_start=find_line_number(text, m.start()),
242
+ ),
243
+ ))
244
+
245
+
246
+ def _detect_perl(ctx: DetectorContext, text: str, result: DetectorResult) -> None:
247
+ file_node_id = ctx.file_path
248
+
249
+ for m in _PERL_USE_RE.finditer(text):
250
+ target = m.group(1)
251
+ result.edges.append(GraphEdge(
252
+ source=file_node_id,
253
+ target=target,
254
+ kind=EdgeKind.IMPORTS,
255
+ label=f"{ctx.file_path} imports {target}",
256
+ ))
257
+
258
+ for m in _PERL_PACKAGE_RE.finditer(text):
259
+ pkg_name = m.group(1)
260
+ result.nodes.append(GraphNode(
261
+ id=f"{ctx.file_path}:{pkg_name}",
262
+ kind=NodeKind.MODULE,
263
+ label=pkg_name,
264
+ fqn=pkg_name,
265
+ module=ctx.module_name,
266
+ location=SourceLocation(
267
+ file_path=ctx.file_path,
268
+ line_start=find_line_number(text, m.start()),
269
+ ),
270
+ ))
271
+
272
+ for m in _PERL_SUB_RE.finditer(text):
273
+ sub_name = m.group(1)
274
+ result.nodes.append(GraphNode(
275
+ id=f"{ctx.file_path}:{sub_name}",
276
+ kind=NodeKind.METHOD,
277
+ label=sub_name,
278
+ fqn=sub_name,
279
+ module=ctx.module_name,
280
+ location=SourceLocation(
281
+ file_path=ctx.file_path,
282
+ line_start=find_line_number(text, m.start()),
283
+ ),
284
+ ))
285
+
286
+
287
+ def _detect_lua(ctx: DetectorContext, text: str, result: DetectorResult) -> None:
288
+ file_node_id = ctx.file_path
289
+
290
+ for m in _LUA_REQUIRE_RE.finditer(text):
291
+ target = m.group(1)
292
+ result.edges.append(GraphEdge(
293
+ source=file_node_id,
294
+ target=target,
295
+ kind=EdgeKind.IMPORTS,
296
+ label=f"{ctx.file_path} imports {target}",
297
+ ))
298
+
299
+ for m in _LUA_FUNCTION_RE.finditer(text):
300
+ fn_name = m.group(1)
301
+ result.nodes.append(GraphNode(
302
+ id=f"{ctx.file_path}:{fn_name}",
303
+ kind=NodeKind.METHOD,
304
+ label=fn_name,
305
+ fqn=fn_name,
306
+ module=ctx.module_name,
307
+ location=SourceLocation(
308
+ file_path=ctx.file_path,
309
+ line_start=find_line_number(text, m.start()),
310
+ ),
311
+ ))
312
+
313
+
314
+ def _detect_dart(ctx: DetectorContext, text: str, result: DetectorResult) -> None:
315
+ file_node_id = ctx.file_path
316
+
317
+ for m in _DART_IMPORT_RE.finditer(text):
318
+ target = m.group(1)
319
+ result.edges.append(GraphEdge(
320
+ source=file_node_id,
321
+ target=target,
322
+ kind=EdgeKind.IMPORTS,
323
+ label=f"{ctx.file_path} imports {target}",
324
+ ))
325
+
326
+ for m in _DART_CLASS_RE.finditer(text):
327
+ class_name = m.group(1)
328
+ base_class = m.group(2)
329
+ interfaces_str = m.group(3)
330
+ node_id = f"{ctx.file_path}:{class_name}"
331
+ result.nodes.append(GraphNode(
332
+ id=node_id,
333
+ kind=NodeKind.CLASS,
334
+ label=class_name,
335
+ fqn=class_name,
336
+ module=ctx.module_name,
337
+ location=SourceLocation(
338
+ file_path=ctx.file_path,
339
+ line_start=find_line_number(text, m.start()),
340
+ ),
341
+ ))
342
+ if base_class:
343
+ result.edges.append(GraphEdge(
344
+ source=node_id,
345
+ target=base_class,
346
+ kind=EdgeKind.EXTENDS,
347
+ label=f"{class_name} extends {base_class}",
348
+ ))
349
+ if interfaces_str:
350
+ for iface in interfaces_str.split(","):
351
+ iface = iface.strip()
352
+ if iface:
353
+ result.edges.append(GraphEdge(
354
+ source=node_id,
355
+ target=iface,
356
+ kind=EdgeKind.IMPLEMENTS,
357
+ label=f"{class_name} implements {iface}",
358
+ ))
359
+
360
+
361
+ def _detect_r(ctx: DetectorContext, text: str, result: DetectorResult) -> None:
362
+ file_node_id = ctx.file_path
363
+
364
+ for m in _R_LIBRARY_RE.finditer(text):
365
+ target = m.group(1)
366
+ result.edges.append(GraphEdge(
367
+ source=file_node_id,
368
+ target=target,
369
+ kind=EdgeKind.IMPORTS,
370
+ label=f"{ctx.file_path} imports {target}",
371
+ ))
372
+
373
+ for m in _R_FUNCTION_RE.finditer(text):
374
+ fn_name = m.group(1)
375
+ result.nodes.append(GraphNode(
376
+ id=f"{ctx.file_path}:{fn_name}",
377
+ kind=NodeKind.METHOD,
378
+ label=fn_name,
379
+ fqn=fn_name,
380
+ module=ctx.module_name,
381
+ location=SourceLocation(
382
+ file_path=ctx.file_path,
383
+ line_start=find_line_number(text, m.start()),
384
+ ),
385
+ ))
386
+
387
+
388
+ # Dispatch table: language -> handler function
389
+ _LANGUAGE_HANDLERS: dict[str, Callable[[DetectorContext, str, DetectorResult], None]] = {
390
+ "ruby": _detect_ruby,
391
+ "swift": _detect_swift,
392
+ "perl": _detect_perl,
393
+ "lua": _detect_lua,
394
+ "dart": _detect_dart,
395
+ "r": _detect_r,
396
+ }
397
+
398
+
399
+ class GenericImportsDetector:
400
+ """Detects imports, classes, and structural patterns across multiple languages."""
401
+
402
+ name: str = "generic_imports"
403
+ supported_languages: tuple[str, ...] = ("ruby", "swift", "perl", "lua", "dart", "r")
404
+
405
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
406
+ result = DetectorResult()
407
+ handler = _LANGUAGE_HANDLERS.get(ctx.language)
408
+ if handler is None:
409
+ return result
410
+
411
+ text = decode_text(ctx)
412
+ handler(ctx, text, result)
413
+ return result
File without changes
@@ -0,0 +1,202 @@
1
+ """Go ORM/database detector for GORM, sqlx, and database/sql."""
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 EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+ # --- GORM patterns ---
12
+ # gorm.Model embedding in struct: type User struct { gorm.Model ... }
13
+ _GORM_MODEL_RE = re.compile(
14
+ r"type\s+(?P<name>\w+)\s+struct\s*\{[^}]*gorm\.Model",
15
+ re.DOTALL,
16
+ )
17
+ # db.AutoMigrate(&User{}, &Product{})
18
+ _GORM_MIGRATE_RE = re.compile(
19
+ r"\.AutoMigrate\s*\(",
20
+ re.MULTILINE,
21
+ )
22
+ # GORM query operations: db.Create(, db.Find(, db.Where(, db.First(, db.Save(, db.Delete(
23
+ _GORM_QUERY_RE = re.compile(
24
+ r"\.(?P<op>Create|Find|Where|First|Save|Delete)\s*\(",
25
+ re.MULTILINE,
26
+ )
27
+
28
+ # --- sqlx patterns ---
29
+ # sqlx.Connect( / sqlx.Open(
30
+ _SQLX_CONNECT_RE = re.compile(
31
+ r"sqlx\.(?P<op>Connect|Open)\s*\(",
32
+ re.MULTILINE,
33
+ )
34
+ # sqlx query operations: db.Select(, db.Get(, db.NamedExec(
35
+ _SQLX_QUERY_RE = re.compile(
36
+ r"\.(?P<op>Select|Get|NamedExec)\s*\(",
37
+ re.MULTILINE,
38
+ )
39
+
40
+ # --- database/sql patterns ---
41
+ # sql.Open(
42
+ _SQL_OPEN_RE = re.compile(
43
+ r"sql\.Open\s*\(",
44
+ re.MULTILINE,
45
+ )
46
+ # db.Query(, db.QueryRow(, db.Exec(
47
+ _SQL_QUERY_RE = re.compile(
48
+ r"\.(?P<op>Query|QueryRow|Exec)\s*\(",
49
+ re.MULTILINE,
50
+ )
51
+
52
+ # --- Framework detection ---
53
+ _HAS_GORM_RE = re.compile(r"\"gorm\.io/")
54
+ _HAS_SQLX_RE = re.compile(r"\"github\.com/jmoiron/sqlx\"")
55
+ _HAS_DATABASE_SQL_RE = re.compile(r"\"database/sql\"")
56
+
57
+
58
+ def _detect_orm(text: str) -> str | None:
59
+ """Determine which ORM/database library is in use."""
60
+ if _HAS_GORM_RE.search(text):
61
+ return "gorm"
62
+ if _HAS_SQLX_RE.search(text):
63
+ return "sqlx"
64
+ if _HAS_DATABASE_SQL_RE.search(text):
65
+ return "database_sql"
66
+ return None
67
+
68
+
69
+ class GoOrmDetector:
70
+ """Detects Go ORM and database patterns for GORM, sqlx, and database/sql."""
71
+
72
+ name: str = "go_orm"
73
+ supported_languages: tuple[str, ...] = ("go",)
74
+
75
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
76
+ result = DetectorResult()
77
+ text = decode_text(ctx)
78
+
79
+ orm = _detect_orm(text)
80
+
81
+ # --- GORM entity models ---
82
+ for m in _GORM_MODEL_RE.finditer(text):
83
+ model_name = m.group("name")
84
+ line = find_line_number(text, m.start())
85
+ node_id = f"go_orm:{ctx.file_path}:entity:{model_name}:{line}"
86
+ result.nodes.append(GraphNode(
87
+ id=node_id,
88
+ kind=NodeKind.ENTITY,
89
+ label=model_name,
90
+ fqn=f"{ctx.file_path}::{model_name}",
91
+ module=ctx.module_name,
92
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
93
+ properties={
94
+ "framework": "gorm",
95
+ "type": "model",
96
+ },
97
+ ))
98
+
99
+ # --- GORM migrations ---
100
+ for m in _GORM_MIGRATE_RE.finditer(text):
101
+ line = find_line_number(text, m.start())
102
+ node_id = f"go_orm:{ctx.file_path}:migration:{line}"
103
+ result.nodes.append(GraphNode(
104
+ id=node_id,
105
+ kind=NodeKind.MIGRATION,
106
+ label="AutoMigrate",
107
+ fqn=f"{ctx.file_path}::AutoMigrate",
108
+ module=ctx.module_name,
109
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
110
+ properties={
111
+ "framework": "gorm",
112
+ "type": "auto_migrate",
113
+ },
114
+ ))
115
+
116
+ # --- GORM queries ---
117
+ if orm == "gorm":
118
+ for m in _GORM_QUERY_RE.finditer(text):
119
+ op = m.group("op")
120
+ line = find_line_number(text, m.start())
121
+ source_id = f"go_orm:{ctx.file_path}:query:{op}:{line}"
122
+ result.edges.append(GraphEdge(
123
+ source=ctx.file_path,
124
+ target=source_id,
125
+ kind=EdgeKind.QUERIES,
126
+ label=f"gorm.{op}",
127
+ properties={
128
+ "framework": "gorm",
129
+ "operation": op,
130
+ },
131
+ ))
132
+
133
+ # --- sqlx connections ---
134
+ for m in _SQLX_CONNECT_RE.finditer(text):
135
+ op = m.group("op")
136
+ line = find_line_number(text, m.start())
137
+ node_id = f"go_orm:{ctx.file_path}:connection:sqlx:{line}"
138
+ result.nodes.append(GraphNode(
139
+ id=node_id,
140
+ kind=NodeKind.DATABASE_CONNECTION,
141
+ label=f"sqlx.{op}",
142
+ fqn=f"{ctx.file_path}::sqlx.{op}",
143
+ module=ctx.module_name,
144
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
145
+ properties={
146
+ "framework": "sqlx",
147
+ "operation": op,
148
+ },
149
+ ))
150
+
151
+ # --- sqlx queries ---
152
+ if orm == "sqlx":
153
+ for m in _SQLX_QUERY_RE.finditer(text):
154
+ op = m.group("op")
155
+ line = find_line_number(text, m.start())
156
+ target_id = f"go_orm:{ctx.file_path}:query:sqlx:{op}:{line}"
157
+ result.edges.append(GraphEdge(
158
+ source=ctx.file_path,
159
+ target=target_id,
160
+ kind=EdgeKind.QUERIES,
161
+ label=f"sqlx.{op}",
162
+ properties={
163
+ "framework": "sqlx",
164
+ "operation": op,
165
+ },
166
+ ))
167
+
168
+ # --- database/sql connections ---
169
+ for m in _SQL_OPEN_RE.finditer(text):
170
+ line = find_line_number(text, m.start())
171
+ node_id = f"go_orm:{ctx.file_path}:connection:sql:{line}"
172
+ result.nodes.append(GraphNode(
173
+ id=node_id,
174
+ kind=NodeKind.DATABASE_CONNECTION,
175
+ label="sql.Open",
176
+ fqn=f"{ctx.file_path}::sql.Open",
177
+ module=ctx.module_name,
178
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
179
+ properties={
180
+ "framework": "database_sql",
181
+ "operation": "Open",
182
+ },
183
+ ))
184
+
185
+ # --- database/sql queries ---
186
+ if orm == "database_sql":
187
+ for m in _SQL_QUERY_RE.finditer(text):
188
+ op = m.group("op")
189
+ line = find_line_number(text, m.start())
190
+ target_id = f"go_orm:{ctx.file_path}:query:sql:{op}:{line}"
191
+ result.edges.append(GraphEdge(
192
+ source=ctx.file_path,
193
+ target=target_id,
194
+ kind=EdgeKind.QUERIES,
195
+ label=f"sql.{op}",
196
+ properties={
197
+ "framework": "database_sql",
198
+ "operation": op,
199
+ },
200
+ ))
201
+
202
+ return result