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,82 @@
1
+ """ConfigDef detector for Java source files using Kafka's ConfigDef.define() pattern."""
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(r"(?:public\s+)?class\s+(\w+)")
18
+ _DEFINE_RE = re.compile(r'\.define\s*\(\s*"([^"]+)"')
19
+
20
+
21
+ class ConfigDefDetector:
22
+ """Detects Kafka ConfigDef.define() configuration definitions."""
23
+
24
+ name: str = "config_def"
25
+ supported_languages: tuple[str, ...] = ("java",)
26
+
27
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
28
+ result = DetectorResult()
29
+ text = decode_text(ctx)
30
+
31
+ if "ConfigDef" not in text:
32
+ return result
33
+
34
+ lines = text.split("\n")
35
+
36
+ # Find class name
37
+ class_name: str | None = None
38
+ for line in lines:
39
+ cm = _CLASS_RE.search(line)
40
+ if cm:
41
+ class_name = cm.group(1)
42
+ break
43
+
44
+ if not class_name:
45
+ return result
46
+
47
+ class_node_id = f"{ctx.file_path}:{class_name}"
48
+ seen_keys: set[str] = set()
49
+
50
+ # Find all .define("config.key") calls
51
+ for i, line in enumerate(lines):
52
+ m = _DEFINE_RE.search(line)
53
+ if not m:
54
+ continue
55
+
56
+ config_key = m.group(1)
57
+ if config_key in seen_keys:
58
+ continue
59
+ seen_keys.add(config_key)
60
+
61
+ node_id = f"config:{config_key}"
62
+
63
+ result.nodes.append(GraphNode(
64
+ id=node_id,
65
+ kind=NodeKind.CONFIG_DEFINITION,
66
+ label=config_key,
67
+ location=SourceLocation(
68
+ file_path=ctx.file_path,
69
+ line_start=i + 1,
70
+ ),
71
+ properties={"config_key": config_key},
72
+ ))
73
+
74
+ result.edges.append(GraphEdge(
75
+ source=class_node_id,
76
+ target=node_id,
77
+ kind=EdgeKind.READS_CONFIG,
78
+ label=f"{class_name} reads config {config_key}",
79
+ properties={"config_key": config_key},
80
+ ))
81
+
82
+ return result
@@ -0,0 +1,105 @@
1
+ """Azure Cosmos DB client usage detector for Java and TypeScript/JavaScript."""
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
+ _CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
19
+ _DATABASE_RE = re.compile(r'\.(?:getDatabase|database)\s*\(\s*"([^"]+)"')
20
+ _CONTAINER_RE = re.compile(r'\.(?:getContainer|container)\s*\(\s*"([^"]+)"')
21
+
22
+
23
+ class CosmosDbDetector:
24
+ """Detects Azure Cosmos DB client usage patterns."""
25
+
26
+ name: str = "cosmos_db"
27
+ supported_languages: tuple[str, ...] = ("java", "typescript", "javascript")
28
+
29
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
30
+ result = DetectorResult()
31
+ text = decode_text(ctx)
32
+
33
+ # Fast bail
34
+ if (
35
+ "CosmosClient" not in text
36
+ and "CosmosDatabase" not in text
37
+ and "CosmosContainer" not in text
38
+ and "@azure/cosmos" not in text
39
+ ):
40
+ return result
41
+
42
+ lines = text.split("\n")
43
+
44
+ # Find class name (for Java) or use file path as source
45
+ class_name: str | None = None
46
+ for line in lines:
47
+ cm = _CLASS_RE.search(line)
48
+ if cm:
49
+ class_name = cm.group(1)
50
+ break
51
+
52
+ source_node_id = f"{ctx.file_path}:{class_name}" if class_name else ctx.file_path
53
+ seen_databases: set[str] = set()
54
+ seen_containers: set[str] = set()
55
+
56
+ for i, line in enumerate(lines):
57
+ # Detect database references
58
+ for db_match in _DATABASE_RE.finditer(line):
59
+ db_name = db_match.group(1)
60
+ if db_name not in seen_databases:
61
+ seen_databases.add(db_name)
62
+ db_node_id = f"azure:cosmos:db:{db_name}"
63
+ result.nodes.append(GraphNode(
64
+ id=db_node_id,
65
+ kind=NodeKind.AZURE_RESOURCE,
66
+ label=f"cosmosdb:{db_name}",
67
+ module=ctx.module_name,
68
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
69
+ properties={
70
+ "cosmos_type": "database",
71
+ "resource_name": db_name,
72
+ },
73
+ ))
74
+ result.edges.append(GraphEdge(
75
+ source=source_node_id,
76
+ target=db_node_id,
77
+ kind=EdgeKind.CONNECTS_TO,
78
+ label=f"{class_name or ctx.file_path} connects to cosmosdb:{db_name}",
79
+ ))
80
+
81
+ # Detect container references
82
+ for cont_match in _CONTAINER_RE.finditer(line):
83
+ container_name = cont_match.group(1)
84
+ if container_name not in seen_containers:
85
+ seen_containers.add(container_name)
86
+ container_node_id = f"azure:cosmos:container:{container_name}"
87
+ result.nodes.append(GraphNode(
88
+ id=container_node_id,
89
+ kind=NodeKind.AZURE_RESOURCE,
90
+ label=f"cosmosdb-container:{container_name}",
91
+ module=ctx.module_name,
92
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
93
+ properties={
94
+ "cosmos_type": "container",
95
+ "resource_name": container_name,
96
+ },
97
+ ))
98
+ result.edges.append(GraphEdge(
99
+ source=source_node_id,
100
+ target=container_node_id,
101
+ kind=EdgeKind.CONNECTS_TO,
102
+ label=f"{class_name or ctx.file_path} connects to container:{container_name}",
103
+ ))
104
+
105
+ return result
@@ -0,0 +1,188 @@
1
+ """GraphQL resolver detector for Java source files."""
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(r"(?:public\s+)?class\s+(\w+)")
18
+
19
+ # Spring GraphQL annotations
20
+ _QUERY_MAPPING_RE = re.compile(r"@QueryMapping(?:\s*\(\s*(?:name\s*=\s*)?\"([^\"]+)\"\s*\))?")
21
+ _MUTATION_MAPPING_RE = re.compile(r"@MutationMapping(?:\s*\(\s*(?:name\s*=\s*)?\"([^\"]+)\"\s*\))?")
22
+ _SUBSCRIPTION_MAPPING_RE = re.compile(r"@SubscriptionMapping(?:\s*\(\s*(?:name\s*=\s*)?\"([^\"]+)\"\s*\))?")
23
+ _SCHEMA_MAPPING_RE = re.compile(r'@SchemaMapping\s*\(\s*(?:typeName\s*=\s*"([^"]+)")?')
24
+ _BATCH_MAPPING_RE = re.compile(r"@BatchMapping(?:\s*\(\s*(?:field\s*=\s*)?\"([^\"]+)\"\s*\))?")
25
+
26
+ # DGS framework annotations
27
+ _DGS_QUERY_RE = re.compile(r"@DgsQuery(?:\s*\(\s*field\s*=\s*\"([^\"]+)\"\s*\))?")
28
+ _DGS_MUTATION_RE = re.compile(r"@DgsMutation(?:\s*\(\s*field\s*=\s*\"([^\"]+)\"\s*\))?")
29
+ _DGS_SUBSCRIPTION_RE = re.compile(r"@DgsSubscription(?:\s*\(\s*field\s*=\s*\"([^\"]+)\"\s*\))?")
30
+ _DGS_DATA_RE = re.compile(r"@DgsData\s*\(\s*parentType\s*=\s*\"([^\"]+)\"(?:\s*,\s*field\s*=\s*\"([^\"]+)\")?")
31
+
32
+ _METHOD_RE = re.compile(r"(?:public|protected|private)?\s*(?:[\w<>\[\],?\s]+)\s+(\w+)\s*\(")
33
+
34
+ # Controller annotation for GraphQL controllers
35
+ _CONTROLLER_RE = re.compile(r"@(?:Controller|DgsComponent)")
36
+
37
+
38
+ class GraphqlResolverDetector:
39
+ """Detects GraphQL resolvers from Spring GraphQL and DGS framework annotations."""
40
+
41
+ name: str = "graphql_resolver"
42
+ supported_languages: tuple[str, ...] = ("java",)
43
+
44
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
45
+ result = DetectorResult()
46
+ text = decode_text(ctx)
47
+ lines = text.split("\n")
48
+
49
+ # Quick check for relevant annotations
50
+ if not any(kw in text for kw in (
51
+ "@QueryMapping", "@MutationMapping", "@SubscriptionMapping",
52
+ "@SchemaMapping", "@BatchMapping",
53
+ "@DgsQuery", "@DgsMutation", "@DgsSubscription", "@DgsData",
54
+ )):
55
+ return result
56
+
57
+ # Find class name
58
+ class_name: str | None = None
59
+ for line in lines:
60
+ cm = _CLASS_RE.search(line)
61
+ if cm:
62
+ class_name = cm.group(1)
63
+ break
64
+
65
+ if not class_name:
66
+ return result
67
+
68
+ class_node_id = f"{ctx.file_path}:{class_name}"
69
+
70
+ # All annotation patterns to scan
71
+ patterns = [
72
+ (_QUERY_MAPPING_RE, "Query"),
73
+ (_MUTATION_MAPPING_RE, "Mutation"),
74
+ (_SUBSCRIPTION_MAPPING_RE, "Subscription"),
75
+ (_DGS_QUERY_RE, "Query"),
76
+ (_DGS_MUTATION_RE, "Mutation"),
77
+ (_DGS_SUBSCRIPTION_RE, "Subscription"),
78
+ ]
79
+
80
+ for i, line in enumerate(lines):
81
+ for pattern, gql_type in patterns:
82
+ m = pattern.search(line)
83
+ if not m:
84
+ continue
85
+
86
+ field_name = m.group(1) if m.lastindex and m.group(1) else None
87
+ # Resolve field name from method if not in annotation
88
+ if not field_name:
89
+ for k in range(i + 1, min(i + 4, len(lines))):
90
+ mm = _METHOD_RE.search(lines[k])
91
+ if mm:
92
+ field_name = mm.group(1)
93
+ break
94
+
95
+ if not field_name:
96
+ continue
97
+
98
+ resolver_id = f"{ctx.file_path}:{class_name}:{gql_type}:{field_name}"
99
+ result.nodes.append(GraphNode(
100
+ id=resolver_id,
101
+ kind=NodeKind.ENDPOINT,
102
+ label=f"GraphQL {gql_type}.{field_name}",
103
+ fqn=f"{class_name}.{field_name}",
104
+ module=ctx.module_name,
105
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
106
+ properties={
107
+ "graphql_type": gql_type,
108
+ "field": field_name,
109
+ "protocol": "graphql",
110
+ },
111
+ ))
112
+
113
+ result.edges.append(GraphEdge(
114
+ source=class_node_id,
115
+ target=resolver_id,
116
+ kind=EdgeKind.EXPOSES,
117
+ label=f"{class_name} exposes {gql_type}.{field_name}",
118
+ ))
119
+
120
+ # Handle @SchemaMapping
121
+ sm = _SCHEMA_MAPPING_RE.search(line)
122
+ if sm:
123
+ type_name = sm.group(1) or "Unknown"
124
+ method_name = None
125
+ for k in range(i + 1, min(i + 4, len(lines))):
126
+ mm = _METHOD_RE.search(lines[k])
127
+ if mm:
128
+ method_name = mm.group(1)
129
+ break
130
+
131
+ if method_name:
132
+ resolver_id = f"{ctx.file_path}:{class_name}:SchemaMapping:{type_name}.{method_name}"
133
+ result.nodes.append(GraphNode(
134
+ id=resolver_id,
135
+ kind=NodeKind.ENDPOINT,
136
+ label=f"GraphQL {type_name}.{method_name}",
137
+ fqn=f"{class_name}.{method_name}",
138
+ module=ctx.module_name,
139
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
140
+ properties={
141
+ "graphql_type": type_name,
142
+ "field": method_name,
143
+ "protocol": "graphql",
144
+ },
145
+ ))
146
+ result.edges.append(GraphEdge(
147
+ source=class_node_id,
148
+ target=resolver_id,
149
+ kind=EdgeKind.EXPOSES,
150
+ label=f"{class_name} exposes {type_name}.{method_name}",
151
+ ))
152
+
153
+ # Handle @DgsData
154
+ dm = _DGS_DATA_RE.search(line)
155
+ if dm:
156
+ parent_type = dm.group(1)
157
+ field_name = dm.group(2) if dm.lastindex and dm.lastindex >= 2 and dm.group(2) else None
158
+ if not field_name:
159
+ for k in range(i + 1, min(i + 4, len(lines))):
160
+ mm = _METHOD_RE.search(lines[k])
161
+ if mm:
162
+ field_name = mm.group(1)
163
+ break
164
+
165
+ if field_name:
166
+ resolver_id = f"{ctx.file_path}:{class_name}:DgsData:{parent_type}.{field_name}"
167
+ result.nodes.append(GraphNode(
168
+ id=resolver_id,
169
+ kind=NodeKind.ENDPOINT,
170
+ label=f"GraphQL {parent_type}.{field_name}",
171
+ fqn=f"{class_name}.{field_name}",
172
+ module=ctx.module_name,
173
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
174
+ properties={
175
+ "graphql_type": parent_type,
176
+ "field": field_name,
177
+ "protocol": "graphql",
178
+ "framework": "dgs",
179
+ },
180
+ ))
181
+ result.edges.append(GraphEdge(
182
+ source=class_node_id,
183
+ target=resolver_id,
184
+ kind=EdgeKind.EXPOSES,
185
+ label=f"{class_name} exposes {parent_type}.{field_name}",
186
+ ))
187
+
188
+ return result
@@ -0,0 +1,142 @@
1
+ """gRPC service detector for Java source files."""
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(r"(?:public\s+)?class\s+(\w+)")
18
+
19
+ # gRPC service implementation pattern: extends XxxGrpc.XxxImplBase
20
+ _GRPC_IMPL_RE = re.compile(
21
+ r"class\s+(\w+)\s+extends\s+(\w+)Grpc\.(\w+)ImplBase"
22
+ )
23
+
24
+ # @GrpcService annotation (grpc-spring-boot-starter)
25
+ _GRPC_SERVICE_ANNO_RE = re.compile(r"@GrpcService")
26
+
27
+ # Override methods in gRPC service
28
+ _OVERRIDE_METHOD_RE = re.compile(
29
+ r"@Override\s+\n?\s*public\s+void\s+(\w+)\s*\("
30
+ )
31
+
32
+ # gRPC channel/stub usage for client detection
33
+ _GRPC_STUB_RE = re.compile(
34
+ r"(\w+)Grpc\.new(?:Blocking|Future|)Stub\s*\("
35
+ )
36
+ _GRPC_CHANNEL_RE = re.compile(
37
+ r'ManagedChannelBuilder\s*\.forAddress\s*\(\s*"([^"]+)"\s*,\s*(\d+)'
38
+ )
39
+
40
+ # Method override pattern (simpler)
41
+ _METHOD_RE = re.compile(
42
+ r"(?:public)\s+(?:void|[\w<>\[\]]+)\s+(\w+)\s*\(\s*(\w+)"
43
+ )
44
+
45
+
46
+ class GrpcServiceDetector:
47
+ """Detects gRPC service implementations and client stubs."""
48
+
49
+ name: str = "grpc_service"
50
+ supported_languages: tuple[str, ...] = ("java",)
51
+
52
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
53
+ result = DetectorResult()
54
+ text = decode_text(ctx)
55
+ lines = text.split("\n")
56
+
57
+ has_grpc_impl = "ImplBase" in text or "@GrpcService" in text
58
+ has_grpc_stub = "Grpc.new" in text
59
+
60
+ if not has_grpc_impl and not has_grpc_stub:
61
+ return result
62
+
63
+ # Find class name
64
+ class_name: str | None = None
65
+ class_line: int = 0
66
+ for i, line in enumerate(lines):
67
+ cm = _CLASS_RE.search(line)
68
+ if cm:
69
+ class_name = cm.group(1)
70
+ class_line = i + 1
71
+ break
72
+
73
+ if not class_name:
74
+ return result
75
+
76
+ class_node_id = f"{ctx.file_path}:{class_name}"
77
+
78
+ # Detect gRPC service implementation
79
+ impl_match = _GRPC_IMPL_RE.search(text)
80
+ if impl_match:
81
+ service_proto = impl_match.group(2) # The proto service name
82
+ service_id = f"grpc:service:{service_proto}"
83
+
84
+ result.nodes.append(GraphNode(
85
+ id=service_id,
86
+ kind=NodeKind.ENDPOINT,
87
+ label=f"gRPC {service_proto}",
88
+ fqn=f"{class_name} ({service_proto})",
89
+ module=ctx.module_name,
90
+ location=SourceLocation(file_path=ctx.file_path, line_start=class_line),
91
+ annotations=["@GrpcService"] if "@GrpcService" in text else [],
92
+ properties={
93
+ "protocol": "grpc",
94
+ "service": service_proto,
95
+ "implementation": class_name,
96
+ },
97
+ ))
98
+
99
+ result.edges.append(GraphEdge(
100
+ source=class_node_id,
101
+ target=service_id,
102
+ kind=EdgeKind.EXPOSES,
103
+ label=f"{class_name} implements gRPC {service_proto}",
104
+ ))
105
+
106
+ # Find RPC methods (overridden methods)
107
+ for i, line in enumerate(lines):
108
+ if "@Override" in line:
109
+ for k in range(i + 1, min(i + 3, len(lines))):
110
+ mm = _METHOD_RE.search(lines[k])
111
+ if mm:
112
+ method_name = mm.group(1)
113
+ rpc_id = f"grpc:rpc:{service_proto}/{method_name}"
114
+ result.nodes.append(GraphNode(
115
+ id=rpc_id,
116
+ kind=NodeKind.ENDPOINT,
117
+ label=f"gRPC {service_proto}/{method_name}",
118
+ fqn=f"{class_name}.{method_name}",
119
+ module=ctx.module_name,
120
+ location=SourceLocation(file_path=ctx.file_path, line_start=k + 1),
121
+ properties={
122
+ "protocol": "grpc",
123
+ "service": service_proto,
124
+ "method": method_name,
125
+ },
126
+ ))
127
+ break
128
+
129
+ # Detect gRPC client stubs
130
+ for m in _GRPC_STUB_RE.finditer(text):
131
+ target_service = m.group(1)
132
+ line_num = text[:m.start()].count("\n") + 1
133
+
134
+ result.edges.append(GraphEdge(
135
+ source=class_node_id,
136
+ target=f"grpc:service:{target_service}",
137
+ kind=EdgeKind.CALLS,
138
+ label=f"{class_name} calls gRPC {target_service}",
139
+ properties={"protocol": "grpc", "target_service": target_service},
140
+ ))
141
+
142
+ return result
@@ -0,0 +1,178 @@
1
+ """IBM MQ detector for Java source files."""
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(r"(?:public\s+)?class\s+(\w+)")
18
+
19
+ # MQQueueManager instantiation with name
20
+ _QM_NEW_RE = re.compile(r'new\s+MQQueueManager\s*\(\s*"([^"]+)"')
21
+
22
+ # accessQueue("QUEUE_NAME", ...)
23
+ _ACCESS_QUEUE_RE = re.compile(r'accessQueue\s*\(\s*"([^"]+)"')
24
+
25
+ # MQQueue / MQTopic field or local variable declarations
26
+ _MQ_QUEUE_DECL_RE = re.compile(r'\bMQQueue\b')
27
+ _MQ_TOPIC_DECL_RE = re.compile(r'\bMQTopic\b')
28
+
29
+ # JMS-style IBM MQ connection factory
30
+ _MQ_JMS_FACTORY_RE = re.compile(r'\bMQConnectionFactory\b|\bMQQueueConnectionFactory\b|\bMQTopicConnectionFactory\b')
31
+
32
+ # createQueue / createTopic with IBM MQ JMS
33
+ _JMS_CREATE_QUEUE_RE = re.compile(r'createQueue\s*\(\s*"([^"]+)"')
34
+ _JMS_CREATE_TOPIC_RE = re.compile(r'createTopic\s*\(\s*"([^"]+)"')
35
+
36
+ # put / get calls indicate send / receive
37
+ _MQ_PUT_RE = re.compile(r'\bput\s*\(')
38
+ _MQ_GET_RE = re.compile(r'\bget\s*\(')
39
+
40
+
41
+ class IbmMqDetector:
42
+ """Detects IBM MQ queue manager, queue, and topic usage in Java source files."""
43
+
44
+ name: str = "ibm_mq"
45
+ supported_languages: tuple[str, ...] = ("java",)
46
+
47
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
48
+ result = DetectorResult()
49
+ text = decode_text(ctx)
50
+ lines = text.split("\n")
51
+
52
+ if "MQQueueManager" not in text and "JmsConnectionFactory" not in text and "com.ibm.mq" not in text and "MQQueue" not in text:
53
+ return result
54
+
55
+ # Find class name
56
+ class_name: str | None = None
57
+ for line in lines:
58
+ cm = _CLASS_RE.search(line)
59
+ if cm:
60
+ class_name = cm.group(1)
61
+ break
62
+
63
+ if not class_name:
64
+ return result
65
+
66
+ class_node_id = f"{ctx.file_path}:{class_name}"
67
+ seen_qms: set[str] = set()
68
+ seen_queues: set[str] = set()
69
+ seen_topics: set[str] = set()
70
+
71
+ def _ensure_qm_node(qm_name: str) -> str:
72
+ qm_id = f"ibmmq:qm:{qm_name}"
73
+ if qm_name not in seen_qms:
74
+ seen_qms.add(qm_name)
75
+ result.nodes.append(GraphNode(
76
+ id=qm_id,
77
+ kind=NodeKind.MESSAGE_QUEUE,
78
+ label=f"ibmmq:qm:{qm_name}",
79
+ properties={"broker": "ibm_mq", "queue_manager": qm_name},
80
+ ))
81
+ return qm_id
82
+
83
+ def _ensure_queue_node(queue_name: str) -> str:
84
+ queue_id = f"ibmmq:queue:{queue_name}"
85
+ if queue_name not in seen_queues:
86
+ seen_queues.add(queue_name)
87
+ result.nodes.append(GraphNode(
88
+ id=queue_id,
89
+ kind=NodeKind.QUEUE,
90
+ label=f"ibmmq:queue:{queue_name}",
91
+ properties={"broker": "ibm_mq", "queue": queue_name},
92
+ ))
93
+ return queue_id
94
+
95
+ def _ensure_topic_node(topic_name: str) -> str:
96
+ topic_id = f"ibmmq:topic:{topic_name}"
97
+ if topic_name not in seen_topics:
98
+ seen_topics.add(topic_name)
99
+ result.nodes.append(GraphNode(
100
+ id=topic_id,
101
+ kind=NodeKind.TOPIC,
102
+ label=f"ibmmq:topic:{topic_name}",
103
+ properties={"broker": "ibm_mq", "topic": topic_name},
104
+ ))
105
+ return topic_id
106
+
107
+ # Track whether we see put/get patterns for edge direction
108
+ has_put = bool(_MQ_PUT_RE.search(text))
109
+ has_get = bool(_MQ_GET_RE.search(text))
110
+
111
+ # Detect MQQueueManager instantiation
112
+ for i, line in enumerate(lines):
113
+ m = _QM_NEW_RE.search(line)
114
+ if m:
115
+ qm_name = m.group(1)
116
+ qm_id = _ensure_qm_node(qm_name)
117
+ result.edges.append(GraphEdge(
118
+ source=class_node_id,
119
+ target=qm_id,
120
+ kind=EdgeKind.CONNECTS_TO,
121
+ label=f"{class_name} connects to queue manager {qm_name}",
122
+ properties={"queue_manager": qm_name},
123
+ ))
124
+
125
+ # Detect accessQueue calls
126
+ for i, line in enumerate(lines):
127
+ m = _ACCESS_QUEUE_RE.search(line)
128
+ if m:
129
+ queue_name = m.group(1)
130
+ queue_id = _ensure_queue_node(queue_name)
131
+ if has_put:
132
+ result.edges.append(GraphEdge(
133
+ source=class_node_id,
134
+ target=queue_id,
135
+ kind=EdgeKind.SENDS_TO,
136
+ label=f"{class_name} sends to {queue_name}",
137
+ properties={"queue": queue_name},
138
+ ))
139
+ if has_get:
140
+ result.edges.append(GraphEdge(
141
+ source=class_node_id,
142
+ target=queue_id,
143
+ kind=EdgeKind.RECEIVES_FROM,
144
+ label=f"{class_name} receives from {queue_name}",
145
+ properties={"queue": queue_name},
146
+ ))
147
+ if not has_put and not has_get:
148
+ result.edges.append(GraphEdge(
149
+ source=class_node_id,
150
+ target=queue_id,
151
+ kind=EdgeKind.CONNECTS_TO,
152
+ label=f"{class_name} accesses {queue_name}",
153
+ properties={"queue": queue_name},
154
+ ))
155
+
156
+ # Detect JMS-style createQueue / createTopic
157
+ for i, line in enumerate(lines):
158
+ m = _JMS_CREATE_QUEUE_RE.search(line)
159
+ if m:
160
+ queue_name = m.group(1)
161
+ _ensure_queue_node(queue_name)
162
+
163
+ m = _JMS_CREATE_TOPIC_RE.search(line)
164
+ if m:
165
+ topic_name = m.group(1)
166
+ _ensure_topic_node(topic_name)
167
+
168
+ # If we found MQTopic declarations but no explicit topic names, create a
169
+ # generic node to show MQ topic usage
170
+ if _MQ_TOPIC_DECL_RE.search(text) and not seen_topics:
171
+ result.nodes.append(GraphNode(
172
+ id=f"ibmmq:topic:__unknown__",
173
+ kind=NodeKind.TOPIC,
174
+ label="ibmmq:topic:unknown",
175
+ properties={"broker": "ibm_mq"},
176
+ ))
177
+
178
+ return result