alita-sdk 0.3.379__py3-none-any.whl → 0.3.627__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.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +156 -0
- alita_sdk/cli/agent_loader.py +245 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3113 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +91 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +93 -0
- alita_sdk/configurations/zephyr_enterprise.py +93 -0
- alita_sdk/configurations/zephyr_essential.py +75 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +388 -46
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +8 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +157 -39
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
- alita_sdk/runtime/langchain/langraph_agent.py +405 -84
- alita_sdk/runtime/langchain/utils.py +106 -7
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +31 -0
- alita_sdk/runtime/toolkits/application.py +29 -10
- alita_sdk/runtime/toolkits/artifact.py +20 -11
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +783 -0
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +356 -69
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +10 -3
- alita_sdk/runtime/tools/application.py +27 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +67 -35
- alita_sdk/runtime/tools/graph.py +10 -4
- alita_sdk/runtime/tools/image_generation.py +148 -46
- alita_sdk/runtime/tools/llm.py +1003 -128
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +65 -48
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +9 -3
- alita_sdk/runtime/tools/vectorstore_base.py +70 -14
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +40 -13
- alita_sdk/runtime/utils/toolkit_utils.py +30 -9
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +134 -35
- alita_sdk/tools/ado/repos/__init__.py +51 -32
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -13
- alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +271 -84
- alita_sdk/tools/bitbucket/__init__.py +17 -11
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +11 -8
- alita_sdk/tools/code_indexer_toolkit.py +82 -22
- alita_sdk/tools/confluence/__init__.py +22 -16
- alita_sdk/tools/confluence/api_wrapper.py +107 -30
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +14 -15
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +16 -11
- alita_sdk/tools/gitlab/api_wrapper.py +218 -48
- alita_sdk/tools/gitlab_org/__init__.py +10 -9
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +11 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -10
- alita_sdk/tools/jira/api_wrapper.py +92 -41
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +12 -4
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +491 -106
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +10 -9
- alita_sdk/tools/pptx/__init__.py +11 -10
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +31 -11
- alita_sdk/tools/qtest/api_wrapper.py +2135 -86
- alita_sdk/tools/rally/__init__.py +10 -9
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -8
- alita_sdk/tools/salesforce/__init__.py +10 -8
- alita_sdk/tools/servicenow/__init__.py +17 -15
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -7
- alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +10 -7
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +12 -9
- alita_sdk/tools/testio/__init__.py +10 -7
- alita_sdk/tools/testrail/__init__.py +11 -10
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +103 -18
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
- alita_sdk/tools/xray/__init__.py +13 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +10 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
- alita_sdk/tools/zephyr_essential/__init__.py +10 -7
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -7
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.379.dist-info/RECORD +0 -360
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.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())
|