alita-sdk 0.3.462__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/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +15 -3
- alita_sdk/cli/agent_loader.py +56 -8
- alita_sdk/cli/agent_ui.py +93 -31
- alita_sdk/cli/agents.py +2274 -230
- alita_sdk/cli/callbacks.py +96 -25
- alita_sdk/cli/cli.py +10 -1
- alita_sdk/cli/config.py +162 -9
- 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/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -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 +14 -17
- alita_sdk/cli/toolkit_loader.py +35 -5
- alita_sdk/cli/tools/__init__.py +36 -2
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +910 -64
- 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 +0 -3
- alita_sdk/configurations/confluence.py +76 -42
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +17 -5
- 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/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +353 -48
- alita_sdk/runtime/clients/sandbox_client.py +0 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +123 -26
- alita_sdk/runtime/langchain/constants.py +642 -1
- 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 +6 -3
- 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 +12 -7
- alita_sdk/runtime/langchain/langraph_agent.py +279 -73
- alita_sdk/runtime/langchain/utils.py +82 -15
- alita_sdk/runtime/llms/preloaded.py +2 -6
- 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 +7 -0
- alita_sdk/runtime/toolkits/application.py +21 -9
- alita_sdk/runtime/toolkits/artifact.py +15 -5
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +139 -251
- 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 +238 -32
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +3 -1
- alita_sdk/runtime/tools/application.py +20 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +43 -15
- alita_sdk/runtime/tools/image_generation.py +50 -44
- alita_sdk/runtime/tools/llm.py +852 -67
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_remote_tool.py +25 -10
- alita_sdk/runtime/tools/mcp_server_tool.py +7 -6
- 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 +9 -6
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +7 -2
- alita_sdk/runtime/tools/vectorstore_base.py +51 -11
- 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 +202 -5
- alita_sdk/runtime/utils/mcp_sse_client.py +36 -7
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +6 -10
- alita_sdk/runtime/utils/toolkit_utils.py +16 -5
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +113 -29
- alita_sdk/tools/ado/repos/__init__.py +51 -33
- 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 -8
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -9
- alita_sdk/tools/ado/work_item/ado_wrapper.py +56 -3
- 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 +170 -45
- alita_sdk/tools/bitbucket/__init__.py +17 -12
- 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/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 +10 -7
- alita_sdk/tools/code_indexer_toolkit.py +73 -23
- alita_sdk/tools/confluence/__init__.py +21 -15
- alita_sdk/tools/confluence/api_wrapper.py +78 -23
- alita_sdk/tools/confluence/loader.py +4 -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 +13 -14
- 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 +15 -11
- alita_sdk/tools/gitlab/api_wrapper.py +207 -41
- alita_sdk/tools/gitlab_org/__init__.py +10 -8
- 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 +10 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -11
- alita_sdk/tools/jira/api_wrapper.py +91 -40
- 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 +11 -3
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +490 -114
- 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 +11 -11
- alita_sdk/tools/pptx/__init__.py +10 -9
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +30 -10
- alita_sdk/tools/qtest/api_wrapper.py +430 -13
- alita_sdk/tools/rally/__init__.py +10 -8
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -9
- alita_sdk/tools/salesforce/__init__.py +10 -9
- alita_sdk/tools/servicenow/__init__.py +17 -14
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -8
- alita_sdk/tools/sharepoint/api_wrapper.py +4 -4
- alita_sdk/tools/slack/__init__.py +10 -8
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +11 -9
- alita_sdk/tools/testio/__init__.py +10 -8
- alita_sdk/tools/testrail/__init__.py +11 -8
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +77 -3
- 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 +17 -13
- alita_sdk/tools/xray/__init__.py +12 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +9 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -8
- alita_sdk/tools/zephyr_essential/__init__.py +10 -8
- 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 -9
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -8
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +147 -7
- 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.462.dist-info/RECORD +0 -384
- alita_sdk-0.3.462.dist-info/entry_points.txt +0 -2
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.462.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Rust Parser - Regex-based parser for Rust source files.
|
|
3
|
+
|
|
4
|
+
Extracts symbols and relationships from .rs files using comprehensive
|
|
5
|
+
regex patterns. Supports Rust-specific features like traits, lifetimes,
|
|
6
|
+
macros, async/await, 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 Rust regex patterns
|
|
34
|
+
PATTERNS = {
|
|
35
|
+
# Module declaration
|
|
36
|
+
'mod_declaration': re.compile(
|
|
37
|
+
r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?mod\s+(\w+)\s*[{;]',
|
|
38
|
+
re.MULTILINE
|
|
39
|
+
),
|
|
40
|
+
|
|
41
|
+
# Use statements
|
|
42
|
+
'use': re.compile(
|
|
43
|
+
r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?use\s+([\w:]+)(?:::\{([^}]+)\})?(?:\s+as\s+(\w+))?\s*;',
|
|
44
|
+
re.MULTILINE
|
|
45
|
+
),
|
|
46
|
+
'use_glob': re.compile(
|
|
47
|
+
r'^\s*(?:pub(?:\([^)]+\))?\s+)?use\s+([\w:]+)::\*\s*;',
|
|
48
|
+
re.MULTILINE
|
|
49
|
+
),
|
|
50
|
+
|
|
51
|
+
# Struct declarations
|
|
52
|
+
'struct': re.compile(
|
|
53
|
+
r'^\s*(?:#\[[^\]]+\]\s*)*' # Attributes
|
|
54
|
+
r'(?:(pub(?:\([^)]+\))?)\s+)?'
|
|
55
|
+
r'struct\s+(\w+)'
|
|
56
|
+
r'(?:<[^>]+>)?' # Generic parameters
|
|
57
|
+
r'(?:\s*\([^)]*\))?' # Tuple struct
|
|
58
|
+
r'(?:\s+where\s+[^{;]+)?', # Where clause
|
|
59
|
+
re.MULTILINE
|
|
60
|
+
),
|
|
61
|
+
|
|
62
|
+
# Enum declarations
|
|
63
|
+
'enum': re.compile(
|
|
64
|
+
r'^\s*(?:#\[[^\]]+\]\s*)*'
|
|
65
|
+
r'(?:(pub(?:\([^)]+\))?)\s+)?'
|
|
66
|
+
r'enum\s+(\w+)'
|
|
67
|
+
r'(?:<[^>]+>)?' # Generic parameters
|
|
68
|
+
r'(?:\s+where\s+[^{]+)?', # Where clause
|
|
69
|
+
re.MULTILINE
|
|
70
|
+
),
|
|
71
|
+
|
|
72
|
+
# Trait declarations
|
|
73
|
+
'trait': re.compile(
|
|
74
|
+
r'^\s*(?:#\[[^\]]+\]\s*)*'
|
|
75
|
+
r'(?:(pub(?:\([^)]+\))?)\s+)?'
|
|
76
|
+
r'(?:(unsafe)\s+)?'
|
|
77
|
+
r'trait\s+(\w+)'
|
|
78
|
+
r'(?:<[^>]+>)?' # Generic parameters
|
|
79
|
+
r'(?:\s*:\s*([^{]+))?' # Supertraits
|
|
80
|
+
r'(?:\s+where\s+[^{]+)?', # Where clause
|
|
81
|
+
re.MULTILINE
|
|
82
|
+
),
|
|
83
|
+
|
|
84
|
+
# Impl blocks
|
|
85
|
+
'impl': re.compile(
|
|
86
|
+
r'^\s*(?:(unsafe)\s+)?'
|
|
87
|
+
r'impl\s*'
|
|
88
|
+
r'(?:<[^>]+>\s*)?' # Generic parameters
|
|
89
|
+
r'(?:(\w+)\s+for\s+)?' # Trait impl
|
|
90
|
+
r'(\w+)' # Type
|
|
91
|
+
r'(?:<[^>]+>)?' # Type parameters
|
|
92
|
+
r'(?:\s+where\s+[^{]+)?', # Where clause
|
|
93
|
+
re.MULTILINE
|
|
94
|
+
),
|
|
95
|
+
|
|
96
|
+
# Function declarations
|
|
97
|
+
'function': re.compile(
|
|
98
|
+
r'^\s*(?:#\[[^\]]+\]\s*)*'
|
|
99
|
+
r'(?:(pub(?:\([^)]+\))?)\s+)?'
|
|
100
|
+
r'(?:(const|async|unsafe|extern(?:\s+"[^"]*")?)\s+)*'
|
|
101
|
+
r'fn\s+(\w+)'
|
|
102
|
+
r'(?:<[^>]+>)?' # Generic parameters
|
|
103
|
+
r'\s*\([^)]*\)' # Parameters
|
|
104
|
+
r'(?:\s*->\s*([^\n{;]+))?', # Return type
|
|
105
|
+
re.MULTILINE
|
|
106
|
+
),
|
|
107
|
+
|
|
108
|
+
# Type alias
|
|
109
|
+
'type_alias': re.compile(
|
|
110
|
+
r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?'
|
|
111
|
+
r'type\s+(\w+)'
|
|
112
|
+
r'(?:<[^>]+>)?' # Generic parameters
|
|
113
|
+
r'\s*=\s*([^;]+);',
|
|
114
|
+
re.MULTILINE
|
|
115
|
+
),
|
|
116
|
+
|
|
117
|
+
# Const declarations
|
|
118
|
+
'const': re.compile(
|
|
119
|
+
r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?'
|
|
120
|
+
r'const\s+(\w+)\s*:\s*([^=]+)\s*=',
|
|
121
|
+
re.MULTILINE
|
|
122
|
+
),
|
|
123
|
+
|
|
124
|
+
# Static declarations
|
|
125
|
+
'static': re.compile(
|
|
126
|
+
r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?'
|
|
127
|
+
r'static\s+(?:mut\s+)?(\w+)\s*:\s*([^=]+)\s*=',
|
|
128
|
+
re.MULTILINE
|
|
129
|
+
),
|
|
130
|
+
|
|
131
|
+
# Macro definitions
|
|
132
|
+
'macro_rules': re.compile(
|
|
133
|
+
r'^\s*(?:#\[[^\]]+\]\s*)*'
|
|
134
|
+
r'macro_rules!\s+(\w+)',
|
|
135
|
+
re.MULTILINE
|
|
136
|
+
),
|
|
137
|
+
|
|
138
|
+
# Declarative macro (macro 2.0)
|
|
139
|
+
'macro_decl': re.compile(
|
|
140
|
+
r'^\s*(?:(pub(?:\([^)]+\))?)\s+)?'
|
|
141
|
+
r'macro\s+(\w+)',
|
|
142
|
+
re.MULTILINE
|
|
143
|
+
),
|
|
144
|
+
|
|
145
|
+
# Attribute macros and derive
|
|
146
|
+
'attribute': re.compile(
|
|
147
|
+
r'#\[(\w+)(?:\([^\]]*\))?\]',
|
|
148
|
+
re.MULTILINE
|
|
149
|
+
),
|
|
150
|
+
'derive': re.compile(
|
|
151
|
+
r'#\[derive\(([^\]]+)\)\]',
|
|
152
|
+
re.MULTILINE
|
|
153
|
+
),
|
|
154
|
+
|
|
155
|
+
# Method calls
|
|
156
|
+
'method_call': re.compile(
|
|
157
|
+
r'\.(\w+)\s*(?::<[^>]+>)?\s*\(',
|
|
158
|
+
re.MULTILINE
|
|
159
|
+
),
|
|
160
|
+
|
|
161
|
+
# Function calls
|
|
162
|
+
'function_call': re.compile(
|
|
163
|
+
r'(?:^|[^\w.])(\w+)\s*(?::<[^>]+>)?\s*\(',
|
|
164
|
+
re.MULTILINE
|
|
165
|
+
),
|
|
166
|
+
|
|
167
|
+
# Macro invocations
|
|
168
|
+
'macro_call': re.compile(
|
|
169
|
+
r'(\w+)!\s*[\(\[\{]',
|
|
170
|
+
re.MULTILINE
|
|
171
|
+
),
|
|
172
|
+
|
|
173
|
+
# Type references (in type position)
|
|
174
|
+
'type_ref': re.compile(
|
|
175
|
+
r':\s*(?:&(?:\'[\w]+\s+)?(?:mut\s+)?)?([A-Z]\w*)',
|
|
176
|
+
re.MULTILINE
|
|
177
|
+
),
|
|
178
|
+
|
|
179
|
+
# Path expressions (e.g., std::collections::HashMap)
|
|
180
|
+
'path_expr': re.compile(
|
|
181
|
+
r'(\w+(?:::\w+)+)',
|
|
182
|
+
re.MULTILINE
|
|
183
|
+
),
|
|
184
|
+
|
|
185
|
+
# Struct instantiation
|
|
186
|
+
'struct_init': re.compile(
|
|
187
|
+
r'([A-Z]\w*)\s*(?:::<[^>]+>)?\s*\{',
|
|
188
|
+
re.MULTILINE
|
|
189
|
+
),
|
|
190
|
+
|
|
191
|
+
# Trait bounds
|
|
192
|
+
'trait_bound': re.compile(
|
|
193
|
+
r'(?:impl|dyn)\s+([A-Z]\w*)',
|
|
194
|
+
re.MULTILINE
|
|
195
|
+
),
|
|
196
|
+
|
|
197
|
+
# Doc comments
|
|
198
|
+
'doc_comment': re.compile(
|
|
199
|
+
r'///\s*(.+)',
|
|
200
|
+
re.MULTILINE
|
|
201
|
+
),
|
|
202
|
+
'doc_comment_block': re.compile(
|
|
203
|
+
r'/\*\*\s*([\s\S]*?)\s*\*/',
|
|
204
|
+
re.MULTILINE
|
|
205
|
+
),
|
|
206
|
+
|
|
207
|
+
# Lifetime annotations (for metadata)
|
|
208
|
+
'lifetime': re.compile(
|
|
209
|
+
r"'(\w+)",
|
|
210
|
+
re.MULTILINE
|
|
211
|
+
),
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
class RustParser(BaseParser):
|
|
216
|
+
"""Rust source code parser using regex patterns."""
|
|
217
|
+
|
|
218
|
+
# Global symbol registry for cross-file resolution
|
|
219
|
+
_global_symbols: Dict[str, Set[str]] = {}
|
|
220
|
+
_symbol_to_file: Dict[str, str] = {}
|
|
221
|
+
|
|
222
|
+
def __init__(self):
|
|
223
|
+
super().__init__("rust")
|
|
224
|
+
|
|
225
|
+
def _get_supported_extensions(self) -> Set[str]:
|
|
226
|
+
return {'.rs'}
|
|
227
|
+
|
|
228
|
+
def _make_range(self, content: str, start_offset: int, end_line: int) -> Range:
|
|
229
|
+
"""Create a Range object from content and offset."""
|
|
230
|
+
start_line = content[:start_offset].count('\n') + 1
|
|
231
|
+
last_newline = content.rfind('\n', 0, start_offset)
|
|
232
|
+
start_col = start_offset - last_newline - 1 if last_newline >= 0 else start_offset
|
|
233
|
+
return Range(
|
|
234
|
+
start=Position(line=start_line, column=start_col),
|
|
235
|
+
end=Position(line=end_line, column=0)
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
def _make_symbol(
|
|
239
|
+
self,
|
|
240
|
+
name: str,
|
|
241
|
+
symbol_type: SymbolType,
|
|
242
|
+
content: str,
|
|
243
|
+
file_path: str,
|
|
244
|
+
start_offset: int,
|
|
245
|
+
end_line: int,
|
|
246
|
+
scope: Scope = Scope.GLOBAL,
|
|
247
|
+
parent: Optional[str] = None,
|
|
248
|
+
full_name: Optional[str] = None,
|
|
249
|
+
docstring: Optional[str] = None,
|
|
250
|
+
visibility: Optional[str] = None,
|
|
251
|
+
is_static: bool = False,
|
|
252
|
+
is_async: bool = False,
|
|
253
|
+
return_type: Optional[str] = None,
|
|
254
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
255
|
+
) -> Symbol:
|
|
256
|
+
"""Create a Symbol with correct fields."""
|
|
257
|
+
return Symbol(
|
|
258
|
+
name=name,
|
|
259
|
+
symbol_type=symbol_type,
|
|
260
|
+
scope=scope,
|
|
261
|
+
range=self._make_range(content, start_offset, end_line),
|
|
262
|
+
file_path=file_path,
|
|
263
|
+
parent_symbol=parent,
|
|
264
|
+
full_name=full_name,
|
|
265
|
+
visibility=visibility,
|
|
266
|
+
is_static=is_static,
|
|
267
|
+
is_async=is_async,
|
|
268
|
+
docstring=docstring,
|
|
269
|
+
return_type=return_type,
|
|
270
|
+
metadata=metadata or {}
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def _make_relationship(
|
|
274
|
+
self,
|
|
275
|
+
source: str,
|
|
276
|
+
target: str,
|
|
277
|
+
rel_type: RelationshipType,
|
|
278
|
+
file_path: str,
|
|
279
|
+
content: str,
|
|
280
|
+
offset: int,
|
|
281
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
282
|
+
) -> Relationship:
|
|
283
|
+
"""Create a Relationship with correct fields."""
|
|
284
|
+
return Relationship(
|
|
285
|
+
source_symbol=source,
|
|
286
|
+
target_symbol=target,
|
|
287
|
+
relationship_type=rel_type,
|
|
288
|
+
source_file=file_path,
|
|
289
|
+
source_range=self._make_range(content, offset, content[:offset].count('\n') + 1),
|
|
290
|
+
metadata=metadata or {}
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def parse_file(self, file_path: str, content: Optional[str] = None) -> ParseResult:
|
|
294
|
+
"""Parse Rust source code and extract symbols and relationships."""
|
|
295
|
+
if content is None:
|
|
296
|
+
try:
|
|
297
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
298
|
+
content = f.read()
|
|
299
|
+
except Exception as e:
|
|
300
|
+
return ParseResult(
|
|
301
|
+
file_path=file_path,
|
|
302
|
+
language=self.language,
|
|
303
|
+
symbols=[],
|
|
304
|
+
relationships=[],
|
|
305
|
+
errors=[str(e)]
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
symbols = self._extract_symbols(content, file_path)
|
|
309
|
+
relationships = self._extract_relationships(content, file_path, symbols)
|
|
310
|
+
|
|
311
|
+
return ParseResult(
|
|
312
|
+
symbols=symbols,
|
|
313
|
+
relationships=relationships,
|
|
314
|
+
file_path=file_path,
|
|
315
|
+
language=self.language
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
def _extract_symbols(self, content: str, file_path: str) -> List[Symbol]:
|
|
319
|
+
"""Extract all symbols from Rust source code."""
|
|
320
|
+
symbols = []
|
|
321
|
+
module_name = self._extract_module_path(file_path)
|
|
322
|
+
|
|
323
|
+
# Extract module declarations
|
|
324
|
+
for match in PATTERNS['mod_declaration'].finditer(content):
|
|
325
|
+
visibility, name = match.groups()
|
|
326
|
+
|
|
327
|
+
symbols.append(self._make_symbol(
|
|
328
|
+
name=name,
|
|
329
|
+
symbol_type=SymbolType.MODULE,
|
|
330
|
+
content=content,
|
|
331
|
+
file_path=file_path,
|
|
332
|
+
start_offset=match.start(),
|
|
333
|
+
end_line=self._find_block_end(content, match.end()) if '{' in match.group() else content[:match.start()].count('\n') + 1,
|
|
334
|
+
full_name=f"{module_name}::{name}" if module_name else name,
|
|
335
|
+
visibility=visibility or 'private',
|
|
336
|
+
metadata={'visibility': visibility or 'private'}
|
|
337
|
+
))
|
|
338
|
+
|
|
339
|
+
# Extract structs
|
|
340
|
+
for match in PATTERNS['struct'].finditer(content):
|
|
341
|
+
visibility, name = match.groups()
|
|
342
|
+
|
|
343
|
+
docstring = self._find_preceding_doc(content, match.start())
|
|
344
|
+
derives = self._find_derives_before(content, match.start())
|
|
345
|
+
|
|
346
|
+
metadata = {'visibility': visibility or 'private'}
|
|
347
|
+
if derives:
|
|
348
|
+
metadata['derives'] = derives
|
|
349
|
+
|
|
350
|
+
symbols.append(self._make_symbol(
|
|
351
|
+
name=name,
|
|
352
|
+
symbol_type=SymbolType.CLASS,
|
|
353
|
+
content=content,
|
|
354
|
+
file_path=file_path,
|
|
355
|
+
start_offset=match.start(),
|
|
356
|
+
end_line=self._find_block_end(content, match.end()),
|
|
357
|
+
full_name=f"{module_name}::{name}" if module_name else name,
|
|
358
|
+
docstring=docstring,
|
|
359
|
+
visibility=visibility or 'private',
|
|
360
|
+
metadata=metadata
|
|
361
|
+
))
|
|
362
|
+
|
|
363
|
+
# Extract enums
|
|
364
|
+
for match in PATTERNS['enum'].finditer(content):
|
|
365
|
+
visibility, name = match.groups()
|
|
366
|
+
|
|
367
|
+
docstring = self._find_preceding_doc(content, match.start())
|
|
368
|
+
derives = self._find_derives_before(content, match.start())
|
|
369
|
+
|
|
370
|
+
metadata = {'visibility': visibility or 'private'}
|
|
371
|
+
if derives:
|
|
372
|
+
metadata['derives'] = derives
|
|
373
|
+
|
|
374
|
+
symbols.append(self._make_symbol(
|
|
375
|
+
name=name,
|
|
376
|
+
symbol_type=SymbolType.ENUM,
|
|
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=f"{module_name}::{name}" if module_name else name,
|
|
382
|
+
docstring=docstring,
|
|
383
|
+
visibility=visibility or 'private',
|
|
384
|
+
metadata=metadata
|
|
385
|
+
))
|
|
386
|
+
|
|
387
|
+
# Extract traits
|
|
388
|
+
for match in PATTERNS['trait'].finditer(content):
|
|
389
|
+
visibility, unsafe_kw, name, supertraits = match.groups()
|
|
390
|
+
|
|
391
|
+
docstring = self._find_preceding_doc(content, match.start())
|
|
392
|
+
|
|
393
|
+
metadata = {'visibility': visibility or 'private'}
|
|
394
|
+
if unsafe_kw:
|
|
395
|
+
metadata['unsafe'] = True
|
|
396
|
+
if supertraits:
|
|
397
|
+
metadata['supertraits'] = [s.strip() for s in supertraits.split('+')]
|
|
398
|
+
|
|
399
|
+
symbols.append(self._make_symbol(
|
|
400
|
+
name=name,
|
|
401
|
+
symbol_type=SymbolType.INTERFACE,
|
|
402
|
+
content=content,
|
|
403
|
+
file_path=file_path,
|
|
404
|
+
start_offset=match.start(),
|
|
405
|
+
end_line=self._find_block_end(content, match.end()),
|
|
406
|
+
full_name=f"{module_name}::{name}" if module_name else name,
|
|
407
|
+
docstring=docstring,
|
|
408
|
+
visibility=visibility or 'private',
|
|
409
|
+
metadata=metadata
|
|
410
|
+
))
|
|
411
|
+
|
|
412
|
+
# Extract functions
|
|
413
|
+
for match in PATTERNS['function'].finditer(content):
|
|
414
|
+
visibility, modifiers, name, return_type = match.groups()
|
|
415
|
+
|
|
416
|
+
docstring = self._find_preceding_doc(content, match.start())
|
|
417
|
+
|
|
418
|
+
metadata = {'visibility': visibility or 'private'}
|
|
419
|
+
is_async = False
|
|
420
|
+
if modifiers:
|
|
421
|
+
modifier_list = modifiers.strip().split()
|
|
422
|
+
metadata['modifiers'] = modifier_list
|
|
423
|
+
if 'async' in modifiers:
|
|
424
|
+
is_async = True
|
|
425
|
+
metadata['is_async'] = True
|
|
426
|
+
if 'unsafe' in modifiers:
|
|
427
|
+
metadata['is_unsafe'] = True
|
|
428
|
+
if 'const' in modifiers:
|
|
429
|
+
metadata['is_const'] = True
|
|
430
|
+
if return_type:
|
|
431
|
+
metadata['return_type'] = return_type.strip()
|
|
432
|
+
|
|
433
|
+
symbols.append(self._make_symbol(
|
|
434
|
+
name=name,
|
|
435
|
+
symbol_type=SymbolType.FUNCTION,
|
|
436
|
+
content=content,
|
|
437
|
+
file_path=file_path,
|
|
438
|
+
start_offset=match.start(),
|
|
439
|
+
end_line=self._find_block_end(content, match.end()),
|
|
440
|
+
full_name=f"{module_name}::{name}" if module_name else name,
|
|
441
|
+
docstring=docstring,
|
|
442
|
+
visibility=visibility or 'private',
|
|
443
|
+
is_async=is_async,
|
|
444
|
+
return_type=return_type.strip() if return_type else None,
|
|
445
|
+
metadata=metadata
|
|
446
|
+
))
|
|
447
|
+
|
|
448
|
+
# Extract type aliases
|
|
449
|
+
for match in PATTERNS['type_alias'].finditer(content):
|
|
450
|
+
visibility, name, aliased_type = match.groups()
|
|
451
|
+
|
|
452
|
+
symbols.append(self._make_symbol(
|
|
453
|
+
name=name,
|
|
454
|
+
symbol_type=SymbolType.TYPE_ALIAS,
|
|
455
|
+
content=content,
|
|
456
|
+
file_path=file_path,
|
|
457
|
+
start_offset=match.start(),
|
|
458
|
+
end_line=content[:match.start()].count('\n') + 1,
|
|
459
|
+
full_name=f"{module_name}::{name}" if module_name else name,
|
|
460
|
+
visibility=visibility or 'private',
|
|
461
|
+
metadata={
|
|
462
|
+
'visibility': visibility or 'private',
|
|
463
|
+
'aliased_type': aliased_type.strip()
|
|
464
|
+
}
|
|
465
|
+
))
|
|
466
|
+
|
|
467
|
+
# Extract constants
|
|
468
|
+
for match in PATTERNS['const'].finditer(content):
|
|
469
|
+
visibility, name, const_type = match.groups()
|
|
470
|
+
|
|
471
|
+
symbols.append(self._make_symbol(
|
|
472
|
+
name=name,
|
|
473
|
+
symbol_type=SymbolType.CONSTANT,
|
|
474
|
+
content=content,
|
|
475
|
+
file_path=file_path,
|
|
476
|
+
start_offset=match.start(),
|
|
477
|
+
end_line=content[:match.start()].count('\n') + 1,
|
|
478
|
+
full_name=f"{module_name}::{name}" if module_name else name,
|
|
479
|
+
visibility=visibility or 'private',
|
|
480
|
+
metadata={
|
|
481
|
+
'visibility': visibility or 'private',
|
|
482
|
+
'type': const_type.strip()
|
|
483
|
+
}
|
|
484
|
+
))
|
|
485
|
+
|
|
486
|
+
# Extract statics
|
|
487
|
+
for match in PATTERNS['static'].finditer(content):
|
|
488
|
+
visibility, name, static_type = match.groups()
|
|
489
|
+
|
|
490
|
+
symbols.append(self._make_symbol(
|
|
491
|
+
name=name,
|
|
492
|
+
symbol_type=SymbolType.VARIABLE,
|
|
493
|
+
content=content,
|
|
494
|
+
file_path=file_path,
|
|
495
|
+
start_offset=match.start(),
|
|
496
|
+
end_line=content[:match.start()].count('\n') + 1,
|
|
497
|
+
full_name=f"{module_name}::{name}" if module_name else name,
|
|
498
|
+
visibility=visibility or 'private',
|
|
499
|
+
is_static=True,
|
|
500
|
+
metadata={
|
|
501
|
+
'visibility': visibility or 'private',
|
|
502
|
+
'type': static_type.strip(),
|
|
503
|
+
'is_static': True
|
|
504
|
+
}
|
|
505
|
+
))
|
|
506
|
+
|
|
507
|
+
# Extract macro_rules definitions
|
|
508
|
+
for match in PATTERNS['macro_rules'].finditer(content):
|
|
509
|
+
name = match.group(1)
|
|
510
|
+
|
|
511
|
+
docstring = self._find_preceding_doc(content, match.start())
|
|
512
|
+
|
|
513
|
+
symbols.append(self._make_symbol(
|
|
514
|
+
name=name,
|
|
515
|
+
symbol_type=SymbolType.FUNCTION,
|
|
516
|
+
content=content,
|
|
517
|
+
file_path=file_path,
|
|
518
|
+
start_offset=match.start(),
|
|
519
|
+
end_line=self._find_block_end(content, match.end()),
|
|
520
|
+
full_name=f"{module_name}::{name}" if module_name else name,
|
|
521
|
+
docstring=docstring,
|
|
522
|
+
metadata={'is_macro': True}
|
|
523
|
+
))
|
|
524
|
+
|
|
525
|
+
return symbols
|
|
526
|
+
|
|
527
|
+
def _extract_relationships(
|
|
528
|
+
self,
|
|
529
|
+
content: str,
|
|
530
|
+
file_path: str,
|
|
531
|
+
symbols: List[Symbol]
|
|
532
|
+
) -> List[Relationship]:
|
|
533
|
+
"""Extract relationships from Rust source code."""
|
|
534
|
+
relationships = []
|
|
535
|
+
current_scope = Path(file_path).stem
|
|
536
|
+
|
|
537
|
+
# Extract use statements
|
|
538
|
+
for match in PATTERNS['use'].finditer(content):
|
|
539
|
+
visibility, path, items, alias = match.groups()
|
|
540
|
+
|
|
541
|
+
if items:
|
|
542
|
+
# Multiple imports: use foo::{bar, baz}
|
|
543
|
+
for item in items.split(','):
|
|
544
|
+
item = item.strip()
|
|
545
|
+
if item:
|
|
546
|
+
relationships.append(self._make_relationship(
|
|
547
|
+
source=current_scope,
|
|
548
|
+
target=f"{path}::{item}",
|
|
549
|
+
rel_type=RelationshipType.IMPORTS,
|
|
550
|
+
file_path=file_path,
|
|
551
|
+
content=content,
|
|
552
|
+
offset=match.start()
|
|
553
|
+
))
|
|
554
|
+
else:
|
|
555
|
+
# Single import
|
|
556
|
+
relationships.append(self._make_relationship(
|
|
557
|
+
source=current_scope,
|
|
558
|
+
target=path,
|
|
559
|
+
rel_type=RelationshipType.IMPORTS,
|
|
560
|
+
file_path=file_path,
|
|
561
|
+
content=content,
|
|
562
|
+
offset=match.start(),
|
|
563
|
+
metadata={'alias': alias} if alias else None
|
|
564
|
+
))
|
|
565
|
+
|
|
566
|
+
# Extract glob imports
|
|
567
|
+
for match in PATTERNS['use_glob'].finditer(content):
|
|
568
|
+
path = match.group(1)
|
|
569
|
+
|
|
570
|
+
relationships.append(self._make_relationship(
|
|
571
|
+
source=current_scope,
|
|
572
|
+
target=path,
|
|
573
|
+
rel_type=RelationshipType.IMPORTS,
|
|
574
|
+
file_path=file_path,
|
|
575
|
+
content=content,
|
|
576
|
+
offset=match.start(),
|
|
577
|
+
metadata={'glob': True}
|
|
578
|
+
))
|
|
579
|
+
|
|
580
|
+
# Extract impl blocks (trait implementations)
|
|
581
|
+
for match in PATTERNS['impl'].finditer(content):
|
|
582
|
+
unsafe_kw, trait_name, type_name = match.groups()
|
|
583
|
+
|
|
584
|
+
if trait_name:
|
|
585
|
+
# Trait implementation
|
|
586
|
+
relationships.append(self._make_relationship(
|
|
587
|
+
source=type_name,
|
|
588
|
+
target=trait_name,
|
|
589
|
+
rel_type=RelationshipType.IMPLEMENTATION,
|
|
590
|
+
file_path=file_path,
|
|
591
|
+
content=content,
|
|
592
|
+
offset=match.start(),
|
|
593
|
+
metadata={'unsafe': True} if unsafe_kw else None
|
|
594
|
+
))
|
|
595
|
+
|
|
596
|
+
# Extract trait supertraits
|
|
597
|
+
for match in PATTERNS['trait'].finditer(content):
|
|
598
|
+
_, _, trait_name, supertraits = match.groups()
|
|
599
|
+
if supertraits:
|
|
600
|
+
for supertrait in supertraits.split('+'):
|
|
601
|
+
supertrait = supertrait.strip()
|
|
602
|
+
if supertrait and not supertrait.startswith("'"): # Skip lifetimes
|
|
603
|
+
relationships.append(self._make_relationship(
|
|
604
|
+
source=trait_name,
|
|
605
|
+
target=supertrait,
|
|
606
|
+
rel_type=RelationshipType.INHERITANCE,
|
|
607
|
+
file_path=file_path,
|
|
608
|
+
content=content,
|
|
609
|
+
offset=match.start()
|
|
610
|
+
))
|
|
611
|
+
|
|
612
|
+
# Extract derive macro usages
|
|
613
|
+
for match in PATTERNS['derive'].finditer(content):
|
|
614
|
+
derives = match.group(1)
|
|
615
|
+
|
|
616
|
+
for derive in derives.split(','):
|
|
617
|
+
derive = derive.strip()
|
|
618
|
+
if derive:
|
|
619
|
+
relationships.append(self._make_relationship(
|
|
620
|
+
source=current_scope,
|
|
621
|
+
target=derive,
|
|
622
|
+
rel_type=RelationshipType.DECORATES,
|
|
623
|
+
file_path=file_path,
|
|
624
|
+
content=content,
|
|
625
|
+
offset=match.start(),
|
|
626
|
+
metadata={'derive': True}
|
|
627
|
+
))
|
|
628
|
+
|
|
629
|
+
# Extract attribute macro usages
|
|
630
|
+
symbol_names = {s.name for s in symbols}
|
|
631
|
+
for match in PATTERNS['attribute'].finditer(content):
|
|
632
|
+
attr = match.group(1)
|
|
633
|
+
|
|
634
|
+
# Skip built-in attributes
|
|
635
|
+
if attr not in {
|
|
636
|
+
'derive', 'cfg', 'test', 'bench', 'allow', 'warn', 'deny', 'forbid',
|
|
637
|
+
'deprecated', 'must_use', 'doc', 'inline', 'cold', 'link', 'link_name',
|
|
638
|
+
'no_mangle', 'repr', 'path', 'macro_use', 'macro_export', 'global_allocator',
|
|
639
|
+
'feature', 'non_exhaustive', 'target_feature', 'track_caller'
|
|
640
|
+
}:
|
|
641
|
+
relationships.append(self._make_relationship(
|
|
642
|
+
source=current_scope,
|
|
643
|
+
target=attr,
|
|
644
|
+
rel_type=RelationshipType.DECORATES,
|
|
645
|
+
file_path=file_path,
|
|
646
|
+
content=content,
|
|
647
|
+
offset=match.start()
|
|
648
|
+
))
|
|
649
|
+
|
|
650
|
+
# Extract function calls
|
|
651
|
+
for match in PATTERNS['function_call'].finditer(content):
|
|
652
|
+
func_name = match.group(1)
|
|
653
|
+
|
|
654
|
+
# Skip keywords and common functions
|
|
655
|
+
if func_name not in symbol_names and func_name not in {
|
|
656
|
+
'if', 'match', 'while', 'for', 'loop', 'return', 'break', 'continue',
|
|
657
|
+
'Some', 'None', 'Ok', 'Err', 'Box', 'Vec', 'String', 'Option', 'Result',
|
|
658
|
+
'println', 'print', 'eprintln', 'eprint', 'format', 'panic', 'assert',
|
|
659
|
+
'assert_eq', 'assert_ne', 'debug_assert', 'unreachable', 'unimplemented',
|
|
660
|
+
'todo', 'cfg', 'env', 'concat', 'stringify', 'include', 'include_str',
|
|
661
|
+
'include_bytes', 'file', 'line', 'column', 'module_path'
|
|
662
|
+
}:
|
|
663
|
+
relationships.append(self._make_relationship(
|
|
664
|
+
source=current_scope,
|
|
665
|
+
target=func_name,
|
|
666
|
+
rel_type=RelationshipType.CALLS,
|
|
667
|
+
file_path=file_path,
|
|
668
|
+
content=content,
|
|
669
|
+
offset=match.start()
|
|
670
|
+
))
|
|
671
|
+
|
|
672
|
+
# Extract macro calls
|
|
673
|
+
for match in PATTERNS['macro_call'].finditer(content):
|
|
674
|
+
macro_name = match.group(1)
|
|
675
|
+
|
|
676
|
+
# Skip common std macros
|
|
677
|
+
if macro_name not in {
|
|
678
|
+
'println', 'print', 'eprintln', 'eprint', 'format', 'panic', 'assert',
|
|
679
|
+
'assert_eq', 'assert_ne', 'debug_assert', 'debug_assert_eq', 'debug_assert_ne',
|
|
680
|
+
'unreachable', 'unimplemented', 'todo', 'cfg', 'env', 'concat', 'stringify',
|
|
681
|
+
'include', 'include_str', 'include_bytes', 'file', 'line', 'column',
|
|
682
|
+
'module_path', 'vec', 'format_args', 'write', 'writeln', 'try', 'matches'
|
|
683
|
+
}:
|
|
684
|
+
relationships.append(self._make_relationship(
|
|
685
|
+
source=current_scope,
|
|
686
|
+
target=macro_name,
|
|
687
|
+
rel_type=RelationshipType.CALLS,
|
|
688
|
+
file_path=file_path,
|
|
689
|
+
content=content,
|
|
690
|
+
offset=match.start(),
|
|
691
|
+
metadata={'macro': True}
|
|
692
|
+
))
|
|
693
|
+
|
|
694
|
+
# Extract struct instantiations
|
|
695
|
+
for match in PATTERNS['struct_init'].finditer(content):
|
|
696
|
+
struct_name = match.group(1)
|
|
697
|
+
|
|
698
|
+
if struct_name not in symbol_names:
|
|
699
|
+
relationships.append(self._make_relationship(
|
|
700
|
+
source=current_scope,
|
|
701
|
+
target=struct_name,
|
|
702
|
+
rel_type=RelationshipType.USES,
|
|
703
|
+
file_path=file_path,
|
|
704
|
+
content=content,
|
|
705
|
+
offset=match.start()
|
|
706
|
+
))
|
|
707
|
+
|
|
708
|
+
# Extract trait bound references
|
|
709
|
+
for match in PATTERNS['trait_bound'].finditer(content):
|
|
710
|
+
trait_name = match.group(1)
|
|
711
|
+
|
|
712
|
+
relationships.append(self._make_relationship(
|
|
713
|
+
source=current_scope,
|
|
714
|
+
target=trait_name,
|
|
715
|
+
rel_type=RelationshipType.REFERENCES,
|
|
716
|
+
file_path=file_path,
|
|
717
|
+
content=content,
|
|
718
|
+
offset=match.start(),
|
|
719
|
+
metadata={'trait_bound': True}
|
|
720
|
+
))
|
|
721
|
+
|
|
722
|
+
# Extract path expressions (crate paths)
|
|
723
|
+
for match in PATTERNS['path_expr'].finditer(content):
|
|
724
|
+
path = match.group(1)
|
|
725
|
+
|
|
726
|
+
# Get the first component
|
|
727
|
+
first_component = path.split('::')[0]
|
|
728
|
+
if first_component not in {'self', 'super', 'crate', 'std', 'core', 'alloc'}:
|
|
729
|
+
relationships.append(self._make_relationship(
|
|
730
|
+
source=current_scope,
|
|
731
|
+
target=path,
|
|
732
|
+
rel_type=RelationshipType.REFERENCES,
|
|
733
|
+
file_path=file_path,
|
|
734
|
+
content=content,
|
|
735
|
+
offset=match.start()
|
|
736
|
+
))
|
|
737
|
+
|
|
738
|
+
return relationships
|
|
739
|
+
|
|
740
|
+
def _extract_module_path(self, file_path: str) -> Optional[str]:
|
|
741
|
+
"""Extract module path from file path."""
|
|
742
|
+
path = Path(file_path)
|
|
743
|
+
|
|
744
|
+
# Get relative path components
|
|
745
|
+
parts = []
|
|
746
|
+
current = path
|
|
747
|
+
while current.name not in {'src', 'lib', 'bin', ''}:
|
|
748
|
+
if current.stem not in {'mod', 'lib', 'main'}:
|
|
749
|
+
parts.insert(0, current.stem)
|
|
750
|
+
current = current.parent
|
|
751
|
+
|
|
752
|
+
return '::'.join(parts) if parts else None
|
|
753
|
+
|
|
754
|
+
def _find_preceding_doc(self, content: str, position: int) -> Optional[str]:
|
|
755
|
+
"""Find doc comment preceding a position."""
|
|
756
|
+
before = content[:position]
|
|
757
|
+
lines = before.split('\n')
|
|
758
|
+
|
|
759
|
+
# Look for /// comments in preceding lines
|
|
760
|
+
doc_lines = []
|
|
761
|
+
for line in reversed(lines[:-1]): # Skip current line
|
|
762
|
+
stripped = line.strip()
|
|
763
|
+
if stripped.startswith('///'):
|
|
764
|
+
doc_lines.insert(0, stripped[3:].strip())
|
|
765
|
+
elif stripped.startswith('#['):
|
|
766
|
+
# Skip attributes
|
|
767
|
+
continue
|
|
768
|
+
elif stripped == '':
|
|
769
|
+
continue
|
|
770
|
+
else:
|
|
771
|
+
break
|
|
772
|
+
|
|
773
|
+
return '\n'.join(doc_lines) if doc_lines else None
|
|
774
|
+
|
|
775
|
+
def _find_derives_before(self, content: str, position: int) -> List[str]:
|
|
776
|
+
"""Find derive attributes before a position."""
|
|
777
|
+
before = content[:position]
|
|
778
|
+
|
|
779
|
+
# Look for #[derive(...)] in the preceding content
|
|
780
|
+
derives = []
|
|
781
|
+
for match in PATTERNS['derive'].finditer(before[-500:]): # Look in last 500 chars
|
|
782
|
+
for derive in match.group(1).split(','):
|
|
783
|
+
derive = derive.strip()
|
|
784
|
+
if derive:
|
|
785
|
+
derives.append(derive)
|
|
786
|
+
|
|
787
|
+
return derives
|
|
788
|
+
|
|
789
|
+
def _find_block_end(self, content: str, start: int) -> int:
|
|
790
|
+
"""Find the end line of a code block."""
|
|
791
|
+
brace_count = 0
|
|
792
|
+
in_block = False
|
|
793
|
+
|
|
794
|
+
for i, char in enumerate(content[start:], start):
|
|
795
|
+
if char == '{':
|
|
796
|
+
brace_count += 1
|
|
797
|
+
in_block = True
|
|
798
|
+
elif char == '}':
|
|
799
|
+
brace_count -= 1
|
|
800
|
+
if in_block and brace_count == 0:
|
|
801
|
+
return content[:i].count('\n') + 1
|
|
802
|
+
|
|
803
|
+
return content[:start].count('\n') + 1
|
|
804
|
+
|
|
805
|
+
def parse_multiple_files(
|
|
806
|
+
self,
|
|
807
|
+
files: List[Tuple[str, Optional[str]]],
|
|
808
|
+
resolve_cross_file: bool = True,
|
|
809
|
+
max_workers: int = 4
|
|
810
|
+
) -> Dict[str, ParseResult]:
|
|
811
|
+
"""
|
|
812
|
+
Parse multiple Rust files with optional cross-file resolution.
|
|
813
|
+
|
|
814
|
+
Args:
|
|
815
|
+
files: List of (file_path, content) tuples
|
|
816
|
+
resolve_cross_file: Whether to resolve cross-file references
|
|
817
|
+
max_workers: Maximum number of parallel workers
|
|
818
|
+
|
|
819
|
+
Returns:
|
|
820
|
+
Dict mapping file paths to ParseResult objects
|
|
821
|
+
"""
|
|
822
|
+
results = {}
|
|
823
|
+
|
|
824
|
+
def parse_single(file_info: Tuple[str, Optional[str]]) -> Tuple[str, Optional[ParseResult]]:
|
|
825
|
+
file_path, content = file_info
|
|
826
|
+
if content is None:
|
|
827
|
+
try:
|
|
828
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
829
|
+
content = f.read()
|
|
830
|
+
except Exception as e:
|
|
831
|
+
logger.warning(f"Failed to read {file_path}: {e}")
|
|
832
|
+
return file_path, None
|
|
833
|
+
|
|
834
|
+
try:
|
|
835
|
+
result = self.parse(content, file_path)
|
|
836
|
+
return file_path, result
|
|
837
|
+
except Exception as e:
|
|
838
|
+
logger.warning(f"Failed to parse {file_path}: {e}")
|
|
839
|
+
return file_path, None
|
|
840
|
+
|
|
841
|
+
# Parse files in parallel
|
|
842
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
843
|
+
for file_path, result in executor.map(parse_single, files):
|
|
844
|
+
if result:
|
|
845
|
+
results[file_path] = result
|
|
846
|
+
|
|
847
|
+
# Register symbols for cross-file resolution
|
|
848
|
+
if resolve_cross_file:
|
|
849
|
+
for symbol in result.symbols:
|
|
850
|
+
self._symbol_to_file[symbol.name] = file_path
|
|
851
|
+
if symbol.qualified_name:
|
|
852
|
+
self._symbol_to_file[symbol.qualified_name] = file_path
|
|
853
|
+
|
|
854
|
+
return results
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
# Register the parser
|
|
858
|
+
parser_registry.register_parser(RustParser())
|