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,768 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Kotlin Parser - Regex-based parser for Kotlin source files.
|
|
3
|
+
|
|
4
|
+
Extracts symbols and relationships from .kt and .kts files using
|
|
5
|
+
comprehensive regex patterns. Supports Kotlin-specific features like
|
|
6
|
+
data classes, sealed classes, companion objects, extension functions,
|
|
7
|
+
and coroutines.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from typing import Dict, List, Optional, Set, Tuple, Union, Any
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
14
|
+
import logging
|
|
15
|
+
|
|
16
|
+
from .base import (
|
|
17
|
+
BaseParser,
|
|
18
|
+
ParseResult,
|
|
19
|
+
Symbol,
|
|
20
|
+
Relationship,
|
|
21
|
+
RelationshipType,
|
|
22
|
+
SymbolType,
|
|
23
|
+
Scope,
|
|
24
|
+
Position,
|
|
25
|
+
Range,
|
|
26
|
+
parser_registry,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# Comprehensive Kotlin regex patterns
|
|
33
|
+
PATTERNS = {
|
|
34
|
+
# Package declaration
|
|
35
|
+
'package': re.compile(
|
|
36
|
+
r'^\s*package\s+([\w.]+)',
|
|
37
|
+
re.MULTILINE
|
|
38
|
+
),
|
|
39
|
+
|
|
40
|
+
# Import patterns
|
|
41
|
+
'import': re.compile(
|
|
42
|
+
r'^\s*import\s+([\w.]+)(?:\s+as\s+(\w+))?',
|
|
43
|
+
re.MULTILINE
|
|
44
|
+
),
|
|
45
|
+
'import_wildcard': re.compile(
|
|
46
|
+
r'^\s*import\s+([\w.]+)\.\*',
|
|
47
|
+
re.MULTILINE
|
|
48
|
+
),
|
|
49
|
+
|
|
50
|
+
# Class declarations
|
|
51
|
+
'class': re.compile(
|
|
52
|
+
r'^\s*(?:(public|private|protected|internal)\s+)?'
|
|
53
|
+
r'(?:(abstract|open|final|sealed|data|enum|annotation|inner|value)\s+)*'
|
|
54
|
+
r'class\s+(\w+)'
|
|
55
|
+
r'(?:<[^>]+>)?' # Generic parameters
|
|
56
|
+
r'(?:\s*\([^)]*\))?' # Primary constructor
|
|
57
|
+
r'(?:\s*:\s*([^{]+))?', # Inheritance
|
|
58
|
+
re.MULTILINE
|
|
59
|
+
),
|
|
60
|
+
|
|
61
|
+
# Interface declarations
|
|
62
|
+
'interface': re.compile(
|
|
63
|
+
r'^\s*(?:(public|private|protected|internal)\s+)?'
|
|
64
|
+
r'(?:(fun)\s+)?' # fun interface
|
|
65
|
+
r'interface\s+(\w+)'
|
|
66
|
+
r'(?:<[^>]+>)?' # Generic parameters
|
|
67
|
+
r'(?:\s*:\s*([^{]+))?', # Extended interfaces
|
|
68
|
+
re.MULTILINE
|
|
69
|
+
),
|
|
70
|
+
|
|
71
|
+
# Object declarations (singleton, companion)
|
|
72
|
+
'object': re.compile(
|
|
73
|
+
r'^\s*(?:(public|private|protected|internal)\s+)?'
|
|
74
|
+
r'(?:(companion)\s+)?'
|
|
75
|
+
r'object\s+(\w+)?'
|
|
76
|
+
r'(?:\s*:\s*([^{]+))?',
|
|
77
|
+
re.MULTILINE
|
|
78
|
+
),
|
|
79
|
+
|
|
80
|
+
# Function declarations
|
|
81
|
+
'function': re.compile(
|
|
82
|
+
r'^\s*(?:(public|private|protected|internal|override|open|final|abstract|inline|suspend|tailrec|operator|infix|external)\s+)*'
|
|
83
|
+
r'fun\s+'
|
|
84
|
+
r'(?:<[^>]+>\s+)?' # Generic parameters
|
|
85
|
+
r'(?:(\w+)\.)?' # Extension receiver
|
|
86
|
+
r'(\w+)' # Function name
|
|
87
|
+
r'\s*\([^)]*\)' # Parameters
|
|
88
|
+
r'(?:\s*:\s*([^\n{=]+))?', # Return type
|
|
89
|
+
re.MULTILINE
|
|
90
|
+
),
|
|
91
|
+
|
|
92
|
+
# Property declarations
|
|
93
|
+
'property': re.compile(
|
|
94
|
+
r'^\s*(?:(public|private|protected|internal|override|open|final|abstract|lateinit|const)\s+)*'
|
|
95
|
+
r'(val|var)\s+'
|
|
96
|
+
r'(?:(\w+)\.)?' # Extension receiver
|
|
97
|
+
r'(\w+)' # Property name
|
|
98
|
+
r'(?:\s*:\s*([^\n=]+))?' # Type
|
|
99
|
+
r'(?:\s*(?:=|by)\s*([^\n]+))?', # Initializer or delegate
|
|
100
|
+
re.MULTILINE
|
|
101
|
+
),
|
|
102
|
+
|
|
103
|
+
# Type alias
|
|
104
|
+
'typealias': re.compile(
|
|
105
|
+
r'^\s*(?:(public|private|protected|internal)\s+)?'
|
|
106
|
+
r'typealias\s+(\w+)'
|
|
107
|
+
r'(?:<[^>]+>)?' # Generic parameters
|
|
108
|
+
r'\s*=\s*([^\n]+)',
|
|
109
|
+
re.MULTILINE
|
|
110
|
+
),
|
|
111
|
+
|
|
112
|
+
# Annotations
|
|
113
|
+
'annotation_use': re.compile(
|
|
114
|
+
r'@(\w+)(?:\([^)]*\))?',
|
|
115
|
+
re.MULTILINE
|
|
116
|
+
),
|
|
117
|
+
|
|
118
|
+
# Function calls
|
|
119
|
+
'function_call': re.compile(
|
|
120
|
+
r'(?:^|[^\w.])(\w+)\s*\(',
|
|
121
|
+
re.MULTILINE
|
|
122
|
+
),
|
|
123
|
+
|
|
124
|
+
# Method calls (with receiver)
|
|
125
|
+
'method_call': re.compile(
|
|
126
|
+
r'\.(\w+)\s*\(',
|
|
127
|
+
re.MULTILINE
|
|
128
|
+
),
|
|
129
|
+
|
|
130
|
+
# Constructor calls
|
|
131
|
+
'constructor_call': re.compile(
|
|
132
|
+
r'(?:^|[^\w])([A-Z]\w*)\s*\(',
|
|
133
|
+
re.MULTILINE
|
|
134
|
+
),
|
|
135
|
+
|
|
136
|
+
# Delegation (by keyword)
|
|
137
|
+
'delegation': re.compile(
|
|
138
|
+
r'(?:class|interface)\s+\w+[^{]*:\s*[^{]*\s+by\s+(\w+)',
|
|
139
|
+
re.MULTILINE
|
|
140
|
+
),
|
|
141
|
+
|
|
142
|
+
# Coroutine patterns
|
|
143
|
+
'coroutine_launch': re.compile(
|
|
144
|
+
r'(?:launch|async|runBlocking|withContext)\s*(?:\([^)]*\))?\s*\{',
|
|
145
|
+
re.MULTILINE
|
|
146
|
+
),
|
|
147
|
+
|
|
148
|
+
# Generic type usage
|
|
149
|
+
'generic_usage': re.compile(
|
|
150
|
+
r'<\s*([A-Z]\w*)\s*(?:,\s*[A-Z]\w*)*\s*>',
|
|
151
|
+
re.MULTILINE
|
|
152
|
+
),
|
|
153
|
+
|
|
154
|
+
# DSL patterns (common Kotlin builders)
|
|
155
|
+
'dsl_builder': re.compile(
|
|
156
|
+
r'(\w+)\s*\{\s*\n',
|
|
157
|
+
re.MULTILINE
|
|
158
|
+
),
|
|
159
|
+
|
|
160
|
+
# Destructuring
|
|
161
|
+
'destructuring': re.compile(
|
|
162
|
+
r'\(\s*(\w+(?:\s*,\s*\w+)*)\s*\)\s*=',
|
|
163
|
+
re.MULTILINE
|
|
164
|
+
),
|
|
165
|
+
|
|
166
|
+
# KDoc patterns
|
|
167
|
+
'kdoc': re.compile(
|
|
168
|
+
r'/\*\*\s*([\s\S]*?)\s*\*/',
|
|
169
|
+
re.MULTILINE
|
|
170
|
+
),
|
|
171
|
+
'kdoc_reference': re.compile(
|
|
172
|
+
r'\[(\w+(?:\.\w+)*)\]',
|
|
173
|
+
re.MULTILINE
|
|
174
|
+
),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class KotlinParser(BaseParser):
|
|
179
|
+
"""Kotlin source code parser using regex patterns."""
|
|
180
|
+
|
|
181
|
+
# Global symbol registry for cross-file resolution
|
|
182
|
+
_global_symbols: Dict[str, Set[str]] = {}
|
|
183
|
+
_symbol_to_file: Dict[str, str] = {}
|
|
184
|
+
|
|
185
|
+
def __init__(self):
|
|
186
|
+
super().__init__("kotlin")
|
|
187
|
+
|
|
188
|
+
def _get_supported_extensions(self) -> Set[str]:
|
|
189
|
+
return {'.kt', '.kts'}
|
|
190
|
+
|
|
191
|
+
def _make_range(self, content: str, start_offset: int, end_line: int) -> Range:
|
|
192
|
+
"""Create a Range object from content and offset."""
|
|
193
|
+
start_line = content[:start_offset].count('\n') + 1
|
|
194
|
+
# Find column (chars since last newline)
|
|
195
|
+
last_newline = content.rfind('\n', 0, start_offset)
|
|
196
|
+
start_col = start_offset - last_newline - 1 if last_newline >= 0 else start_offset
|
|
197
|
+
return Range(
|
|
198
|
+
start=Position(line=start_line, column=start_col),
|
|
199
|
+
end=Position(line=end_line, column=0)
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def _make_symbol(
|
|
203
|
+
self,
|
|
204
|
+
name: str,
|
|
205
|
+
symbol_type: SymbolType,
|
|
206
|
+
content: str,
|
|
207
|
+
file_path: str,
|
|
208
|
+
start_offset: int,
|
|
209
|
+
end_line: int,
|
|
210
|
+
scope: Scope = Scope.GLOBAL,
|
|
211
|
+
parent: Optional[str] = None,
|
|
212
|
+
full_name: Optional[str] = None,
|
|
213
|
+
docstring: Optional[str] = None,
|
|
214
|
+
visibility: Optional[str] = None,
|
|
215
|
+
is_static: bool = False,
|
|
216
|
+
is_async: bool = False,
|
|
217
|
+
return_type: Optional[str] = None,
|
|
218
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
219
|
+
) -> Symbol:
|
|
220
|
+
"""Create a Symbol with correct fields."""
|
|
221
|
+
return Symbol(
|
|
222
|
+
name=name,
|
|
223
|
+
symbol_type=symbol_type,
|
|
224
|
+
scope=scope,
|
|
225
|
+
range=self._make_range(content, start_offset, end_line),
|
|
226
|
+
file_path=file_path,
|
|
227
|
+
parent_symbol=parent,
|
|
228
|
+
full_name=full_name,
|
|
229
|
+
visibility=visibility,
|
|
230
|
+
is_static=is_static,
|
|
231
|
+
is_async=is_async,
|
|
232
|
+
docstring=docstring,
|
|
233
|
+
return_type=return_type,
|
|
234
|
+
metadata=metadata or {}
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def _make_relationship(
|
|
238
|
+
self,
|
|
239
|
+
source: str,
|
|
240
|
+
target: str,
|
|
241
|
+
rel_type: RelationshipType,
|
|
242
|
+
file_path: str,
|
|
243
|
+
content: str,
|
|
244
|
+
offset: int,
|
|
245
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
246
|
+
) -> Relationship:
|
|
247
|
+
"""Create a Relationship with correct fields."""
|
|
248
|
+
return Relationship(
|
|
249
|
+
source_symbol=source,
|
|
250
|
+
target_symbol=target,
|
|
251
|
+
relationship_type=rel_type,
|
|
252
|
+
source_file=file_path,
|
|
253
|
+
source_range=self._make_range(content, offset, content[:offset].count('\n') + 1),
|
|
254
|
+
metadata=metadata or {}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def parse_file(self, file_path: Union[str, Path], content: Optional[str] = None) -> ParseResult:
|
|
258
|
+
"""Parse a Kotlin file and extract symbols and relationships."""
|
|
259
|
+
import time
|
|
260
|
+
start_time = time.time()
|
|
261
|
+
file_path = str(file_path)
|
|
262
|
+
|
|
263
|
+
if content is None:
|
|
264
|
+
try:
|
|
265
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
266
|
+
content = f.read()
|
|
267
|
+
except Exception as e:
|
|
268
|
+
return ParseResult(
|
|
269
|
+
file_path=file_path,
|
|
270
|
+
language=self.language,
|
|
271
|
+
symbols=[],
|
|
272
|
+
relationships=[],
|
|
273
|
+
errors=[str(e)]
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
symbols = self._extract_symbols(content, file_path)
|
|
277
|
+
relationships = self._extract_relationships(content, file_path, symbols)
|
|
278
|
+
|
|
279
|
+
return ParseResult(
|
|
280
|
+
file_path=file_path,
|
|
281
|
+
language=self.language,
|
|
282
|
+
symbols=symbols,
|
|
283
|
+
relationships=relationships,
|
|
284
|
+
parse_time=time.time() - start_time
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def _extract_symbols(self, content: str, file_path: str) -> List[Symbol]:
|
|
288
|
+
"""Extract all symbols from Kotlin source code."""
|
|
289
|
+
symbols = []
|
|
290
|
+
package_name = self._extract_package(content)
|
|
291
|
+
|
|
292
|
+
# Extract classes
|
|
293
|
+
for match in PATTERNS['class'].finditer(content):
|
|
294
|
+
visibility, modifiers, name, inheritance = match.groups()
|
|
295
|
+
end_line = self._find_block_end(content, match.end())
|
|
296
|
+
|
|
297
|
+
# Determine symbol type based on modifiers
|
|
298
|
+
symbol_type = SymbolType.CLASS
|
|
299
|
+
metadata = {'visibility': visibility or 'public'}
|
|
300
|
+
|
|
301
|
+
if modifiers:
|
|
302
|
+
metadata['modifiers'] = modifiers.strip()
|
|
303
|
+
if 'enum' in modifiers:
|
|
304
|
+
symbol_type = SymbolType.ENUM
|
|
305
|
+
elif 'data' in modifiers:
|
|
306
|
+
metadata['is_data_class'] = True
|
|
307
|
+
elif 'sealed' in modifiers:
|
|
308
|
+
metadata['is_sealed'] = True
|
|
309
|
+
|
|
310
|
+
qualified_name = f"{package_name}.{name}" if package_name else name
|
|
311
|
+
docstring = self._find_preceding_kdoc(content, match.start())
|
|
312
|
+
|
|
313
|
+
symbols.append(self._make_symbol(
|
|
314
|
+
name=name,
|
|
315
|
+
symbol_type=symbol_type,
|
|
316
|
+
content=content,
|
|
317
|
+
file_path=file_path,
|
|
318
|
+
start_offset=match.start(),
|
|
319
|
+
end_line=end_line,
|
|
320
|
+
scope=Scope.GLOBAL,
|
|
321
|
+
full_name=qualified_name,
|
|
322
|
+
docstring=docstring,
|
|
323
|
+
visibility=visibility or 'public',
|
|
324
|
+
metadata=metadata
|
|
325
|
+
))
|
|
326
|
+
|
|
327
|
+
# Extract interfaces
|
|
328
|
+
for match in PATTERNS['interface'].finditer(content):
|
|
329
|
+
visibility, fun_modifier, name, extends = match.groups()
|
|
330
|
+
end_line = self._find_block_end(content, match.end())
|
|
331
|
+
|
|
332
|
+
metadata = {'visibility': visibility or 'public'}
|
|
333
|
+
if fun_modifier:
|
|
334
|
+
metadata['is_fun_interface'] = True
|
|
335
|
+
|
|
336
|
+
qualified_name = f"{package_name}.{name}" if package_name else name
|
|
337
|
+
docstring = self._find_preceding_kdoc(content, match.start())
|
|
338
|
+
|
|
339
|
+
symbols.append(self._make_symbol(
|
|
340
|
+
name=name,
|
|
341
|
+
symbol_type=SymbolType.INTERFACE,
|
|
342
|
+
content=content,
|
|
343
|
+
file_path=file_path,
|
|
344
|
+
start_offset=match.start(),
|
|
345
|
+
end_line=end_line,
|
|
346
|
+
scope=Scope.GLOBAL,
|
|
347
|
+
full_name=qualified_name,
|
|
348
|
+
docstring=docstring,
|
|
349
|
+
visibility=visibility or 'public',
|
|
350
|
+
metadata=metadata
|
|
351
|
+
))
|
|
352
|
+
|
|
353
|
+
# Extract objects
|
|
354
|
+
for match in PATTERNS['object'].finditer(content):
|
|
355
|
+
visibility, companion, name, implements = match.groups()
|
|
356
|
+
if not name and not companion:
|
|
357
|
+
continue # Skip anonymous objects in expressions
|
|
358
|
+
|
|
359
|
+
obj_name = name if name else 'Companion'
|
|
360
|
+
end_line = self._find_block_end(content, match.end())
|
|
361
|
+
|
|
362
|
+
metadata = {'visibility': visibility or 'public'}
|
|
363
|
+
if companion:
|
|
364
|
+
metadata['is_companion'] = True
|
|
365
|
+
|
|
366
|
+
qualified_name = f"{package_name}.{obj_name}" if package_name else obj_name
|
|
367
|
+
docstring = self._find_preceding_kdoc(content, match.start())
|
|
368
|
+
|
|
369
|
+
symbols.append(self._make_symbol(
|
|
370
|
+
name=obj_name,
|
|
371
|
+
symbol_type=SymbolType.CLASS,
|
|
372
|
+
content=content,
|
|
373
|
+
file_path=file_path,
|
|
374
|
+
start_offset=match.start(),
|
|
375
|
+
end_line=end_line,
|
|
376
|
+
scope=Scope.GLOBAL,
|
|
377
|
+
full_name=qualified_name,
|
|
378
|
+
docstring=docstring,
|
|
379
|
+
visibility=visibility or 'public',
|
|
380
|
+
metadata=metadata
|
|
381
|
+
))
|
|
382
|
+
|
|
383
|
+
# Extract functions
|
|
384
|
+
for match in PATTERNS['function'].finditer(content):
|
|
385
|
+
modifiers, receiver, name, return_type = match.groups()
|
|
386
|
+
end_line = self._find_function_end(content, match.end())
|
|
387
|
+
|
|
388
|
+
metadata = {}
|
|
389
|
+
is_async = False
|
|
390
|
+
if modifiers:
|
|
391
|
+
modifier_list = modifiers.strip().split()
|
|
392
|
+
metadata['modifiers'] = modifier_list
|
|
393
|
+
if 'suspend' in modifier_list:
|
|
394
|
+
is_async = True
|
|
395
|
+
metadata['is_suspend'] = True
|
|
396
|
+
if 'inline' in modifier_list:
|
|
397
|
+
metadata['is_inline'] = True
|
|
398
|
+
|
|
399
|
+
if receiver:
|
|
400
|
+
metadata['extension_receiver'] = receiver
|
|
401
|
+
|
|
402
|
+
qualified_name = f"{package_name}.{name}" if package_name else name
|
|
403
|
+
docstring = self._find_preceding_kdoc(content, match.start())
|
|
404
|
+
|
|
405
|
+
symbols.append(self._make_symbol(
|
|
406
|
+
name=name,
|
|
407
|
+
symbol_type=SymbolType.FUNCTION,
|
|
408
|
+
content=content,
|
|
409
|
+
file_path=file_path,
|
|
410
|
+
start_offset=match.start(),
|
|
411
|
+
end_line=end_line,
|
|
412
|
+
scope=Scope.GLOBAL,
|
|
413
|
+
full_name=qualified_name,
|
|
414
|
+
docstring=docstring,
|
|
415
|
+
is_async=is_async,
|
|
416
|
+
return_type=return_type.strip() if return_type else None,
|
|
417
|
+
metadata=metadata
|
|
418
|
+
))
|
|
419
|
+
|
|
420
|
+
# Extract properties
|
|
421
|
+
for match in PATTERNS['property'].finditer(content):
|
|
422
|
+
modifiers, val_var, receiver, name, type_decl, initializer = match.groups()
|
|
423
|
+
line = content[:match.start()].count('\n') + 1
|
|
424
|
+
|
|
425
|
+
metadata = {'mutable': val_var == 'var'}
|
|
426
|
+
if modifiers:
|
|
427
|
+
metadata['modifiers'] = modifiers.strip().split()
|
|
428
|
+
if receiver:
|
|
429
|
+
metadata['extension_receiver'] = receiver
|
|
430
|
+
if type_decl:
|
|
431
|
+
metadata['type'] = type_decl.strip()
|
|
432
|
+
|
|
433
|
+
qualified_name = f"{package_name}.{name}" if package_name else name
|
|
434
|
+
|
|
435
|
+
symbols.append(self._make_symbol(
|
|
436
|
+
name=name,
|
|
437
|
+
symbol_type=SymbolType.VARIABLE,
|
|
438
|
+
content=content,
|
|
439
|
+
file_path=file_path,
|
|
440
|
+
start_offset=match.start(),
|
|
441
|
+
end_line=line,
|
|
442
|
+
scope=Scope.GLOBAL,
|
|
443
|
+
full_name=qualified_name,
|
|
444
|
+
metadata=metadata
|
|
445
|
+
))
|
|
446
|
+
|
|
447
|
+
# Extract type aliases
|
|
448
|
+
for match in PATTERNS['typealias'].finditer(content):
|
|
449
|
+
visibility, name, aliased_type = match.groups()
|
|
450
|
+
line = content[:match.start()].count('\n') + 1
|
|
451
|
+
|
|
452
|
+
qualified_name = f"{package_name}.{name}" if package_name else name
|
|
453
|
+
|
|
454
|
+
symbols.append(self._make_symbol(
|
|
455
|
+
name=name,
|
|
456
|
+
symbol_type=SymbolType.TYPE_ALIAS,
|
|
457
|
+
content=content,
|
|
458
|
+
file_path=file_path,
|
|
459
|
+
start_offset=match.start(),
|
|
460
|
+
end_line=line,
|
|
461
|
+
scope=Scope.GLOBAL,
|
|
462
|
+
full_name=qualified_name,
|
|
463
|
+
visibility=visibility or 'public',
|
|
464
|
+
metadata={'aliased_type': aliased_type.strip()}
|
|
465
|
+
))
|
|
466
|
+
|
|
467
|
+
return symbols
|
|
468
|
+
|
|
469
|
+
def _extract_relationships(
|
|
470
|
+
self,
|
|
471
|
+
content: str,
|
|
472
|
+
file_path: str,
|
|
473
|
+
symbols: List[Symbol]
|
|
474
|
+
) -> List[Relationship]:
|
|
475
|
+
"""Extract relationships from Kotlin source code."""
|
|
476
|
+
relationships = []
|
|
477
|
+
current_scope = Path(file_path).stem
|
|
478
|
+
|
|
479
|
+
# Extract imports
|
|
480
|
+
for match in PATTERNS['import'].finditer(content):
|
|
481
|
+
import_path, alias = match.groups()
|
|
482
|
+
|
|
483
|
+
relationships.append(self._make_relationship(
|
|
484
|
+
source=current_scope,
|
|
485
|
+
target=import_path,
|
|
486
|
+
rel_type=RelationshipType.IMPORTS,
|
|
487
|
+
file_path=file_path,
|
|
488
|
+
content=content,
|
|
489
|
+
offset=match.start(),
|
|
490
|
+
metadata={'alias': alias} if alias else None
|
|
491
|
+
))
|
|
492
|
+
|
|
493
|
+
# Extract wildcard imports
|
|
494
|
+
for match in PATTERNS['import_wildcard'].finditer(content):
|
|
495
|
+
import_path = match.group(1)
|
|
496
|
+
|
|
497
|
+
relationships.append(self._make_relationship(
|
|
498
|
+
source=current_scope,
|
|
499
|
+
target=import_path,
|
|
500
|
+
rel_type=RelationshipType.IMPORTS,
|
|
501
|
+
file_path=file_path,
|
|
502
|
+
content=content,
|
|
503
|
+
offset=match.start(),
|
|
504
|
+
metadata={'wildcard': True}
|
|
505
|
+
))
|
|
506
|
+
|
|
507
|
+
# Extract inheritance relationships
|
|
508
|
+
for match in PATTERNS['class'].finditer(content):
|
|
509
|
+
_, _, class_name, inheritance = match.groups()
|
|
510
|
+
if inheritance:
|
|
511
|
+
parents = self._parse_inheritance(inheritance)
|
|
512
|
+
for parent, rel_type in parents:
|
|
513
|
+
relationships.append(self._make_relationship(
|
|
514
|
+
source=class_name,
|
|
515
|
+
target=parent,
|
|
516
|
+
rel_type=rel_type,
|
|
517
|
+
file_path=file_path,
|
|
518
|
+
content=content,
|
|
519
|
+
offset=match.start()
|
|
520
|
+
))
|
|
521
|
+
|
|
522
|
+
# Extract interface extension
|
|
523
|
+
for match in PATTERNS['interface'].finditer(content):
|
|
524
|
+
_, _, iface_name, extends = match.groups()
|
|
525
|
+
if extends:
|
|
526
|
+
for parent in self._parse_type_list(extends):
|
|
527
|
+
relationships.append(self._make_relationship(
|
|
528
|
+
source=iface_name,
|
|
529
|
+
target=parent,
|
|
530
|
+
rel_type=RelationshipType.INHERITANCE,
|
|
531
|
+
file_path=file_path,
|
|
532
|
+
content=content,
|
|
533
|
+
offset=match.start()
|
|
534
|
+
))
|
|
535
|
+
|
|
536
|
+
# Extract object implementations
|
|
537
|
+
for match in PATTERNS['object'].finditer(content):
|
|
538
|
+
_, companion, obj_name, implements = match.groups()
|
|
539
|
+
if implements and obj_name:
|
|
540
|
+
for parent in self._parse_type_list(implements):
|
|
541
|
+
relationships.append(self._make_relationship(
|
|
542
|
+
source=obj_name,
|
|
543
|
+
target=parent,
|
|
544
|
+
rel_type=RelationshipType.IMPLEMENTATION,
|
|
545
|
+
file_path=file_path,
|
|
546
|
+
content=content,
|
|
547
|
+
offset=match.start()
|
|
548
|
+
))
|
|
549
|
+
|
|
550
|
+
# Extract delegation relationships
|
|
551
|
+
for match in PATTERNS['delegation'].finditer(content):
|
|
552
|
+
delegate = match.group(1)
|
|
553
|
+
|
|
554
|
+
relationships.append(self._make_relationship(
|
|
555
|
+
source=current_scope,
|
|
556
|
+
target=delegate,
|
|
557
|
+
rel_type=RelationshipType.COMPOSITION,
|
|
558
|
+
file_path=file_path,
|
|
559
|
+
content=content,
|
|
560
|
+
offset=match.start(),
|
|
561
|
+
metadata={'delegation': True}
|
|
562
|
+
))
|
|
563
|
+
|
|
564
|
+
# Extract annotation usage
|
|
565
|
+
for match in PATTERNS['annotation_use'].finditer(content):
|
|
566
|
+
annotation = match.group(1)
|
|
567
|
+
|
|
568
|
+
# Skip common built-in annotations
|
|
569
|
+
if annotation not in {'Override', 'Deprecated', 'Suppress', 'JvmStatic',
|
|
570
|
+
'JvmField', 'JvmOverloads', 'JvmName', 'Throws',
|
|
571
|
+
'Nullable', 'NotNull', 'Test', 'Before', 'After'}:
|
|
572
|
+
relationships.append(self._make_relationship(
|
|
573
|
+
source=current_scope,
|
|
574
|
+
target=annotation,
|
|
575
|
+
rel_type=RelationshipType.DECORATES,
|
|
576
|
+
file_path=file_path,
|
|
577
|
+
content=content,
|
|
578
|
+
offset=match.start()
|
|
579
|
+
))
|
|
580
|
+
|
|
581
|
+
# Extract function calls
|
|
582
|
+
symbol_names = {s.name for s in symbols}
|
|
583
|
+
for match in PATTERNS['function_call'].finditer(content):
|
|
584
|
+
func_name = match.group(1)
|
|
585
|
+
|
|
586
|
+
# Skip keywords and common functions
|
|
587
|
+
if func_name not in symbol_names and func_name not in {
|
|
588
|
+
'if', 'when', 'for', 'while', 'try', 'catch', 'finally',
|
|
589
|
+
'return', 'throw', 'break', 'continue', 'print', 'println',
|
|
590
|
+
'listOf', 'mapOf', 'setOf', 'arrayOf', 'mutableListOf',
|
|
591
|
+
'mutableMapOf', 'mutableSetOf', 'lazy', 'require', 'check',
|
|
592
|
+
'assert', 'error', 'TODO', 'also', 'apply', 'let', 'run', 'with'
|
|
593
|
+
}:
|
|
594
|
+
relationships.append(self._make_relationship(
|
|
595
|
+
source=current_scope,
|
|
596
|
+
target=func_name,
|
|
597
|
+
rel_type=RelationshipType.CALLS,
|
|
598
|
+
file_path=file_path,
|
|
599
|
+
content=content,
|
|
600
|
+
offset=match.start()
|
|
601
|
+
))
|
|
602
|
+
|
|
603
|
+
# Extract constructor calls
|
|
604
|
+
for match in PATTERNS['constructor_call'].finditer(content):
|
|
605
|
+
class_name = match.group(1)
|
|
606
|
+
|
|
607
|
+
if class_name not in symbol_names:
|
|
608
|
+
relationships.append(self._make_relationship(
|
|
609
|
+
source=current_scope,
|
|
610
|
+
target=class_name,
|
|
611
|
+
rel_type=RelationshipType.USES,
|
|
612
|
+
file_path=file_path,
|
|
613
|
+
content=content,
|
|
614
|
+
offset=match.start()
|
|
615
|
+
))
|
|
616
|
+
|
|
617
|
+
return relationships
|
|
618
|
+
|
|
619
|
+
def _extract_package(self, content: str) -> Optional[str]:
|
|
620
|
+
"""Extract package name from content."""
|
|
621
|
+
match = PATTERNS['package'].search(content)
|
|
622
|
+
return match.group(1) if match else None
|
|
623
|
+
|
|
624
|
+
def _parse_inheritance(self, inheritance: str) -> List[Tuple[str, RelationshipType]]:
|
|
625
|
+
"""Parse inheritance clause to extract parent types."""
|
|
626
|
+
results = []
|
|
627
|
+
|
|
628
|
+
# Remove generic parameters for simpler parsing
|
|
629
|
+
clean = re.sub(r'<[^>]*>', '', inheritance)
|
|
630
|
+
|
|
631
|
+
# Split by comma
|
|
632
|
+
for part in clean.split(','):
|
|
633
|
+
part = part.strip()
|
|
634
|
+
if not part:
|
|
635
|
+
continue
|
|
636
|
+
|
|
637
|
+
# Remove constructor call parentheses
|
|
638
|
+
part = re.sub(r'\([^)]*\)', '', part).strip()
|
|
639
|
+
|
|
640
|
+
# Get the type name
|
|
641
|
+
type_name = part.split()[0] if part else None
|
|
642
|
+
if type_name:
|
|
643
|
+
# Heuristic: if it has parentheses in original, it's a class
|
|
644
|
+
if '(' in inheritance and type_name in inheritance.split('(')[0]:
|
|
645
|
+
results.append((type_name, RelationshipType.INHERITANCE))
|
|
646
|
+
else:
|
|
647
|
+
# Could be either, default to implementation for interfaces
|
|
648
|
+
results.append((type_name, RelationshipType.IMPLEMENTATION))
|
|
649
|
+
|
|
650
|
+
return results
|
|
651
|
+
|
|
652
|
+
def _parse_type_list(self, type_list: str) -> List[str]:
|
|
653
|
+
"""Parse a comma-separated list of types."""
|
|
654
|
+
# Remove generic parameters
|
|
655
|
+
clean = re.sub(r'<[^>]*>', '', type_list)
|
|
656
|
+
|
|
657
|
+
types = []
|
|
658
|
+
for part in clean.split(','):
|
|
659
|
+
part = part.strip()
|
|
660
|
+
if part:
|
|
661
|
+
# Get just the type name
|
|
662
|
+
type_name = part.split()[0]
|
|
663
|
+
type_name = re.sub(r'\([^)]*\)', '', type_name).strip()
|
|
664
|
+
if type_name:
|
|
665
|
+
types.append(type_name)
|
|
666
|
+
|
|
667
|
+
return types
|
|
668
|
+
|
|
669
|
+
def _find_preceding_kdoc(self, content: str, position: int) -> Optional[str]:
|
|
670
|
+
"""Find KDoc comment preceding a position."""
|
|
671
|
+
# Look for KDoc before the position
|
|
672
|
+
before = content[:position]
|
|
673
|
+
match = re.search(r'/\*\*\s*([\s\S]*?)\s*\*/\s*$', before)
|
|
674
|
+
|
|
675
|
+
if match:
|
|
676
|
+
doc = match.group(1)
|
|
677
|
+
# Clean up the doc
|
|
678
|
+
lines = doc.split('\n')
|
|
679
|
+
cleaned = []
|
|
680
|
+
for line in lines:
|
|
681
|
+
line = re.sub(r'^\s*\*\s?', '', line)
|
|
682
|
+
cleaned.append(line.strip())
|
|
683
|
+
return '\n'.join(cleaned).strip()
|
|
684
|
+
|
|
685
|
+
return None
|
|
686
|
+
|
|
687
|
+
def _find_block_end(self, content: str, start: int) -> int:
|
|
688
|
+
"""Find the end line of a code block."""
|
|
689
|
+
brace_count = 0
|
|
690
|
+
in_block = False
|
|
691
|
+
|
|
692
|
+
for i, char in enumerate(content[start:], start):
|
|
693
|
+
if char == '{':
|
|
694
|
+
brace_count += 1
|
|
695
|
+
in_block = True
|
|
696
|
+
elif char == '}':
|
|
697
|
+
brace_count -= 1
|
|
698
|
+
if in_block and brace_count == 0:
|
|
699
|
+
return content[:i].count('\n') + 1
|
|
700
|
+
|
|
701
|
+
return content[:start].count('\n') + 1
|
|
702
|
+
|
|
703
|
+
def _find_function_end(self, content: str, start: int) -> int:
|
|
704
|
+
"""Find the end line of a function."""
|
|
705
|
+
# Check for expression body (=) vs block body ({)
|
|
706
|
+
rest = content[start:start + 100]
|
|
707
|
+
|
|
708
|
+
if '=' in rest.split('\n')[0] and '{' not in rest.split('\n')[0]:
|
|
709
|
+
# Expression body - find newline
|
|
710
|
+
newline_pos = content.find('\n', start)
|
|
711
|
+
return content[:newline_pos].count('\n') + 1 if newline_pos != -1 else content[:start].count('\n') + 1
|
|
712
|
+
else:
|
|
713
|
+
return self._find_block_end(content, start)
|
|
714
|
+
|
|
715
|
+
def parse_multiple_files(
|
|
716
|
+
self,
|
|
717
|
+
files: List[Tuple[str, Optional[str]]],
|
|
718
|
+
resolve_cross_file: bool = True,
|
|
719
|
+
max_workers: int = 4
|
|
720
|
+
) -> Dict[str, ParseResult]:
|
|
721
|
+
"""
|
|
722
|
+
Parse multiple Kotlin files with optional cross-file resolution.
|
|
723
|
+
|
|
724
|
+
Args:
|
|
725
|
+
files: List of (file_path, content) tuples
|
|
726
|
+
resolve_cross_file: Whether to resolve cross-file references
|
|
727
|
+
max_workers: Maximum number of parallel workers
|
|
728
|
+
|
|
729
|
+
Returns:
|
|
730
|
+
Dict mapping file paths to ParseResult objects
|
|
731
|
+
"""
|
|
732
|
+
results = {}
|
|
733
|
+
|
|
734
|
+
def parse_single(file_info: Tuple[str, Optional[str]]) -> Tuple[str, Optional[ParseResult]]:
|
|
735
|
+
file_path, content = file_info
|
|
736
|
+
if content is None:
|
|
737
|
+
try:
|
|
738
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
739
|
+
content = f.read()
|
|
740
|
+
except Exception as e:
|
|
741
|
+
logger.warning(f"Failed to read {file_path}: {e}")
|
|
742
|
+
return file_path, None
|
|
743
|
+
|
|
744
|
+
try:
|
|
745
|
+
result = self.parse(content, file_path)
|
|
746
|
+
return file_path, result
|
|
747
|
+
except Exception as e:
|
|
748
|
+
logger.warning(f"Failed to parse {file_path}: {e}")
|
|
749
|
+
return file_path, None
|
|
750
|
+
|
|
751
|
+
# Parse files in parallel
|
|
752
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
753
|
+
for file_path, result in executor.map(parse_single, files):
|
|
754
|
+
if result:
|
|
755
|
+
results[file_path] = result
|
|
756
|
+
|
|
757
|
+
# Register symbols for cross-file resolution
|
|
758
|
+
if resolve_cross_file:
|
|
759
|
+
for symbol in result.symbols:
|
|
760
|
+
self._symbol_to_file[symbol.name] = file_path
|
|
761
|
+
if symbol.qualified_name:
|
|
762
|
+
self._symbol_to_file[symbol.qualified_name] = file_path
|
|
763
|
+
|
|
764
|
+
return results
|
|
765
|
+
|
|
766
|
+
|
|
767
|
+
# Register the parser
|
|
768
|
+
parser_registry.register_parser(KotlinParser())
|