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,118 @@
1
+ """PowerShell script detector for functions, modules, parameters, and dot-sourcing."""
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
+ _FUNC_RE = re.compile(r'function\s+([\w-]+)\s*(?:\([^)]*\))?\s*\{', re.IGNORECASE)
18
+ _IMPORT_RE = re.compile(r'Import-Module\s+(\S+)', re.IGNORECASE)
19
+ _DOT_SOURCE_RE = re.compile(r'\.\s+["\']?(\S+\.ps(?:1|m1))["\']?')
20
+ _PARAM_RE = re.compile(r'\[Parameter[^]]*\]\s*\[(\w+)\]\s*\$(\w+)')
21
+ _CMDLET_BINDING_RE = re.compile(r'\[CmdletBinding\(\)\]', re.IGNORECASE)
22
+
23
+
24
+ class PowerShellDetector:
25
+ """Detects PowerShell script structures: functions, modules, parameters, and dot-sourcing."""
26
+
27
+ name: str = "powershell"
28
+ supported_languages: tuple[str, ...] = ("powershell",)
29
+
30
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
31
+ result = DetectorResult()
32
+ text = decode_text(ctx)
33
+ lines = text.split("\n")
34
+
35
+ # Track which functions have CmdletBinding for advanced function marking
36
+ # We do a two-pass approach: first find functions, then check for CmdletBinding nearby
37
+ func_positions: list[tuple[int, str]] = []
38
+
39
+ # Detect function definitions
40
+ for i, line in enumerate(lines):
41
+ m = _FUNC_RE.search(line)
42
+ if not m:
43
+ continue
44
+ func_name = m.group(1)
45
+ func_positions.append((i, func_name))
46
+
47
+ # Check if [CmdletBinding()] appears within the next few lines
48
+ is_advanced = False
49
+ for j in range(i + 1, min(i + 5, len(lines))):
50
+ if _CMDLET_BINDING_RE.search(lines[j]):
51
+ is_advanced = True
52
+ break
53
+
54
+ props: dict = {}
55
+ if is_advanced:
56
+ props["advanced_function"] = True
57
+
58
+ result.nodes.append(GraphNode(
59
+ id=f"{ctx.file_path}:{func_name}",
60
+ kind=NodeKind.METHOD,
61
+ label=func_name,
62
+ fqn=func_name,
63
+ module=ctx.module_name,
64
+ location=SourceLocation(
65
+ file_path=ctx.file_path,
66
+ line_start=i + 1,
67
+ ),
68
+ properties=props,
69
+ ))
70
+
71
+ # Detect Import-Module statements
72
+ for i, line in enumerate(lines):
73
+ m = _IMPORT_RE.search(line)
74
+ if not m:
75
+ continue
76
+ module_name = m.group(1)
77
+ result.edges.append(GraphEdge(
78
+ source=ctx.file_path,
79
+ target=module_name,
80
+ kind=EdgeKind.IMPORTS,
81
+ label=f"{ctx.file_path} imports module {module_name}",
82
+ ))
83
+
84
+ # Detect dot-sourcing
85
+ for i, line in enumerate(lines):
86
+ m = _DOT_SOURCE_RE.search(line)
87
+ if not m:
88
+ continue
89
+ sourced_file = m.group(1)
90
+ result.edges.append(GraphEdge(
91
+ source=ctx.file_path,
92
+ target=sourced_file,
93
+ kind=EdgeKind.IMPORTS,
94
+ label=f"{ctx.file_path} dot-sources {sourced_file}",
95
+ ))
96
+
97
+ # Detect typed parameters with [Parameter] attribute
98
+ for i, line in enumerate(lines):
99
+ m = _PARAM_RE.search(line)
100
+ if not m:
101
+ continue
102
+ param_type = m.group(1)
103
+ param_name = m.group(2)
104
+
105
+ result.nodes.append(GraphNode(
106
+ id=f"{ctx.file_path}:param:{param_name}",
107
+ kind=NodeKind.CONFIG_DEFINITION,
108
+ label=f"${param_name}: {param_type}",
109
+ fqn=param_name,
110
+ module=ctx.module_name,
111
+ location=SourceLocation(
112
+ file_path=ctx.file_path,
113
+ line_start=i + 1,
114
+ ),
115
+ properties={"param_type": param_type},
116
+ ))
117
+
118
+ return result
File without changes
@@ -0,0 +1,55 @@
1
+ """Express.js route detector."""
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 EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class ExpressRouteDetector:
13
+ """Detects Express.js route definitions (app.get, router.post, etc.)."""
14
+
15
+ name: str = "typescript.express_routes"
16
+ supported_languages: tuple[str, ...] = ("typescript", "javascript")
17
+
18
+ # app.get('/path', handler) or router.post('/path', middleware, handler)
19
+ _ROUTE_PATTERN = re.compile(
20
+ r"(\w+)\.(get|post|put|delete|patch|options|head|all)\(\s*['\"`]([^'\"`]+)['\"`]"
21
+ )
22
+
23
+ # Router prefix: app.use('/api/v1', router) or app.use('/users', userRouter)
24
+ _USE_PATTERN = re.compile(
25
+ r"(\w+)\.use\(\s*['\"`]([^'\"`]+)['\"`]\s*,\s*(\w+)"
26
+ )
27
+
28
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
29
+ result = DetectorResult()
30
+ text = decode_text(ctx)
31
+
32
+ for match in self._ROUTE_PATTERN.finditer(text):
33
+ router_name = match.group(1)
34
+ method = match.group(2).upper()
35
+ path = match.group(3)
36
+ line = text[:match.start()].count("\n") + 1
37
+
38
+ node_id = f"endpoint:{ctx.module_name or ''}:{method}:{path}"
39
+ result.nodes.append(GraphNode(
40
+ id=node_id,
41
+ kind=NodeKind.ENDPOINT,
42
+ label=f"{method} {path}",
43
+ fqn=f"{ctx.file_path}::{method}:{path}",
44
+ module=ctx.module_name,
45
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
46
+ properties={
47
+ "protocol": "REST",
48
+ "http_method": method,
49
+ "path_pattern": path,
50
+ "framework": "express",
51
+ "router": router_name,
52
+ },
53
+ ))
54
+
55
+ return result
@@ -0,0 +1,156 @@
1
+ """Fastify route detector."""
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 EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class FastifyRouteDetector:
13
+ """Detects Fastify route definitions, plugins, and hooks."""
14
+
15
+ name: str = "fastify_routes"
16
+ supported_languages: tuple[str, ...] = ("typescript", "javascript")
17
+
18
+ # fastify.get('/path', handler) or fastify.post('/path', opts, handler)
19
+ _SHORTHAND_PATTERN = re.compile(
20
+ r"(\w+)\.(get|post|put|delete|patch)\(\s*['\"`]([^'\"`]+)['\"`]"
21
+ )
22
+
23
+ # fastify.route({ method: 'GET', url: '/path' })
24
+ _ROUTE_PATTERN = re.compile(
25
+ r"(\w+)\.route\(\s*\{[\s\S]*?"
26
+ r"method\s*:\s*['\"`](\w+)['\"`]"
27
+ r"[\s\S]*?"
28
+ r"url\s*:\s*['\"`]([^'\"`]+)['\"`]",
29
+ )
30
+
31
+ # fastify.register(plugin) or fastify.register(import('./plugin'))
32
+ _REGISTER_PATTERN = re.compile(
33
+ r"(\w+)\.register\(\s*(\w+|import\([^)]+\))"
34
+ )
35
+
36
+ # fastify.addHook('onRequest', handler)
37
+ _HOOK_PATTERN = re.compile(
38
+ r"(\w+)\.addHook\(\s*['\"`](\w+)['\"`]"
39
+ )
40
+
41
+ # schema: { body: SomeType } inside route options
42
+ _SCHEMA_PATTERN = re.compile(
43
+ r"schema\s*:\s*\{([^}]+)\}"
44
+ )
45
+
46
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
47
+ result = DetectorResult()
48
+ text = decode_text(ctx)
49
+
50
+ # Detect shorthand routes: fastify.get(), .post(), etc.
51
+ for match in self._SHORTHAND_PATTERN.finditer(text):
52
+ method = match.group(2).upper()
53
+ path = match.group(3)
54
+ line = text[: match.start()].count("\n") + 1
55
+
56
+ node_id = f"fastify:{ctx.file_path}:{method}:{path}:{line}"
57
+ result.nodes.append(
58
+ GraphNode(
59
+ id=node_id,
60
+ kind=NodeKind.ENDPOINT,
61
+ label=f"{method} {path}",
62
+ fqn=f"{ctx.file_path}::{method}:{path}",
63
+ module=ctx.module_name,
64
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
65
+ properties={
66
+ "protocol": "REST",
67
+ "http_method": method,
68
+ "path_pattern": path,
69
+ "framework": "fastify",
70
+ },
71
+ )
72
+ )
73
+
74
+ # Detect fastify.route({ method, url })
75
+ for match in self._ROUTE_PATTERN.finditer(text):
76
+ method = match.group(2).upper()
77
+ path = match.group(3)
78
+ line = text[: match.start()].count("\n") + 1
79
+
80
+ node_id = f"fastify:{ctx.file_path}:{method}:{path}:{line}"
81
+ # Avoid duplicates from shorthand detection
82
+ if any(n.id == node_id for n in result.nodes):
83
+ continue
84
+
85
+ # Check for schema in the route call block (search forward from match start
86
+ # until we find the closing ");")
87
+ route_start = match.start()
88
+ route_block = text[route_start:text.find(");", route_start) + 2]
89
+ schema_props = {}
90
+ schema_match = self._SCHEMA_PATTERN.search(route_block)
91
+ if schema_match:
92
+ schema_props["schema"] = schema_match.group(1).strip()
93
+
94
+ props = {
95
+ "protocol": "REST",
96
+ "http_method": method,
97
+ "path_pattern": path,
98
+ "framework": "fastify",
99
+ }
100
+ props.update(schema_props)
101
+
102
+ result.nodes.append(
103
+ GraphNode(
104
+ id=node_id,
105
+ kind=NodeKind.ENDPOINT,
106
+ label=f"{method} {path}",
107
+ fqn=f"{ctx.file_path}::{method}:{path}",
108
+ module=ctx.module_name,
109
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
110
+ properties=props,
111
+ )
112
+ )
113
+
114
+ # Detect fastify.register(plugin)
115
+ for match in self._REGISTER_PATTERN.finditer(text):
116
+ plugin_ref = match.group(2)
117
+ line = text[: match.start()].count("\n") + 1
118
+
119
+ edge_id_source = f"fastify:{ctx.file_path}:server:{line}"
120
+ edge_id_target = f"fastify:{ctx.file_path}:plugin:{plugin_ref}:{line}"
121
+
122
+ result.edges.append(
123
+ GraphEdge(
124
+ source=edge_id_source,
125
+ target=edge_id_target,
126
+ kind=EdgeKind.IMPORTS,
127
+ label=f"register {plugin_ref}",
128
+ properties={
129
+ "framework": "fastify",
130
+ "plugin": plugin_ref,
131
+ },
132
+ )
133
+ )
134
+
135
+ # Detect fastify.addHook('hookName', handler)
136
+ for match in self._HOOK_PATTERN.finditer(text):
137
+ hook_name = match.group(2)
138
+ line = text[: match.start()].count("\n") + 1
139
+
140
+ node_id = f"fastify:{ctx.file_path}:hook:{hook_name}:{line}"
141
+ result.nodes.append(
142
+ GraphNode(
143
+ id=node_id,
144
+ kind=NodeKind.MIDDLEWARE,
145
+ label=f"hook:{hook_name}",
146
+ fqn=f"{ctx.file_path}::hook:{hook_name}",
147
+ module=ctx.module_name,
148
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
149
+ properties={
150
+ "framework": "fastify",
151
+ "hook_name": hook_name,
152
+ },
153
+ )
154
+ )
155
+
156
+ return result
@@ -0,0 +1,100 @@
1
+ """GraphQL resolver detector for TypeScript/JavaScript."""
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 EdgeKind, GraphEdge, GraphNode, NodeKind, SourceLocation
10
+
11
+
12
+ class GraphQLResolverDetector:
13
+ """Detects GraphQL resolvers and type definitions in TypeScript/JavaScript."""
14
+
15
+ name: str = "typescript.graphql_resolvers"
16
+ supported_languages: tuple[str, ...] = ("typescript", "javascript")
17
+
18
+ # NestJS GraphQL: @Query(), @Mutation(), @Resolver()
19
+ _NESTJS_RESOLVER = re.compile(
20
+ r"@Resolver\(\s*(?:of\s*=>\s*)?(\w+)?\s*\)\s*\n\s*(?:export\s+)?class\s+(\w+)"
21
+ )
22
+ _NESTJS_QUERY = re.compile(
23
+ r"@(Query|Mutation|Subscription)\(.*?\)\s*\n\s*(?:async\s+)?(\w+)"
24
+ )
25
+
26
+ # Apollo/generic: type Query { ... } or typeDefs with gql template
27
+ _TYPEDEF_PATTERN = re.compile(
28
+ r"type\s+(Query|Mutation|Subscription)\s*\{([^}]+)\}"
29
+ )
30
+
31
+ # resolvers object: Query: { users: ... }
32
+ _RESOLVER_FIELD = re.compile(
33
+ r"(Query|Mutation|Subscription)\s*:\s*\{([^}]+)\}"
34
+ )
35
+
36
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
37
+ result = DetectorResult()
38
+ text = decode_text(ctx)
39
+
40
+ # NestJS-style resolvers
41
+ for match in self._NESTJS_RESOLVER.finditer(text):
42
+ entity_type = match.group(1)
43
+ class_name = match.group(2)
44
+ line = text[:match.start()].count("\n") + 1
45
+
46
+ class_id = f"class:{ctx.file_path}::{class_name}"
47
+ result.nodes.append(GraphNode(
48
+ id=class_id,
49
+ kind=NodeKind.CLASS,
50
+ label=class_name,
51
+ fqn=f"{ctx.file_path}::{class_name}",
52
+ module=ctx.module_name,
53
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
54
+ annotations=["@Resolver"],
55
+ properties={"framework": "nestjs-graphql", "entity_type": entity_type},
56
+ ))
57
+
58
+ for match in self._NESTJS_QUERY.finditer(text):
59
+ op_type = match.group(1)
60
+ func_name = match.group(2)
61
+ line = text[:match.start()].count("\n") + 1
62
+
63
+ node_id = f"endpoint:{ctx.module_name or ''}:graphql:{op_type}:{func_name}"
64
+ result.nodes.append(GraphNode(
65
+ id=node_id,
66
+ kind=NodeKind.ENDPOINT,
67
+ label=f"GraphQL {op_type}: {func_name}",
68
+ fqn=f"{ctx.file_path}::{func_name}",
69
+ module=ctx.module_name,
70
+ location=SourceLocation(file_path=ctx.file_path, line_start=line),
71
+ properties={
72
+ "protocol": "GraphQL",
73
+ "operation_type": op_type.lower(),
74
+ "field_name": func_name,
75
+ },
76
+ ))
77
+
78
+ # Schema-defined resolvers
79
+ for match in self._TYPEDEF_PATTERN.finditer(text):
80
+ op_type = match.group(1)
81
+ fields_block = match.group(2)
82
+ base_line = text[:match.start()].count("\n") + 1
83
+
84
+ for field_match in re.finditer(r"(\w+)\s*(?:\([^)]*\))?\s*:", fields_block):
85
+ field_name = field_match.group(1)
86
+ node_id = f"endpoint:{ctx.module_name or ''}:graphql:{op_type}:{field_name}"
87
+ result.nodes.append(GraphNode(
88
+ id=node_id,
89
+ kind=NodeKind.ENDPOINT,
90
+ label=f"GraphQL {op_type}: {field_name}",
91
+ module=ctx.module_name,
92
+ location=SourceLocation(file_path=ctx.file_path, line_start=base_line),
93
+ properties={
94
+ "protocol": "GraphQL",
95
+ "operation_type": op_type.lower(),
96
+ "field_name": field_name,
97
+ },
98
+ ))
99
+
100
+ return result
@@ -0,0 +1,164 @@
1
+ """KafkaJS detector for TypeScript/JavaScript source files.
2
+
3
+ Detects usage of the KafkaJS library in Node.js applications.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import re
9
+
10
+ from osscodeiq.detectors.base import DetectorContext, DetectorResult
11
+ from osscodeiq.detectors.utils import decode_text
12
+ from osscodeiq.models.graph import (
13
+ EdgeKind,
14
+ GraphEdge,
15
+ GraphNode,
16
+ NodeKind,
17
+ SourceLocation,
18
+ )
19
+
20
+ # Connection pattern: new Kafka({
21
+ _KAFKA_NEW_RE = re.compile(r"new\s+Kafka\s*\(\s*\{", re.MULTILINE)
22
+
23
+ # Producer pattern: kafka.producer()
24
+ _PRODUCER_RE = re.compile(r"\.producer\s*\(\s*\)", re.MULTILINE)
25
+
26
+ # Producer send: producer.send({ topic: 'name' })
27
+ _PRODUCER_SEND_RE = re.compile(
28
+ r"\.send\s*\(\s*\{\s*topic\s*:\s*['\"]([^'\"]+)['\"]", re.MULTILINE
29
+ )
30
+
31
+ # Consumer pattern: kafka.consumer({ groupId: 'group' })
32
+ _CONSUMER_RE = re.compile(
33
+ r"\.consumer\s*\(\s*\{\s*groupId\s*:\s*['\"]([^'\"]+)['\"]", re.MULTILINE
34
+ )
35
+
36
+ # Consumer subscribe: consumer.subscribe({ topic: 'name' })
37
+ _SUBSCRIBE_RE = re.compile(
38
+ r"\.subscribe\s*\(\s*\{\s*topic\s*:\s*['\"]([^'\"]+)['\"]", re.MULTILINE
39
+ )
40
+
41
+ # Consumer run: consumer.run({ eachMessage:
42
+ _RUN_EACH_RE = re.compile(
43
+ r"\.run\s*\(\s*\{\s*eachMessage\s*:", re.MULTILINE
44
+ )
45
+
46
+
47
+ class KafkaJSDetector:
48
+ """Detects KafkaJS usage in TypeScript/JavaScript applications."""
49
+
50
+ name: str = "kafka_js"
51
+ supported_languages: tuple[str, ...] = ("typescript", "javascript")
52
+
53
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
54
+ result = DetectorResult()
55
+ text = decode_text(ctx)
56
+ lines = text.split("\n")
57
+ fp = ctx.file_path
58
+
59
+ # Quick bail-out
60
+ if "Kafka" not in text and "kafka" not in text:
61
+ return result
62
+
63
+ seen_topics: set[str] = set()
64
+ file_node_id = f"kafka_js:{fp}"
65
+
66
+ def _ensure_topic(topic: str, line: int) -> str:
67
+ topic_id = f"kafka_js:{fp}:topic:{topic}"
68
+ if topic not in seen_topics:
69
+ seen_topics.add(topic)
70
+ result.nodes.append(GraphNode(
71
+ id=topic_id,
72
+ kind=NodeKind.TOPIC,
73
+ label=f"kafka:{topic}",
74
+ module=ctx.module_name,
75
+ location=SourceLocation(file_path=fp, line_start=line),
76
+ properties={"broker": "kafka", "topic": topic},
77
+ ))
78
+ return topic_id
79
+
80
+ # Detect new Kafka({ -> DATABASE_CONNECTION node
81
+ for i, line in enumerate(lines):
82
+ lineno = i + 1
83
+ if _KAFKA_NEW_RE.search(line):
84
+ result.nodes.append(GraphNode(
85
+ id=f"kafka_js:{fp}:connection:{lineno}",
86
+ kind=NodeKind.DATABASE_CONNECTION,
87
+ label="KafkaJS connection",
88
+ module=ctx.module_name,
89
+ location=SourceLocation(file_path=fp, line_start=lineno),
90
+ properties={"broker": "kafka", "library": "kafkajs"},
91
+ ))
92
+
93
+ # Detect kafka.producer() -> properties on file node
94
+ for i, line in enumerate(lines):
95
+ lineno = i + 1
96
+ if _PRODUCER_RE.search(line):
97
+ result.nodes.append(GraphNode(
98
+ id=f"kafka_js:{fp}:producer:{lineno}",
99
+ kind=NodeKind.TOPIC,
100
+ label="kafka:producer",
101
+ module=ctx.module_name,
102
+ location=SourceLocation(file_path=fp, line_start=lineno),
103
+ properties={"role": "producer"},
104
+ ))
105
+
106
+ # Detect producer.send({ topic: 'name' }) -> TOPIC + PRODUCES edge
107
+ for i, line in enumerate(lines):
108
+ lineno = i + 1
109
+ m = _PRODUCER_SEND_RE.search(line)
110
+ if m:
111
+ topic = m.group(1)
112
+ topic_id = _ensure_topic(topic, lineno)
113
+ result.edges.append(GraphEdge(
114
+ source=file_node_id,
115
+ target=topic_id,
116
+ kind=EdgeKind.PRODUCES,
117
+ label=f"produces to {topic}",
118
+ properties={"topic": topic},
119
+ ))
120
+
121
+ # Detect kafka.consumer({ groupId: 'group' }) -> properties
122
+ for i, line in enumerate(lines):
123
+ lineno = i + 1
124
+ m = _CONSUMER_RE.search(line)
125
+ if m:
126
+ group_id = m.group(1)
127
+ result.nodes.append(GraphNode(
128
+ id=f"kafka_js:{fp}:consumer:{lineno}",
129
+ kind=NodeKind.TOPIC,
130
+ label=f"kafka:consumer:{group_id}",
131
+ module=ctx.module_name,
132
+ location=SourceLocation(file_path=fp, line_start=lineno),
133
+ properties={"role": "consumer", "group_id": group_id},
134
+ ))
135
+
136
+ # Detect consumer.subscribe({ topic: 'name' }) -> TOPIC + CONSUMES edge
137
+ for i, line in enumerate(lines):
138
+ lineno = i + 1
139
+ m = _SUBSCRIBE_RE.search(line)
140
+ if m:
141
+ topic = m.group(1)
142
+ topic_id = _ensure_topic(topic, lineno)
143
+ result.edges.append(GraphEdge(
144
+ source=file_node_id,
145
+ target=topic_id,
146
+ kind=EdgeKind.CONSUMES,
147
+ label=f"consumes from {topic}",
148
+ properties={"topic": topic},
149
+ ))
150
+
151
+ # Detect consumer.run({ eachMessage: }) -> EVENT node
152
+ for i, line in enumerate(lines):
153
+ lineno = i + 1
154
+ if _RUN_EACH_RE.search(line):
155
+ result.nodes.append(GraphNode(
156
+ id=f"kafka_js:{fp}:event:{lineno}",
157
+ kind=NodeKind.EVENT,
158
+ label="kafka:eachMessage",
159
+ module=ctx.module_name,
160
+ location=SourceLocation(file_path=fp, line_start=lineno),
161
+ properties={"handler": "eachMessage"},
162
+ ))
163
+
164
+ return result