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,188 @@
1
+ """Regex-based Terraform/HCL detector for infrastructure resource definitions."""
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, find_line_number
10
+ from osscodeiq.models.graph import (
11
+ EdgeKind,
12
+ GraphEdge,
13
+ GraphNode,
14
+ NodeKind,
15
+ SourceLocation,
16
+ )
17
+
18
+ _RESOURCE_RE = re.compile(r'resource\s+"([^"]+)"\s+"([^"]+)"')
19
+ _DATA_RE = re.compile(r'data\s+"([^"]+)"\s+"([^"]+)"')
20
+ _MODULE_RE = re.compile(r'module\s+"([^"]+)"')
21
+ _VARIABLE_RE = re.compile(r'variable\s+"([^"]+)"')
22
+ _OUTPUT_RE = re.compile(r'output\s+"([^"]+)"')
23
+ _PROVIDER_RE = re.compile(r'provider\s+"([^"]+)"')
24
+ _SOURCE_RE = re.compile(r'source\s*=\s*"([^"]+)"')
25
+
26
+
27
+
28
+ def _extract_provider(resource_type: str) -> str | None:
29
+ """Extract provider name from a resource type like 'azurerm_storage_account'."""
30
+ parts = resource_type.split("_", 1)
31
+ return parts[0] if len(parts) > 1 else None
32
+
33
+
34
+ def _find_source_in_block(text: str, block_start: int) -> str | None:
35
+ """Find a source = "..." attribute within a block starting at block_start."""
36
+ # Scan forward from block_start to find the opening brace and then source attr
37
+ brace_pos = text.find("{", block_start)
38
+ if brace_pos == -1:
39
+ return None
40
+ # Look for source within a reasonable range (next ~500 chars)
41
+ block_snippet = text[brace_pos:brace_pos + 500]
42
+ m = _SOURCE_RE.search(block_snippet)
43
+ return m.group(1) if m else None
44
+
45
+
46
+ class TerraformDetector:
47
+ """Detects Terraform/HCL infrastructure resources, modules, variables, and outputs."""
48
+
49
+ name: str = "terraform"
50
+ supported_languages: tuple[str, ...] = ("terraform",)
51
+
52
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
53
+ result = DetectorResult()
54
+ text = decode_text(ctx)
55
+
56
+ # Resource declarations
57
+ for m in _RESOURCE_RE.finditer(text):
58
+ resource_type = m.group(1)
59
+ resource_name = m.group(2)
60
+ provider = _extract_provider(resource_type)
61
+
62
+ node_id = f"tf:resource:{resource_type}:{resource_name}"
63
+ properties: dict[str, Any] = {"resource_type": resource_type}
64
+ if provider:
65
+ properties["provider"] = provider
66
+
67
+ result.nodes.append(GraphNode(
68
+ id=node_id,
69
+ kind=NodeKind.INFRA_RESOURCE,
70
+ label=f"{resource_type}.{resource_name}",
71
+ fqn=f"{resource_type}.{resource_name}",
72
+ module=ctx.module_name,
73
+ location=SourceLocation(
74
+ file_path=ctx.file_path,
75
+ line_start=find_line_number(text, m.start()),
76
+ ),
77
+ properties=properties,
78
+ ))
79
+
80
+ # Data source declarations
81
+ for m in _DATA_RE.finditer(text):
82
+ data_type = m.group(1)
83
+ data_name = m.group(2)
84
+ provider = _extract_provider(data_type)
85
+
86
+ node_id = f"tf:data:{data_type}:{data_name}"
87
+ properties = {"resource_type": data_type, "data_source": True}
88
+ if provider:
89
+ properties["provider"] = provider
90
+
91
+ result.nodes.append(GraphNode(
92
+ id=node_id,
93
+ kind=NodeKind.INFRA_RESOURCE,
94
+ label=f"data.{data_type}.{data_name}",
95
+ fqn=f"data.{data_type}.{data_name}",
96
+ module=ctx.module_name,
97
+ location=SourceLocation(
98
+ file_path=ctx.file_path,
99
+ line_start=find_line_number(text, m.start()),
100
+ ),
101
+ properties=properties,
102
+ ))
103
+
104
+ # Module declarations
105
+ for m in _MODULE_RE.finditer(text):
106
+ module_name = m.group(1)
107
+ source = _find_source_in_block(text, m.start())
108
+
109
+ node_id = f"tf:module:{module_name}"
110
+ properties: dict[str, Any] = {}
111
+ if source:
112
+ properties["source"] = source
113
+
114
+ result.nodes.append(GraphNode(
115
+ id=node_id,
116
+ kind=NodeKind.MODULE,
117
+ label=f"module.{module_name}",
118
+ fqn=f"module.{module_name}",
119
+ module=ctx.module_name,
120
+ location=SourceLocation(
121
+ file_path=ctx.file_path,
122
+ line_start=find_line_number(text, m.start()),
123
+ ),
124
+ properties=properties,
125
+ ))
126
+
127
+ # Edge: depends on source module
128
+ if source:
129
+ result.edges.append(GraphEdge(
130
+ source=node_id,
131
+ target=source,
132
+ kind=EdgeKind.DEPENDS_ON,
133
+ label=f"module.{module_name} depends on {source}",
134
+ properties={"module_source": source},
135
+ ))
136
+
137
+ # Variable declarations
138
+ for m in _VARIABLE_RE.finditer(text):
139
+ var_name = m.group(1)
140
+ node_id = f"tf:var:{var_name}"
141
+ result.nodes.append(GraphNode(
142
+ id=node_id,
143
+ kind=NodeKind.CONFIG_DEFINITION,
144
+ label=f"var.{var_name}",
145
+ fqn=f"var.{var_name}",
146
+ module=ctx.module_name,
147
+ location=SourceLocation(
148
+ file_path=ctx.file_path,
149
+ line_start=find_line_number(text, m.start()),
150
+ ),
151
+ properties={"config_type": "variable"},
152
+ ))
153
+
154
+ # Output declarations
155
+ for m in _OUTPUT_RE.finditer(text):
156
+ output_name = m.group(1)
157
+ node_id = f"tf:output:{output_name}"
158
+ result.nodes.append(GraphNode(
159
+ id=node_id,
160
+ kind=NodeKind.CONFIG_DEFINITION,
161
+ label=f"output.{output_name}",
162
+ fqn=f"output.{output_name}",
163
+ module=ctx.module_name,
164
+ location=SourceLocation(
165
+ file_path=ctx.file_path,
166
+ line_start=find_line_number(text, m.start()),
167
+ ),
168
+ properties={"config_type": "output"},
169
+ ))
170
+
171
+ # Provider declarations
172
+ for m in _PROVIDER_RE.finditer(text):
173
+ provider_name = m.group(1)
174
+ node_id = f"tf:provider:{provider_name}"
175
+ result.nodes.append(GraphNode(
176
+ id=node_id,
177
+ kind=NodeKind.INFRA_RESOURCE,
178
+ label=f"provider.{provider_name}",
179
+ fqn=f"provider.{provider_name}",
180
+ module=ctx.module_name,
181
+ location=SourceLocation(
182
+ file_path=ctx.file_path,
183
+ line_start=find_line_number(text, m.start()),
184
+ ),
185
+ properties={"resource_type": "provider", "provider": provider_name},
186
+ ))
187
+
188
+ return result
File without changes
@@ -0,0 +1,424 @@
1
+ """Azure Functions trigger and binding detector for Java, C#, 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
+ # Java/C# annotation patterns
19
+ _FUNCTION_NAME_RE = re.compile(r'@FunctionName\s*\(\s*"([^"]+)"')
20
+ _HTTP_TRIGGER_RE = re.compile(r'@HttpTrigger\s*\(')
21
+ _SB_QUEUE_RE = re.compile(r'@ServiceBusQueueTrigger\s*\([^)]*queueName\s*=\s*"([^"]+)"')
22
+ _SB_TOPIC_RE = re.compile(r'@ServiceBusTopicTrigger\s*\([^)]*topicName\s*=\s*"([^"]+)"')
23
+ _EH_TRIGGER_RE = re.compile(r'@EventHubTrigger\s*\([^)]*eventHubName\s*=\s*"([^"]+)"')
24
+ _TIMER_RE = re.compile(r'@TimerTrigger\s*\([^)]*schedule\s*=\s*"([^"]+)"')
25
+ _COSMOS_TRIGGER_RE = re.compile(r'@CosmosDB(?:Trigger|Input|Output)\s*\(')
26
+
27
+ # TypeScript/JavaScript v4 programming model patterns
28
+ _TS_FUNC_RE = re.compile(
29
+ r"app\.(http|serviceBusQueue|serviceBusTopic|eventHub|timer|cosmosDB)\s*\(\s*['\"]([^'\"]+)['\"]"
30
+ )
31
+
32
+ _CLASS_RE = re.compile(r"(?:public\s+)?class\s+(\w+)")
33
+
34
+
35
+ class AzureFunctionsDetector:
36
+ """Detects Azure Functions triggers and bindings in Java, C#, TypeScript, and JavaScript."""
37
+
38
+ name: str = "azure_functions"
39
+ supported_languages: tuple[str, ...] = ("java", "csharp", "typescript", "javascript")
40
+
41
+ def detect(self, ctx: DetectorContext) -> DetectorResult:
42
+ result = DetectorResult()
43
+ text = decode_text(ctx)
44
+
45
+ # Fast bail
46
+ if (
47
+ "FunctionName" not in text
48
+ and "@FunctionName" not in text
49
+ and "@HttpTrigger" not in text
50
+ and "app.http" not in text
51
+ and "app.serviceBus" not in text
52
+ and "app.eventHub" not in text
53
+ and "app.timer" not in text
54
+ and "app.cosmosDB" not in text
55
+ ):
56
+ return result
57
+
58
+ lines = text.split("\n")
59
+
60
+ # Detect Java/C# annotation-based functions
61
+ self._detect_annotation_functions(ctx, lines, text, result)
62
+
63
+ # Detect TypeScript/JavaScript v4 programming model functions
64
+ self._detect_ts_functions(ctx, lines, text, result)
65
+
66
+ return result
67
+
68
+ def _detect_annotation_functions(
69
+ self,
70
+ ctx: DetectorContext,
71
+ lines: list[str],
72
+ text: str,
73
+ result: DetectorResult,
74
+ ) -> None:
75
+ """Detect Java/C# Azure Functions via annotations."""
76
+ # Find class name for edge source
77
+ class_name: str | None = None
78
+ for line in lines:
79
+ cm = _CLASS_RE.search(line)
80
+ if cm:
81
+ class_name = cm.group(1)
82
+ break
83
+
84
+ class_node_id = f"{ctx.file_path}:{class_name}" if class_name else ctx.file_path
85
+
86
+ # Scan for @FunctionName annotations and their associated triggers
87
+ for i, line in enumerate(lines):
88
+ fn_match = _FUNCTION_NAME_RE.search(line)
89
+ if not fn_match:
90
+ continue
91
+
92
+ func_name = fn_match.group(1)
93
+ func_node_id = f"azure:func:{func_name}"
94
+
95
+ # Look ahead for trigger annotations (within the next several lines)
96
+ trigger_type = "unknown"
97
+ properties: dict[str, Any] = {"trigger_type": trigger_type}
98
+ context_lines = "\n".join(lines[i : min(i + 15, len(lines))])
99
+
100
+ if _HTTP_TRIGGER_RE.search(context_lines):
101
+ trigger_type = "http"
102
+ properties["trigger_type"] = trigger_type
103
+
104
+ func_node = GraphNode(
105
+ id=func_node_id,
106
+ kind=NodeKind.AZURE_FUNCTION,
107
+ label=func_name,
108
+ fqn=f"{class_name}.{func_name}" if class_name else func_name,
109
+ module=ctx.module_name,
110
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
111
+ annotations=["@FunctionName", "@HttpTrigger"],
112
+ properties=properties,
113
+ )
114
+ result.nodes.append(func_node)
115
+
116
+ # Also create an ENDPOINT node
117
+ endpoint_id = f"azure:func:{func_name}:endpoint"
118
+ endpoint_node = GraphNode(
119
+ id=endpoint_id,
120
+ kind=NodeKind.ENDPOINT,
121
+ label=f"HTTP {func_name}",
122
+ fqn=f"{class_name}.{func_name}" if class_name else func_name,
123
+ module=ctx.module_name,
124
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
125
+ properties={"http_trigger": True, "function_name": func_name},
126
+ )
127
+ result.nodes.append(endpoint_node)
128
+
129
+ result.edges.append(GraphEdge(
130
+ source=func_node_id,
131
+ target=endpoint_id,
132
+ kind=EdgeKind.EXPOSES,
133
+ label=f"{func_name} exposes HTTP endpoint",
134
+ ))
135
+ continue
136
+
137
+ sb_queue_match = _SB_QUEUE_RE.search(context_lines)
138
+ if sb_queue_match:
139
+ queue_name = sb_queue_match.group(1)
140
+ trigger_type = "serviceBusQueue"
141
+ properties["trigger_type"] = trigger_type
142
+ properties["queue_name"] = queue_name
143
+
144
+ func_node = GraphNode(
145
+ id=func_node_id,
146
+ kind=NodeKind.AZURE_FUNCTION,
147
+ label=func_name,
148
+ fqn=f"{class_name}.{func_name}" if class_name else func_name,
149
+ module=ctx.module_name,
150
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
151
+ annotations=["@FunctionName", "@ServiceBusQueueTrigger"],
152
+ properties=properties,
153
+ )
154
+ result.nodes.append(func_node)
155
+
156
+ queue_node_id = f"azure:servicebus:queue:{queue_name}"
157
+ result.nodes.append(GraphNode(
158
+ id=queue_node_id,
159
+ kind=NodeKind.QUEUE,
160
+ label=f"servicebus:{queue_name}",
161
+ properties={"broker": "azure_servicebus", "queue": queue_name},
162
+ ))
163
+
164
+ result.edges.append(GraphEdge(
165
+ source=queue_node_id,
166
+ target=func_node_id,
167
+ kind=EdgeKind.TRIGGERS,
168
+ label=f"queue {queue_name} triggers {func_name}",
169
+ ))
170
+ continue
171
+
172
+ sb_topic_match = _SB_TOPIC_RE.search(context_lines)
173
+ if sb_topic_match:
174
+ topic_name = sb_topic_match.group(1)
175
+ trigger_type = "serviceBusTopic"
176
+ properties["trigger_type"] = trigger_type
177
+ properties["topic_name"] = topic_name
178
+
179
+ func_node = GraphNode(
180
+ id=func_node_id,
181
+ kind=NodeKind.AZURE_FUNCTION,
182
+ label=func_name,
183
+ fqn=f"{class_name}.{func_name}" if class_name else func_name,
184
+ module=ctx.module_name,
185
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
186
+ annotations=["@FunctionName", "@ServiceBusTopicTrigger"],
187
+ properties=properties,
188
+ )
189
+ result.nodes.append(func_node)
190
+
191
+ topic_node_id = f"azure:servicebus:topic:{topic_name}"
192
+ result.nodes.append(GraphNode(
193
+ id=topic_node_id,
194
+ kind=NodeKind.TOPIC,
195
+ label=f"servicebus:{topic_name}",
196
+ properties={"broker": "azure_servicebus", "topic": topic_name},
197
+ ))
198
+
199
+ result.edges.append(GraphEdge(
200
+ source=topic_node_id,
201
+ target=func_node_id,
202
+ kind=EdgeKind.TRIGGERS,
203
+ label=f"topic {topic_name} triggers {func_name}",
204
+ ))
205
+ continue
206
+
207
+ eh_match = _EH_TRIGGER_RE.search(context_lines)
208
+ if eh_match:
209
+ hub_name = eh_match.group(1)
210
+ trigger_type = "eventHub"
211
+ properties["trigger_type"] = trigger_type
212
+ properties["event_hub_name"] = hub_name
213
+
214
+ func_node = GraphNode(
215
+ id=func_node_id,
216
+ kind=NodeKind.AZURE_FUNCTION,
217
+ label=func_name,
218
+ fqn=f"{class_name}.{func_name}" if class_name else func_name,
219
+ module=ctx.module_name,
220
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
221
+ annotations=["@FunctionName", "@EventHubTrigger"],
222
+ properties=properties,
223
+ )
224
+ result.nodes.append(func_node)
225
+
226
+ topic_node_id = f"azure:eventhub:{hub_name}"
227
+ result.nodes.append(GraphNode(
228
+ id=topic_node_id,
229
+ kind=NodeKind.TOPIC,
230
+ label=f"eventhub:{hub_name}",
231
+ properties={"broker": "azure_eventhub", "event_hub": hub_name},
232
+ ))
233
+
234
+ result.edges.append(GraphEdge(
235
+ source=topic_node_id,
236
+ target=func_node_id,
237
+ kind=EdgeKind.TRIGGERS,
238
+ label=f"event hub {hub_name} triggers {func_name}",
239
+ ))
240
+ continue
241
+
242
+ timer_match = _TIMER_RE.search(context_lines)
243
+ if timer_match:
244
+ schedule = timer_match.group(1)
245
+ trigger_type = "timer"
246
+ properties["trigger_type"] = trigger_type
247
+ properties["schedule"] = schedule
248
+
249
+ func_node = GraphNode(
250
+ id=func_node_id,
251
+ kind=NodeKind.AZURE_FUNCTION,
252
+ label=func_name,
253
+ fqn=f"{class_name}.{func_name}" if class_name else func_name,
254
+ module=ctx.module_name,
255
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
256
+ annotations=["@FunctionName", "@TimerTrigger"],
257
+ properties=properties,
258
+ )
259
+ result.nodes.append(func_node)
260
+ continue
261
+
262
+ cosmos_match = _COSMOS_TRIGGER_RE.search(context_lines)
263
+ if cosmos_match:
264
+ trigger_type = "cosmosDB"
265
+ properties["trigger_type"] = trigger_type
266
+
267
+ func_node = GraphNode(
268
+ id=func_node_id,
269
+ kind=NodeKind.AZURE_FUNCTION,
270
+ label=func_name,
271
+ fqn=f"{class_name}.{func_name}" if class_name else func_name,
272
+ module=ctx.module_name,
273
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
274
+ annotations=["@FunctionName", "@CosmosDBTrigger"],
275
+ properties=properties,
276
+ )
277
+ result.nodes.append(func_node)
278
+
279
+ resource_node_id = f"azure:cosmos:func:{func_name}"
280
+ result.nodes.append(GraphNode(
281
+ id=resource_node_id,
282
+ kind=NodeKind.AZURE_RESOURCE,
283
+ label=f"cosmosdb:{func_name}",
284
+ properties={"cosmos_type": "trigger", "function_name": func_name},
285
+ ))
286
+
287
+ result.edges.append(GraphEdge(
288
+ source=resource_node_id,
289
+ target=func_node_id,
290
+ kind=EdgeKind.TRIGGERS,
291
+ label=f"CosmosDB triggers {func_name}",
292
+ ))
293
+ continue
294
+
295
+ # No specific trigger found, still record the function
296
+ func_node = GraphNode(
297
+ id=func_node_id,
298
+ kind=NodeKind.AZURE_FUNCTION,
299
+ label=func_name,
300
+ fqn=f"{class_name}.{func_name}" if class_name else func_name,
301
+ module=ctx.module_name,
302
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
303
+ annotations=["@FunctionName"],
304
+ properties=properties,
305
+ )
306
+ result.nodes.append(func_node)
307
+
308
+ def _detect_ts_functions(
309
+ self,
310
+ ctx: DetectorContext,
311
+ lines: list[str],
312
+ text: str,
313
+ result: DetectorResult,
314
+ ) -> None:
315
+ """Detect TypeScript/JavaScript v4 programming model Azure Functions."""
316
+ _trigger_type_map = {
317
+ "http": "http",
318
+ "serviceBusQueue": "serviceBusQueue",
319
+ "serviceBusTopic": "serviceBusTopic",
320
+ "eventHub": "eventHub",
321
+ "timer": "timer",
322
+ "cosmosDB": "cosmosDB",
323
+ }
324
+
325
+ for i, line in enumerate(lines):
326
+ m = _TS_FUNC_RE.search(line)
327
+ if not m:
328
+ continue
329
+
330
+ app_method = m.group(1)
331
+ func_name = m.group(2)
332
+ trigger_type = _trigger_type_map.get(app_method, app_method)
333
+ func_node_id = f"azure:func:{func_name}"
334
+
335
+ properties: dict[str, Any] = {"trigger_type": trigger_type}
336
+
337
+ func_node = GraphNode(
338
+ id=func_node_id,
339
+ kind=NodeKind.AZURE_FUNCTION,
340
+ label=func_name,
341
+ module=ctx.module_name,
342
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
343
+ properties=properties,
344
+ )
345
+ result.nodes.append(func_node)
346
+
347
+ # Create additional nodes/edges depending on trigger type
348
+ if trigger_type == "http":
349
+ endpoint_id = f"azure:func:{func_name}:endpoint"
350
+ endpoint_node = GraphNode(
351
+ id=endpoint_id,
352
+ kind=NodeKind.ENDPOINT,
353
+ label=f"HTTP {func_name}",
354
+ module=ctx.module_name,
355
+ location=SourceLocation(file_path=ctx.file_path, line_start=i + 1),
356
+ properties={"http_trigger": True, "function_name": func_name},
357
+ )
358
+ result.nodes.append(endpoint_node)
359
+ result.edges.append(GraphEdge(
360
+ source=func_node_id,
361
+ target=endpoint_id,
362
+ kind=EdgeKind.EXPOSES,
363
+ label=f"{func_name} exposes HTTP endpoint",
364
+ ))
365
+
366
+ elif trigger_type == "serviceBusQueue":
367
+ queue_node_id = f"azure:servicebus:queue:{func_name}"
368
+ result.nodes.append(GraphNode(
369
+ id=queue_node_id,
370
+ kind=NodeKind.QUEUE,
371
+ label=f"servicebus:{func_name}",
372
+ properties={"broker": "azure_servicebus", "queue": func_name},
373
+ ))
374
+ result.edges.append(GraphEdge(
375
+ source=queue_node_id,
376
+ target=func_node_id,
377
+ kind=EdgeKind.TRIGGERS,
378
+ label=f"queue triggers {func_name}",
379
+ ))
380
+
381
+ elif trigger_type == "serviceBusTopic":
382
+ topic_node_id = f"azure:servicebus:topic:{func_name}"
383
+ result.nodes.append(GraphNode(
384
+ id=topic_node_id,
385
+ kind=NodeKind.TOPIC,
386
+ label=f"servicebus:{func_name}",
387
+ properties={"broker": "azure_servicebus", "topic": func_name},
388
+ ))
389
+ result.edges.append(GraphEdge(
390
+ source=topic_node_id,
391
+ target=func_node_id,
392
+ kind=EdgeKind.TRIGGERS,
393
+ label=f"topic triggers {func_name}",
394
+ ))
395
+
396
+ elif trigger_type == "eventHub":
397
+ hub_node_id = f"azure:eventhub:{func_name}"
398
+ result.nodes.append(GraphNode(
399
+ id=hub_node_id,
400
+ kind=NodeKind.TOPIC,
401
+ label=f"eventhub:{func_name}",
402
+ properties={"broker": "azure_eventhub", "event_hub": func_name},
403
+ ))
404
+ result.edges.append(GraphEdge(
405
+ source=hub_node_id,
406
+ target=func_node_id,
407
+ kind=EdgeKind.TRIGGERS,
408
+ label=f"event hub triggers {func_name}",
409
+ ))
410
+
411
+ elif trigger_type == "cosmosDB":
412
+ resource_node_id = f"azure:cosmos:func:{func_name}"
413
+ result.nodes.append(GraphNode(
414
+ id=resource_node_id,
415
+ kind=NodeKind.AZURE_RESOURCE,
416
+ label=f"cosmosdb:{func_name}",
417
+ properties={"cosmos_type": "trigger", "function_name": func_name},
418
+ ))
419
+ result.edges.append(GraphEdge(
420
+ source=resource_node_id,
421
+ target=func_node_id,
422
+ kind=EdgeKind.TRIGGERS,
423
+ label=f"CosmosDB triggers {func_name}",
424
+ ))