alita-sdk 0.3.379__py3-none-any.whl → 0.3.627__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +156 -0
- alita_sdk/cli/agent_loader.py +245 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3113 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/testcases/__init__.py +94 -0
- alita_sdk/cli/testcases/data_generation.py +119 -0
- alita_sdk/cli/testcases/discovery.py +96 -0
- alita_sdk/cli/testcases/executor.py +84 -0
- alita_sdk/cli/testcases/logger.py +85 -0
- alita_sdk/cli/testcases/parser.py +172 -0
- alita_sdk/cli/testcases/prompts.py +91 -0
- alita_sdk/cli/testcases/reporting.py +125 -0
- alita_sdk/cli/testcases/setup.py +108 -0
- alita_sdk/cli/testcases/test_runner.py +282 -0
- alita_sdk/cli/testcases/utils.py +39 -0
- alita_sdk/cli/testcases/validation.py +90 -0
- alita_sdk/cli/testcases/workflow.py +196 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +1 -1
- alita_sdk/configurations/ado.py +141 -20
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/figma.py +76 -0
- alita_sdk/configurations/gitlab.py +91 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +329 -0
- alita_sdk/configurations/qtest.py +72 -1
- alita_sdk/configurations/report_portal.py +96 -0
- alita_sdk/configurations/sharepoint.py +148 -0
- alita_sdk/configurations/testio.py +83 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +93 -0
- alita_sdk/configurations/zephyr_enterprise.py +93 -0
- alita_sdk/configurations/zephyr_essential.py +75 -0
- alita_sdk/runtime/clients/artifact.py +3 -3
- alita_sdk/runtime/clients/client.py +388 -46
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +8 -21
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +157 -39
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
- alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
- alita_sdk/runtime/langchain/langraph_agent.py +405 -84
- alita_sdk/runtime/langchain/utils.py +106 -7
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +31 -0
- alita_sdk/runtime/toolkits/application.py +29 -10
- alita_sdk/runtime/toolkits/artifact.py +20 -11
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +783 -0
- alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +251 -6
- alita_sdk/runtime/toolkits/tools.py +356 -69
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +10 -3
- alita_sdk/runtime/tools/application.py +27 -6
- alita_sdk/runtime/tools/artifact.py +511 -28
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +67 -35
- alita_sdk/runtime/tools/graph.py +10 -4
- alita_sdk/runtime/tools/image_generation.py +148 -46
- alita_sdk/runtime/tools/llm.py +1003 -128
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -4
- alita_sdk/runtime/tools/sandbox.py +65 -48
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +9 -3
- alita_sdk/runtime/tools/vectorstore_base.py +70 -14
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/constants.py +5 -1
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/serialization.py +155 -0
- alita_sdk/runtime/utils/streamlit.py +40 -13
- alita_sdk/runtime/utils/toolkit_utils.py +30 -9
- alita_sdk/runtime/utils/utils.py +36 -0
- alita_sdk/tools/__init__.py +134 -35
- alita_sdk/tools/ado/repos/__init__.py +51 -32
- alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
- alita_sdk/tools/ado/test_plan/__init__.py +25 -9
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
- alita_sdk/tools/ado/utils.py +1 -18
- alita_sdk/tools/ado/wiki/__init__.py +25 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
- alita_sdk/tools/ado/work_item/__init__.py +26 -13
- alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +11 -8
- alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +271 -84
- alita_sdk/tools/bitbucket/__init__.py +17 -11
- alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
- alita_sdk/tools/browser/__init__.py +5 -4
- alita_sdk/tools/carrier/__init__.py +5 -6
- alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
- alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
- alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
- alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +10 -7
- alita_sdk/tools/cloud/azure/__init__.py +10 -7
- alita_sdk/tools/cloud/gcp/__init__.py +10 -7
- alita_sdk/tools/cloud/k8s/__init__.py +10 -7
- alita_sdk/tools/code/linter/__init__.py +10 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +11 -8
- alita_sdk/tools/code_indexer_toolkit.py +82 -22
- alita_sdk/tools/confluence/__init__.py +22 -16
- alita_sdk/tools/confluence/api_wrapper.py +107 -30
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +12 -5
- alita_sdk/tools/elastic/__init__.py +11 -8
- alita_sdk/tools/elitea_base.py +493 -30
- alita_sdk/tools/figma/__init__.py +58 -11
- alita_sdk/tools/figma/api_wrapper.py +1235 -143
- alita_sdk/tools/figma/figma_client.py +73 -0
- alita_sdk/tools/figma/toon_tools.py +2748 -0
- alita_sdk/tools/github/__init__.py +14 -15
- alita_sdk/tools/github/github_client.py +224 -100
- alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
- alita_sdk/tools/github/schemas.py +14 -5
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/github/tool_prompts.py +9 -22
- alita_sdk/tools/gitlab/__init__.py +16 -11
- alita_sdk/tools/gitlab/api_wrapper.py +218 -48
- alita_sdk/tools/gitlab_org/__init__.py +10 -9
- alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
- alita_sdk/tools/google/bigquery/__init__.py +13 -12
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +11 -8
- alita_sdk/tools/google_places/api_wrapper.py +1 -1
- alita_sdk/tools/jira/__init__.py +17 -10
- alita_sdk/tools/jira/api_wrapper.py +92 -41
- alita_sdk/tools/keycloak/__init__.py +11 -8
- alita_sdk/tools/localgit/__init__.py +9 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +12 -4
- alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
- alita_sdk/tools/ocr/__init__.py +11 -8
- alita_sdk/tools/openapi/__init__.py +491 -106
- alita_sdk/tools/openapi/api_wrapper.py +1368 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +20 -12
- alita_sdk/tools/pandas/api_wrapper.py +38 -25
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +10 -9
- alita_sdk/tools/pptx/__init__.py +11 -10
- alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
- alita_sdk/tools/qtest/__init__.py +31 -11
- alita_sdk/tools/qtest/api_wrapper.py +2135 -86
- alita_sdk/tools/rally/__init__.py +10 -9
- alita_sdk/tools/rally/api_wrapper.py +1 -1
- alita_sdk/tools/report_portal/__init__.py +12 -8
- alita_sdk/tools/salesforce/__init__.py +10 -8
- alita_sdk/tools/servicenow/__init__.py +17 -15
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +10 -7
- alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +10 -7
- alita_sdk/tools/slack/api_wrapper.py +2 -2
- alita_sdk/tools/sql/__init__.py +12 -9
- alita_sdk/tools/testio/__init__.py +10 -7
- alita_sdk/tools/testrail/__init__.py +11 -10
- alita_sdk/tools/testrail/api_wrapper.py +1 -1
- alita_sdk/tools/utils/__init__.py +9 -4
- alita_sdk/tools/utils/content_parser.py +103 -18
- alita_sdk/tools/utils/text_operations.py +410 -0
- alita_sdk/tools/utils/tool_prompts.py +79 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
- alita_sdk/tools/xray/__init__.py +13 -9
- alita_sdk/tools/yagmail/__init__.py +9 -3
- alita_sdk/tools/zephyr/__init__.py +10 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
- alita_sdk/tools/zephyr_essential/__init__.py +10 -7
- alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
- alita_sdk/tools/zephyr_essential/client.py +2 -2
- alita_sdk/tools/zephyr_scale/__init__.py +11 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
- alita_sdk/tools/zephyr_squad/__init__.py +10 -7
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
- alita_sdk-0.3.627.dist-info/RECORD +468 -0
- alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
- alita_sdk-0.3.379.dist-info/RECORD +0 -360
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,851 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Go Parser - Regex-based parser for Go source files.
|
|
3
|
+
|
|
4
|
+
Extracts symbols and relationships from .go files using comprehensive
|
|
5
|
+
regex patterns. Supports Go-specific features like interfaces, goroutines,
|
|
6
|
+
channels, defer, and struct embedding.
|
|
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 Go regex patterns
|
|
34
|
+
PATTERNS = {
|
|
35
|
+
# Package declaration
|
|
36
|
+
'package': re.compile(
|
|
37
|
+
r'^\s*package\s+(\w+)',
|
|
38
|
+
re.MULTILINE
|
|
39
|
+
),
|
|
40
|
+
|
|
41
|
+
# Import statements
|
|
42
|
+
'import_single': re.compile(
|
|
43
|
+
r'^\s*import\s+(?:(\w+)\s+)?"([^"]+)"',
|
|
44
|
+
re.MULTILINE
|
|
45
|
+
),
|
|
46
|
+
'import_block': re.compile(
|
|
47
|
+
r'import\s*\(\s*([\s\S]*?)\s*\)',
|
|
48
|
+
re.MULTILINE
|
|
49
|
+
),
|
|
50
|
+
'import_line': re.compile(
|
|
51
|
+
r'^\s*(?:(\w+)\s+)?"([^"]+)"',
|
|
52
|
+
re.MULTILINE
|
|
53
|
+
),
|
|
54
|
+
|
|
55
|
+
# Struct declarations
|
|
56
|
+
'struct': re.compile(
|
|
57
|
+
r'^\s*type\s+(\w+)\s+struct\s*\{',
|
|
58
|
+
re.MULTILINE
|
|
59
|
+
),
|
|
60
|
+
|
|
61
|
+
# Interface declarations
|
|
62
|
+
'interface': re.compile(
|
|
63
|
+
r'^\s*type\s+(\w+)\s+interface\s*\{',
|
|
64
|
+
re.MULTILINE
|
|
65
|
+
),
|
|
66
|
+
|
|
67
|
+
# Type alias and definitions
|
|
68
|
+
'type_alias': re.compile(
|
|
69
|
+
r'^\s*type\s+(\w+)\s*=\s*([^\n{]+)',
|
|
70
|
+
re.MULTILINE
|
|
71
|
+
),
|
|
72
|
+
'type_def': re.compile(
|
|
73
|
+
r'^\s*type\s+(\w+)\s+([^\n={]+)',
|
|
74
|
+
re.MULTILINE
|
|
75
|
+
),
|
|
76
|
+
|
|
77
|
+
# Function declarations
|
|
78
|
+
'function': re.compile(
|
|
79
|
+
r'^\s*func\s+(\w+)\s*(?:\[[^\]]+\])?\s*\([^)]*\)\s*(?:\([^)]*\)|[^\n{]+)?',
|
|
80
|
+
re.MULTILINE
|
|
81
|
+
),
|
|
82
|
+
|
|
83
|
+
# Method declarations (with receiver)
|
|
84
|
+
'method': re.compile(
|
|
85
|
+
r'^\s*func\s+\(\s*(\w+)\s+\*?(\w+)\s*\)\s*(\w+)\s*(?:\[[^\]]+\])?\s*\([^)]*\)',
|
|
86
|
+
re.MULTILINE
|
|
87
|
+
),
|
|
88
|
+
|
|
89
|
+
# Variable declarations
|
|
90
|
+
'var': re.compile(
|
|
91
|
+
r'^\s*var\s+(\w+)\s+([^\n=]+)',
|
|
92
|
+
re.MULTILINE
|
|
93
|
+
),
|
|
94
|
+
'var_block': re.compile(
|
|
95
|
+
r'var\s*\(\s*([\s\S]*?)\s*\)',
|
|
96
|
+
re.MULTILINE
|
|
97
|
+
),
|
|
98
|
+
|
|
99
|
+
# Constant declarations
|
|
100
|
+
'const': re.compile(
|
|
101
|
+
r'^\s*const\s+(\w+)\s*(?:([^\n=]+))?\s*=',
|
|
102
|
+
re.MULTILINE
|
|
103
|
+
),
|
|
104
|
+
'const_block': re.compile(
|
|
105
|
+
r'const\s*\(\s*([\s\S]*?)\s*\)',
|
|
106
|
+
re.MULTILINE
|
|
107
|
+
),
|
|
108
|
+
|
|
109
|
+
# Struct field with embedded type
|
|
110
|
+
'embedded_type': re.compile(
|
|
111
|
+
r'^\s*\*?([A-Z]\w*)\s*$',
|
|
112
|
+
re.MULTILINE
|
|
113
|
+
),
|
|
114
|
+
|
|
115
|
+
# Struct field with type
|
|
116
|
+
'struct_field': re.compile(
|
|
117
|
+
r'^\s*(\w+)\s+(\*?\[?\]?\*?(?:map\[[^\]]+\])?[A-Z]\w*)',
|
|
118
|
+
re.MULTILINE
|
|
119
|
+
),
|
|
120
|
+
|
|
121
|
+
# Interface method
|
|
122
|
+
'interface_method': re.compile(
|
|
123
|
+
r'^\s*(\w+)\s*\([^)]*\)',
|
|
124
|
+
re.MULTILINE
|
|
125
|
+
),
|
|
126
|
+
|
|
127
|
+
# Function calls
|
|
128
|
+
'function_call': re.compile(
|
|
129
|
+
r'(?:^|[^\w.])(\w+)\s*\(',
|
|
130
|
+
re.MULTILINE
|
|
131
|
+
),
|
|
132
|
+
|
|
133
|
+
# Method calls
|
|
134
|
+
'method_call': re.compile(
|
|
135
|
+
r'\.(\w+)\s*\(',
|
|
136
|
+
re.MULTILINE
|
|
137
|
+
),
|
|
138
|
+
|
|
139
|
+
# Type assertions
|
|
140
|
+
'type_assertion': re.compile(
|
|
141
|
+
r'\.\(\s*\*?(\w+)\s*\)',
|
|
142
|
+
re.MULTILINE
|
|
143
|
+
),
|
|
144
|
+
|
|
145
|
+
# Struct literal / composite literal
|
|
146
|
+
'struct_literal': re.compile(
|
|
147
|
+
r'([A-Z]\w*)\s*\{',
|
|
148
|
+
re.MULTILINE
|
|
149
|
+
),
|
|
150
|
+
|
|
151
|
+
# Package qualified access
|
|
152
|
+
'pkg_access': re.compile(
|
|
153
|
+
r'(\w+)\.([A-Z]\w*)',
|
|
154
|
+
re.MULTILINE
|
|
155
|
+
),
|
|
156
|
+
|
|
157
|
+
# Goroutine
|
|
158
|
+
'goroutine': re.compile(
|
|
159
|
+
r'\bgo\s+(\w+)',
|
|
160
|
+
re.MULTILINE
|
|
161
|
+
),
|
|
162
|
+
|
|
163
|
+
# Channel operations
|
|
164
|
+
'channel_make': re.compile(
|
|
165
|
+
r'make\s*\(\s*chan\s+([^\),]+)',
|
|
166
|
+
re.MULTILINE
|
|
167
|
+
),
|
|
168
|
+
|
|
169
|
+
# Defer statement
|
|
170
|
+
'defer': re.compile(
|
|
171
|
+
r'\bdefer\s+(\w+)',
|
|
172
|
+
re.MULTILINE
|
|
173
|
+
),
|
|
174
|
+
|
|
175
|
+
# Error handling pattern
|
|
176
|
+
'error_return': re.compile(
|
|
177
|
+
r'return\s+(?:\w+,\s*)?(\w+Error|errors\.New|fmt\.Errorf)',
|
|
178
|
+
re.MULTILINE
|
|
179
|
+
),
|
|
180
|
+
|
|
181
|
+
# Doc comments
|
|
182
|
+
'doc_comment': re.compile(
|
|
183
|
+
r'^//\s*(.+)',
|
|
184
|
+
re.MULTILINE
|
|
185
|
+
),
|
|
186
|
+
|
|
187
|
+
# Build tags
|
|
188
|
+
'build_tag': re.compile(
|
|
189
|
+
r'//go:build\s+(.+)',
|
|
190
|
+
re.MULTILINE
|
|
191
|
+
),
|
|
192
|
+
'plus_build': re.compile(
|
|
193
|
+
r'//\s*\+build\s+(.+)',
|
|
194
|
+
re.MULTILINE
|
|
195
|
+
),
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class GoParser(BaseParser):
|
|
200
|
+
"""Go source code parser using regex patterns."""
|
|
201
|
+
|
|
202
|
+
# Global symbol registry for cross-file resolution
|
|
203
|
+
_global_symbols: Dict[str, Set[str]] = {}
|
|
204
|
+
_symbol_to_file: Dict[str, str] = {}
|
|
205
|
+
|
|
206
|
+
def __init__(self):
|
|
207
|
+
super().__init__("go")
|
|
208
|
+
|
|
209
|
+
def _get_supported_extensions(self) -> Set[str]:
|
|
210
|
+
return {'.go'}
|
|
211
|
+
|
|
212
|
+
def _make_range(self, content: str, start_offset: int, end_line: int) -> Range:
|
|
213
|
+
"""Create a Range object from content and offset."""
|
|
214
|
+
start_line = content[:start_offset].count('\n') + 1
|
|
215
|
+
last_newline = content.rfind('\n', 0, start_offset)
|
|
216
|
+
start_col = start_offset - last_newline - 1 if last_newline >= 0 else start_offset
|
|
217
|
+
return Range(
|
|
218
|
+
start=Position(line=start_line, column=start_col),
|
|
219
|
+
end=Position(line=end_line, column=0)
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
def _make_symbol(
|
|
223
|
+
self,
|
|
224
|
+
name: str,
|
|
225
|
+
symbol_type: SymbolType,
|
|
226
|
+
content: str,
|
|
227
|
+
file_path: str,
|
|
228
|
+
start_offset: int,
|
|
229
|
+
end_line: int,
|
|
230
|
+
scope: Scope = Scope.GLOBAL,
|
|
231
|
+
parent: Optional[str] = None,
|
|
232
|
+
full_name: Optional[str] = None,
|
|
233
|
+
docstring: Optional[str] = None,
|
|
234
|
+
visibility: Optional[str] = None,
|
|
235
|
+
is_static: bool = False,
|
|
236
|
+
is_async: bool = False,
|
|
237
|
+
return_type: Optional[str] = None,
|
|
238
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
239
|
+
) -> Symbol:
|
|
240
|
+
"""Create a Symbol with correct fields."""
|
|
241
|
+
return Symbol(
|
|
242
|
+
name=name,
|
|
243
|
+
symbol_type=symbol_type,
|
|
244
|
+
scope=scope,
|
|
245
|
+
range=self._make_range(content, start_offset, end_line),
|
|
246
|
+
file_path=file_path,
|
|
247
|
+
parent_symbol=parent,
|
|
248
|
+
full_name=full_name,
|
|
249
|
+
visibility=visibility,
|
|
250
|
+
is_static=is_static,
|
|
251
|
+
is_async=is_async,
|
|
252
|
+
docstring=docstring,
|
|
253
|
+
return_type=return_type,
|
|
254
|
+
metadata=metadata or {}
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def _make_relationship(
|
|
258
|
+
self,
|
|
259
|
+
source: str,
|
|
260
|
+
target: str,
|
|
261
|
+
rel_type: RelationshipType,
|
|
262
|
+
file_path: str,
|
|
263
|
+
content: str,
|
|
264
|
+
offset: int,
|
|
265
|
+
metadata: Optional[Dict[str, Any]] = None
|
|
266
|
+
) -> Relationship:
|
|
267
|
+
"""Create a Relationship with correct fields."""
|
|
268
|
+
return Relationship(
|
|
269
|
+
source_symbol=source,
|
|
270
|
+
target_symbol=target,
|
|
271
|
+
relationship_type=rel_type,
|
|
272
|
+
source_file=file_path,
|
|
273
|
+
source_range=self._make_range(content, offset, content[:offset].count('\n') + 1),
|
|
274
|
+
metadata=metadata or {}
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
def parse_file(self, file_path: str, content: Optional[str] = None) -> ParseResult:
|
|
278
|
+
"""Parse Go source code and extract symbols and relationships."""
|
|
279
|
+
if content is None:
|
|
280
|
+
try:
|
|
281
|
+
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
282
|
+
content = f.read()
|
|
283
|
+
except Exception as e:
|
|
284
|
+
return ParseResult(
|
|
285
|
+
file_path=file_path,
|
|
286
|
+
language=self.language,
|
|
287
|
+
symbols=[],
|
|
288
|
+
relationships=[],
|
|
289
|
+
errors=[str(e)]
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
symbols = self._extract_symbols(content, file_path)
|
|
293
|
+
relationships = self._extract_relationships(content, file_path, symbols)
|
|
294
|
+
|
|
295
|
+
return ParseResult(
|
|
296
|
+
symbols=symbols,
|
|
297
|
+
relationships=relationships,
|
|
298
|
+
file_path=file_path,
|
|
299
|
+
language=self.language
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
def _extract_symbols(self, content: str, file_path: str) -> List[Symbol]:
|
|
303
|
+
"""Extract all symbols from Go source code."""
|
|
304
|
+
symbols = []
|
|
305
|
+
package_name = self._extract_package(content)
|
|
306
|
+
|
|
307
|
+
# Extract structs
|
|
308
|
+
for match in PATTERNS['struct'].finditer(content):
|
|
309
|
+
name = match.group(1)
|
|
310
|
+
|
|
311
|
+
docstring = self._find_preceding_doc(content, match.start())
|
|
312
|
+
is_exported = name[0].isupper()
|
|
313
|
+
|
|
314
|
+
# Find embedded types in struct
|
|
315
|
+
struct_content = self._extract_block_content(content, match.end())
|
|
316
|
+
embedded = self._find_embedded_types(struct_content)
|
|
317
|
+
|
|
318
|
+
metadata = {'exported': is_exported}
|
|
319
|
+
if embedded:
|
|
320
|
+
metadata['embedded_types'] = embedded
|
|
321
|
+
|
|
322
|
+
symbols.append(self._make_symbol(
|
|
323
|
+
name=name,
|
|
324
|
+
symbol_type=SymbolType.CLASS,
|
|
325
|
+
content=content,
|
|
326
|
+
file_path=file_path,
|
|
327
|
+
start_offset=match.start(),
|
|
328
|
+
end_line=self._find_block_end(content, match.end()),
|
|
329
|
+
full_name=f"{package_name}.{name}" if package_name else name,
|
|
330
|
+
docstring=docstring,
|
|
331
|
+
visibility='public' if is_exported else 'private',
|
|
332
|
+
metadata=metadata
|
|
333
|
+
))
|
|
334
|
+
|
|
335
|
+
# Extract interfaces
|
|
336
|
+
for match in PATTERNS['interface'].finditer(content):
|
|
337
|
+
name = match.group(1)
|
|
338
|
+
|
|
339
|
+
docstring = self._find_preceding_doc(content, match.start())
|
|
340
|
+
is_exported = name[0].isupper()
|
|
341
|
+
|
|
342
|
+
# Find embedded interfaces
|
|
343
|
+
iface_content = self._extract_block_content(content, match.end())
|
|
344
|
+
embedded = self._find_embedded_types(iface_content)
|
|
345
|
+
|
|
346
|
+
metadata = {'exported': is_exported}
|
|
347
|
+
if embedded:
|
|
348
|
+
metadata['embedded_interfaces'] = embedded
|
|
349
|
+
|
|
350
|
+
symbols.append(self._make_symbol(
|
|
351
|
+
name=name,
|
|
352
|
+
symbol_type=SymbolType.INTERFACE,
|
|
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"{package_name}.{name}" if package_name else name,
|
|
358
|
+
docstring=docstring,
|
|
359
|
+
visibility='public' if is_exported else 'private',
|
|
360
|
+
metadata=metadata
|
|
361
|
+
))
|
|
362
|
+
|
|
363
|
+
# Extract type aliases
|
|
364
|
+
for match in PATTERNS['type_alias'].finditer(content):
|
|
365
|
+
name, aliased = match.groups()
|
|
366
|
+
line = content[:match.start()].count('\n') + 1
|
|
367
|
+
|
|
368
|
+
# Skip if it's actually a struct or interface (handled above)
|
|
369
|
+
if 'struct' in aliased or 'interface' in aliased:
|
|
370
|
+
continue
|
|
371
|
+
|
|
372
|
+
is_exported = name[0].isupper()
|
|
373
|
+
|
|
374
|
+
symbols.append(self._make_symbol(
|
|
375
|
+
name=name,
|
|
376
|
+
symbol_type=SymbolType.TYPE_ALIAS,
|
|
377
|
+
content=content,
|
|
378
|
+
file_path=file_path,
|
|
379
|
+
start_offset=match.start(),
|
|
380
|
+
end_line=line,
|
|
381
|
+
full_name=f"{package_name}.{name}" if package_name else name,
|
|
382
|
+
visibility='public' if is_exported else 'private',
|
|
383
|
+
metadata={
|
|
384
|
+
'exported': is_exported,
|
|
385
|
+
'aliased_type': aliased.strip()
|
|
386
|
+
}
|
|
387
|
+
))
|
|
388
|
+
|
|
389
|
+
# Extract type definitions (non-alias, non-struct, non-interface)
|
|
390
|
+
for match in PATTERNS['type_def'].finditer(content):
|
|
391
|
+
name, underlying = match.groups()
|
|
392
|
+
line = content[:match.start()].count('\n') + 1
|
|
393
|
+
|
|
394
|
+
# Skip struct, interface, and already handled aliases
|
|
395
|
+
if 'struct' in underlying or 'interface' in underlying or '=' in underlying:
|
|
396
|
+
continue
|
|
397
|
+
|
|
398
|
+
is_exported = name[0].isupper()
|
|
399
|
+
|
|
400
|
+
symbols.append(self._make_symbol(
|
|
401
|
+
name=name,
|
|
402
|
+
symbol_type=SymbolType.TYPE_ALIAS,
|
|
403
|
+
content=content,
|
|
404
|
+
file_path=file_path,
|
|
405
|
+
start_offset=match.start(),
|
|
406
|
+
end_line=line,
|
|
407
|
+
full_name=f"{package_name}.{name}" if package_name else name,
|
|
408
|
+
visibility='public' if is_exported else 'private',
|
|
409
|
+
metadata={
|
|
410
|
+
'exported': is_exported,
|
|
411
|
+
'underlying_type': underlying.strip()
|
|
412
|
+
}
|
|
413
|
+
))
|
|
414
|
+
|
|
415
|
+
# Extract functions
|
|
416
|
+
for match in PATTERNS['function'].finditer(content):
|
|
417
|
+
name = match.group(1)
|
|
418
|
+
|
|
419
|
+
docstring = self._find_preceding_doc(content, match.start())
|
|
420
|
+
is_exported = name[0].isupper()
|
|
421
|
+
|
|
422
|
+
# Parse return type if present
|
|
423
|
+
full_match = match.group(0)
|
|
424
|
+
return_type = self._extract_return_type(full_match)
|
|
425
|
+
|
|
426
|
+
metadata = {'exported': is_exported}
|
|
427
|
+
if return_type:
|
|
428
|
+
metadata['return_type'] = return_type
|
|
429
|
+
|
|
430
|
+
symbols.append(self._make_symbol(
|
|
431
|
+
name=name,
|
|
432
|
+
symbol_type=SymbolType.FUNCTION,
|
|
433
|
+
content=content,
|
|
434
|
+
file_path=file_path,
|
|
435
|
+
start_offset=match.start(),
|
|
436
|
+
end_line=self._find_block_end(content, match.end()),
|
|
437
|
+
full_name=f"{package_name}.{name}" if package_name else name,
|
|
438
|
+
docstring=docstring,
|
|
439
|
+
visibility='public' if is_exported else 'private',
|
|
440
|
+
return_type=return_type,
|
|
441
|
+
metadata=metadata
|
|
442
|
+
))
|
|
443
|
+
|
|
444
|
+
# Extract methods
|
|
445
|
+
for match in PATTERNS['method'].finditer(content):
|
|
446
|
+
receiver_name, receiver_type, method_name = match.groups()
|
|
447
|
+
|
|
448
|
+
docstring = self._find_preceding_doc(content, match.start())
|
|
449
|
+
is_exported = method_name[0].isupper()
|
|
450
|
+
|
|
451
|
+
symbols.append(self._make_symbol(
|
|
452
|
+
name=method_name,
|
|
453
|
+
symbol_type=SymbolType.METHOD,
|
|
454
|
+
content=content,
|
|
455
|
+
file_path=file_path,
|
|
456
|
+
start_offset=match.start(),
|
|
457
|
+
end_line=self._find_block_end(content, match.end()),
|
|
458
|
+
full_name=f"{package_name}.{receiver_type}.{method_name}" if package_name else f"{receiver_type}.{method_name}",
|
|
459
|
+
parent=receiver_type,
|
|
460
|
+
docstring=docstring,
|
|
461
|
+
visibility='public' if is_exported else 'private',
|
|
462
|
+
metadata={
|
|
463
|
+
'exported': is_exported,
|
|
464
|
+
'receiver_type': receiver_type,
|
|
465
|
+
'receiver_name': receiver_name
|
|
466
|
+
}
|
|
467
|
+
))
|
|
468
|
+
|
|
469
|
+
# Extract package-level variables
|
|
470
|
+
for match in PATTERNS['var'].finditer(content):
|
|
471
|
+
name, var_type = match.groups()
|
|
472
|
+
line = content[:match.start()].count('\n') + 1
|
|
473
|
+
|
|
474
|
+
is_exported = name[0].isupper()
|
|
475
|
+
|
|
476
|
+
symbols.append(self._make_symbol(
|
|
477
|
+
name=name,
|
|
478
|
+
symbol_type=SymbolType.VARIABLE,
|
|
479
|
+
content=content,
|
|
480
|
+
file_path=file_path,
|
|
481
|
+
start_offset=match.start(),
|
|
482
|
+
end_line=line,
|
|
483
|
+
full_name=f"{package_name}.{name}" if package_name else name,
|
|
484
|
+
visibility='public' if is_exported else 'private',
|
|
485
|
+
metadata={
|
|
486
|
+
'exported': is_exported,
|
|
487
|
+
'type': var_type.strip()
|
|
488
|
+
}
|
|
489
|
+
))
|
|
490
|
+
|
|
491
|
+
# Extract constants
|
|
492
|
+
for match in PATTERNS['const'].finditer(content):
|
|
493
|
+
name, const_type = match.groups()
|
|
494
|
+
line = content[:match.start()].count('\n') + 1
|
|
495
|
+
|
|
496
|
+
is_exported = name[0].isupper()
|
|
497
|
+
|
|
498
|
+
metadata = {'exported': is_exported}
|
|
499
|
+
if const_type:
|
|
500
|
+
metadata['type'] = const_type.strip()
|
|
501
|
+
|
|
502
|
+
symbols.append(self._make_symbol(
|
|
503
|
+
name=name,
|
|
504
|
+
symbol_type=SymbolType.CONSTANT,
|
|
505
|
+
content=content,
|
|
506
|
+
file_path=file_path,
|
|
507
|
+
start_offset=match.start(),
|
|
508
|
+
end_line=line,
|
|
509
|
+
full_name=f"{package_name}.{name}" if package_name else name,
|
|
510
|
+
visibility='public' if is_exported else 'private',
|
|
511
|
+
metadata=metadata
|
|
512
|
+
))
|
|
513
|
+
|
|
514
|
+
return symbols
|
|
515
|
+
|
|
516
|
+
def _extract_relationships(
|
|
517
|
+
self,
|
|
518
|
+
content: str,
|
|
519
|
+
file_path: str,
|
|
520
|
+
symbols: List[Symbol]
|
|
521
|
+
) -> List[Relationship]:
|
|
522
|
+
"""Extract relationships from Go source code."""
|
|
523
|
+
relationships = []
|
|
524
|
+
current_scope = Path(file_path).stem
|
|
525
|
+
package_name = self._extract_package(content)
|
|
526
|
+
|
|
527
|
+
# Extract single imports
|
|
528
|
+
for match in PATTERNS['import_single'].finditer(content):
|
|
529
|
+
alias, path = match.groups()
|
|
530
|
+
|
|
531
|
+
relationships.append(self._make_relationship(
|
|
532
|
+
source=package_name or current_scope,
|
|
533
|
+
target=path,
|
|
534
|
+
rel_type=RelationshipType.IMPORTS,
|
|
535
|
+
file_path=file_path,
|
|
536
|
+
content=content,
|
|
537
|
+
offset=match.start(),
|
|
538
|
+
metadata={'alias': alias} if alias else None
|
|
539
|
+
))
|
|
540
|
+
|
|
541
|
+
# Extract import blocks
|
|
542
|
+
for match in PATTERNS['import_block'].finditer(content):
|
|
543
|
+
block_content = match.group(1)
|
|
544
|
+
|
|
545
|
+
for line_match in PATTERNS['import_line'].finditer(block_content):
|
|
546
|
+
alias, path = line_match.groups()
|
|
547
|
+
|
|
548
|
+
metadata = {}
|
|
549
|
+
if alias:
|
|
550
|
+
metadata['alias'] = alias
|
|
551
|
+
if alias == '_':
|
|
552
|
+
metadata['blank_import'] = True
|
|
553
|
+
|
|
554
|
+
relationships.append(self._make_relationship(
|
|
555
|
+
source=package_name or current_scope,
|
|
556
|
+
target=path,
|
|
557
|
+
rel_type=RelationshipType.IMPORTS,
|
|
558
|
+
file_path=file_path,
|
|
559
|
+
content=content,
|
|
560
|
+
offset=match.start(),
|
|
561
|
+
metadata=metadata if metadata else None
|
|
562
|
+
))
|
|
563
|
+
|
|
564
|
+
# Extract struct embedding (composition)
|
|
565
|
+
for match in PATTERNS['struct'].finditer(content):
|
|
566
|
+
struct_name = match.group(1)
|
|
567
|
+
|
|
568
|
+
struct_content = self._extract_block_content(content, match.end())
|
|
569
|
+
embedded = self._find_embedded_types(struct_content)
|
|
570
|
+
|
|
571
|
+
for embedded_type in embedded:
|
|
572
|
+
relationships.append(self._make_relationship(
|
|
573
|
+
source=struct_name,
|
|
574
|
+
target=embedded_type,
|
|
575
|
+
rel_type=RelationshipType.COMPOSITION,
|
|
576
|
+
file_path=file_path,
|
|
577
|
+
content=content,
|
|
578
|
+
offset=match.start(),
|
|
579
|
+
metadata={'embedding': True}
|
|
580
|
+
))
|
|
581
|
+
|
|
582
|
+
# Extract interface embedding
|
|
583
|
+
for match in PATTERNS['interface'].finditer(content):
|
|
584
|
+
iface_name = match.group(1)
|
|
585
|
+
|
|
586
|
+
iface_content = self._extract_block_content(content, match.end())
|
|
587
|
+
embedded = self._find_embedded_types(iface_content)
|
|
588
|
+
|
|
589
|
+
for embedded_iface in embedded:
|
|
590
|
+
relationships.append(self._make_relationship(
|
|
591
|
+
source=iface_name,
|
|
592
|
+
target=embedded_iface,
|
|
593
|
+
rel_type=RelationshipType.INHERITANCE,
|
|
594
|
+
file_path=file_path,
|
|
595
|
+
content=content,
|
|
596
|
+
offset=match.start(),
|
|
597
|
+
metadata={'embedding': True}
|
|
598
|
+
))
|
|
599
|
+
|
|
600
|
+
# Extract method relationships (receiver type)
|
|
601
|
+
for match in PATTERNS['method'].finditer(content):
|
|
602
|
+
_, receiver_type, method_name = match.groups()
|
|
603
|
+
|
|
604
|
+
relationships.append(self._make_relationship(
|
|
605
|
+
source=method_name,
|
|
606
|
+
target=receiver_type,
|
|
607
|
+
rel_type=RelationshipType.CONTAINS,
|
|
608
|
+
file_path=file_path,
|
|
609
|
+
content=content,
|
|
610
|
+
offset=match.start()
|
|
611
|
+
))
|
|
612
|
+
|
|
613
|
+
# Extract function calls
|
|
614
|
+
symbol_names = {s.name for s in symbols}
|
|
615
|
+
for match in PATTERNS['function_call'].finditer(content):
|
|
616
|
+
func_name = match.group(1)
|
|
617
|
+
|
|
618
|
+
# Skip keywords, builtins, and local symbols
|
|
619
|
+
if func_name not in symbol_names and func_name not in {
|
|
620
|
+
'if', 'for', 'switch', 'select', 'go', 'defer', 'return', 'range',
|
|
621
|
+
'make', 'new', 'len', 'cap', 'append', 'copy', 'delete', 'close',
|
|
622
|
+
'panic', 'recover', 'print', 'println', 'complex', 'real', 'imag',
|
|
623
|
+
'func', 'type', 'var', 'const', 'map', 'chan', 'interface', 'struct'
|
|
624
|
+
}:
|
|
625
|
+
relationships.append(self._make_relationship(
|
|
626
|
+
source=current_scope,
|
|
627
|
+
target=func_name,
|
|
628
|
+
rel_type=RelationshipType.CALLS,
|
|
629
|
+
file_path=file_path,
|
|
630
|
+
content=content,
|
|
631
|
+
offset=match.start()
|
|
632
|
+
))
|
|
633
|
+
|
|
634
|
+
# Extract package-qualified calls
|
|
635
|
+
for match in PATTERNS['pkg_access'].finditer(content):
|
|
636
|
+
pkg, name = match.groups()
|
|
637
|
+
|
|
638
|
+
# Skip if pkg looks like a variable (lowercase)
|
|
639
|
+
if pkg[0].islower() and pkg not in {'os', 'io', 'fmt', 'log', 'net', 'http', 'sync', 'time', 'json', 'xml', 'sql'}:
|
|
640
|
+
continue
|
|
641
|
+
|
|
642
|
+
relationships.append(self._make_relationship(
|
|
643
|
+
source=current_scope,
|
|
644
|
+
target=f"{pkg}.{name}",
|
|
645
|
+
rel_type=RelationshipType.REFERENCES,
|
|
646
|
+
file_path=file_path,
|
|
647
|
+
content=content,
|
|
648
|
+
offset=match.start()
|
|
649
|
+
))
|
|
650
|
+
|
|
651
|
+
# Extract struct literal usage (instantiation)
|
|
652
|
+
for match in PATTERNS['struct_literal'].finditer(content):
|
|
653
|
+
type_name = match.group(1)
|
|
654
|
+
|
|
655
|
+
if type_name not in symbol_names:
|
|
656
|
+
relationships.append(self._make_relationship(
|
|
657
|
+
source=current_scope,
|
|
658
|
+
target=type_name,
|
|
659
|
+
rel_type=RelationshipType.USES,
|
|
660
|
+
file_path=file_path,
|
|
661
|
+
content=content,
|
|
662
|
+
offset=match.start()
|
|
663
|
+
))
|
|
664
|
+
|
|
665
|
+
# Extract type assertions
|
|
666
|
+
for match in PATTERNS['type_assertion'].finditer(content):
|
|
667
|
+
type_name = match.group(1)
|
|
668
|
+
|
|
669
|
+
relationships.append(self._make_relationship(
|
|
670
|
+
source=current_scope,
|
|
671
|
+
target=type_name,
|
|
672
|
+
rel_type=RelationshipType.REFERENCES,
|
|
673
|
+
file_path=file_path,
|
|
674
|
+
content=content,
|
|
675
|
+
offset=match.start(),
|
|
676
|
+
metadata={'type_assertion': True}
|
|
677
|
+
))
|
|
678
|
+
|
|
679
|
+
# Extract goroutine calls
|
|
680
|
+
for match in PATTERNS['goroutine'].finditer(content):
|
|
681
|
+
func_name = match.group(1)
|
|
682
|
+
|
|
683
|
+
if func_name not in {'func'}: # Skip anonymous functions
|
|
684
|
+
relationships.append(self._make_relationship(
|
|
685
|
+
source=current_scope,
|
|
686
|
+
target=func_name,
|
|
687
|
+
rel_type=RelationshipType.CALLS,
|
|
688
|
+
file_path=file_path,
|
|
689
|
+
content=content,
|
|
690
|
+
offset=match.start(),
|
|
691
|
+
metadata={'goroutine': True}
|
|
692
|
+
))
|
|
693
|
+
|
|
694
|
+
# Extract deferred calls
|
|
695
|
+
for match in PATTERNS['defer'].finditer(content):
|
|
696
|
+
func_name = match.group(1)
|
|
697
|
+
|
|
698
|
+
if func_name not in {'func'}: # Skip anonymous functions
|
|
699
|
+
relationships.append(self._make_relationship(
|
|
700
|
+
source=current_scope,
|
|
701
|
+
target=func_name,
|
|
702
|
+
rel_type=RelationshipType.CALLS,
|
|
703
|
+
file_path=file_path,
|
|
704
|
+
content=content,
|
|
705
|
+
offset=match.start(),
|
|
706
|
+
metadata={'deferred': True}
|
|
707
|
+
))
|
|
708
|
+
|
|
709
|
+
return relationships
|
|
710
|
+
|
|
711
|
+
def _extract_package(self, content: str) -> Optional[str]:
|
|
712
|
+
"""Extract package name from content."""
|
|
713
|
+
match = PATTERNS['package'].search(content)
|
|
714
|
+
return match.group(1) if match else None
|
|
715
|
+
|
|
716
|
+
def _extract_block_content(self, content: str, start: int) -> str:
|
|
717
|
+
"""Extract content between braces."""
|
|
718
|
+
brace_count = 0
|
|
719
|
+
block_start = None
|
|
720
|
+
|
|
721
|
+
for i, char in enumerate(content[start:], start):
|
|
722
|
+
if char == '{':
|
|
723
|
+
if block_start is None:
|
|
724
|
+
block_start = i + 1
|
|
725
|
+
brace_count += 1
|
|
726
|
+
elif char == '}':
|
|
727
|
+
brace_count -= 1
|
|
728
|
+
if brace_count == 0 and block_start is not None:
|
|
729
|
+
return content[block_start:i]
|
|
730
|
+
|
|
731
|
+
return ""
|
|
732
|
+
|
|
733
|
+
def _find_embedded_types(self, block_content: str) -> List[str]:
|
|
734
|
+
"""Find embedded types in a struct or interface block."""
|
|
735
|
+
embedded = []
|
|
736
|
+
|
|
737
|
+
for line in block_content.split('\n'):
|
|
738
|
+
line = line.strip()
|
|
739
|
+
if not line or line.startswith('//'):
|
|
740
|
+
continue
|
|
741
|
+
|
|
742
|
+
# Check for embedded type (just a type name, possibly with *)
|
|
743
|
+
match = re.match(r'^\*?([A-Z]\w*)(?:\s*`[^`]*`)?$', line)
|
|
744
|
+
if match:
|
|
745
|
+
embedded.append(match.group(1))
|
|
746
|
+
|
|
747
|
+
return embedded
|
|
748
|
+
|
|
749
|
+
def _extract_return_type(self, func_decl: str) -> Optional[str]:
|
|
750
|
+
"""Extract return type from function declaration."""
|
|
751
|
+
# Look for return type after parameters
|
|
752
|
+
match = re.search(r'\)\s*(?:\([^)]+\)|([^\n{]+))', func_decl)
|
|
753
|
+
if match:
|
|
754
|
+
ret = match.group(1) or match.group(0)
|
|
755
|
+
ret = ret.strip()
|
|
756
|
+
if ret and not ret.startswith('{'):
|
|
757
|
+
# Clean up
|
|
758
|
+
ret = re.sub(r'^\)\s*', '', ret)
|
|
759
|
+
return ret.strip() if ret else None
|
|
760
|
+
return None
|
|
761
|
+
|
|
762
|
+
def _find_preceding_doc(self, content: str, position: int) -> Optional[str]:
|
|
763
|
+
"""Find doc comment preceding a position."""
|
|
764
|
+
before = content[:position]
|
|
765
|
+
lines = before.split('\n')
|
|
766
|
+
|
|
767
|
+
doc_lines = []
|
|
768
|
+
for line in reversed(lines[:-1]):
|
|
769
|
+
stripped = line.strip()
|
|
770
|
+
if stripped.startswith('//'):
|
|
771
|
+
# Skip build tags
|
|
772
|
+
if stripped.startswith('//go:') or stripped.startswith('// +build'):
|
|
773
|
+
break
|
|
774
|
+
doc_lines.insert(0, stripped[2:].strip())
|
|
775
|
+
elif stripped == '':
|
|
776
|
+
continue
|
|
777
|
+
else:
|
|
778
|
+
break
|
|
779
|
+
|
|
780
|
+
return '\n'.join(doc_lines) if doc_lines else None
|
|
781
|
+
|
|
782
|
+
def _find_block_end(self, content: str, start: int) -> int:
|
|
783
|
+
"""Find the end line of a code block."""
|
|
784
|
+
brace_count = 0
|
|
785
|
+
in_block = False
|
|
786
|
+
|
|
787
|
+
for i, char in enumerate(content[start:], start):
|
|
788
|
+
if char == '{':
|
|
789
|
+
brace_count += 1
|
|
790
|
+
in_block = True
|
|
791
|
+
elif char == '}':
|
|
792
|
+
brace_count -= 1
|
|
793
|
+
if in_block and brace_count == 0:
|
|
794
|
+
return content[:i].count('\n') + 1
|
|
795
|
+
|
|
796
|
+
return content[:start].count('\n') + 1
|
|
797
|
+
|
|
798
|
+
def parse_multiple_files(
|
|
799
|
+
self,
|
|
800
|
+
files: List[Tuple[str, Optional[str]]],
|
|
801
|
+
resolve_cross_file: bool = True,
|
|
802
|
+
max_workers: int = 4
|
|
803
|
+
) -> Dict[str, ParseResult]:
|
|
804
|
+
"""
|
|
805
|
+
Parse multiple Go files with optional cross-file resolution.
|
|
806
|
+
|
|
807
|
+
Args:
|
|
808
|
+
files: List of (file_path, content) tuples
|
|
809
|
+
resolve_cross_file: Whether to resolve cross-file references
|
|
810
|
+
max_workers: Maximum number of parallel workers
|
|
811
|
+
|
|
812
|
+
Returns:
|
|
813
|
+
Dict mapping file paths to ParseResult objects
|
|
814
|
+
"""
|
|
815
|
+
results = {}
|
|
816
|
+
|
|
817
|
+
def parse_single(file_info: Tuple[str, Optional[str]]) -> Tuple[str, Optional[ParseResult]]:
|
|
818
|
+
file_path, content = file_info
|
|
819
|
+
if content is None:
|
|
820
|
+
try:
|
|
821
|
+
with open(file_path, 'r', encoding='utf-8') as f:
|
|
822
|
+
content = f.read()
|
|
823
|
+
except Exception as e:
|
|
824
|
+
logger.warning(f"Failed to read {file_path}: {e}")
|
|
825
|
+
return file_path, None
|
|
826
|
+
|
|
827
|
+
try:
|
|
828
|
+
result = self.parse(content, file_path)
|
|
829
|
+
return file_path, result
|
|
830
|
+
except Exception as e:
|
|
831
|
+
logger.warning(f"Failed to parse {file_path}: {e}")
|
|
832
|
+
return file_path, None
|
|
833
|
+
|
|
834
|
+
# Parse files in parallel
|
|
835
|
+
with ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
836
|
+
for file_path, result in executor.map(parse_single, files):
|
|
837
|
+
if result:
|
|
838
|
+
results[file_path] = result
|
|
839
|
+
|
|
840
|
+
# Register symbols for cross-file resolution
|
|
841
|
+
if resolve_cross_file:
|
|
842
|
+
for symbol in result.symbols:
|
|
843
|
+
self._symbol_to_file[symbol.name] = file_path
|
|
844
|
+
if symbol.qualified_name:
|
|
845
|
+
self._symbol_to_file[symbol.qualified_name] = file_path
|
|
846
|
+
|
|
847
|
+
return results
|
|
848
|
+
|
|
849
|
+
|
|
850
|
+
# Register the parser
|
|
851
|
+
parser_registry.register_parser(GoParser())
|