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