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,832 @@
1
+ """
2
+ Swift Parser - Regex-based parser for Swift source files.
3
+
4
+ Extracts symbols and relationships from .swift files using comprehensive
5
+ regex patterns. Supports Swift-specific features like protocols, extensions,
6
+ optionals, closures, property wrappers, and async/await.
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 Swift regex patterns
34
+ PATTERNS = {
35
+ # Import statements
36
+ 'import': re.compile(
37
+ r'^\s*import\s+(?:(\w+)\s+)?(\w+(?:\.\w+)*)',
38
+ re.MULTILINE
39
+ ),
40
+
41
+ # Class declarations
42
+ 'class': re.compile(
43
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*' # Attributes
44
+ r'(?:(open|public|internal|fileprivate|private)\s+)?'
45
+ r'(?:(final)\s+)?'
46
+ r'class\s+(\w+)'
47
+ r'(?:<[^>]+>)?' # Generic parameters
48
+ r'(?:\s*:\s*([^{]+))?', # Inheritance
49
+ re.MULTILINE
50
+ ),
51
+
52
+ # Struct declarations
53
+ 'struct': re.compile(
54
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*'
55
+ r'(?:(public|internal|fileprivate|private)\s+)?'
56
+ r'struct\s+(\w+)'
57
+ r'(?:<[^>]+>)?' # Generic parameters
58
+ r'(?:\s*:\s*([^{]+))?', # Protocol conformance
59
+ re.MULTILINE
60
+ ),
61
+
62
+ # Protocol declarations
63
+ 'protocol': re.compile(
64
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*'
65
+ r'(?:(public|internal|fileprivate|private)\s+)?'
66
+ r'protocol\s+(\w+)'
67
+ r'(?:\s*:\s*([^{]+))?', # Protocol inheritance
68
+ re.MULTILINE
69
+ ),
70
+
71
+ # Enum declarations
72
+ 'enum': re.compile(
73
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*'
74
+ r'(?:(public|internal|fileprivate|private)\s+)?'
75
+ r'(?:(indirect)\s+)?'
76
+ r'enum\s+(\w+)'
77
+ r'(?:<[^>]+>)?' # Generic parameters
78
+ r'(?:\s*:\s*([^{]+))?', # Raw type or protocol conformance
79
+ re.MULTILINE
80
+ ),
81
+
82
+ # Extension declarations
83
+ 'extension': re.compile(
84
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*'
85
+ r'(?:(public|internal|fileprivate|private)\s+)?'
86
+ r'extension\s+(\w+)'
87
+ r'(?:\s*:\s*([^{]+))?', # Protocol conformance
88
+ re.MULTILINE
89
+ ),
90
+
91
+ # Actor declarations (Swift 5.5+)
92
+ 'actor': re.compile(
93
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*'
94
+ r'(?:(public|internal|fileprivate|private)\s+)?'
95
+ r'actor\s+(\w+)'
96
+ r'(?:<[^>]+>)?' # Generic parameters
97
+ r'(?:\s*:\s*([^{]+))?', # Protocol conformance
98
+ re.MULTILINE
99
+ ),
100
+
101
+ # Function declarations
102
+ 'function': re.compile(
103
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*'
104
+ r'(?:(open|public|internal|fileprivate|private)\s+)?'
105
+ r'(?:(override|static|class|final|mutating|nonmutating|async|nonisolated)\s+)*'
106
+ r'func\s+(\w+)'
107
+ r'(?:<[^>]+>)?' # Generic parameters
108
+ r'\s*\([^)]*\)' # Parameters
109
+ r'(?:\s*(?:async\s+)?(?:throws\s+)?->\s*([^\n{]+))?', # Return type
110
+ re.MULTILINE
111
+ ),
112
+
113
+ # Initializer declarations
114
+ 'init': re.compile(
115
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*'
116
+ r'(?:(public|internal|fileprivate|private)\s+)?'
117
+ r'(?:(convenience|required|override)\s+)*'
118
+ r'init\s*\??\s*\([^)]*\)',
119
+ re.MULTILINE
120
+ ),
121
+
122
+ # Property declarations
123
+ 'property': re.compile(
124
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*'
125
+ r'(?:(open|public|internal|fileprivate|private)\s+)?'
126
+ r'(?:(static|class|lazy|weak|unowned)\s+)*'
127
+ r'(let|var)\s+(\w+)\s*:\s*([^\n=]+)',
128
+ re.MULTILINE
129
+ ),
130
+
131
+ # Computed property
132
+ 'computed_property': re.compile(
133
+ r'^\s*(?:@\w+(?:\([^)]*\))?\s*)*'
134
+ r'(?:(public|internal|fileprivate|private)\s+)?'
135
+ r'(?:(static|class)\s+)?'
136
+ r'var\s+(\w+)\s*:\s*([^{]+)\s*\{',
137
+ re.MULTILINE
138
+ ),
139
+
140
+ # Type alias
141
+ 'typealias': re.compile(
142
+ r'^\s*(?:(public|internal|fileprivate|private)\s+)?'
143
+ r'typealias\s+(\w+)'
144
+ r'(?:<[^>]+>)?' # Generic parameters
145
+ r'\s*=\s*([^\n]+)',
146
+ re.MULTILINE
147
+ ),
148
+
149
+ # Associated type
150
+ 'associatedtype': re.compile(
151
+ r'^\s*associatedtype\s+(\w+)'
152
+ r'(?:\s*:\s*([^\n=]+))?' # Type constraints
153
+ r'(?:\s*=\s*([^\n]+))?', # Default type
154
+ re.MULTILINE
155
+ ),
156
+
157
+ # Property wrapper
158
+ 'property_wrapper': re.compile(
159
+ r'@(\w+)(?:\([^)]*\))?',
160
+ re.MULTILINE
161
+ ),
162
+
163
+ # Method calls
164
+ 'method_call': re.compile(
165
+ r'\.(\w+)\s*\(',
166
+ re.MULTILINE
167
+ ),
168
+
169
+ # Function calls
170
+ 'function_call': re.compile(
171
+ r'(?:^|[^\w.])(\w+)\s*\(',
172
+ re.MULTILINE
173
+ ),
174
+
175
+ # Type references
176
+ 'type_ref': re.compile(
177
+ r':\s*(?:\[)?(?:\w+\.)?([A-Z]\w*)',
178
+ re.MULTILINE
179
+ ),
180
+
181
+ # Initializer calls
182
+ 'init_call': re.compile(
183
+ r'([A-Z]\w*)\s*\(',
184
+ re.MULTILINE
185
+ ),
186
+
187
+ # Generic type usage
188
+ 'generic_usage': re.compile(
189
+ r'<\s*([A-Z]\w*)\s*(?:,\s*[A-Z]\w*)*\s*>',
190
+ re.MULTILINE
191
+ ),
192
+
193
+ # Closure type
194
+ 'closure_type': re.compile(
195
+ r'\(\s*(?:[^)]+)?\s*\)\s*(?:async\s+)?(?:throws\s+)?->\s*(\w+)',
196
+ re.MULTILINE
197
+ ),
198
+
199
+ # Swift doc comments
200
+ 'doc_comment': re.compile(
201
+ r'///\s*(.+)',
202
+ re.MULTILINE
203
+ ),
204
+ 'doc_comment_block': re.compile(
205
+ r'/\*\*\s*([\s\S]*?)\s*\*/',
206
+ re.MULTILINE
207
+ ),
208
+
209
+ # MARK comments (for structure understanding)
210
+ 'mark': re.compile(
211
+ r'//\s*MARK:\s*-?\s*(.+)',
212
+ re.MULTILINE
213
+ ),
214
+ }
215
+
216
+
217
+ class SwiftParser(BaseParser):
218
+ """Swift source code parser using regex patterns."""
219
+
220
+ # Global symbol registry for cross-file resolution
221
+ _global_symbols: Dict[str, Set[str]] = {}
222
+ _symbol_to_file: Dict[str, str] = {}
223
+
224
+ def __init__(self):
225
+ super().__init__("swift")
226
+
227
+ def _get_supported_extensions(self) -> Set[str]:
228
+ return {'.swift'}
229
+
230
+ def _make_range(self, content: str, start_offset: int, end_line: int) -> Range:
231
+ """Create a Range object from content and offset."""
232
+ start_line = content[:start_offset].count('\n') + 1
233
+ last_newline = content.rfind('\n', 0, start_offset)
234
+ start_col = start_offset - last_newline - 1 if last_newline >= 0 else start_offset
235
+ return Range(
236
+ start=Position(line=start_line, column=start_col),
237
+ end=Position(line=end_line, column=0)
238
+ )
239
+
240
+ def _make_symbol(
241
+ self,
242
+ name: str,
243
+ symbol_type: SymbolType,
244
+ content: str,
245
+ file_path: str,
246
+ start_offset: int,
247
+ end_line: int,
248
+ scope: Scope = Scope.GLOBAL,
249
+ parent: Optional[str] = None,
250
+ full_name: Optional[str] = None,
251
+ docstring: Optional[str] = None,
252
+ visibility: Optional[str] = None,
253
+ is_static: bool = False,
254
+ is_async: bool = False,
255
+ return_type: Optional[str] = None,
256
+ metadata: Optional[Dict[str, Any]] = None
257
+ ) -> Symbol:
258
+ """Create a Symbol with correct fields."""
259
+ return Symbol(
260
+ name=name,
261
+ symbol_type=symbol_type,
262
+ scope=scope,
263
+ range=self._make_range(content, start_offset, end_line),
264
+ file_path=file_path,
265
+ parent_symbol=parent,
266
+ full_name=full_name,
267
+ visibility=visibility,
268
+ is_static=is_static,
269
+ is_async=is_async,
270
+ docstring=docstring,
271
+ return_type=return_type,
272
+ metadata=metadata or {}
273
+ )
274
+
275
+ def _make_relationship(
276
+ self,
277
+ source: str,
278
+ target: str,
279
+ rel_type: RelationshipType,
280
+ file_path: str,
281
+ content: str,
282
+ offset: int,
283
+ metadata: Optional[Dict[str, Any]] = None
284
+ ) -> Relationship:
285
+ """Create a Relationship with correct fields."""
286
+ return Relationship(
287
+ source_symbol=source,
288
+ target_symbol=target,
289
+ relationship_type=rel_type,
290
+ source_file=file_path,
291
+ source_range=self._make_range(content, offset, content[:offset].count('\n') + 1),
292
+ metadata=metadata or {}
293
+ )
294
+
295
+ def parse_file(self, file_path: str, content: Optional[str] = None) -> ParseResult:
296
+ """Parse Swift source code and extract symbols and relationships."""
297
+ if content is None:
298
+ try:
299
+ with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
300
+ content = f.read()
301
+ except Exception as e:
302
+ return ParseResult(
303
+ file_path=file_path,
304
+ language=self.language,
305
+ symbols=[],
306
+ relationships=[],
307
+ errors=[str(e)]
308
+ )
309
+
310
+ symbols = self._extract_symbols(content, file_path)
311
+ relationships = self._extract_relationships(content, file_path, symbols)
312
+
313
+ return ParseResult(
314
+ symbols=symbols,
315
+ relationships=relationships,
316
+ file_path=file_path,
317
+ language=self.language
318
+ )
319
+
320
+ def _extract_symbols(self, content: str, file_path: str) -> List[Symbol]:
321
+ """Extract all symbols from Swift source code."""
322
+ symbols = []
323
+ module_name = Path(file_path).stem
324
+
325
+ # Extract classes
326
+ for match in PATTERNS['class'].finditer(content):
327
+ visibility, final, name, inheritance = match.groups()
328
+
329
+ docstring = self._find_preceding_doc(content, match.start())
330
+
331
+ metadata = {'visibility': visibility or 'internal'}
332
+ if final:
333
+ metadata['final'] = True
334
+
335
+ symbols.append(self._make_symbol(
336
+ name=name,
337
+ symbol_type=SymbolType.CLASS,
338
+ content=content,
339
+ file_path=file_path,
340
+ start_offset=match.start(),
341
+ end_line=self._find_block_end(content, match.end()),
342
+ full_name=f"{module_name}.{name}",
343
+ docstring=docstring,
344
+ visibility=visibility or 'internal',
345
+ metadata=metadata
346
+ ))
347
+
348
+ # Extract structs
349
+ for match in PATTERNS['struct'].finditer(content):
350
+ visibility, name, conformance = match.groups()
351
+
352
+ docstring = self._find_preceding_doc(content, match.start())
353
+
354
+ symbols.append(self._make_symbol(
355
+ name=name,
356
+ symbol_type=SymbolType.CLASS,
357
+ content=content,
358
+ file_path=file_path,
359
+ start_offset=match.start(),
360
+ end_line=self._find_block_end(content, match.end()),
361
+ full_name=f"{module_name}.{name}",
362
+ docstring=docstring,
363
+ visibility=visibility or 'internal',
364
+ metadata={
365
+ 'visibility': visibility or 'internal',
366
+ 'is_struct': True
367
+ }
368
+ ))
369
+
370
+ # Extract protocols
371
+ for match in PATTERNS['protocol'].finditer(content):
372
+ visibility, name, inheritance = match.groups()
373
+
374
+ docstring = self._find_preceding_doc(content, match.start())
375
+
376
+ symbols.append(self._make_symbol(
377
+ name=name,
378
+ symbol_type=SymbolType.INTERFACE,
379
+ content=content,
380
+ file_path=file_path,
381
+ start_offset=match.start(),
382
+ end_line=self._find_block_end(content, match.end()),
383
+ full_name=f"{module_name}.{name}",
384
+ docstring=docstring,
385
+ visibility=visibility or 'internal',
386
+ metadata={'visibility': visibility or 'internal'}
387
+ ))
388
+
389
+ # Extract enums
390
+ for match in PATTERNS['enum'].finditer(content):
391
+ visibility, indirect, name, raw_type = match.groups()
392
+
393
+ docstring = self._find_preceding_doc(content, match.start())
394
+
395
+ metadata = {'visibility': visibility or 'internal'}
396
+ if indirect:
397
+ metadata['indirect'] = True
398
+ if raw_type:
399
+ metadata['raw_type'] = raw_type.strip()
400
+
401
+ symbols.append(self._make_symbol(
402
+ name=name,
403
+ symbol_type=SymbolType.ENUM,
404
+ content=content,
405
+ file_path=file_path,
406
+ start_offset=match.start(),
407
+ end_line=self._find_block_end(content, match.end()),
408
+ full_name=f"{module_name}.{name}",
409
+ docstring=docstring,
410
+ visibility=visibility or 'internal',
411
+ metadata=metadata
412
+ ))
413
+
414
+ # Extract actors
415
+ for match in PATTERNS['actor'].finditer(content):
416
+ visibility, name, conformance = match.groups()
417
+
418
+ docstring = self._find_preceding_doc(content, match.start())
419
+
420
+ symbols.append(self._make_symbol(
421
+ name=name,
422
+ symbol_type=SymbolType.CLASS,
423
+ content=content,
424
+ file_path=file_path,
425
+ start_offset=match.start(),
426
+ end_line=self._find_block_end(content, match.end()),
427
+ full_name=f"{module_name}.{name}",
428
+ docstring=docstring,
429
+ visibility=visibility or 'internal',
430
+ metadata={
431
+ 'visibility': visibility or 'internal',
432
+ 'is_actor': True
433
+ }
434
+ ))
435
+
436
+ # Extract extensions
437
+ for match in PATTERNS['extension'].finditer(content):
438
+ visibility, extended_type, conformance = match.groups()
439
+
440
+ metadata = {'visibility': visibility or 'internal', 'is_extension': True}
441
+ if conformance:
442
+ metadata['conformance'] = [c.strip() for c in conformance.split(',')]
443
+
444
+ symbols.append(self._make_symbol(
445
+ name=f"{extended_type}_extension",
446
+ symbol_type=SymbolType.CLASS,
447
+ content=content,
448
+ file_path=file_path,
449
+ start_offset=match.start(),
450
+ end_line=self._find_block_end(content, match.end()),
451
+ full_name=f"{module_name}.{extended_type}_extension",
452
+ visibility=visibility or 'internal',
453
+ metadata=metadata
454
+ ))
455
+
456
+ # Extract functions
457
+ for match in PATTERNS['function'].finditer(content):
458
+ visibility, modifiers, name, return_type = match.groups()
459
+
460
+ docstring = self._find_preceding_doc(content, match.start())
461
+
462
+ metadata = {'visibility': visibility or 'internal'}
463
+ is_async = False
464
+ is_static = False
465
+ if modifiers:
466
+ modifier_list = modifiers.strip().split()
467
+ metadata['modifiers'] = modifier_list
468
+ if 'async' in modifier_list:
469
+ is_async = True
470
+ metadata['is_async'] = True
471
+ if 'static' in modifier_list or 'class' in modifier_list:
472
+ is_static = True
473
+ metadata['is_static'] = True
474
+ if return_type:
475
+ metadata['return_type'] = return_type.strip()
476
+
477
+ symbols.append(self._make_symbol(
478
+ name=name,
479
+ symbol_type=SymbolType.FUNCTION,
480
+ content=content,
481
+ file_path=file_path,
482
+ start_offset=match.start(),
483
+ end_line=self._find_block_end(content, match.end()),
484
+ full_name=f"{module_name}.{name}",
485
+ docstring=docstring,
486
+ visibility=visibility or 'internal',
487
+ is_static=is_static,
488
+ is_async=is_async,
489
+ return_type=return_type.strip() if return_type else None,
490
+ metadata=metadata
491
+ ))
492
+
493
+ # Extract properties
494
+ for match in PATTERNS['property'].finditer(content):
495
+ visibility, modifiers, let_var, name, prop_type = match.groups()
496
+ line = content[:match.start()].count('\n') + 1
497
+
498
+ metadata = {
499
+ 'visibility': visibility or 'internal',
500
+ 'mutable': let_var == 'var',
501
+ 'type': prop_type.strip()
502
+ }
503
+ is_static = False
504
+ if modifiers:
505
+ modifier_list = modifiers.strip().split()
506
+ metadata['modifiers'] = modifier_list
507
+ if 'static' in modifier_list or 'class' in modifier_list:
508
+ is_static = True
509
+
510
+ symbols.append(self._make_symbol(
511
+ name=name,
512
+ symbol_type=SymbolType.VARIABLE,
513
+ content=content,
514
+ file_path=file_path,
515
+ start_offset=match.start(),
516
+ end_line=line,
517
+ full_name=f"{module_name}.{name}",
518
+ visibility=visibility or 'internal',
519
+ is_static=is_static,
520
+ metadata=metadata
521
+ ))
522
+
523
+ # Extract type aliases
524
+ for match in PATTERNS['typealias'].finditer(content):
525
+ visibility, name, aliased_type = match.groups()
526
+ line = content[:match.start()].count('\n') + 1
527
+
528
+ symbols.append(self._make_symbol(
529
+ name=name,
530
+ symbol_type=SymbolType.TYPE_ALIAS,
531
+ content=content,
532
+ file_path=file_path,
533
+ start_offset=match.start(),
534
+ end_line=line,
535
+ full_name=f"{module_name}.{name}",
536
+ visibility=visibility or 'internal',
537
+ metadata={
538
+ 'visibility': visibility or 'internal',
539
+ 'aliased_type': aliased_type.strip()
540
+ }
541
+ ))
542
+
543
+ return symbols
544
+
545
+ def _extract_relationships(
546
+ self,
547
+ content: str,
548
+ file_path: str,
549
+ symbols: List[Symbol]
550
+ ) -> List[Relationship]:
551
+ """Extract relationships from Swift source code."""
552
+ relationships = []
553
+ current_scope = Path(file_path).stem
554
+
555
+ # Extract imports
556
+ for match in PATTERNS['import'].finditer(content):
557
+ kind, module = match.groups()
558
+
559
+ metadata = {}
560
+ if kind:
561
+ metadata['kind'] = kind # class, struct, func, etc.
562
+
563
+ relationships.append(self._make_relationship(
564
+ source=current_scope,
565
+ target=module,
566
+ rel_type=RelationshipType.IMPORTS,
567
+ file_path=file_path,
568
+ content=content,
569
+ offset=match.start(),
570
+ metadata=metadata if metadata else None
571
+ ))
572
+
573
+ # Extract class inheritance
574
+ for match in PATTERNS['class'].finditer(content):
575
+ _, _, class_name, inheritance = match.groups()
576
+ if inheritance:
577
+ parents = self._parse_inheritance(inheritance)
578
+ for i, parent in enumerate(parents):
579
+ # First parent is superclass, rest are protocols
580
+ rel_type = RelationshipType.INHERITANCE if i == 0 and not parent.startswith('Any') else RelationshipType.IMPLEMENTATION
581
+ relationships.append(self._make_relationship(
582
+ source=class_name,
583
+ target=parent,
584
+ rel_type=rel_type,
585
+ file_path=file_path,
586
+ content=content,
587
+ offset=match.start()
588
+ ))
589
+
590
+ # Extract struct protocol conformance
591
+ for match in PATTERNS['struct'].finditer(content):
592
+ _, struct_name, conformance = match.groups()
593
+ if conformance:
594
+ for protocol in self._parse_inheritance(conformance):
595
+ relationships.append(self._make_relationship(
596
+ source=struct_name,
597
+ target=protocol,
598
+ rel_type=RelationshipType.IMPLEMENTATION,
599
+ file_path=file_path,
600
+ content=content,
601
+ offset=match.start()
602
+ ))
603
+
604
+ # Extract protocol inheritance
605
+ for match in PATTERNS['protocol'].finditer(content):
606
+ _, protocol_name, inheritance = match.groups()
607
+ if inheritance:
608
+ for parent in self._parse_inheritance(inheritance):
609
+ relationships.append(self._make_relationship(
610
+ source=protocol_name,
611
+ target=parent,
612
+ rel_type=RelationshipType.INHERITANCE,
613
+ file_path=file_path,
614
+ content=content,
615
+ offset=match.start()
616
+ ))
617
+
618
+ # Extract enum conformance
619
+ for match in PATTERNS['enum'].finditer(content):
620
+ _, _, enum_name, raw_or_conformance = match.groups()
621
+ if raw_or_conformance:
622
+ for item in self._parse_inheritance(raw_or_conformance):
623
+ # Could be raw type or protocol
624
+ relationships.append(self._make_relationship(
625
+ source=enum_name,
626
+ target=item,
627
+ rel_type=RelationshipType.IMPLEMENTATION,
628
+ file_path=file_path,
629
+ content=content,
630
+ offset=match.start()
631
+ ))
632
+
633
+ # Extract extension relationships
634
+ for match in PATTERNS['extension'].finditer(content):
635
+ _, extended_type, conformance = match.groups()
636
+
637
+ # Extension extends a type
638
+ relationships.append(self._make_relationship(
639
+ source=f"{extended_type}_extension",
640
+ target=extended_type,
641
+ rel_type=RelationshipType.INHERITANCE,
642
+ file_path=file_path,
643
+ content=content,
644
+ offset=match.start(),
645
+ metadata={'extension': True}
646
+ ))
647
+
648
+ # Protocol conformance in extension
649
+ if conformance:
650
+ for protocol in self._parse_inheritance(conformance):
651
+ relationships.append(self._make_relationship(
652
+ source=extended_type,
653
+ target=protocol,
654
+ rel_type=RelationshipType.IMPLEMENTATION,
655
+ file_path=file_path,
656
+ content=content,
657
+ offset=match.start(),
658
+ metadata={'via_extension': True}
659
+ ))
660
+
661
+ # Extract property wrapper usage
662
+ symbol_names = {s.name for s in symbols}
663
+ for match in PATTERNS['property_wrapper'].finditer(content):
664
+ wrapper = match.group(1)
665
+
666
+ # Skip built-in attributes
667
+ if wrapper not in {
668
+ 'available', 'objc', 'objcMembers', 'nonobjc', 'escaping', 'autoclosure',
669
+ 'discardableResult', 'inlinable', 'usableFromInline', 'frozen', 'unknown',
670
+ 'IBOutlet', 'IBAction', 'IBDesignable', 'IBInspectable', 'main', 'testable',
671
+ 'Published', 'State', 'Binding', 'ObservedObject', 'EnvironmentObject',
672
+ 'Environment', 'AppStorage', 'SceneStorage', 'FetchRequest', 'NSManaged'
673
+ } and wrapper[0].isupper():
674
+ relationships.append(self._make_relationship(
675
+ source=current_scope,
676
+ target=wrapper,
677
+ rel_type=RelationshipType.DECORATES,
678
+ file_path=file_path,
679
+ content=content,
680
+ offset=match.start()
681
+ ))
682
+
683
+ # Extract initializer calls / type instantiation
684
+ for match in PATTERNS['init_call'].finditer(content):
685
+ type_name = match.group(1)
686
+
687
+ if type_name not in symbol_names and type_name not in {
688
+ 'String', 'Int', 'Double', 'Float', 'Bool', 'Array', 'Dictionary', 'Set',
689
+ 'Optional', 'Result', 'UUID', 'URL', 'Date', 'Data', 'Error', 'NSError',
690
+ 'Range', 'ClosedRange', 'Substring', 'Character', 'CGFloat', 'CGPoint',
691
+ 'CGSize', 'CGRect', 'UIColor', 'NSColor', 'UIImage', 'NSImage', 'UIView',
692
+ 'NSView', 'DispatchQueue', 'Task', 'URLSession', 'JSONDecoder', 'JSONEncoder'
693
+ }:
694
+ relationships.append(self._make_relationship(
695
+ source=current_scope,
696
+ target=type_name,
697
+ rel_type=RelationshipType.USES,
698
+ file_path=file_path,
699
+ content=content,
700
+ offset=match.start()
701
+ ))
702
+
703
+ # Extract method calls
704
+ for match in PATTERNS['method_call'].finditer(content):
705
+ method_name = match.group(1)
706
+
707
+ # Skip common methods
708
+ if method_name not in {
709
+ 'map', 'flatMap', 'compactMap', 'filter', 'reduce', 'forEach', 'sorted',
710
+ 'first', 'last', 'append', 'insert', 'remove', 'contains', 'count',
711
+ 'isEmpty', 'joined', 'split', 'prefix', 'suffix', 'dropFirst', 'dropLast',
712
+ 'init', 'deinit', 'description', 'debugDescription', 'hash'
713
+ }:
714
+ relationships.append(self._make_relationship(
715
+ source=current_scope,
716
+ target=method_name,
717
+ rel_type=RelationshipType.CALLS,
718
+ file_path=file_path,
719
+ content=content,
720
+ offset=match.start()
721
+ ))
722
+
723
+ return relationships
724
+
725
+ def _parse_inheritance(self, inheritance: str) -> List[str]:
726
+ """Parse inheritance/conformance clause to extract types."""
727
+ # Remove generic parameters and where clauses
728
+ clean = re.sub(r'<[^>]*>', '', inheritance)
729
+ clean = re.sub(r'\s+where\s+.*', '', clean)
730
+
731
+ types = []
732
+ for part in clean.split(','):
733
+ part = part.strip()
734
+ if part:
735
+ # Get just the type name
736
+ type_name = part.split()[0].strip()
737
+ if type_name:
738
+ types.append(type_name)
739
+
740
+ return types
741
+
742
+ def _find_preceding_doc(self, content: str, position: int) -> Optional[str]:
743
+ """Find doc comment preceding a position."""
744
+ before = content[:position]
745
+ lines = before.split('\n')
746
+
747
+ # Look for /// comments in preceding lines
748
+ doc_lines = []
749
+ for line in reversed(lines[:-1]):
750
+ stripped = line.strip()
751
+ if stripped.startswith('///'):
752
+ doc_lines.insert(0, stripped[3:].strip())
753
+ elif stripped.startswith('@'):
754
+ # Skip attributes
755
+ continue
756
+ elif stripped == '':
757
+ continue
758
+ else:
759
+ break
760
+
761
+ return '\n'.join(doc_lines) if doc_lines else None
762
+
763
+ def _find_block_end(self, content: str, start: int) -> int:
764
+ """Find the end line of a code block."""
765
+ brace_count = 0
766
+ in_block = False
767
+
768
+ for i, char in enumerate(content[start:], start):
769
+ if char == '{':
770
+ brace_count += 1
771
+ in_block = True
772
+ elif char == '}':
773
+ brace_count -= 1
774
+ if in_block and brace_count == 0:
775
+ return content[:i].count('\n') + 1
776
+
777
+ return content[:start].count('\n') + 1
778
+
779
+ def parse_multiple_files(
780
+ self,
781
+ files: List[Tuple[str, Optional[str]]],
782
+ resolve_cross_file: bool = True,
783
+ max_workers: int = 4
784
+ ) -> Dict[str, ParseResult]:
785
+ """
786
+ Parse multiple Swift files with optional cross-file resolution.
787
+
788
+ Args:
789
+ files: List of (file_path, content) tuples
790
+ resolve_cross_file: Whether to resolve cross-file references
791
+ max_workers: Maximum number of parallel workers
792
+
793
+ Returns:
794
+ Dict mapping file paths to ParseResult objects
795
+ """
796
+ results = {}
797
+
798
+ def parse_single(file_info: Tuple[str, Optional[str]]) -> Tuple[str, Optional[ParseResult]]:
799
+ file_path, content = file_info
800
+ if content is None:
801
+ try:
802
+ with open(file_path, 'r', encoding='utf-8') as f:
803
+ content = f.read()
804
+ except Exception as e:
805
+ logger.warning(f"Failed to read {file_path}: {e}")
806
+ return file_path, None
807
+
808
+ try:
809
+ result = self.parse(content, file_path)
810
+ return file_path, result
811
+ except Exception as e:
812
+ logger.warning(f"Failed to parse {file_path}: {e}")
813
+ return file_path, None
814
+
815
+ # Parse files in parallel
816
+ with ThreadPoolExecutor(max_workers=max_workers) as executor:
817
+ for file_path, result in executor.map(parse_single, files):
818
+ if result:
819
+ results[file_path] = result
820
+
821
+ # Register symbols for cross-file resolution
822
+ if resolve_cross_file:
823
+ for symbol in result.symbols:
824
+ self._symbol_to_file[symbol.name] = file_path
825
+ if symbol.qualified_name:
826
+ self._symbol_to_file[symbol.qualified_name] = file_path
827
+
828
+ return results
829
+
830
+
831
+ # Register the parser
832
+ parser_registry.register_parser(SwiftParser())