mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 00:15:05 +00:00
Add BasedPyright as alternative LSP for Python (#1705)
This commit is contained in:
1 parent
2c10e25c97
commit
96ee9b65e2
10 files changed
+499
-12
No files matched your search
@@ -0,0 +1,98 @@
|
||||
from collections.abc import Iterator
|
||||
from pathlib import Path
|
||||
from textwrap import dedent
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from test.conftest import start_ls_context
|
||||
from test.solidlsp.conftest import document_symbol_names
|
||||
|
||||
pytestmark = pytest.mark.python
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def basedpyright_project(tmp_path_factory: pytest.TempPathFactory) -> Path:
|
||||
project_root = tmp_path_factory.mktemp("basedpyright-project")
|
||||
files = {
|
||||
"pyproject.toml": """
|
||||
[tool.basedpyright]
|
||||
typeCheckingMode = "basic"
|
||||
reportExplicitAny = "error"
|
||||
""",
|
||||
"models.py": """
|
||||
class Greeter:
|
||||
def greet(self, name: str) -> str:
|
||||
return f"Hello, {name}"
|
||||
""",
|
||||
"service.py": """
|
||||
from models import Greeter
|
||||
|
||||
|
||||
def build_message(name: str) -> str:
|
||||
greeter = Greeter()
|
||||
return greeter.greet(name)
|
||||
""",
|
||||
"consumer.py": """
|
||||
from service import build_message
|
||||
|
||||
|
||||
MESSAGE = build_message("Ada")
|
||||
""",
|
||||
"diagnostics.py": """
|
||||
from typing import Any
|
||||
|
||||
|
||||
def echo(value: Any) -> Any:
|
||||
return value
|
||||
""",
|
||||
}
|
||||
for relative_path, content in files.items():
|
||||
(project_root / relative_path).write_text(dedent(content).lstrip(), encoding="utf-8")
|
||||
return project_root
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def basedpyright_language_server(basedpyright_project: Path) -> Iterator[SolidLanguageServer]:
|
||||
with start_ls_context(
|
||||
language=Language.PYTHON_BASEDPYRIGHT,
|
||||
repo_path=str(basedpyright_project),
|
||||
) as language_server:
|
||||
yield language_server
|
||||
|
||||
|
||||
def test_basedpyright_starts(
|
||||
basedpyright_language_server: SolidLanguageServer,
|
||||
basedpyright_project: Path,
|
||||
) -> None:
|
||||
assert basedpyright_language_server.language == Language.PYTHON_BASEDPYRIGHT
|
||||
assert basedpyright_language_server.is_running()
|
||||
assert Path(basedpyright_language_server.language_server.repository_root_path).resolve() == basedpyright_project.resolve()
|
||||
|
||||
|
||||
def test_basedpyright_document_symbols(basedpyright_language_server: SolidLanguageServer) -> None:
|
||||
assert "build_message" in document_symbol_names(basedpyright_language_server, "service.py")
|
||||
|
||||
|
||||
def test_basedpyright_definitions_and_references(basedpyright_language_server: SolidLanguageServer) -> None:
|
||||
defining_symbol = basedpyright_language_server.request_defining_symbol("service.py", 4, 15)
|
||||
assert defining_symbol is not None
|
||||
assert defining_symbol["name"] == "Greeter"
|
||||
assert defining_symbol["location"]["relativePath"] == "models.py"
|
||||
|
||||
symbols = basedpyright_language_server.request_document_symbols("models.py").get_all_symbols_and_roots()
|
||||
greeter_symbol = next(symbol for symbol in symbols[0] if symbol.get("name") == "Greeter")
|
||||
selection_start = greeter_symbol["selectionRange"]["start"]
|
||||
references = basedpyright_language_server.request_references(
|
||||
"models.py",
|
||||
selection_start["line"],
|
||||
selection_start["character"],
|
||||
)
|
||||
assert any(reference["uri"].endswith("/service.py") for reference in references), references
|
||||
|
||||
|
||||
def test_basedpyright_specific_diagnostics(basedpyright_language_server: SolidLanguageServer) -> None:
|
||||
diagnostics = basedpyright_language_server.request_text_document_diagnostics("diagnostics.py", min_severity=1)
|
||||
messages = [diagnostic["message"] for diagnostic in diagnostics]
|
||||
assert any("Any" in message for message in messages), messages
|
||||
@@ -0,0 +1,141 @@
|
||||
from pathlib import Path
|
||||
from unittest.mock import Mock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp.dependency_provider import LanguageServerDependencyProviderUvx
|
||||
from solidlsp.language_servers.basedpyright_server import BASEDPYRIGHT_VERSION, BasedPyrightLanguageServer
|
||||
from solidlsp.language_servers.pyright_server import PyrightServer
|
||||
from solidlsp.ls_config import Language, LanguageServerConfig
|
||||
from solidlsp.settings import SolidLSPSettings
|
||||
|
||||
|
||||
def _make_basedpyright_server(
|
||||
tmp_path: Path,
|
||||
custom_settings: dict[str, object] | None = None,
|
||||
) -> BasedPyrightLanguageServer:
|
||||
settings = SolidLSPSettings(
|
||||
solidlsp_dir=str(tmp_path / "global"),
|
||||
project_data_path=str(tmp_path / "project"),
|
||||
ls_specific_settings={Language.PYTHON_BASEDPYRIGHT: custom_settings or {}},
|
||||
)
|
||||
server_interface = Mock()
|
||||
with patch.object(BasedPyrightLanguageServer, "_create_language_server_interface", return_value=server_interface):
|
||||
return BasedPyrightLanguageServer(
|
||||
LanguageServerConfig(code_language=Language.PYTHON_BASEDPYRIGHT),
|
||||
str(tmp_path),
|
||||
settings,
|
||||
)
|
||||
|
||||
|
||||
def _make_pyright_server(tmp_path: Path) -> PyrightServer:
|
||||
settings = SolidLSPSettings(
|
||||
solidlsp_dir=str(tmp_path / "global"),
|
||||
project_data_path=str(tmp_path / "project"),
|
||||
)
|
||||
server_interface = Mock()
|
||||
with patch.object(PyrightServer, "_create_language_server_interface", return_value=server_interface):
|
||||
return PyrightServer(LanguageServerConfig(code_language=Language.PYTHON), str(tmp_path), settings)
|
||||
|
||||
|
||||
def test_dependency_provider_uses_basedpyright_profile(tmp_path: Path) -> None:
|
||||
server = _make_basedpyright_server(tmp_path)
|
||||
provider = server._create_dependency_provider()
|
||||
|
||||
assert isinstance(provider, LanguageServerDependencyProviderUvx)
|
||||
assert provider._package == "basedpyright"
|
||||
assert provider._entrypoint == "basedpyright-langserver"
|
||||
assert provider._default_version == BASEDPYRIGHT_VERSION
|
||||
assert provider._version_setting_key == "basedpyright_version"
|
||||
|
||||
|
||||
def test_version_override_builds_uvx_command(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
custom_version = "1.99.0"
|
||||
server = _make_basedpyright_server(tmp_path, {"basedpyright_version": custom_version})
|
||||
provider = server._create_dependency_provider()
|
||||
monkeypatch.delenv("UVX", raising=False)
|
||||
|
||||
with patch("solidlsp.dependency_provider.shutil.which", return_value="/opt/bin/uvx"):
|
||||
command = provider.create_launch_command()
|
||||
|
||||
assert command == [
|
||||
"/opt/bin/uvx",
|
||||
"-p",
|
||||
"3.13",
|
||||
"--from",
|
||||
f"basedpyright=={custom_version}",
|
||||
"basedpyright-langserver",
|
||||
"--stdio",
|
||||
]
|
||||
|
||||
|
||||
def test_builds_uv_tool_run_fallback_command(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
server = _make_basedpyright_server(tmp_path)
|
||||
provider = server._create_dependency_provider()
|
||||
monkeypatch.delenv("UVX", raising=False)
|
||||
|
||||
def find_uv(executable: str) -> str | None:
|
||||
return None if executable == "uvx" else "/opt/bin/uv"
|
||||
|
||||
with patch("solidlsp.dependency_provider.shutil.which", side_effect=find_uv):
|
||||
command = provider.create_launch_command()
|
||||
|
||||
assert command == [
|
||||
"/opt/bin/uv",
|
||||
"tool",
|
||||
"run",
|
||||
"-p",
|
||||
"3.13",
|
||||
"--from",
|
||||
f"basedpyright=={BASEDPYRIGHT_VERSION}",
|
||||
"basedpyright-langserver",
|
||||
"--stdio",
|
||||
]
|
||||
|
||||
|
||||
def test_ls_path_preserves_default_and_extra_arguments(tmp_path: Path) -> None:
|
||||
server = _make_basedpyright_server(
|
||||
tmp_path,
|
||||
{
|
||||
"ls_path": "/custom/basedpyright-langserver",
|
||||
"ls_extra_args": ["--verbose"],
|
||||
},
|
||||
)
|
||||
|
||||
assert server._create_dependency_provider().create_launch_command() == [
|
||||
"/custom/basedpyright-langserver",
|
||||
"--stdio",
|
||||
"--verbose",
|
||||
]
|
||||
|
||||
|
||||
def test_base_command_and_args_overrides_are_preserved(tmp_path: Path) -> None:
|
||||
server = _make_basedpyright_server(
|
||||
tmp_path,
|
||||
{
|
||||
"ls_base_cmd": ["custom-launcher", "basedpyright-langserver"],
|
||||
"ls_args": ["--custom-stdio"],
|
||||
"ls_extra_args": ["--verbose"],
|
||||
},
|
||||
)
|
||||
|
||||
assert server._create_dependency_provider().create_launch_command() == [
|
||||
"custom-launcher",
|
||||
"basedpyright-langserver",
|
||||
"--custom-stdio",
|
||||
"--verbose",
|
||||
]
|
||||
|
||||
|
||||
def test_language_registry_uses_separate_server_classes() -> None:
|
||||
assert Language.PYTHON.get_ls_class() is PyrightServer
|
||||
assert Language.PYTHON_BASEDPYRIGHT.get_ls_class() is BasedPyrightLanguageServer
|
||||
|
||||
|
||||
def test_language_identity_separates_cache_directories(tmp_path: Path) -> None:
|
||||
pyright_server = _make_pyright_server(tmp_path)
|
||||
basedpyright_server = _make_basedpyright_server(tmp_path)
|
||||
|
||||
assert pyright_server.language == Language.PYTHON
|
||||
assert basedpyright_server.language == Language.PYTHON_BASEDPYRIGHT
|
||||
assert pyright_server.cache_dir != basedpyright_server.cache_dir
|
||||
Reference in new issue
Block a user