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,155 @@
|
|
|
1
|
+
"""SQL structure detector for tables, views, indexes, procedures, and foreign keys."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.detectors.utils import decode_text
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
EdgeKind,
|
|
11
|
+
GraphEdge,
|
|
12
|
+
GraphNode,
|
|
13
|
+
NodeKind,
|
|
14
|
+
SourceLocation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_TABLE_RE = re.compile(
|
|
18
|
+
r'CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:\w+\.)?(\w+)',
|
|
19
|
+
re.IGNORECASE,
|
|
20
|
+
)
|
|
21
|
+
_VIEW_RE = re.compile(
|
|
22
|
+
r'CREATE\s+(?:OR\s+REPLACE\s+)?VIEW\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:\w+\.)?(\w+)',
|
|
23
|
+
re.IGNORECASE,
|
|
24
|
+
)
|
|
25
|
+
_INDEX_RE = re.compile(
|
|
26
|
+
r'CREATE\s+(?:UNIQUE\s+)?INDEX\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:\w+\.)?(\w+)',
|
|
27
|
+
re.IGNORECASE,
|
|
28
|
+
)
|
|
29
|
+
_PROCEDURE_RE = re.compile(
|
|
30
|
+
r'CREATE\s+(?:OR\s+REPLACE\s+)?PROCEDURE\s+(?:\w+\.)?(\w+)',
|
|
31
|
+
re.IGNORECASE,
|
|
32
|
+
)
|
|
33
|
+
_FK_RE = re.compile(
|
|
34
|
+
r'REFERENCES\s+(?:\w+\.)?(\w+)',
|
|
35
|
+
re.IGNORECASE,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class SqlStructureDetector:
|
|
40
|
+
"""Detects SQL structures: tables, views, indexes, procedures, and foreign key relationships."""
|
|
41
|
+
|
|
42
|
+
name: str = "sql_structure"
|
|
43
|
+
supported_languages: tuple[str, ...] = ("sql",)
|
|
44
|
+
|
|
45
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
46
|
+
result = DetectorResult()
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
text = decode_text(ctx)
|
|
50
|
+
except Exception:
|
|
51
|
+
return result
|
|
52
|
+
|
|
53
|
+
filepath = ctx.file_path
|
|
54
|
+
lines = text.split("\n")
|
|
55
|
+
|
|
56
|
+
# Track current table for FK association
|
|
57
|
+
current_table: str | None = None
|
|
58
|
+
current_table_id: str | None = None
|
|
59
|
+
|
|
60
|
+
for i, line in enumerate(lines):
|
|
61
|
+
line_num = i + 1
|
|
62
|
+
|
|
63
|
+
# Tables
|
|
64
|
+
m = _TABLE_RE.search(line)
|
|
65
|
+
if m:
|
|
66
|
+
table_name = m.group(1)
|
|
67
|
+
current_table = table_name
|
|
68
|
+
current_table_id = f"sql:{filepath}:table:{table_name}"
|
|
69
|
+
|
|
70
|
+
result.nodes.append(GraphNode(
|
|
71
|
+
id=current_table_id,
|
|
72
|
+
kind=NodeKind.ENTITY,
|
|
73
|
+
label=table_name,
|
|
74
|
+
fqn=table_name,
|
|
75
|
+
module=ctx.module_name,
|
|
76
|
+
location=SourceLocation(
|
|
77
|
+
file_path=filepath,
|
|
78
|
+
line_start=line_num,
|
|
79
|
+
),
|
|
80
|
+
properties={"entity_type": "table"},
|
|
81
|
+
))
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
# Views
|
|
85
|
+
m = _VIEW_RE.search(line)
|
|
86
|
+
if m:
|
|
87
|
+
view_name = m.group(1)
|
|
88
|
+
result.nodes.append(GraphNode(
|
|
89
|
+
id=f"sql:{filepath}:view:{view_name}",
|
|
90
|
+
kind=NodeKind.ENTITY,
|
|
91
|
+
label=view_name,
|
|
92
|
+
fqn=view_name,
|
|
93
|
+
module=ctx.module_name,
|
|
94
|
+
location=SourceLocation(
|
|
95
|
+
file_path=filepath,
|
|
96
|
+
line_start=line_num,
|
|
97
|
+
),
|
|
98
|
+
properties={"entity_type": "view"},
|
|
99
|
+
))
|
|
100
|
+
current_table = None
|
|
101
|
+
current_table_id = None
|
|
102
|
+
continue
|
|
103
|
+
|
|
104
|
+
# Indexes
|
|
105
|
+
m = _INDEX_RE.search(line)
|
|
106
|
+
if m:
|
|
107
|
+
index_name = m.group(1)
|
|
108
|
+
result.nodes.append(GraphNode(
|
|
109
|
+
id=f"sql:{filepath}:index:{index_name}",
|
|
110
|
+
kind=NodeKind.CONFIG_DEFINITION,
|
|
111
|
+
label=index_name,
|
|
112
|
+
fqn=index_name,
|
|
113
|
+
module=ctx.module_name,
|
|
114
|
+
location=SourceLocation(
|
|
115
|
+
file_path=filepath,
|
|
116
|
+
line_start=line_num,
|
|
117
|
+
),
|
|
118
|
+
properties={"definition_type": "index"},
|
|
119
|
+
))
|
|
120
|
+
continue
|
|
121
|
+
|
|
122
|
+
# Procedures
|
|
123
|
+
m = _PROCEDURE_RE.search(line)
|
|
124
|
+
if m:
|
|
125
|
+
proc_name = m.group(1)
|
|
126
|
+
result.nodes.append(GraphNode(
|
|
127
|
+
id=f"sql:{filepath}:procedure:{proc_name}",
|
|
128
|
+
kind=NodeKind.ENTITY,
|
|
129
|
+
label=proc_name,
|
|
130
|
+
fqn=proc_name,
|
|
131
|
+
module=ctx.module_name,
|
|
132
|
+
location=SourceLocation(
|
|
133
|
+
file_path=filepath,
|
|
134
|
+
line_start=line_num,
|
|
135
|
+
),
|
|
136
|
+
properties={"entity_type": "procedure"},
|
|
137
|
+
))
|
|
138
|
+
current_table = None
|
|
139
|
+
current_table_id = None
|
|
140
|
+
continue
|
|
141
|
+
|
|
142
|
+
# Foreign key references
|
|
143
|
+
m = _FK_RE.search(line)
|
|
144
|
+
if m and current_table_id:
|
|
145
|
+
ref_table = m.group(1)
|
|
146
|
+
ref_table_id = f"sql:{filepath}:table:{ref_table}"
|
|
147
|
+
result.edges.append(GraphEdge(
|
|
148
|
+
source=current_table_id,
|
|
149
|
+
target=ref_table_id,
|
|
150
|
+
kind=EdgeKind.DEPENDS_ON,
|
|
151
|
+
label=f"{current_table} references {ref_table}",
|
|
152
|
+
properties={"relationship": "foreign_key"},
|
|
153
|
+
))
|
|
154
|
+
|
|
155
|
+
return result
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Generic TOML structure detector for all .toml files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.detectors.utils import decode_text
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
EdgeKind,
|
|
11
|
+
GraphEdge,
|
|
12
|
+
GraphNode,
|
|
13
|
+
NodeKind,
|
|
14
|
+
SourceLocation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if sys.version_info >= (3, 11):
|
|
18
|
+
import tomllib
|
|
19
|
+
else:
|
|
20
|
+
try:
|
|
21
|
+
import tomli as tomllib # type: ignore[no-redef]
|
|
22
|
+
except ImportError:
|
|
23
|
+
tomllib = None # type: ignore[assignment]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TomlStructureDetector:
|
|
27
|
+
"""Detects TOML file structures: sections, top-level keys, and file identity."""
|
|
28
|
+
|
|
29
|
+
name: str = "toml_structure"
|
|
30
|
+
supported_languages: tuple[str, ...] = ("toml",)
|
|
31
|
+
|
|
32
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
33
|
+
result = DetectorResult()
|
|
34
|
+
|
|
35
|
+
file_id = f"toml:{ctx.file_path}"
|
|
36
|
+
|
|
37
|
+
# Create CONFIG_FILE node for the file itself
|
|
38
|
+
result.nodes.append(GraphNode(
|
|
39
|
+
id=file_id,
|
|
40
|
+
kind=NodeKind.CONFIG_FILE,
|
|
41
|
+
label=ctx.file_path,
|
|
42
|
+
fqn=ctx.file_path,
|
|
43
|
+
module=ctx.module_name,
|
|
44
|
+
location=SourceLocation(
|
|
45
|
+
file_path=ctx.file_path,
|
|
46
|
+
line_start=1,
|
|
47
|
+
),
|
|
48
|
+
properties={"format": "toml"},
|
|
49
|
+
))
|
|
50
|
+
|
|
51
|
+
if tomllib is None:
|
|
52
|
+
return result
|
|
53
|
+
|
|
54
|
+
# Parse TOML from raw content
|
|
55
|
+
try:
|
|
56
|
+
data = tomllib.loads(decode_text(ctx))
|
|
57
|
+
except Exception:
|
|
58
|
+
return result
|
|
59
|
+
|
|
60
|
+
if not isinstance(data, dict):
|
|
61
|
+
return result
|
|
62
|
+
|
|
63
|
+
for key, value in data.items():
|
|
64
|
+
key_str = str(key)
|
|
65
|
+
|
|
66
|
+
# Tables (sections) are dicts at top level
|
|
67
|
+
is_section = isinstance(value, dict)
|
|
68
|
+
key_id = f"toml:{ctx.file_path}:{key_str}"
|
|
69
|
+
|
|
70
|
+
props: dict[str, object] = {}
|
|
71
|
+
if is_section:
|
|
72
|
+
props["section"] = True
|
|
73
|
+
|
|
74
|
+
result.nodes.append(GraphNode(
|
|
75
|
+
id=key_id,
|
|
76
|
+
kind=NodeKind.CONFIG_KEY,
|
|
77
|
+
label=key_str,
|
|
78
|
+
fqn=f"{ctx.file_path}:{key_str}",
|
|
79
|
+
module=ctx.module_name,
|
|
80
|
+
location=SourceLocation(
|
|
81
|
+
file_path=ctx.file_path,
|
|
82
|
+
),
|
|
83
|
+
properties=props,
|
|
84
|
+
))
|
|
85
|
+
|
|
86
|
+
result.edges.append(GraphEdge(
|
|
87
|
+
source=file_id,
|
|
88
|
+
target=key_id,
|
|
89
|
+
kind=EdgeKind.CONTAINS,
|
|
90
|
+
label=f"{ctx.file_path} contains {key_str}",
|
|
91
|
+
))
|
|
92
|
+
|
|
93
|
+
return result
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Detector for tsconfig.json files (TypeScript compiler configuration)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
|
|
8
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
EdgeKind,
|
|
11
|
+
GraphEdge,
|
|
12
|
+
GraphNode,
|
|
13
|
+
NodeKind,
|
|
14
|
+
SourceLocation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_TSCONFIG_RE = re.compile(r'^tsconfig(?:\..+)?\.json$')
|
|
18
|
+
|
|
19
|
+
_TRACKED_COMPILER_OPTIONS = ("strict", "target", "module", "outDir", "rootDir")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class TsconfigJsonDetector:
|
|
23
|
+
"""Detects configuration structure from tsconfig.json files."""
|
|
24
|
+
|
|
25
|
+
name: str = "tsconfig_json"
|
|
26
|
+
supported_languages: tuple[str, ...] = ("json",)
|
|
27
|
+
|
|
28
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
29
|
+
result = DetectorResult()
|
|
30
|
+
|
|
31
|
+
# Only trigger for tsconfig.json or tsconfig.*.json
|
|
32
|
+
basename = os.path.basename(ctx.file_path)
|
|
33
|
+
if not _TSCONFIG_RE.match(basename):
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
data = ctx.parsed_data
|
|
37
|
+
if not isinstance(data, dict) or not isinstance(data.get("data"), dict):
|
|
38
|
+
return result
|
|
39
|
+
|
|
40
|
+
cfg = data["data"]
|
|
41
|
+
filepath = ctx.file_path
|
|
42
|
+
config_id = f"tsconfig:{filepath}"
|
|
43
|
+
|
|
44
|
+
# CONFIG_FILE node
|
|
45
|
+
result.nodes.append(GraphNode(
|
|
46
|
+
id=config_id,
|
|
47
|
+
kind=NodeKind.CONFIG_FILE,
|
|
48
|
+
label=basename,
|
|
49
|
+
fqn=filepath,
|
|
50
|
+
module=ctx.module_name,
|
|
51
|
+
location=SourceLocation(file_path=filepath),
|
|
52
|
+
properties={"config_type": "tsconfig"},
|
|
53
|
+
))
|
|
54
|
+
|
|
55
|
+
# DEPENDS_ON edge for "extends"
|
|
56
|
+
extends = cfg.get("extends")
|
|
57
|
+
if isinstance(extends, str) and extends:
|
|
58
|
+
result.edges.append(GraphEdge(
|
|
59
|
+
source=config_id,
|
|
60
|
+
target=extends,
|
|
61
|
+
kind=EdgeKind.DEPENDS_ON,
|
|
62
|
+
label=f"{basename} extends {extends}",
|
|
63
|
+
properties={"relation": "extends"},
|
|
64
|
+
))
|
|
65
|
+
|
|
66
|
+
# DEPENDS_ON edges for "references"
|
|
67
|
+
references = cfg.get("references")
|
|
68
|
+
if isinstance(references, list):
|
|
69
|
+
for ref in references:
|
|
70
|
+
if not isinstance(ref, dict):
|
|
71
|
+
continue
|
|
72
|
+
ref_path = ref.get("path")
|
|
73
|
+
if isinstance(ref_path, str) and ref_path:
|
|
74
|
+
result.edges.append(GraphEdge(
|
|
75
|
+
source=config_id,
|
|
76
|
+
target=ref_path,
|
|
77
|
+
kind=EdgeKind.DEPENDS_ON,
|
|
78
|
+
label=f"{basename} references {ref_path}",
|
|
79
|
+
properties={"relation": "reference"},
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
# CONFIG_KEY nodes for key compiler options
|
|
83
|
+
compiler_options = cfg.get("compilerOptions")
|
|
84
|
+
if isinstance(compiler_options, dict):
|
|
85
|
+
for opt in _TRACKED_COMPILER_OPTIONS:
|
|
86
|
+
if opt not in compiler_options:
|
|
87
|
+
continue
|
|
88
|
+
value = compiler_options[opt]
|
|
89
|
+
key_id = f"tsconfig:{filepath}:option:{opt}"
|
|
90
|
+
result.nodes.append(GraphNode(
|
|
91
|
+
id=key_id,
|
|
92
|
+
kind=NodeKind.CONFIG_KEY,
|
|
93
|
+
label=f"compilerOptions.{opt}",
|
|
94
|
+
module=ctx.module_name,
|
|
95
|
+
location=SourceLocation(file_path=filepath),
|
|
96
|
+
properties={"key": opt, "value": value},
|
|
97
|
+
))
|
|
98
|
+
result.edges.append(GraphEdge(
|
|
99
|
+
source=config_id,
|
|
100
|
+
target=key_id,
|
|
101
|
+
kind=EdgeKind.CONTAINS,
|
|
102
|
+
label=f"{basename} defines {opt}",
|
|
103
|
+
))
|
|
104
|
+
|
|
105
|
+
return result
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Generic YAML structure detector for all .yaml/.yml files."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
6
|
+
from osscodeiq.models.graph import (
|
|
7
|
+
EdgeKind,
|
|
8
|
+
GraphEdge,
|
|
9
|
+
GraphNode,
|
|
10
|
+
NodeKind,
|
|
11
|
+
SourceLocation,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class YamlStructureDetector:
|
|
16
|
+
"""Detects YAML file structures: top-level keys and file identity."""
|
|
17
|
+
|
|
18
|
+
name: str = "yaml_structure"
|
|
19
|
+
supported_languages: tuple[str, ...] = ("yaml",)
|
|
20
|
+
|
|
21
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
22
|
+
result = DetectorResult()
|
|
23
|
+
|
|
24
|
+
file_id = f"yaml:{ctx.file_path}"
|
|
25
|
+
|
|
26
|
+
# Create CONFIG_FILE node for the file itself
|
|
27
|
+
result.nodes.append(GraphNode(
|
|
28
|
+
id=file_id,
|
|
29
|
+
kind=NodeKind.CONFIG_FILE,
|
|
30
|
+
label=ctx.file_path,
|
|
31
|
+
fqn=ctx.file_path,
|
|
32
|
+
module=ctx.module_name,
|
|
33
|
+
location=SourceLocation(
|
|
34
|
+
file_path=ctx.file_path,
|
|
35
|
+
line_start=1,
|
|
36
|
+
),
|
|
37
|
+
properties={"format": "yaml"},
|
|
38
|
+
))
|
|
39
|
+
|
|
40
|
+
if not isinstance(ctx.parsed_data, dict):
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
doc_type = ctx.parsed_data.get("type", "")
|
|
44
|
+
|
|
45
|
+
# Collect all top-level keys from documents
|
|
46
|
+
top_level_keys: set[str] = set()
|
|
47
|
+
|
|
48
|
+
if doc_type == "yaml_multi":
|
|
49
|
+
# Multi-document YAML: iterate over documents list
|
|
50
|
+
documents = ctx.parsed_data.get("documents", [])
|
|
51
|
+
for doc in documents:
|
|
52
|
+
if isinstance(doc, dict):
|
|
53
|
+
top_level_keys.update(str(k) for k in doc)
|
|
54
|
+
elif doc_type == "yaml":
|
|
55
|
+
# Single-document YAML
|
|
56
|
+
data = ctx.parsed_data.get("data")
|
|
57
|
+
if isinstance(data, dict):
|
|
58
|
+
top_level_keys.update(str(k) for k in data)
|
|
59
|
+
|
|
60
|
+
# Create CONFIG_KEY nodes for top-level keys
|
|
61
|
+
for key_str in sorted(top_level_keys):
|
|
62
|
+
key_id = f"yaml:{ctx.file_path}:{key_str}"
|
|
63
|
+
|
|
64
|
+
result.nodes.append(GraphNode(
|
|
65
|
+
id=key_id,
|
|
66
|
+
kind=NodeKind.CONFIG_KEY,
|
|
67
|
+
label=key_str,
|
|
68
|
+
fqn=f"{ctx.file_path}:{key_str}",
|
|
69
|
+
module=ctx.module_name,
|
|
70
|
+
location=SourceLocation(
|
|
71
|
+
file_path=ctx.file_path,
|
|
72
|
+
),
|
|
73
|
+
))
|
|
74
|
+
|
|
75
|
+
result.edges.append(GraphEdge(
|
|
76
|
+
source=file_id,
|
|
77
|
+
target=key_id,
|
|
78
|
+
kind=EdgeKind.CONTAINS,
|
|
79
|
+
label=f"{ctx.file_path} contains {key_str}",
|
|
80
|
+
))
|
|
81
|
+
|
|
82
|
+
return result
|
|
File without changes
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""C/C++ structures detector for classes, structs, namespaces, functions, and enums."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from osscodeiq.detectors.base import DetectorContext, DetectorResult
|
|
8
|
+
from osscodeiq.detectors.utils import decode_text
|
|
9
|
+
from osscodeiq.models.graph import (
|
|
10
|
+
EdgeKind,
|
|
11
|
+
GraphEdge,
|
|
12
|
+
GraphNode,
|
|
13
|
+
NodeKind,
|
|
14
|
+
SourceLocation,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_CLASS_RE = re.compile(
|
|
18
|
+
r'(?:template\s*<[^>]*>\s*)?class\s+(\w+)(?:\s*:\s*(?:public|protected|private)\s+(\w+))?'
|
|
19
|
+
)
|
|
20
|
+
_STRUCT_RE = re.compile(
|
|
21
|
+
r'struct\s+(\w+)(?:\s*:\s*(?:public|protected|private)\s+(\w+))?\s*\{'
|
|
22
|
+
)
|
|
23
|
+
_NAMESPACE_RE = re.compile(r'namespace\s+(\w+)\s*\{')
|
|
24
|
+
_FUNC_RE = re.compile(
|
|
25
|
+
r'^(?:[\w:*&<>\s]+)\s+(\w+)\s*\([^)]*\)\s*(?:const\s*)?\{', re.MULTILINE
|
|
26
|
+
)
|
|
27
|
+
_INCLUDE_RE = re.compile(r'#include\s+[<"]([^>"]+)[>"]')
|
|
28
|
+
_ENUM_RE = re.compile(r'enum\s+(?:class\s+)?(\w+)')
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _is_forward_declaration(line: str) -> bool:
|
|
32
|
+
"""Check if a line is a forward declaration (ends with ; but has no body)."""
|
|
33
|
+
stripped = line.rstrip()
|
|
34
|
+
# A line ending with ; that contains { is a single-line definition, not a forward decl
|
|
35
|
+
return stripped.endswith(';') and '{' not in stripped
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class CppStructuresDetector:
|
|
39
|
+
"""Detects C/C++ structural elements: classes, structs, namespaces, functions, enums."""
|
|
40
|
+
|
|
41
|
+
name: str = "cpp_structures"
|
|
42
|
+
supported_languages: tuple[str, ...] = ("cpp", "c")
|
|
43
|
+
|
|
44
|
+
def detect(self, ctx: DetectorContext) -> DetectorResult:
|
|
45
|
+
result = DetectorResult()
|
|
46
|
+
text = decode_text(ctx)
|
|
47
|
+
lines = text.split("\n")
|
|
48
|
+
|
|
49
|
+
file_node_id = ctx.file_path
|
|
50
|
+
|
|
51
|
+
# Detect #include directives
|
|
52
|
+
for i, line in enumerate(lines):
|
|
53
|
+
m = _INCLUDE_RE.search(line)
|
|
54
|
+
if not m:
|
|
55
|
+
continue
|
|
56
|
+
included = m.group(1)
|
|
57
|
+
result.edges.append(GraphEdge(
|
|
58
|
+
source=ctx.file_path,
|
|
59
|
+
target=included,
|
|
60
|
+
kind=EdgeKind.IMPORTS,
|
|
61
|
+
label=f"{ctx.file_path} includes {included}",
|
|
62
|
+
))
|
|
63
|
+
|
|
64
|
+
# Detect namespace declarations
|
|
65
|
+
for i, line in enumerate(lines):
|
|
66
|
+
m = _NAMESPACE_RE.search(line)
|
|
67
|
+
if not m:
|
|
68
|
+
continue
|
|
69
|
+
ns_name = m.group(1)
|
|
70
|
+
result.nodes.append(GraphNode(
|
|
71
|
+
id=f"{ctx.file_path}:{ns_name}",
|
|
72
|
+
kind=NodeKind.MODULE,
|
|
73
|
+
label=ns_name,
|
|
74
|
+
fqn=ns_name,
|
|
75
|
+
module=ctx.module_name,
|
|
76
|
+
location=SourceLocation(
|
|
77
|
+
file_path=ctx.file_path,
|
|
78
|
+
line_start=i + 1,
|
|
79
|
+
),
|
|
80
|
+
properties={"namespace": True},
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
# Detect class declarations (including template classes)
|
|
84
|
+
for i, line in enumerate(lines):
|
|
85
|
+
m = _CLASS_RE.search(line)
|
|
86
|
+
if not m:
|
|
87
|
+
continue
|
|
88
|
+
# Skip forward declarations
|
|
89
|
+
if _is_forward_declaration(line):
|
|
90
|
+
continue
|
|
91
|
+
class_name = m.group(1)
|
|
92
|
+
base_class = m.group(2)
|
|
93
|
+
is_template = 'template' in line[:m.start() + len(m.group(0))]
|
|
94
|
+
|
|
95
|
+
props: dict = {}
|
|
96
|
+
if is_template:
|
|
97
|
+
props["is_template"] = True
|
|
98
|
+
|
|
99
|
+
node_id = f"{ctx.file_path}:{class_name}"
|
|
100
|
+
result.nodes.append(GraphNode(
|
|
101
|
+
id=node_id,
|
|
102
|
+
kind=NodeKind.CLASS,
|
|
103
|
+
label=class_name,
|
|
104
|
+
fqn=class_name,
|
|
105
|
+
module=ctx.module_name,
|
|
106
|
+
location=SourceLocation(
|
|
107
|
+
file_path=ctx.file_path,
|
|
108
|
+
line_start=i + 1,
|
|
109
|
+
),
|
|
110
|
+
properties=props,
|
|
111
|
+
))
|
|
112
|
+
|
|
113
|
+
if base_class:
|
|
114
|
+
result.edges.append(GraphEdge(
|
|
115
|
+
source=node_id,
|
|
116
|
+
target=base_class,
|
|
117
|
+
kind=EdgeKind.EXTENDS,
|
|
118
|
+
label=f"{class_name} extends {base_class}",
|
|
119
|
+
))
|
|
120
|
+
|
|
121
|
+
# Detect struct declarations
|
|
122
|
+
for i, line in enumerate(lines):
|
|
123
|
+
m = _STRUCT_RE.search(line)
|
|
124
|
+
if not m:
|
|
125
|
+
continue
|
|
126
|
+
if _is_forward_declaration(line):
|
|
127
|
+
continue
|
|
128
|
+
struct_name = m.group(1)
|
|
129
|
+
base_struct = m.group(2)
|
|
130
|
+
|
|
131
|
+
node_id = f"{ctx.file_path}:{struct_name}"
|
|
132
|
+
result.nodes.append(GraphNode(
|
|
133
|
+
id=node_id,
|
|
134
|
+
kind=NodeKind.CLASS,
|
|
135
|
+
label=struct_name,
|
|
136
|
+
fqn=struct_name,
|
|
137
|
+
module=ctx.module_name,
|
|
138
|
+
location=SourceLocation(
|
|
139
|
+
file_path=ctx.file_path,
|
|
140
|
+
line_start=i + 1,
|
|
141
|
+
),
|
|
142
|
+
properties={"struct": True},
|
|
143
|
+
))
|
|
144
|
+
|
|
145
|
+
if base_struct:
|
|
146
|
+
result.edges.append(GraphEdge(
|
|
147
|
+
source=node_id,
|
|
148
|
+
target=base_struct,
|
|
149
|
+
kind=EdgeKind.EXTENDS,
|
|
150
|
+
label=f"{struct_name} extends {base_struct}",
|
|
151
|
+
))
|
|
152
|
+
|
|
153
|
+
# Detect enum declarations
|
|
154
|
+
for i, line in enumerate(lines):
|
|
155
|
+
m = _ENUM_RE.search(line)
|
|
156
|
+
if not m:
|
|
157
|
+
continue
|
|
158
|
+
if _is_forward_declaration(line):
|
|
159
|
+
continue
|
|
160
|
+
enum_name = m.group(1)
|
|
161
|
+
|
|
162
|
+
result.nodes.append(GraphNode(
|
|
163
|
+
id=f"{ctx.file_path}:{enum_name}",
|
|
164
|
+
kind=NodeKind.ENUM,
|
|
165
|
+
label=enum_name,
|
|
166
|
+
fqn=enum_name,
|
|
167
|
+
module=ctx.module_name,
|
|
168
|
+
location=SourceLocation(
|
|
169
|
+
file_path=ctx.file_path,
|
|
170
|
+
line_start=i + 1,
|
|
171
|
+
),
|
|
172
|
+
))
|
|
173
|
+
|
|
174
|
+
# Detect file-scope function definitions
|
|
175
|
+
for m in _FUNC_RE.finditer(text):
|
|
176
|
+
func_name = m.group(1)
|
|
177
|
+
# Compute line number from character offset
|
|
178
|
+
line_num = text[:m.start()].count("\n") + 1
|
|
179
|
+
|
|
180
|
+
result.nodes.append(GraphNode(
|
|
181
|
+
id=f"{ctx.file_path}:{func_name}",
|
|
182
|
+
kind=NodeKind.METHOD,
|
|
183
|
+
label=func_name,
|
|
184
|
+
fqn=func_name,
|
|
185
|
+
module=ctx.module_name,
|
|
186
|
+
location=SourceLocation(
|
|
187
|
+
file_path=ctx.file_path,
|
|
188
|
+
line_start=line_num,
|
|
189
|
+
),
|
|
190
|
+
))
|
|
191
|
+
|
|
192
|
+
return result
|
|
File without changes
|