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,162 @@
1
+ """Regex-based Go structures detector for Go 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, find_line_number
10
+ from osscodeiq.models.graph import (
11
+ EdgeKind,
12
+ GraphEdge,
13
+ GraphNode,
14
+ NodeKind,
15
+ SourceLocation,
16
+ )
17
+
18
+ _STRUCT_RE = re.compile(r'type\s+(\w+)\s+struct\s*\{')
19
+ _INTERFACE_RE = re.compile(r'type\s+(\w+)\s+interface\s*\{')
20
+ _METHOD_RE = re.compile(r'func\s+\(\s*\w+\s+\*?(\w+)\s*\)\s+(\w+)\s*\(')
21
+ _FUNC_RE = re.compile(r'^func\s+(\w+)\s*\(', re.MULTILINE)
22
+ _PACKAGE_RE = re.compile(r'^package\s+(\w+)', re.MULTILINE)
23
+ _IMPORT_SINGLE_RE = re.compile(r'^import\s+"([^"]+)"', re.MULTILINE)
24
+ _IMPORT_BLOCK_RE = re.compile(r'import\s*\((.*?)\)', re.DOTALL)
25
+ _IMPORT_PATH_RE = re.compile(r'"([^"]+)"')
26
+
27
+
28
+
29
+ class GoStructuresDetector:
30
+ """Detects Go structs, interfaces, methods, functions, and packages."""
31
+
32
+ name: str = "go_structures"
33
+ supported_languages: tuple[str, ...] = ("go",)
34
+
35
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
36
+ result = DetectorResult()
37
+ text = decode_text(ctx)
38
+
39
+ # Package declaration (one per file)
40
+ pkg_match = _PACKAGE_RE.search(text)
41
+ pkg_name = pkg_match.group(1) if pkg_match else None
42
+ if pkg_name:
43
+ pkg_node_id = f"{ctx.file_path}:package:{pkg_name}"
44
+ result.nodes.append(GraphNode(
45
+ id=pkg_node_id,
46
+ kind=NodeKind.MODULE,
47
+ label=pkg_name,
48
+ fqn=pkg_name,
49
+ module=ctx.module_name,
50
+ location=SourceLocation(
51
+ file_path=ctx.file_path,
52
+ line_start=find_line_number(text, pkg_match.start()),
53
+ ),
54
+ properties={"package": pkg_name},
55
+ ))
56
+
57
+ # Imports
58
+ file_node_id = ctx.file_path
59
+ for m in _IMPORT_SINGLE_RE.finditer(text):
60
+ import_path = m.group(1)
61
+ result.edges.append(GraphEdge(
62
+ source=file_node_id,
63
+ target=import_path,
64
+ kind=EdgeKind.IMPORTS,
65
+ label=f"{ctx.file_path} imports {import_path}",
66
+ ))
67
+
68
+ for block_m in _IMPORT_BLOCK_RE.finditer(text):
69
+ block_text = block_m.group(1)
70
+ for path_m in _IMPORT_PATH_RE.finditer(block_text):
71
+ import_path = path_m.group(1)
72
+ result.edges.append(GraphEdge(
73
+ source=file_node_id,
74
+ target=import_path,
75
+ kind=EdgeKind.IMPORTS,
76
+ label=f"{ctx.file_path} imports {import_path}",
77
+ ))
78
+
79
+ # Struct declarations
80
+ for m in _STRUCT_RE.finditer(text):
81
+ struct_name = m.group(1)
82
+ exported = struct_name[0].isupper()
83
+ node_id = f"{ctx.file_path}:{struct_name}"
84
+ result.nodes.append(GraphNode(
85
+ id=node_id,
86
+ kind=NodeKind.CLASS,
87
+ label=struct_name,
88
+ fqn=f"{pkg_name}.{struct_name}" if pkg_name else struct_name,
89
+ module=ctx.module_name,
90
+ location=SourceLocation(
91
+ file_path=ctx.file_path,
92
+ line_start=find_line_number(text, m.start()),
93
+ ),
94
+ properties={"exported": exported, "type": "struct"},
95
+ ))
96
+
97
+ # Interface declarations
98
+ for m in _INTERFACE_RE.finditer(text):
99
+ iface_name = m.group(1)
100
+ exported = iface_name[0].isupper()
101
+ node_id = f"{ctx.file_path}:{iface_name}"
102
+ result.nodes.append(GraphNode(
103
+ id=node_id,
104
+ kind=NodeKind.INTERFACE,
105
+ label=iface_name,
106
+ fqn=f"{pkg_name}.{iface_name}" if pkg_name else iface_name,
107
+ module=ctx.module_name,
108
+ location=SourceLocation(
109
+ file_path=ctx.file_path,
110
+ line_start=find_line_number(text, m.start()),
111
+ ),
112
+ properties={"exported": exported},
113
+ ))
114
+
115
+ # Method declarations (receiver methods)
116
+ for m in _METHOD_RE.finditer(text):
117
+ receiver_type = m.group(1)
118
+ method_name = m.group(2)
119
+ exported = method_name[0].isupper()
120
+ node_id = f"{ctx.file_path}:{receiver_type}:{method_name}"
121
+ type_node_id = f"{ctx.file_path}:{receiver_type}"
122
+ result.nodes.append(GraphNode(
123
+ id=node_id,
124
+ kind=NodeKind.METHOD,
125
+ label=f"{receiver_type}.{method_name}",
126
+ fqn=f"{pkg_name}.{receiver_type}.{method_name}" if pkg_name else f"{receiver_type}.{method_name}",
127
+ module=ctx.module_name,
128
+ location=SourceLocation(
129
+ file_path=ctx.file_path,
130
+ line_start=find_line_number(text, m.start()),
131
+ ),
132
+ properties={"exported": exported, "receiver_type": receiver_type},
133
+ ))
134
+ result.edges.append(GraphEdge(
135
+ source=type_node_id,
136
+ target=node_id,
137
+ kind=EdgeKind.DEFINES,
138
+ label=f"{receiver_type} defines {method_name}",
139
+ ))
140
+
141
+ # Package-level function declarations (exclude methods already matched)
142
+ method_positions = {m.start() for m in _METHOD_RE.finditer(text)}
143
+ for m in _FUNC_RE.finditer(text):
144
+ if m.start() in method_positions:
145
+ continue
146
+ func_name = m.group(1)
147
+ exported = func_name[0].isupper()
148
+ node_id = f"{ctx.file_path}:{func_name}"
149
+ result.nodes.append(GraphNode(
150
+ id=node_id,
151
+ kind=NodeKind.METHOD,
152
+ label=func_name,
153
+ fqn=f"{pkg_name}.{func_name}" if pkg_name else func_name,
154
+ module=ctx.module_name,
155
+ location=SourceLocation(
156
+ file_path=ctx.file_path,
157
+ line_start=find_line_number(text, m.start()),
158
+ ),
159
+ properties={"exported": exported, "type": "function"},
160
+ ))
161
+
162
+ return result
@@ -0,0 +1,157 @@
1
+ """Go web framework detector for Gin, Echo, Chi, gorilla/mux, and net/http."""
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 GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+ # --- Route patterns ---
12
+ # Gin/Echo (uppercase methods): r.GET("/path", handler)
13
+ _UPPER_ROUTE_RE = re.compile(
14
+ r"\.(?P<method>GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS)\s*\(\s*\"(?P<path>[^\"]*)\"",
15
+ re.MULTILINE,
16
+ )
17
+ # Chi (lowercase methods): r.Get("/path", handler)
18
+ _LOWER_ROUTE_RE = re.compile(
19
+ r"\.(?P<method>Get|Post|Put|Delete|Patch|Head|Options)\s*\(\s*\"(?P<path>[^\"]*)\"",
20
+ re.MULTILINE,
21
+ )
22
+ # gorilla/mux: r.HandleFunc("/path", handler).Methods("GET")
23
+ _HANDLEFUNC_RE = re.compile(
24
+ r"\.HandleFunc\s*\(\s*\"(?P<path>[^\"]*)\".*?\.Methods\s*\(\s*\"(?P<method>[A-Z]+)\"",
25
+ re.DOTALL,
26
+ )
27
+ # gorilla/mux HandleFunc without .Methods() (default any)
28
+ _HANDLEFUNC_NO_METHOD_RE = re.compile(
29
+ r"\.HandleFunc\s*\(\s*\"(?P<path>[^\"]*)\"",
30
+ re.MULTILINE,
31
+ )
32
+ # net/http: http.HandleFunc("/path", handler) / http.Handle("/path", handler)
33
+ _HTTP_HANDLE_RE = re.compile(
34
+ r"http\.(?:HandleFunc|Handle)\s*\(\s*\"(?P<path>[^\"]*)\"",
35
+ re.MULTILINE,
36
+ )
37
+
38
+ # --- Framework detection ---
39
+ _GIN_RE = re.compile(r"gin\.(?:Default|New)\s*\(")
40
+ _ECHO_RE = re.compile(r"echo\.New\s*\(")
41
+ _CHI_RE = re.compile(r"chi\.NewRouter\s*\(")
42
+ _MUX_RE = re.compile(r"mux\.NewRouter\s*\(")
43
+
44
+ # --- Middleware ---
45
+ _USE_RE = re.compile(r"\.Use\s*\(\s*(\w+)")
46
+
47
+
48
+ def _detect_framework(text: str) -> str:
49
+ """Determine which framework is in use based on constructor patterns."""
50
+ if _GIN_RE.search(text):
51
+ return "gin"
52
+ if _ECHO_RE.search(text):
53
+ return "echo"
54
+ if _CHI_RE.search(text):
55
+ return "chi"
56
+ if _MUX_RE.search(text):
57
+ return "mux"
58
+ return "net_http"
59
+
60
+
61
+ class GoWebDetector:
62
+ """Detects Go web framework route definitions across Gin, Echo, Chi, mux, and net/http."""
63
+
64
+ name: str = "go_web"
65
+ supported_languages: tuple[str, ...] = ("go",)
66
+
67
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
68
+ result = DetectorResult()
69
+ text = decode_text(ctx)
70
+
71
+ framework = _detect_framework(text)
72
+
73
+ # Collect (method, path, line, matched_framework) tuples
74
+ routes: list[tuple[str, str, int, str]] = []
75
+
76
+ # Gin/Echo uppercase routes
77
+ for m in _UPPER_ROUTE_RE.finditer(text):
78
+ method = m.group("method")
79
+ path = m.group("path")
80
+ line = find_line_number(text, m.start())
81
+ routes.append((method, path, line, framework))
82
+
83
+ # Chi lowercase routes
84
+ for m in _LOWER_ROUTE_RE.finditer(text):
85
+ method = m.group("method").upper()
86
+ path = m.group("path")
87
+ line = find_line_number(text, m.start())
88
+ routes.append((method, path, line, "chi"))
89
+
90
+ # gorilla/mux HandleFunc with .Methods()
91
+ handlefunc_with_method_positions: set[int] = set()
92
+ for m in _HANDLEFUNC_RE.finditer(text):
93
+ method = m.group("method")
94
+ path = m.group("path")
95
+ line = find_line_number(text, m.start())
96
+ routes.append((method, path, line, "mux"))
97
+ handlefunc_with_method_positions.add(m.start())
98
+
99
+ # gorilla/mux HandleFunc without .Methods() — only if mux detected
100
+ # and not already captured with methods
101
+ if framework == "mux":
102
+ for m in _HANDLEFUNC_NO_METHOD_RE.finditer(text):
103
+ if m.start() in handlefunc_with_method_positions:
104
+ continue
105
+ # Check if this same position was already matched by _HANDLEFUNC_RE
106
+ already_matched = False
107
+ for pos in handlefunc_with_method_positions:
108
+ if pos == m.start():
109
+ already_matched = True
110
+ break
111
+ if not already_matched:
112
+ path = m.group("path")
113
+ line = find_line_number(text, m.start())
114
+ routes.append(("ANY", path, line, "mux"))
115
+
116
+ # net/http Handle/HandleFunc
117
+ for m in _HTTP_HANDLE_RE.finditer(text):
118
+ path = m.group("path")
119
+ line = find_line_number(text, m.start())
120
+ routes.append(("ANY", path, line, "net_http"))
121
+
122
+ # Emit ENDPOINT nodes
123
+ for method, path, line, fw in routes:
124
+ node_id = f"go_web:{ctx.file_path}:{method}:{path}:{line}"
125
+ result.nodes.append(GraphNode(
126
+ id=node_id,
127
+ kind=NodeKind.ENDPOINT,
128
+ label=f"{method} {path}",
129
+ fqn=f"{ctx.file_path}::{method}:{path}",
130
+ module=ctx.module_name,
131
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
132
+ properties={
133
+ "framework": fw,
134
+ "http_method": method,
135
+ "path": path,
136
+ },
137
+ ))
138
+
139
+ # Middleware nodes
140
+ for m in _USE_RE.finditer(text):
141
+ mw_name = m.group(1)
142
+ line = find_line_number(text, m.start())
143
+ node_id = f"go_web:{ctx.file_path}:middleware:{mw_name}:{line}"
144
+ result.nodes.append(GraphNode(
145
+ id=node_id,
146
+ kind=NodeKind.MIDDLEWARE,
147
+ label=mw_name,
148
+ fqn=f"{ctx.file_path}::middleware:{mw_name}",
149
+ module=ctx.module_name,
150
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
151
+ properties={
152
+ "framework": framework,
153
+ "middleware": mw_name,
154
+ },
155
+ ))
156
+
157
+ return result
File without changes
@@ -0,0 +1,135 @@
1
+ """Bicep IaC detector for Azure infrastructure resource definitions."""
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
+ # resource <symbolName> '<type>@<apiVersion>'
19
+ _RESOURCE_RE = re.compile(r"resource\s+(\w+)\s+'([^']+)'")
20
+
21
+ # param <name> <type>
22
+ _PARAM_RE = re.compile(r"param\s+(\w+)\s+(\w+)")
23
+
24
+ # module <name> '<path>'
25
+ _MODULE_RE = re.compile(r"module\s+(\w+)\s+'([^']+)'")
26
+
27
+
28
+ def _parse_resource_type(type_str: str) -> dict[str, str]:
29
+ """Parse 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' into parts."""
30
+ props: dict[str, str] = {}
31
+ if "@" in type_str:
32
+ azure_type, api_version = type_str.rsplit("@", 1)
33
+ props["azure_type"] = azure_type
34
+ props["api_version"] = api_version
35
+ else:
36
+ props["azure_type"] = type_str
37
+ return props
38
+
39
+
40
+ class BicepDetector:
41
+ """Detects Azure infrastructure resources from Bicep files."""
42
+
43
+ name: str = "bicep"
44
+ supported_languages: tuple[str, ...] = ("bicep",)
45
+
46
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
47
+ result = DetectorResult()
48
+ text = decode_text(ctx)
49
+ lines = text.split("\n")
50
+
51
+ # Detect resource declarations
52
+ for i, line in enumerate(lines):
53
+ m = _RESOURCE_RE.search(line)
54
+ if not m:
55
+ continue
56
+
57
+ resource_name = m.group(1)
58
+ type_str = m.group(2)
59
+ props = _parse_resource_type(type_str)
60
+ azure_type = props.get("azure_type", "")
61
+
62
+ # Use AZURE_RESOURCE for Microsoft.* types, INFRA_RESOURCE otherwise
63
+ if azure_type.startswith("Microsoft."):
64
+ kind = NodeKind.AZURE_RESOURCE
65
+ else:
66
+ kind = NodeKind.INFRA_RESOURCE
67
+
68
+ node_id = f"{ctx.file_path}:resource:{resource_name}"
69
+ label = f"{resource_name} ({azure_type})"
70
+
71
+ result.nodes.append(GraphNode(
72
+ id=node_id,
73
+ kind=kind,
74
+ label=label,
75
+ fqn=azure_type,
76
+ module=ctx.module_name,
77
+ location=SourceLocation(
78
+ file_path=ctx.file_path,
79
+ line_start=i + 1,
80
+ ),
81
+ properties=props,
82
+ ))
83
+
84
+ # Detect param declarations
85
+ for i, line in enumerate(lines):
86
+ m = _PARAM_RE.search(line)
87
+ if not m:
88
+ continue
89
+
90
+ param_name = m.group(1)
91
+ param_type = m.group(2)
92
+
93
+ result.nodes.append(GraphNode(
94
+ id=f"{ctx.file_path}:param:{param_name}",
95
+ kind=NodeKind.CONFIG_KEY,
96
+ label=f"param {param_name}: {param_type}",
97
+ module=ctx.module_name,
98
+ location=SourceLocation(
99
+ file_path=ctx.file_path,
100
+ line_start=i + 1,
101
+ ),
102
+ properties={"param_type": param_type},
103
+ ))
104
+
105
+ # Detect module references
106
+ for i, line in enumerate(lines):
107
+ m = _MODULE_RE.search(line)
108
+ if not m:
109
+ continue
110
+
111
+ module_name = m.group(1)
112
+ module_path = m.group(2)
113
+
114
+ result.nodes.append(GraphNode(
115
+ id=f"{ctx.file_path}:module:{module_name}",
116
+ kind=NodeKind.INFRA_RESOURCE,
117
+ label=f"module {module_name} ({module_path})",
118
+ module=ctx.module_name,
119
+ location=SourceLocation(
120
+ file_path=ctx.file_path,
121
+ line_start=i + 1,
122
+ ),
123
+ properties={"module_path": module_path},
124
+ ))
125
+
126
+ # Edge: current file depends on the referenced module
127
+ result.edges.append(GraphEdge(
128
+ source=ctx.file_path,
129
+ target=module_path,
130
+ kind=EdgeKind.DEPENDS_ON,
131
+ label=f"{ctx.file_path} depends on module {module_path}",
132
+ properties={"module_name": module_name},
133
+ ))
134
+
135
+ return result
@@ -0,0 +1,182 @@
1
+ """Dockerfile detector for container infrastructure definitions."""
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
+ _FROM_RE = re.compile(r'^FROM\s+(\S+)(?:\s+AS\s+(\w+))?', re.MULTILINE | re.IGNORECASE)
18
+ _EXPOSE_RE = re.compile(r'^EXPOSE\s+(\d+)', re.MULTILINE)
19
+ _ENV_RE = re.compile(r'^ENV\s+(\w+)[=\s]', re.MULTILINE)
20
+ _LABEL_RE = re.compile(r'^LABEL\s+(\S+)=', re.MULTILINE)
21
+ _COPY_FROM_RE = re.compile(r'COPY\s+--from=(\w+)', re.MULTILINE | re.IGNORECASE)
22
+ _ARG_RE = re.compile(r'^ARG\s+(\w+)', re.MULTILINE)
23
+
24
+
25
+
26
+ class DockerfileDetector:
27
+ """Detects infrastructure resources from Dockerfile definitions."""
28
+
29
+ name: str = "dockerfile"
30
+ supported_languages: tuple[str, ...] = ("dockerfile",)
31
+
32
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
33
+ result = DetectorResult()
34
+ text = decode_text(ctx)
35
+
36
+ stages: dict[str, str] = {} # stage alias -> base image
37
+ stage_node_ids: dict[str, str] = {} # stage alias -> node id
38
+ # Ordered list of (start_offset, node_id) for determining current stage
39
+ from_offsets: list[tuple[int, str]] = []
40
+ stage_order = 0
41
+
42
+ # Detect FROM instructions (base image dependencies)
43
+ for m in _FROM_RE.finditer(text):
44
+ image = m.group(1)
45
+ alias = m.group(2)
46
+ line = find_line_number(text, m.start())
47
+
48
+ # Track stage aliases for multi-stage build references
49
+ if alias:
50
+ stages[alias] = image
51
+
52
+ # Parse image name and tag
53
+ props: dict[str, str | int] = {"image": image}
54
+ if ":" in image and not image.startswith("$"):
55
+ img_name, tag = image.rsplit(":", 1)
56
+ props["image_name"] = img_name
57
+ props["tag"] = tag
58
+ else:
59
+ props["image_name"] = image
60
+
61
+ if alias:
62
+ props["stage_alias"] = alias
63
+ props["build_stage"] = alias
64
+
65
+ props["stage_order"] = stage_order
66
+ stage_order += 1
67
+
68
+ node_id = f"docker:{ctx.file_path}:from:{image}"
69
+ label = f"FROM {image}" + (f" AS {alias}" if alias else "")
70
+
71
+ if alias:
72
+ stage_node_ids[alias] = node_id
73
+ from_offsets.append((m.start(), node_id))
74
+
75
+ result.nodes.append(GraphNode(
76
+ id=node_id,
77
+ kind=NodeKind.INFRA_RESOURCE,
78
+ label=label,
79
+ fqn=image,
80
+ module=ctx.module_name,
81
+ location=SourceLocation(
82
+ file_path=ctx.file_path,
83
+ line_start=line,
84
+ ),
85
+ properties=props,
86
+ ))
87
+
88
+ # Edge: this Dockerfile depends on the base image
89
+ result.edges.append(GraphEdge(
90
+ source=ctx.file_path,
91
+ target=image,
92
+ kind=EdgeKind.DEPENDS_ON,
93
+ label=f"{ctx.file_path} depends on image {image}",
94
+ ))
95
+
96
+ # Detect COPY --from instructions (multi-stage build dependencies)
97
+ for m in _COPY_FROM_RE.finditer(text):
98
+ source_stage = m.group(1)
99
+ if source_stage in stage_node_ids:
100
+ # Find the current stage (most recent FROM before this COPY)
101
+ current_node_id = None
102
+ for offset, nid in reversed(from_offsets):
103
+ if offset < m.start():
104
+ current_node_id = nid
105
+ break
106
+ if current_node_id and current_node_id != stage_node_ids[source_stage]:
107
+ result.edges.append(GraphEdge(
108
+ source=current_node_id,
109
+ target=stage_node_ids[source_stage],
110
+ kind=EdgeKind.DEPENDS_ON,
111
+ label=f"COPY --from={source_stage}",
112
+ ))
113
+
114
+ # Detect EXPOSE instructions (exposed ports)
115
+ for m in _EXPOSE_RE.finditer(text):
116
+ port = m.group(1)
117
+ line = find_line_number(text, m.start())
118
+
119
+ result.nodes.append(GraphNode(
120
+ id=f"docker:{ctx.file_path}:expose:{port}",
121
+ kind=NodeKind.ENDPOINT,
122
+ label=f"EXPOSE {port}",
123
+ module=ctx.module_name,
124
+ location=SourceLocation(
125
+ file_path=ctx.file_path,
126
+ line_start=line,
127
+ ),
128
+ properties={"port": port, "protocol": "tcp"},
129
+ ))
130
+
131
+ # Detect ENV instructions (configuration definitions)
132
+ for m in _ENV_RE.finditer(text):
133
+ key = m.group(1)
134
+ line = find_line_number(text, m.start())
135
+
136
+ result.nodes.append(GraphNode(
137
+ id=f"docker:{ctx.file_path}:env:{key}",
138
+ kind=NodeKind.CONFIG_DEFINITION,
139
+ label=f"ENV {key}",
140
+ module=ctx.module_name,
141
+ location=SourceLocation(
142
+ file_path=ctx.file_path,
143
+ line_start=line,
144
+ ),
145
+ properties={"env_key": key},
146
+ ))
147
+
148
+ # Detect LABEL instructions
149
+ for m in _LABEL_RE.finditer(text):
150
+ label_key = m.group(1)
151
+ line = find_line_number(text, m.start())
152
+
153
+ result.nodes.append(GraphNode(
154
+ id=f"docker:{ctx.file_path}:label:{label_key}",
155
+ kind=NodeKind.CONFIG_DEFINITION,
156
+ label=f"LABEL {label_key}",
157
+ module=ctx.module_name,
158
+ location=SourceLocation(
159
+ file_path=ctx.file_path,
160
+ line_start=line,
161
+ ),
162
+ properties={"label_key": label_key},
163
+ ))
164
+
165
+ # Detect ARG instructions (build arguments)
166
+ for m in _ARG_RE.finditer(text):
167
+ arg_name = m.group(1)
168
+ line = find_line_number(text, m.start())
169
+
170
+ result.nodes.append(GraphNode(
171
+ id=f"docker:{ctx.file_path}:arg:{arg_name}",
172
+ kind=NodeKind.CONFIG_DEFINITION,
173
+ label=f"ARG {arg_name}",
174
+ module=ctx.module_name,
175
+ location=SourceLocation(
176
+ file_path=ctx.file_path,
177
+ line_start=line,
178
+ ),
179
+ properties={"arg_name": arg_name},
180
+ ))
181
+
182
+ return result