alita-sdk 0.3.257__py3-none-any.whl → 0.3.562__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 (278) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent/__init__.py +5 -0
  4. alita_sdk/cli/agent/default.py +258 -0
  5. alita_sdk/cli/agent_executor.py +155 -0
  6. alita_sdk/cli/agent_loader.py +215 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3601 -0
  9. alita_sdk/cli/callbacks.py +647 -0
  10. alita_sdk/cli/cli.py +168 -0
  11. alita_sdk/cli/config.py +306 -0
  12. alita_sdk/cli/context/__init__.py +30 -0
  13. alita_sdk/cli/context/cleanup.py +198 -0
  14. alita_sdk/cli/context/manager.py +731 -0
  15. alita_sdk/cli/context/message.py +285 -0
  16. alita_sdk/cli/context/strategies.py +289 -0
  17. alita_sdk/cli/context/token_estimation.py +127 -0
  18. alita_sdk/cli/formatting.py +182 -0
  19. alita_sdk/cli/input_handler.py +419 -0
  20. alita_sdk/cli/inventory.py +1073 -0
  21. alita_sdk/cli/mcp_loader.py +315 -0
  22. alita_sdk/cli/toolkit.py +327 -0
  23. alita_sdk/cli/toolkit_loader.py +85 -0
  24. alita_sdk/cli/tools/__init__.py +43 -0
  25. alita_sdk/cli/tools/approval.py +224 -0
  26. alita_sdk/cli/tools/filesystem.py +1751 -0
  27. alita_sdk/cli/tools/planning.py +389 -0
  28. alita_sdk/cli/tools/terminal.py +414 -0
  29. alita_sdk/community/__init__.py +72 -12
  30. alita_sdk/community/inventory/__init__.py +236 -0
  31. alita_sdk/community/inventory/config.py +257 -0
  32. alita_sdk/community/inventory/enrichment.py +2137 -0
  33. alita_sdk/community/inventory/extractors.py +1469 -0
  34. alita_sdk/community/inventory/ingestion.py +3172 -0
  35. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  36. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  37. alita_sdk/community/inventory/parsers/base.py +295 -0
  38. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  39. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  40. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  41. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  42. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  43. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  44. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  45. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  46. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  47. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  48. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  49. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  50. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  51. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  52. alita_sdk/community/inventory/patterns/loader.py +348 -0
  53. alita_sdk/community/inventory/patterns/registry.py +198 -0
  54. alita_sdk/community/inventory/presets.py +535 -0
  55. alita_sdk/community/inventory/retrieval.py +1403 -0
  56. alita_sdk/community/inventory/toolkit.py +173 -0
  57. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  58. alita_sdk/community/inventory/visualize.py +1370 -0
  59. alita_sdk/configurations/__init__.py +11 -0
  60. alita_sdk/configurations/ado.py +148 -2
  61. alita_sdk/configurations/azure_search.py +1 -1
  62. alita_sdk/configurations/bigquery.py +1 -1
  63. alita_sdk/configurations/bitbucket.py +94 -2
  64. alita_sdk/configurations/browser.py +18 -0
  65. alita_sdk/configurations/carrier.py +19 -0
  66. alita_sdk/configurations/confluence.py +130 -1
  67. alita_sdk/configurations/delta_lake.py +1 -1
  68. alita_sdk/configurations/figma.py +76 -5
  69. alita_sdk/configurations/github.py +65 -1
  70. alita_sdk/configurations/gitlab.py +81 -0
  71. alita_sdk/configurations/google_places.py +17 -0
  72. alita_sdk/configurations/jira.py +103 -0
  73. alita_sdk/configurations/openapi.py +111 -0
  74. alita_sdk/configurations/postman.py +1 -1
  75. alita_sdk/configurations/qtest.py +72 -3
  76. alita_sdk/configurations/report_portal.py +115 -0
  77. alita_sdk/configurations/salesforce.py +19 -0
  78. alita_sdk/configurations/service_now.py +1 -12
  79. alita_sdk/configurations/sharepoint.py +167 -0
  80. alita_sdk/configurations/sonar.py +18 -0
  81. alita_sdk/configurations/sql.py +20 -0
  82. alita_sdk/configurations/testio.py +101 -0
  83. alita_sdk/configurations/testrail.py +88 -0
  84. alita_sdk/configurations/xray.py +94 -1
  85. alita_sdk/configurations/zephyr_enterprise.py +94 -1
  86. alita_sdk/configurations/zephyr_essential.py +95 -0
  87. alita_sdk/runtime/clients/artifact.py +21 -4
  88. alita_sdk/runtime/clients/client.py +458 -67
  89. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  90. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  91. alita_sdk/runtime/clients/sandbox_client.py +352 -0
  92. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  93. alita_sdk/runtime/langchain/assistant.py +183 -43
  94. alita_sdk/runtime/langchain/constants.py +647 -1
  95. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  96. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
  97. alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
  100. alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
  101. alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
  102. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
  103. alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
  104. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
  105. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
  106. alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
  107. alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
  108. alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
  109. alita_sdk/runtime/langchain/langraph_agent.py +407 -92
  110. alita_sdk/runtime/langchain/utils.py +102 -8
  111. alita_sdk/runtime/llms/preloaded.py +2 -6
  112. alita_sdk/runtime/models/mcp_models.py +61 -0
  113. alita_sdk/runtime/skills/__init__.py +91 -0
  114. alita_sdk/runtime/skills/callbacks.py +498 -0
  115. alita_sdk/runtime/skills/discovery.py +540 -0
  116. alita_sdk/runtime/skills/executor.py +610 -0
  117. alita_sdk/runtime/skills/input_builder.py +371 -0
  118. alita_sdk/runtime/skills/models.py +330 -0
  119. alita_sdk/runtime/skills/registry.py +355 -0
  120. alita_sdk/runtime/skills/skill_runner.py +330 -0
  121. alita_sdk/runtime/toolkits/__init__.py +28 -0
  122. alita_sdk/runtime/toolkits/application.py +14 -4
  123. alita_sdk/runtime/toolkits/artifact.py +24 -9
  124. alita_sdk/runtime/toolkits/datasource.py +13 -6
  125. alita_sdk/runtime/toolkits/mcp.py +780 -0
  126. alita_sdk/runtime/toolkits/planning.py +178 -0
  127. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  128. alita_sdk/runtime/toolkits/subgraph.py +11 -6
  129. alita_sdk/runtime/toolkits/tools.py +314 -70
  130. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  131. alita_sdk/runtime/tools/__init__.py +24 -0
  132. alita_sdk/runtime/tools/application.py +16 -4
  133. alita_sdk/runtime/tools/artifact.py +367 -33
  134. alita_sdk/runtime/tools/data_analysis.py +183 -0
  135. alita_sdk/runtime/tools/function.py +100 -4
  136. alita_sdk/runtime/tools/graph.py +81 -0
  137. alita_sdk/runtime/tools/image_generation.py +218 -0
  138. alita_sdk/runtime/tools/llm.py +1013 -177
  139. alita_sdk/runtime/tools/loop.py +3 -1
  140. alita_sdk/runtime/tools/loop_output.py +3 -1
  141. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  142. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  143. alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
  144. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  145. alita_sdk/runtime/tools/planning/models.py +246 -0
  146. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  147. alita_sdk/runtime/tools/router.py +2 -1
  148. alita_sdk/runtime/tools/sandbox.py +375 -0
  149. alita_sdk/runtime/tools/skill_router.py +776 -0
  150. alita_sdk/runtime/tools/tool.py +3 -1
  151. alita_sdk/runtime/tools/vectorstore.py +69 -65
  152. alita_sdk/runtime/tools/vectorstore_base.py +163 -90
  153. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  154. alita_sdk/runtime/utils/mcp_client.py +492 -0
  155. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  156. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  157. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  158. alita_sdk/runtime/utils/streamlit.py +41 -14
  159. alita_sdk/runtime/utils/toolkit_utils.py +28 -9
  160. alita_sdk/runtime/utils/utils.py +48 -0
  161. alita_sdk/tools/__init__.py +135 -37
  162. alita_sdk/tools/ado/__init__.py +2 -2
  163. alita_sdk/tools/ado/repos/__init__.py +15 -19
  164. alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
  165. alita_sdk/tools/ado/test_plan/__init__.py +26 -8
  166. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
  167. alita_sdk/tools/ado/wiki/__init__.py +27 -12
  168. alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
  169. alita_sdk/tools/ado/work_item/__init__.py +27 -12
  170. alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
  171. alita_sdk/tools/advanced_jira_mining/__init__.py +12 -8
  172. alita_sdk/tools/aws/delta_lake/__init__.py +14 -11
  173. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  174. alita_sdk/tools/azure_ai/search/__init__.py +13 -8
  175. alita_sdk/tools/base/tool.py +5 -1
  176. alita_sdk/tools/base_indexer_toolkit.py +454 -110
  177. alita_sdk/tools/bitbucket/__init__.py +27 -19
  178. alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
  179. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
  180. alita_sdk/tools/browser/__init__.py +41 -16
  181. alita_sdk/tools/browser/crawler.py +3 -1
  182. alita_sdk/tools/browser/utils.py +15 -6
  183. alita_sdk/tools/carrier/__init__.py +18 -17
  184. alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
  185. alita_sdk/tools/carrier/excel_reporter.py +8 -4
  186. alita_sdk/tools/chunkers/__init__.py +3 -1
  187. alita_sdk/tools/chunkers/code/codeparser.py +1 -1
  188. alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
  189. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  190. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  191. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  192. alita_sdk/tools/cloud/aws/__init__.py +11 -7
  193. alita_sdk/tools/cloud/azure/__init__.py +11 -7
  194. alita_sdk/tools/cloud/gcp/__init__.py +11 -7
  195. alita_sdk/tools/cloud/k8s/__init__.py +11 -7
  196. alita_sdk/tools/code/linter/__init__.py +9 -8
  197. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  198. alita_sdk/tools/code/sonar/__init__.py +20 -13
  199. alita_sdk/tools/code_indexer_toolkit.py +199 -0
  200. alita_sdk/tools/confluence/__init__.py +21 -14
  201. alita_sdk/tools/confluence/api_wrapper.py +197 -58
  202. alita_sdk/tools/confluence/loader.py +14 -2
  203. alita_sdk/tools/custom_open_api/__init__.py +11 -5
  204. alita_sdk/tools/elastic/__init__.py +10 -8
  205. alita_sdk/tools/elitea_base.py +546 -64
  206. alita_sdk/tools/figma/__init__.py +11 -8
  207. alita_sdk/tools/figma/api_wrapper.py +352 -153
  208. alita_sdk/tools/github/__init__.py +17 -17
  209. alita_sdk/tools/github/api_wrapper.py +9 -26
  210. alita_sdk/tools/github/github_client.py +81 -12
  211. alita_sdk/tools/github/schemas.py +2 -1
  212. alita_sdk/tools/github/tool.py +5 -1
  213. alita_sdk/tools/gitlab/__init__.py +18 -13
  214. alita_sdk/tools/gitlab/api_wrapper.py +224 -80
  215. alita_sdk/tools/gitlab_org/__init__.py +13 -10
  216. alita_sdk/tools/google/bigquery/__init__.py +13 -13
  217. alita_sdk/tools/google/bigquery/tool.py +5 -1
  218. alita_sdk/tools/google_places/__init__.py +20 -11
  219. alita_sdk/tools/jira/__init__.py +21 -11
  220. alita_sdk/tools/jira/api_wrapper.py +315 -168
  221. alita_sdk/tools/keycloak/__init__.py +10 -8
  222. alita_sdk/tools/localgit/__init__.py +8 -3
  223. alita_sdk/tools/localgit/local_git.py +62 -54
  224. alita_sdk/tools/localgit/tool.py +5 -1
  225. alita_sdk/tools/memory/__init__.py +38 -14
  226. alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
  227. alita_sdk/tools/ocr/__init__.py +10 -8
  228. alita_sdk/tools/openapi/__init__.py +281 -108
  229. alita_sdk/tools/openapi/api_wrapper.py +883 -0
  230. alita_sdk/tools/openapi/tool.py +20 -0
  231. alita_sdk/tools/pandas/__init__.py +18 -11
  232. alita_sdk/tools/pandas/api_wrapper.py +40 -45
  233. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  234. alita_sdk/tools/postman/__init__.py +10 -11
  235. alita_sdk/tools/postman/api_wrapper.py +19 -8
  236. alita_sdk/tools/postman/postman_analysis.py +8 -1
  237. alita_sdk/tools/pptx/__init__.py +10 -10
  238. alita_sdk/tools/qtest/__init__.py +21 -14
  239. alita_sdk/tools/qtest/api_wrapper.py +1784 -88
  240. alita_sdk/tools/rally/__init__.py +12 -10
  241. alita_sdk/tools/report_portal/__init__.py +22 -16
  242. alita_sdk/tools/salesforce/__init__.py +21 -16
  243. alita_sdk/tools/servicenow/__init__.py +20 -16
  244. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  245. alita_sdk/tools/sharepoint/__init__.py +16 -14
  246. alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
  247. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  248. alita_sdk/tools/sharepoint/utils.py +8 -2
  249. alita_sdk/tools/slack/__init__.py +11 -7
  250. alita_sdk/tools/sql/__init__.py +21 -19
  251. alita_sdk/tools/sql/api_wrapper.py +71 -23
  252. alita_sdk/tools/testio/__init__.py +20 -13
  253. alita_sdk/tools/testrail/__init__.py +12 -11
  254. alita_sdk/tools/testrail/api_wrapper.py +214 -46
  255. alita_sdk/tools/utils/__init__.py +28 -4
  256. alita_sdk/tools/utils/content_parser.py +182 -62
  257. alita_sdk/tools/utils/text_operations.py +254 -0
  258. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
  259. alita_sdk/tools/xray/__init__.py +17 -14
  260. alita_sdk/tools/xray/api_wrapper.py +58 -113
  261. alita_sdk/tools/yagmail/__init__.py +8 -3
  262. alita_sdk/tools/zephyr/__init__.py +11 -7
  263. alita_sdk/tools/zephyr_enterprise/__init__.py +15 -9
  264. alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
  265. alita_sdk/tools/zephyr_essential/__init__.py +15 -10
  266. alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
  267. alita_sdk/tools/zephyr_essential/client.py +6 -4
  268. alita_sdk/tools/zephyr_scale/__init__.py +12 -8
  269. alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
  270. alita_sdk/tools/zephyr_squad/__init__.py +11 -7
  271. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/METADATA +184 -37
  272. alita_sdk-0.3.562.dist-info/RECORD +450 -0
  273. alita_sdk-0.3.562.dist-info/entry_points.txt +2 -0
  274. alita_sdk/tools/bitbucket/tools.py +0 -304
  275. alita_sdk-0.3.257.dist-info/RECORD +0 -343
  276. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/WHEEL +0 -0
  277. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/licenses/LICENSE +0 -0
  278. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,907 @@
1
+ """
2
+ C#/.NET Parser - Regex-based parser for C# source files.
3
+
4
+ Extracts symbols and relationships from .cs files using comprehensive
5
+ regex patterns. Supports C# features like async/await, LINQ, generics,
6
+ records, nullable types, and pattern matching.
7
+ """
8
+
9
+ import re
10
+ from typing import Dict, List, Optional, Set, Tuple
11
+ from pathlib import Path
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ import logging
14
+
15
+ from .base import (
16
+ BaseParser,
17
+ ParseResult,
18
+ Symbol,
19
+ Relationship,
20
+ RelationshipType,
21
+ SymbolType,
22
+ Scope,
23
+ Position,
24
+ Range,
25
+ parser_registry,
26
+ )
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+ from typing import Any
31
+
32
+
33
+ # Comprehensive C# regex patterns
34
+ PATTERNS = {
35
+ # Namespace declaration
36
+ 'namespace': re.compile(
37
+ r'^\s*namespace\s+([\w.]+)(?:\s*;|\s*\{)',
38
+ re.MULTILINE
39
+ ),
40
+
41
+ # Using directives
42
+ 'using': re.compile(
43
+ r'^\s*using\s+(?:static\s+)?([\w.]+)\s*;',
44
+ re.MULTILINE
45
+ ),
46
+ 'using_alias': re.compile(
47
+ r'^\s*using\s+(\w+)\s*=\s*([\w.<>]+)\s*;',
48
+ re.MULTILINE
49
+ ),
50
+ 'using_global': re.compile(
51
+ r'^\s*global\s+using\s+(?:static\s+)?([\w.]+)\s*;',
52
+ re.MULTILINE
53
+ ),
54
+
55
+ # Class declarations
56
+ 'class': re.compile(
57
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*' # Attributes
58
+ r'(?:(public|private|protected|internal)\s+)?'
59
+ r'(?:(abstract|sealed|static|partial)\s+)*'
60
+ r'class\s+(\w+)'
61
+ r'(?:<[^>]+>)?' # Generic parameters
62
+ r'(?:\s*:\s*([^{]+))?', # Inheritance
63
+ re.MULTILINE
64
+ ),
65
+
66
+ # Record declarations (C# 9+)
67
+ 'record': re.compile(
68
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*'
69
+ r'(?:(public|private|protected|internal)\s+)?'
70
+ r'(?:(abstract|sealed)\s+)?'
71
+ r'record\s+(?:struct\s+|class\s+)?'
72
+ r'(\w+)'
73
+ r'(?:<[^>]+>)?' # Generic parameters
74
+ r'(?:\s*\([^)]*\))?' # Primary constructor
75
+ r'(?:\s*:\s*([^{;]+))?', # Inheritance
76
+ re.MULTILINE
77
+ ),
78
+
79
+ # Interface declarations
80
+ 'interface': re.compile(
81
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*'
82
+ r'(?:(public|private|protected|internal)\s+)?'
83
+ r'(?:partial\s+)?'
84
+ r'interface\s+(\w+)'
85
+ r'(?:<[^>]+>)?' # Generic parameters
86
+ r'(?:\s*:\s*([^{]+))?', # Extended interfaces
87
+ re.MULTILINE
88
+ ),
89
+
90
+ # Struct declarations
91
+ 'struct': re.compile(
92
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*'
93
+ r'(?:(public|private|protected|internal)\s+)?'
94
+ r'(?:(readonly|ref|partial)\s+)*'
95
+ r'struct\s+(\w+)'
96
+ r'(?:<[^>]+>)?' # Generic parameters
97
+ r'(?:\s*:\s*([^{]+))?', # Implemented interfaces
98
+ re.MULTILINE
99
+ ),
100
+
101
+ # Enum declarations
102
+ 'enum': re.compile(
103
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*'
104
+ r'(?:(public|private|protected|internal)\s+)?'
105
+ r'enum\s+(\w+)'
106
+ r'(?:\s*:\s*(\w+))?', # Underlying type
107
+ re.MULTILINE
108
+ ),
109
+
110
+ # Delegate declarations
111
+ 'delegate': re.compile(
112
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*'
113
+ r'(?:(public|private|protected|internal)\s+)?'
114
+ r'delegate\s+([\w<>,\s\[\]?]+)\s+(\w+)\s*\(',
115
+ re.MULTILINE
116
+ ),
117
+
118
+ # Method declarations
119
+ 'method': re.compile(
120
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*'
121
+ r'(?:(public|private|protected|internal|new|override|virtual|abstract|sealed|static|extern|async|partial)\s+)*'
122
+ r'([\w<>,\s\[\]?]+)\s+' # Return type
123
+ r'(\w+)\s*' # Method name
124
+ r'(?:<[^>]+>)?\s*' # Generic parameters
125
+ r'\([^)]*\)', # Parameters
126
+ re.MULTILINE
127
+ ),
128
+
129
+ # Property declarations
130
+ 'property': re.compile(
131
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*'
132
+ r'(?:(public|private|protected|internal|new|override|virtual|abstract|sealed|static)\s+)*'
133
+ r'(?:required\s+)?'
134
+ r'([\w<>,\s\[\]?]+)\s+' # Type
135
+ r'(\w+)\s*' # Property name
136
+ r'(?:\{|=>)',
137
+ re.MULTILINE
138
+ ),
139
+
140
+ # Field declarations
141
+ 'field': re.compile(
142
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*'
143
+ r'(?:(public|private|protected|internal|new|static|readonly|const|volatile)\s+)*'
144
+ r'([\w<>,\s\[\]?]+)\s+' # Type
145
+ r'(\w+)\s*' # Field name
146
+ r'(?:=|;)',
147
+ re.MULTILINE
148
+ ),
149
+
150
+ # Event declarations
151
+ 'event': re.compile(
152
+ r'^\s*(?:\[[\w\(\),\s]+\]\s*)*'
153
+ r'(?:(public|private|protected|internal|static|virtual|override|new)\s+)*'
154
+ r'event\s+'
155
+ r'([\w<>,\s]+)\s+' # Event type
156
+ r'(\w+)',
157
+ re.MULTILINE
158
+ ),
159
+
160
+ # Attribute usage
161
+ 'attribute': re.compile(
162
+ r'\[(\w+)(?:\([^\]]*\))?\]',
163
+ re.MULTILINE
164
+ ),
165
+
166
+ # Constructor calls (new keyword)
167
+ 'constructor_call': re.compile(
168
+ r'new\s+([A-Z]\w*)'
169
+ r'(?:<[^>]+>)?' # Generic parameters
170
+ r'\s*(?:\(|\{|\[)',
171
+ re.MULTILINE
172
+ ),
173
+
174
+ # Method calls
175
+ 'method_call': re.compile(
176
+ r'\.(\w+)\s*(?:<[^>]+>)?\s*\(',
177
+ re.MULTILINE
178
+ ),
179
+
180
+ # Static method/property access
181
+ 'static_access': re.compile(
182
+ r'([A-Z]\w*)\.(\w+)',
183
+ re.MULTILINE
184
+ ),
185
+
186
+ # Generic type usage
187
+ 'generic_usage': re.compile(
188
+ r'<\s*([A-Z]\w*)\s*(?:,\s*[A-Z]\w*)*\s*>',
189
+ re.MULTILINE
190
+ ),
191
+
192
+ # typeof expression
193
+ 'typeof': re.compile(
194
+ r'typeof\s*\(\s*(\w+)',
195
+ re.MULTILINE
196
+ ),
197
+
198
+ # nameof expression
199
+ 'nameof': re.compile(
200
+ r'nameof\s*\(\s*([\w.]+)',
201
+ re.MULTILINE
202
+ ),
203
+
204
+ # LINQ query
205
+ 'linq_from': re.compile(
206
+ r'\bfrom\s+\w+\s+in\s+(\w+)',
207
+ re.MULTILINE
208
+ ),
209
+
210
+ # Extension method definition
211
+ 'extension_method': re.compile(
212
+ r'static\s+[\w<>,\s\[\]?]+\s+(\w+)\s*\(\s*this\s+([\w<>,\s\[\]?]+)\s+(\w+)',
213
+ re.MULTILINE
214
+ ),
215
+
216
+ # XML documentation
217
+ 'xml_doc': re.compile(
218
+ r'///\s*(.+)',
219
+ re.MULTILINE
220
+ ),
221
+ 'xml_see_ref': re.compile(
222
+ r'<see\s+cref="([^"]+)"',
223
+ re.MULTILINE
224
+ ),
225
+ }
226
+
227
+
228
+ class CSharpParser(BaseParser):
229
+ """C# source code parser using regex patterns."""
230
+
231
+ # Global symbol registry for cross-file resolution
232
+ _global_symbols: Dict[str, Set[str]] = {}
233
+ _symbol_to_file: Dict[str, str] = {}
234
+
235
+ def __init__(self):
236
+ super().__init__("csharp")
237
+
238
+ def _get_supported_extensions(self) -> Set[str]:
239
+ return {'.cs'}
240
+
241
+ def _make_range(self, content: str, start_offset: int, end_line: int) -> Range:
242
+ """Create a Range object from content and offset."""
243
+ start_line = content[:start_offset].count('\n') + 1
244
+ last_newline = content.rfind('\n', 0, start_offset)
245
+ start_col = start_offset - last_newline - 1 if last_newline >= 0 else start_offset
246
+ return Range(
247
+ start=Position(line=start_line, column=start_col),
248
+ end=Position(line=end_line, column=0)
249
+ )
250
+
251
+ def _make_symbol(
252
+ self,
253
+ name: str,
254
+ symbol_type: SymbolType,
255
+ content: str,
256
+ file_path: str,
257
+ start_offset: int,
258
+ end_line: int,
259
+ scope: Scope = Scope.GLOBAL,
260
+ parent: Optional[str] = None,
261
+ full_name: Optional[str] = None,
262
+ docstring: Optional[str] = None,
263
+ visibility: Optional[str] = None,
264
+ is_static: bool = False,
265
+ is_async: bool = False,
266
+ return_type: Optional[str] = None,
267
+ metadata: Optional[Dict[str, Any]] = None
268
+ ) -> Symbol:
269
+ """Create a Symbol with correct fields."""
270
+ return Symbol(
271
+ name=name,
272
+ symbol_type=symbol_type,
273
+ scope=scope,
274
+ range=self._make_range(content, start_offset, end_line),
275
+ file_path=file_path,
276
+ parent_symbol=parent,
277
+ full_name=full_name,
278
+ visibility=visibility,
279
+ is_static=is_static,
280
+ is_async=is_async,
281
+ docstring=docstring,
282
+ return_type=return_type,
283
+ metadata=metadata or {}
284
+ )
285
+
286
+ def _make_relationship(
287
+ self,
288
+ source: str,
289
+ target: str,
290
+ rel_type: RelationshipType,
291
+ file_path: str,
292
+ content: str,
293
+ offset: int,
294
+ metadata: Optional[Dict[str, Any]] = None
295
+ ) -> Relationship:
296
+ """Create a Relationship with correct fields."""
297
+ return Relationship(
298
+ source_symbol=source,
299
+ target_symbol=target,
300
+ relationship_type=rel_type,
301
+ source_file=file_path,
302
+ source_range=self._make_range(content, offset, content[:offset].count('\n') + 1),
303
+ metadata=metadata or {}
304
+ )
305
+
306
+ def parse_file(self, file_path: str, content: Optional[str] = None) -> ParseResult:
307
+ """Parse C# source code and extract symbols and relationships."""
308
+ if content is None:
309
+ try:
310
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
311
+ content = f.read()
312
+ except Exception as e:
313
+ return ParseResult(
314
+ file_path=file_path,
315
+ language=self.language,
316
+ symbols=[],
317
+ relationships=[],
318
+ errors=[str(e)]
319
+ )
320
+
321
+ symbols = self._extract_symbols(content, file_path)
322
+ relationships = self._extract_relationships(content, file_path, symbols)
323
+
324
+ return ParseResult(
325
+ symbols=symbols,
326
+ relationships=relationships,
327
+ file_path=file_path,
328
+ language=self.language
329
+ )
330
+
331
+ def _extract_symbols(self, content: str, file_path: str) -> List[Symbol]:
332
+ """Extract all symbols from C# source code."""
333
+ symbols = []
334
+ namespace = self._extract_namespace(content)
335
+
336
+ # Extract classes
337
+ for match in PATTERNS['class'].finditer(content):
338
+ visibility, modifiers, name, inheritance = match.groups()
339
+
340
+ metadata = {'visibility': visibility or 'internal'}
341
+ if modifiers:
342
+ metadata['modifiers'] = modifiers.strip().split()
343
+
344
+ qualified_name = f"{namespace}.{name}" if namespace else name
345
+ docstring = self._find_preceding_xml_doc(content, match.start())
346
+
347
+ symbols.append(self._make_symbol(
348
+ name=name,
349
+ symbol_type=SymbolType.CLASS,
350
+ content=content,
351
+ file_path=file_path,
352
+ start_offset=match.start(),
353
+ end_line=self._find_block_end(content, match.end()),
354
+ full_name=qualified_name,
355
+ docstring=docstring,
356
+ visibility=visibility or 'internal',
357
+ metadata=metadata
358
+ ))
359
+
360
+ # Extract records
361
+ for match in PATTERNS['record'].finditer(content):
362
+ visibility, modifiers, name, inheritance = match.groups()
363
+
364
+ metadata = {
365
+ 'visibility': visibility or 'internal',
366
+ 'is_record': True
367
+ }
368
+ if modifiers:
369
+ metadata['modifiers'] = modifiers.strip().split()
370
+
371
+ qualified_name = f"{namespace}.{name}" if namespace else name
372
+ docstring = self._find_preceding_xml_doc(content, match.start())
373
+
374
+ symbols.append(self._make_symbol(
375
+ name=name,
376
+ symbol_type=SymbolType.CLASS,
377
+ content=content,
378
+ file_path=file_path,
379
+ start_offset=match.start(),
380
+ end_line=self._find_block_end(content, match.end()),
381
+ full_name=qualified_name,
382
+ docstring=docstring,
383
+ visibility=visibility or 'internal',
384
+ metadata=metadata
385
+ ))
386
+
387
+ # Extract interfaces
388
+ for match in PATTERNS['interface'].finditer(content):
389
+ visibility, name, extends = match.groups()
390
+
391
+ qualified_name = f"{namespace}.{name}" if namespace else name
392
+ docstring = self._find_preceding_xml_doc(content, match.start())
393
+
394
+ symbols.append(self._make_symbol(
395
+ name=name,
396
+ symbol_type=SymbolType.INTERFACE,
397
+ content=content,
398
+ file_path=file_path,
399
+ start_offset=match.start(),
400
+ end_line=self._find_block_end(content, match.end()),
401
+ full_name=qualified_name,
402
+ docstring=docstring,
403
+ visibility=visibility or 'internal',
404
+ metadata={'visibility': visibility or 'internal'}
405
+ ))
406
+
407
+ # Extract structs
408
+ for match in PATTERNS['struct'].finditer(content):
409
+ visibility, modifiers, name, implements = match.groups()
410
+
411
+ metadata = {'visibility': visibility or 'internal'}
412
+ if modifiers:
413
+ metadata['modifiers'] = modifiers.strip().split()
414
+
415
+ qualified_name = f"{namespace}.{name}" if namespace else name
416
+ docstring = self._find_preceding_xml_doc(content, match.start())
417
+ metadata['is_struct'] = True
418
+
419
+ symbols.append(self._make_symbol(
420
+ name=name,
421
+ symbol_type=SymbolType.CLASS,
422
+ content=content,
423
+ file_path=file_path,
424
+ start_offset=match.start(),
425
+ end_line=self._find_block_end(content, match.end()),
426
+ full_name=qualified_name,
427
+ docstring=docstring,
428
+ visibility=visibility or 'internal',
429
+ metadata=metadata
430
+ ))
431
+
432
+ # Extract enums
433
+ for match in PATTERNS['enum'].finditer(content):
434
+ visibility, name, underlying_type = match.groups()
435
+
436
+ metadata = {'visibility': visibility or 'internal'}
437
+ if underlying_type:
438
+ metadata['underlying_type'] = underlying_type
439
+
440
+ qualified_name = f"{namespace}.{name}" if namespace else name
441
+
442
+ symbols.append(self._make_symbol(
443
+ name=name,
444
+ symbol_type=SymbolType.ENUM,
445
+ content=content,
446
+ file_path=file_path,
447
+ start_offset=match.start(),
448
+ end_line=self._find_block_end(content, match.end()),
449
+ full_name=qualified_name,
450
+ visibility=visibility or 'internal',
451
+ metadata=metadata
452
+ ))
453
+
454
+ # Extract delegates
455
+ for match in PATTERNS['delegate'].finditer(content):
456
+ visibility, return_type, name = match.groups()
457
+
458
+ qualified_name = f"{namespace}.{name}" if namespace else name
459
+
460
+ symbols.append(self._make_symbol(
461
+ name=name,
462
+ symbol_type=SymbolType.TYPE_ALIAS,
463
+ content=content,
464
+ file_path=file_path,
465
+ start_offset=match.start(),
466
+ end_line=content[:match.start()].count('\n') + 1,
467
+ full_name=qualified_name,
468
+ visibility=visibility or 'internal',
469
+ return_type=return_type.strip(),
470
+ metadata={
471
+ 'visibility': visibility or 'internal',
472
+ 'is_delegate': True,
473
+ 'return_type': return_type.strip()
474
+ }
475
+ ))
476
+
477
+ # Extract methods
478
+ for match in PATTERNS['method'].finditer(content):
479
+ modifiers, return_type, name = match.groups()
480
+
481
+ # Skip if it looks like a constructor or property getter/setter
482
+ if name in {'get', 'set', 'init', 'add', 'remove'}:
483
+ continue
484
+
485
+ metadata = {}
486
+ is_async = False
487
+ is_static = False
488
+ if modifiers:
489
+ modifier_list = modifiers.strip().split()
490
+ metadata['modifiers'] = modifier_list
491
+ if 'async' in modifier_list:
492
+ is_async = True
493
+ metadata['is_async'] = True
494
+ if 'static' in modifier_list:
495
+ is_static = True
496
+ metadata['is_static'] = True
497
+
498
+ metadata['return_type'] = return_type.strip()
499
+ docstring = self._find_preceding_xml_doc(content, match.start())
500
+
501
+ symbols.append(self._make_symbol(
502
+ name=name,
503
+ symbol_type=SymbolType.METHOD,
504
+ content=content,
505
+ file_path=file_path,
506
+ start_offset=match.start(),
507
+ end_line=self._find_method_end(content, match.end()),
508
+ scope=Scope.CLASS,
509
+ full_name=f"{namespace}.{name}" if namespace else name,
510
+ docstring=docstring,
511
+ is_async=is_async,
512
+ is_static=is_static,
513
+ return_type=return_type.strip(),
514
+ metadata=metadata
515
+ ))
516
+
517
+ # Extract properties
518
+ for match in PATTERNS['property'].finditer(content):
519
+ modifiers, type_decl, name = match.groups()
520
+
521
+ metadata = {'type': type_decl.strip(), 'is_property': True}
522
+ if modifiers:
523
+ modifier_list = modifiers.strip().split()
524
+ metadata['modifiers'] = modifier_list
525
+
526
+ symbols.append(self._make_symbol(
527
+ name=name,
528
+ symbol_type=SymbolType.VARIABLE,
529
+ content=content,
530
+ file_path=file_path,
531
+ start_offset=match.start(),
532
+ end_line=content[:match.start()].count('\n') + 1,
533
+ scope=Scope.CLASS,
534
+ full_name=f"{namespace}.{name}" if namespace else name,
535
+ metadata=metadata
536
+ ))
537
+
538
+ # Extract events
539
+ for match in PATTERNS['event'].finditer(content):
540
+ modifiers, event_type, name = match.groups()
541
+
542
+ metadata = {'event_type': event_type.strip(), 'is_event': True}
543
+ if modifiers:
544
+ metadata['modifiers'] = modifiers.strip().split()
545
+
546
+ symbols.append(self._make_symbol(
547
+ name=name,
548
+ symbol_type=SymbolType.VARIABLE,
549
+ content=content,
550
+ file_path=file_path,
551
+ start_offset=match.start(),
552
+ end_line=content[:match.start()].count('\n') + 1,
553
+ scope=Scope.CLASS,
554
+ full_name=f"{namespace}.{name}" if namespace else name,
555
+ metadata=metadata
556
+ ))
557
+
558
+ return symbols
559
+
560
+ def _extract_relationships(
561
+ self,
562
+ content: str,
563
+ file_path: str,
564
+ symbols: List[Symbol]
565
+ ) -> List[Relationship]:
566
+ """Extract relationships from C# source code."""
567
+ relationships = []
568
+ current_scope = Path(file_path).stem
569
+
570
+ # Extract using directives
571
+ for match in PATTERNS['using'].finditer(content):
572
+ namespace_ref = match.group(1)
573
+
574
+ relationships.append(self._make_relationship(
575
+ source=current_scope,
576
+ target=namespace_ref,
577
+ rel_type=RelationshipType.IMPORTS,
578
+ file_path=file_path,
579
+ content=content,
580
+ offset=match.start()
581
+ ))
582
+
583
+ # Extract using aliases
584
+ for match in PATTERNS['using_alias'].finditer(content):
585
+ alias, type_ref = match.groups()
586
+
587
+ relationships.append(self._make_relationship(
588
+ source=current_scope,
589
+ target=type_ref,
590
+ rel_type=RelationshipType.IMPORTS,
591
+ file_path=file_path,
592
+ content=content,
593
+ offset=match.start(),
594
+ metadata={'alias': alias}
595
+ ))
596
+
597
+ # Extract global using
598
+ for match in PATTERNS['using_global'].finditer(content):
599
+ namespace_ref = match.group(1)
600
+
601
+ relationships.append(self._make_relationship(
602
+ source=current_scope,
603
+ target=namespace_ref,
604
+ rel_type=RelationshipType.IMPORTS,
605
+ file_path=file_path,
606
+ content=content,
607
+ offset=match.start(),
608
+ metadata={'global': True}
609
+ ))
610
+
611
+ # Extract class inheritance
612
+ for match in PATTERNS['class'].finditer(content):
613
+ _, _, class_name, inheritance = match.groups()
614
+ if inheritance:
615
+ for parent in self._parse_inheritance(inheritance):
616
+ rel_type = RelationshipType.IMPLEMENTATION if parent.startswith('I') and parent[1:2].isupper() else RelationshipType.INHERITANCE
617
+ relationships.append(self._make_relationship(
618
+ source=class_name,
619
+ target=parent,
620
+ rel_type=rel_type,
621
+ file_path=file_path,
622
+ content=content,
623
+ offset=match.start()
624
+ ))
625
+
626
+ # Extract record inheritance
627
+ for match in PATTERNS['record'].finditer(content):
628
+ _, _, record_name, inheritance = match.groups()
629
+ if inheritance:
630
+ for parent in self._parse_inheritance(inheritance):
631
+ rel_type = RelationshipType.IMPLEMENTATION if parent.startswith('I') and parent[1:2].isupper() else RelationshipType.INHERITANCE
632
+ relationships.append(self._make_relationship(
633
+ source=record_name,
634
+ target=parent,
635
+ rel_type=rel_type,
636
+ file_path=file_path,
637
+ content=content,
638
+ offset=match.start()
639
+ ))
640
+
641
+ # Extract interface extension
642
+ for match in PATTERNS['interface'].finditer(content):
643
+ _, iface_name, extends = match.groups()
644
+ if extends:
645
+ for parent in self._parse_inheritance(extends):
646
+ relationships.append(self._make_relationship(
647
+ source=iface_name,
648
+ target=parent,
649
+ rel_type=RelationshipType.INHERITANCE,
650
+ file_path=file_path,
651
+ content=content,
652
+ offset=match.start()
653
+ ))
654
+
655
+ # Extract struct implementations
656
+ for match in PATTERNS['struct'].finditer(content):
657
+ _, _, struct_name, implements = match.groups()
658
+ if implements:
659
+ for iface in self._parse_inheritance(implements):
660
+ relationships.append(self._make_relationship(
661
+ source=struct_name,
662
+ target=iface,
663
+ rel_type=RelationshipType.IMPLEMENTATION,
664
+ file_path=file_path,
665
+ content=content,
666
+ offset=match.start()
667
+ ))
668
+
669
+ # Extract attribute usage
670
+ for match in PATTERNS['attribute'].finditer(content):
671
+ attribute = match.group(1)
672
+
673
+ # Skip common built-in attributes
674
+ if attribute not in {'Serializable', 'Obsolete', 'Conditional', 'Flags',
675
+ 'DllImport', 'StructLayout', 'MarshalAs', 'FieldOffset',
676
+ 'Required', 'JsonProperty', 'JsonIgnore', 'Test',
677
+ 'Fact', 'Theory', 'DataMember', 'DataContract'}:
678
+ relationships.append(self._make_relationship(
679
+ source=current_scope,
680
+ target=attribute,
681
+ rel_type=RelationshipType.DECORATES,
682
+ file_path=file_path,
683
+ content=content,
684
+ offset=match.start()
685
+ ))
686
+
687
+ # Extract constructor calls
688
+ symbol_names = {s.name for s in symbols}
689
+ for match in PATTERNS['constructor_call'].finditer(content):
690
+ class_name = match.group(1)
691
+
692
+ if class_name not in symbol_names and class_name not in {
693
+ 'List', 'Dictionary', 'HashSet', 'StringBuilder', 'Task',
694
+ 'Exception', 'ArgumentException', 'Guid', 'DateTime', 'TimeSpan',
695
+ 'CancellationTokenSource', 'MemoryStream', 'StreamReader', 'StreamWriter'
696
+ }:
697
+ relationships.append(self._make_relationship(
698
+ source=current_scope,
699
+ target=class_name,
700
+ rel_type=RelationshipType.USES,
701
+ file_path=file_path,
702
+ content=content,
703
+ offset=match.start()
704
+ ))
705
+
706
+ # Extract method calls
707
+ for match in PATTERNS['method_call'].finditer(content):
708
+ method_name = match.group(1)
709
+
710
+ # Skip common methods
711
+ if method_name not in {
712
+ 'ToString', 'GetType', 'Equals', 'GetHashCode', 'CompareTo',
713
+ 'Add', 'Remove', 'Contains', 'Clear', 'Count', 'Length',
714
+ 'Select', 'Where', 'OrderBy', 'GroupBy', 'Join', 'ToList', 'ToArray',
715
+ 'FirstOrDefault', 'First', 'Last', 'Any', 'All', 'Sum', 'Average',
716
+ 'ConfigureAwait', 'Wait', 'Result', 'GetAwaiter'
717
+ }:
718
+ relationships.append(self._make_relationship(
719
+ source=current_scope,
720
+ target=method_name,
721
+ rel_type=RelationshipType.CALLS,
722
+ file_path=file_path,
723
+ content=content,
724
+ offset=match.start()
725
+ ))
726
+
727
+ # Extract static access
728
+ for match in PATTERNS['static_access'].finditer(content):
729
+ type_name, member = match.groups()
730
+
731
+ if type_name not in symbol_names and type_name not in {
732
+ 'Console', 'Math', 'String', 'Convert', 'Enum', 'Array',
733
+ 'Task', 'File', 'Directory', 'Path', 'Environment',
734
+ 'Guid', 'DateTime', 'TimeSpan', 'Int32', 'Boolean', 'Double'
735
+ }:
736
+ relationships.append(self._make_relationship(
737
+ source=current_scope,
738
+ target=type_name,
739
+ rel_type=RelationshipType.REFERENCES,
740
+ file_path=file_path,
741
+ content=content,
742
+ offset=match.start()
743
+ ))
744
+
745
+ # Extract typeof references
746
+ for match in PATTERNS['typeof'].finditer(content):
747
+ type_name = match.group(1)
748
+
749
+ if type_name not in symbol_names:
750
+ relationships.append(self._make_relationship(
751
+ source=current_scope,
752
+ target=type_name,
753
+ rel_type=RelationshipType.REFERENCES,
754
+ file_path=file_path,
755
+ content=content,
756
+ offset=match.start()
757
+ ))
758
+
759
+ # Extract XML doc references
760
+ for match in PATTERNS['xml_see_ref'].finditer(content):
761
+ ref = match.group(1)
762
+
763
+ # Extract just the type/member name
764
+ ref_name = ref.split('.')[-1].split('(')[0]
765
+ if ref_name:
766
+ relationships.append(self._make_relationship(
767
+ source=current_scope,
768
+ target=ref_name,
769
+ rel_type=RelationshipType.REFERENCES,
770
+ file_path=file_path,
771
+ content=content,
772
+ offset=match.start(),
773
+ metadata={'from_documentation': True}
774
+ ))
775
+
776
+ return relationships
777
+
778
+ def _extract_namespace(self, content: str) -> Optional[str]:
779
+ """Extract namespace from content."""
780
+ match = PATTERNS['namespace'].search(content)
781
+ return match.group(1) if match else None
782
+
783
+ def _parse_inheritance(self, inheritance: str) -> List[str]:
784
+ """Parse inheritance clause to extract parent types."""
785
+ # Remove generic parameters for simpler parsing
786
+ clean = re.sub(r'<[^>]*>', '', inheritance)
787
+
788
+ types = []
789
+ for part in clean.split(','):
790
+ part = part.strip()
791
+ if part:
792
+ # Get just the type name (without where clauses, etc.)
793
+ type_name = part.split()[0].strip()
794
+ if type_name and type_name != 'where':
795
+ types.append(type_name)
796
+
797
+ return types
798
+
799
+ def _find_preceding_xml_doc(self, content: str, position: int) -> Optional[str]:
800
+ """Find XML documentation comment preceding a position."""
801
+ before = content[:position]
802
+ lines = before.split('\n')
803
+
804
+ # Look for /// comments in preceding lines
805
+ doc_lines = []
806
+ for line in reversed(lines[:-1]): # Skip current line
807
+ stripped = line.strip()
808
+ if stripped.startswith('///'):
809
+ doc_lines.insert(0, stripped[3:].strip())
810
+ elif stripped.startswith('['):
811
+ # Skip attributes
812
+ continue
813
+ elif stripped == '':
814
+ continue
815
+ else:
816
+ break
817
+
818
+ if doc_lines:
819
+ # Clean up XML tags
820
+ doc = ' '.join(doc_lines)
821
+ doc = re.sub(r'<[^>]+>', '', doc)
822
+ return doc.strip()
823
+
824
+ return None
825
+
826
+ def _find_block_end(self, content: str, start: int) -> int:
827
+ """Find the end line of a code block."""
828
+ brace_count = 0
829
+ in_block = False
830
+
831
+ for i, char in enumerate(content[start:], start):
832
+ if char == '{':
833
+ brace_count += 1
834
+ in_block = True
835
+ elif char == '}':
836
+ brace_count -= 1
837
+ if in_block and brace_count == 0:
838
+ return content[:i].count('\n') + 1
839
+
840
+ return content[:start].count('\n') + 1
841
+
842
+ def _find_method_end(self, content: str, start: int) -> int:
843
+ """Find the end line of a method."""
844
+ # Check for expression body (=>) vs block body ({)
845
+ rest = content[start:start + 100]
846
+
847
+ if '=>' in rest.split('\n')[0] and '{' not in rest.split('\n')[0]:
848
+ # Expression body - find semicolon
849
+ semi_pos = content.find(';', start)
850
+ return content[:semi_pos].count('\n') + 1 if semi_pos != -1 else content[:start].count('\n') + 1
851
+ else:
852
+ return self._find_block_end(content, start)
853
+
854
+ def parse_multiple_files(
855
+ self,
856
+ files: List[Tuple[str, Optional[str]]],
857
+ resolve_cross_file: bool = True,
858
+ max_workers: int = 4
859
+ ) -> Dict[str, ParseResult]:
860
+ """
861
+ Parse multiple C# files with optional cross-file resolution.
862
+
863
+ Args:
864
+ files: List of (file_path, content) tuples
865
+ resolve_cross_file: Whether to resolve cross-file references
866
+ max_workers: Maximum number of parallel workers
867
+
868
+ Returns:
869
+ Dict mapping file paths to ParseResult objects
870
+ """
871
+ results = {}
872
+
873
+ def parse_single(file_info: Tuple[str, Optional[str]]) -> Tuple[str, Optional[ParseResult]]:
874
+ file_path, content = file_info
875
+ if content is None:
876
+ try:
877
+ with open(file_path, 'r', encoding='utf-8') as f:
878
+ content = f.read()
879
+ except Exception as e:
880
+ logger.warning(f"Failed to read {file_path}: {e}")
881
+ return file_path, None
882
+
883
+ try:
884
+ result = self.parse(content, file_path)
885
+ return file_path, result
886
+ except Exception as e:
887
+ logger.warning(f"Failed to parse {file_path}: {e}")
888
+ return file_path, None
889
+
890
+ # Parse files in parallel
891
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
892
+ for file_path, result in executor.map(parse_single, files):
893
+ if result:
894
+ results[file_path] = result
895
+
896
+ # Register symbols for cross-file resolution
897
+ if resolve_cross_file:
898
+ for symbol in result.symbols:
899
+ self._symbol_to_file[symbol.name] = file_path
900
+ if symbol.qualified_name:
901
+ self._symbol_to_file[symbol.qualified_name] = file_path
902
+
903
+ return results
904
+
905
+
906
+ # Register the parser
907
+ parser_registry.register_parser(CSharpParser())