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,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