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.
- 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 +155 -0
- alita_sdk/cli/agent_loader.py +215 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3601 -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/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 +11 -0
- alita_sdk/configurations/ado.py +148 -2
- alita_sdk/configurations/azure_search.py +1 -1
- alita_sdk/configurations/bigquery.py +1 -1
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/browser.py +18 -0
- alita_sdk/configurations/carrier.py +19 -0
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/delta_lake.py +1 -1
- alita_sdk/configurations/figma.py +76 -5
- alita_sdk/configurations/github.py +65 -1
- alita_sdk/configurations/gitlab.py +81 -0
- alita_sdk/configurations/google_places.py +17 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +111 -0
- alita_sdk/configurations/postman.py +1 -1
- alita_sdk/configurations/qtest.py +72 -3
- alita_sdk/configurations/report_portal.py +115 -0
- alita_sdk/configurations/salesforce.py +19 -0
- alita_sdk/configurations/service_now.py +1 -12
- alita_sdk/configurations/sharepoint.py +167 -0
- alita_sdk/configurations/sonar.py +18 -0
- alita_sdk/configurations/sql.py +20 -0
- alita_sdk/configurations/testio.py +101 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +94 -1
- alita_sdk/configurations/zephyr_enterprise.py +94 -1
- alita_sdk/configurations/zephyr_essential.py +95 -0
- alita_sdk/runtime/clients/artifact.py +21 -4
- alita_sdk/runtime/clients/client.py +458 -67
- 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 +352 -0
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +183 -43
- 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 +209 -31
- alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
- alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
- alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
- alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
- alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
- alita_sdk/runtime/langchain/langraph_agent.py +407 -92
- alita_sdk/runtime/langchain/utils.py +102 -8
- 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 +28 -0
- alita_sdk/runtime/toolkits/application.py +14 -4
- alita_sdk/runtime/toolkits/artifact.py +24 -9
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +780 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +11 -6
- alita_sdk/runtime/toolkits/tools.py +314 -70
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +24 -0
- alita_sdk/runtime/tools/application.py +16 -4
- alita_sdk/runtime/tools/artifact.py +367 -33
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +100 -4
- alita_sdk/runtime/tools/graph.py +81 -0
- alita_sdk/runtime/tools/image_generation.py +218 -0
- alita_sdk/runtime/tools/llm.py +1013 -177
- 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 +3 -1
- 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 -1
- alita_sdk/runtime/tools/sandbox.py +375 -0
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +69 -65
- alita_sdk/runtime/tools/vectorstore_base.py +163 -90
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- 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/streamlit.py +41 -14
- alita_sdk/runtime/utils/toolkit_utils.py +28 -9
- alita_sdk/runtime/utils/utils.py +48 -0
- alita_sdk/tools/__init__.py +135 -37
- alita_sdk/tools/ado/__init__.py +2 -2
- alita_sdk/tools/ado/repos/__init__.py +15 -19
- alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
- alita_sdk/tools/ado/test_plan/__init__.py +26 -8
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
- alita_sdk/tools/ado/wiki/__init__.py +27 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
- alita_sdk/tools/ado/work_item/__init__.py +27 -12
- alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +12 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +14 -11
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +13 -8
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +454 -110
- alita_sdk/tools/bitbucket/__init__.py +27 -19
- alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
- alita_sdk/tools/browser/__init__.py +41 -16
- alita_sdk/tools/browser/crawler.py +3 -1
- alita_sdk/tools/browser/utils.py +15 -6
- alita_sdk/tools/carrier/__init__.py +18 -17
- alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
- alita_sdk/tools/carrier/excel_reporter.py +8 -4
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/codeparser.py +1 -1
- alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
- 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 +11 -7
- alita_sdk/tools/cloud/azure/__init__.py +11 -7
- alita_sdk/tools/cloud/gcp/__init__.py +11 -7
- alita_sdk/tools/cloud/k8s/__init__.py +11 -7
- alita_sdk/tools/code/linter/__init__.py +9 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +20 -13
- alita_sdk/tools/code_indexer_toolkit.py +199 -0
- alita_sdk/tools/confluence/__init__.py +21 -14
- alita_sdk/tools/confluence/api_wrapper.py +197 -58
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +11 -5
- alita_sdk/tools/elastic/__init__.py +10 -8
- alita_sdk/tools/elitea_base.py +546 -64
- alita_sdk/tools/figma/__init__.py +11 -8
- alita_sdk/tools/figma/api_wrapper.py +352 -153
- alita_sdk/tools/github/__init__.py +17 -17
- alita_sdk/tools/github/api_wrapper.py +9 -26
- alita_sdk/tools/github/github_client.py +81 -12
- alita_sdk/tools/github/schemas.py +2 -1
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/gitlab/__init__.py +18 -13
- alita_sdk/tools/gitlab/api_wrapper.py +224 -80
- alita_sdk/tools/gitlab_org/__init__.py +13 -10
- alita_sdk/tools/google/bigquery/__init__.py +13 -13
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +20 -11
- alita_sdk/tools/jira/__init__.py +21 -11
- alita_sdk/tools/jira/api_wrapper.py +315 -168
- alita_sdk/tools/keycloak/__init__.py +10 -8
- alita_sdk/tools/localgit/__init__.py +8 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +38 -14
- alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
- alita_sdk/tools/ocr/__init__.py +10 -8
- alita_sdk/tools/openapi/__init__.py +281 -108
- alita_sdk/tools/openapi/api_wrapper.py +883 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +18 -11
- alita_sdk/tools/pandas/api_wrapper.py +40 -45
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +10 -11
- alita_sdk/tools/postman/api_wrapper.py +19 -8
- alita_sdk/tools/postman/postman_analysis.py +8 -1
- alita_sdk/tools/pptx/__init__.py +10 -10
- alita_sdk/tools/qtest/__init__.py +21 -14
- alita_sdk/tools/qtest/api_wrapper.py +1784 -88
- alita_sdk/tools/rally/__init__.py +12 -10
- alita_sdk/tools/report_portal/__init__.py +22 -16
- alita_sdk/tools/salesforce/__init__.py +21 -16
- alita_sdk/tools/servicenow/__init__.py +20 -16
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +16 -14
- alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +11 -7
- alita_sdk/tools/sql/__init__.py +21 -19
- alita_sdk/tools/sql/api_wrapper.py +71 -23
- alita_sdk/tools/testio/__init__.py +20 -13
- alita_sdk/tools/testrail/__init__.py +12 -11
- alita_sdk/tools/testrail/api_wrapper.py +214 -46
- alita_sdk/tools/utils/__init__.py +28 -4
- alita_sdk/tools/utils/content_parser.py +182 -62
- alita_sdk/tools/utils/text_operations.py +254 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
- alita_sdk/tools/xray/__init__.py +17 -14
- alita_sdk/tools/xray/api_wrapper.py +58 -113
- alita_sdk/tools/yagmail/__init__.py +8 -3
- alita_sdk/tools/zephyr/__init__.py +11 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +15 -9
- alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
- alita_sdk/tools/zephyr_essential/__init__.py +15 -10
- alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
- alita_sdk/tools/zephyr_essential/client.py +6 -4
- alita_sdk/tools/zephyr_scale/__init__.py +12 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
- alita_sdk/tools/zephyr_squad/__init__.py +11 -7
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/METADATA +184 -37
- alita_sdk-0.3.562.dist-info/RECORD +450 -0
- alita_sdk-0.3.562.dist-info/entry_points.txt +2 -0
- alita_sdk/tools/bitbucket/tools.py +0 -304
- alita_sdk-0.3.257.dist-info/RECORD +0 -343
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/licenses/LICENSE +0 -0
- {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())
|