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.
- osscodeiq/__init__.py +0 -0
- osscodeiq/analyzer.py +467 -0
- osscodeiq/cache/__init__.py +0 -0
- osscodeiq/cache/hasher.py +23 -0
- osscodeiq/cache/store.py +300 -0
- osscodeiq/classifiers/__init__.py +0 -0
- osscodeiq/classifiers/layer_classifier.py +69 -0
- osscodeiq/cli.py +721 -0
- osscodeiq/config.py +113 -0
- osscodeiq/detectors/__init__.py +0 -0
- osscodeiq/detectors/auth/__init__.py +0 -0
- osscodeiq/detectors/auth/certificate_auth.py +139 -0
- osscodeiq/detectors/auth/ldap_auth.py +89 -0
- osscodeiq/detectors/auth/session_header_auth.py +120 -0
- osscodeiq/detectors/base.py +41 -0
- osscodeiq/detectors/config/__init__.py +0 -0
- osscodeiq/detectors/config/batch_structure.py +128 -0
- osscodeiq/detectors/config/cloudformation.py +183 -0
- osscodeiq/detectors/config/docker_compose.py +179 -0
- osscodeiq/detectors/config/github_actions.py +150 -0
- osscodeiq/detectors/config/gitlab_ci.py +216 -0
- osscodeiq/detectors/config/helm_chart.py +187 -0
- osscodeiq/detectors/config/ini_structure.py +101 -0
- osscodeiq/detectors/config/json_structure.py +72 -0
- osscodeiq/detectors/config/kubernetes.py +305 -0
- osscodeiq/detectors/config/kubernetes_rbac.py +212 -0
- osscodeiq/detectors/config/openapi.py +194 -0
- osscodeiq/detectors/config/package_json.py +99 -0
- osscodeiq/detectors/config/properties_detector.py +108 -0
- osscodeiq/detectors/config/pyproject_toml.py +169 -0
- osscodeiq/detectors/config/sql_structure.py +155 -0
- osscodeiq/detectors/config/toml_structure.py +93 -0
- osscodeiq/detectors/config/tsconfig_json.py +105 -0
- osscodeiq/detectors/config/yaml_structure.py +82 -0
- osscodeiq/detectors/cpp/__init__.py +0 -0
- osscodeiq/detectors/cpp/cpp_structures.py +192 -0
- osscodeiq/detectors/csharp/__init__.py +0 -0
- osscodeiq/detectors/csharp/csharp_efcore.py +184 -0
- osscodeiq/detectors/csharp/csharp_minimal_apis.py +156 -0
- osscodeiq/detectors/csharp/csharp_structures.py +317 -0
- osscodeiq/detectors/docs/__init__.py +0 -0
- osscodeiq/detectors/docs/markdown_structure.py +117 -0
- osscodeiq/detectors/frontend/__init__.py +0 -0
- osscodeiq/detectors/frontend/angular_components.py +177 -0
- osscodeiq/detectors/frontend/frontend_routes.py +259 -0
- osscodeiq/detectors/frontend/react_components.py +148 -0
- osscodeiq/detectors/frontend/svelte_components.py +84 -0
- osscodeiq/detectors/frontend/vue_components.py +150 -0
- osscodeiq/detectors/generic/__init__.py +1 -0
- osscodeiq/detectors/generic/imports_detector.py +413 -0
- osscodeiq/detectors/go/__init__.py +0 -0
- osscodeiq/detectors/go/go_orm.py +202 -0
- osscodeiq/detectors/go/go_structures.py +162 -0
- osscodeiq/detectors/go/go_web.py +157 -0
- osscodeiq/detectors/iac/__init__.py +0 -0
- osscodeiq/detectors/iac/bicep.py +135 -0
- osscodeiq/detectors/iac/dockerfile.py +182 -0
- osscodeiq/detectors/iac/terraform.py +188 -0
- osscodeiq/detectors/java/__init__.py +0 -0
- osscodeiq/detectors/java/azure_functions.py +424 -0
- osscodeiq/detectors/java/azure_messaging.py +350 -0
- osscodeiq/detectors/java/class_hierarchy.py +349 -0
- osscodeiq/detectors/java/config_def.py +82 -0
- osscodeiq/detectors/java/cosmos_db.py +105 -0
- osscodeiq/detectors/java/graphql_resolver.py +188 -0
- osscodeiq/detectors/java/grpc_service.py +142 -0
- osscodeiq/detectors/java/ibm_mq.py +178 -0
- osscodeiq/detectors/java/jaxrs.py +160 -0
- osscodeiq/detectors/java/jdbc.py +196 -0
- osscodeiq/detectors/java/jms.py +116 -0
- osscodeiq/detectors/java/jpa_entity.py +143 -0
- osscodeiq/detectors/java/kafka.py +113 -0
- osscodeiq/detectors/java/kafka_protocol.py +70 -0
- osscodeiq/detectors/java/micronaut.py +248 -0
- osscodeiq/detectors/java/module_deps.py +191 -0
- osscodeiq/detectors/java/public_api.py +206 -0
- osscodeiq/detectors/java/quarkus.py +176 -0
- osscodeiq/detectors/java/rabbitmq.py +150 -0
- osscodeiq/detectors/java/raw_sql.py +136 -0
- osscodeiq/detectors/java/repository.py +131 -0
- osscodeiq/detectors/java/rmi.py +129 -0
- osscodeiq/detectors/java/spring_events.py +117 -0
- osscodeiq/detectors/java/spring_rest.py +168 -0
- osscodeiq/detectors/java/spring_security.py +212 -0
- osscodeiq/detectors/java/tibco_ems.py +193 -0
- osscodeiq/detectors/java/websocket.py +188 -0
- osscodeiq/detectors/kotlin/__init__.py +0 -0
- osscodeiq/detectors/kotlin/kotlin_structures.py +124 -0
- osscodeiq/detectors/kotlin/ktor_routes.py +163 -0
- osscodeiq/detectors/proto/__init__.py +0 -0
- osscodeiq/detectors/proto/proto_structure.py +153 -0
- osscodeiq/detectors/python/__init__.py +0 -0
- osscodeiq/detectors/python/celery_tasks.py +88 -0
- osscodeiq/detectors/python/django_auth.py +132 -0
- osscodeiq/detectors/python/django_models.py +157 -0
- osscodeiq/detectors/python/django_views.py +74 -0
- osscodeiq/detectors/python/fastapi_auth.py +143 -0
- osscodeiq/detectors/python/fastapi_routes.py +68 -0
- osscodeiq/detectors/python/flask_routes.py +67 -0
- osscodeiq/detectors/python/kafka_python.py +175 -0
- osscodeiq/detectors/python/pydantic_models.py +115 -0
- osscodeiq/detectors/python/python_structures.py +234 -0
- osscodeiq/detectors/python/sqlalchemy_models.py +82 -0
- osscodeiq/detectors/registry.py +100 -0
- osscodeiq/detectors/rust/__init__.py +0 -0
- osscodeiq/detectors/rust/actix_web.py +234 -0
- osscodeiq/detectors/rust/rust_structures.py +174 -0
- osscodeiq/detectors/scala/__init__.py +0 -0
- osscodeiq/detectors/scala/scala_structures.py +128 -0
- osscodeiq/detectors/shell/__init__.py +0 -0
- osscodeiq/detectors/shell/bash_detector.py +127 -0
- osscodeiq/detectors/shell/powershell_detector.py +118 -0
- osscodeiq/detectors/typescript/__init__.py +0 -0
- osscodeiq/detectors/typescript/express_routes.py +55 -0
- osscodeiq/detectors/typescript/fastify_routes.py +156 -0
- osscodeiq/detectors/typescript/graphql_resolvers.py +100 -0
- osscodeiq/detectors/typescript/kafka_js.py +164 -0
- osscodeiq/detectors/typescript/mongoose_orm.py +151 -0
- osscodeiq/detectors/typescript/nestjs_controllers.py +99 -0
- osscodeiq/detectors/typescript/nestjs_guards.py +138 -0
- osscodeiq/detectors/typescript/passport_jwt.py +133 -0
- osscodeiq/detectors/typescript/prisma_orm.py +96 -0
- osscodeiq/detectors/typescript/remix_routes.py +160 -0
- osscodeiq/detectors/typescript/sequelize_orm.py +136 -0
- osscodeiq/detectors/typescript/typeorm_entities.py +86 -0
- osscodeiq/detectors/typescript/typescript_structures.py +185 -0
- osscodeiq/detectors/utils.py +49 -0
- osscodeiq/discovery/__init__.py +11 -0
- osscodeiq/discovery/change_detector.py +97 -0
- osscodeiq/discovery/file_discovery.py +342 -0
- osscodeiq/flow/__init__.py +0 -0
- osscodeiq/flow/engine.py +78 -0
- osscodeiq/flow/models.py +72 -0
- osscodeiq/flow/renderer.py +127 -0
- osscodeiq/flow/templates/interactive.html +252 -0
- osscodeiq/flow/vendor/cytoscape-dagre.min.js +8 -0
- osscodeiq/flow/vendor/cytoscape.min.js +32 -0
- osscodeiq/flow/vendor/dagre.min.js +3809 -0
- osscodeiq/flow/views.py +357 -0
- osscodeiq/graph/__init__.py +0 -0
- osscodeiq/graph/backend.py +52 -0
- osscodeiq/graph/backends/__init__.py +23 -0
- osscodeiq/graph/backends/kuzu.py +576 -0
- osscodeiq/graph/backends/networkx.py +135 -0
- osscodeiq/graph/backends/sqlite_backend.py +406 -0
- osscodeiq/graph/builder.py +297 -0
- osscodeiq/graph/query.py +228 -0
- osscodeiq/graph/store.py +183 -0
- osscodeiq/graph/views.py +231 -0
- osscodeiq/models/__init__.py +17 -0
- osscodeiq/models/graph.py +116 -0
- osscodeiq/output/__init__.py +0 -0
- osscodeiq/output/dot.py +171 -0
- osscodeiq/output/mermaid.py +160 -0
- osscodeiq/output/safety.py +58 -0
- osscodeiq/output/serializers.py +42 -0
- osscodeiq/parsing/__init__.py +5 -0
- osscodeiq/parsing/languages/__init__.py +0 -0
- osscodeiq/parsing/languages/base.py +23 -0
- osscodeiq/parsing/languages/java.py +68 -0
- osscodeiq/parsing/languages/python.py +57 -0
- osscodeiq/parsing/languages/typescript.py +95 -0
- osscodeiq/parsing/parser_manager.py +125 -0
- osscodeiq/parsing/structured/__init__.py +0 -0
- osscodeiq/parsing/structured/gradle_parser.py +78 -0
- osscodeiq/parsing/structured/json_parser.py +24 -0
- osscodeiq/parsing/structured/properties_parser.py +56 -0
- osscodeiq/parsing/structured/sql_parser.py +54 -0
- osscodeiq/parsing/structured/xml_parser.py +148 -0
- osscodeiq/parsing/structured/yaml_parser.py +38 -0
- osscodeiq/server/__init__.py +7 -0
- osscodeiq/server/app.py +53 -0
- osscodeiq/server/mcp_server.py +174 -0
- osscodeiq/server/middleware.py +16 -0
- osscodeiq/server/routes.py +184 -0
- osscodeiq/server/service.py +445 -0
- osscodeiq/server/templates/welcome.html +56 -0
- osscodeiq-0.0.0.dist-info/METADATA +30 -0
- osscodeiq-0.0.0.dist-info/RECORD +183 -0
- osscodeiq-0.0.0.dist-info/WHEEL +5 -0
- osscodeiq-0.0.0.dist-info/entry_points.txt +2 -0
- osscodeiq-0.0.0.dist-info/licenses/LICENSE +21 -0
- 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
|