Merge pull request #1212 from oraios/broken-uvx

This commit is contained in:
Michael Panchenko authored and GitHub committed 2026-03-24 10:40:09 +01:00
commit 1707bb43d1
52 files changed
+1716 -1107

No files matched your search

+2 -2
View File
@@ -5,7 +5,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Development Commands
**Essential Commands (use these exact commands):**
- `uv run poe format` - Format code (BLACK + RUFF) - ONLY allowed formatting command
- `uv run poe format` - Format code (RUFF) - ONLY allowed formatting command
- `uv run poe type-check` - Run mypy type checking - ONLY allowed type checking command
- `uv run poe test` - Run tests with default markers (excludes java/rust by default)
- `uv run poe test -m "python or go"` - Run specific language tests
@@ -108,7 +108,7 @@ Configuration is loaded from (in order of precedence):
## Working with the Codebase
- Project uses Python 3.11 with `uv` for dependency management
- Strict typing with mypy, formatted with black + ruff
- Strict typing with mypy, formatted with ruff
- Language servers run as separate processes with LSP communication
- Memory system enables persistent project knowledge
- Context/mode system allows workflow customization
+59 -54
View File
@@ -8,34 +8,42 @@ version = "0.1.4"
description = ""
authors = [{ name = "Oraios AI", email = "info@oraios-ai.de" }]
readme = "README.md"
requires-python = ">=3.11, <3.12"
requires-python = ">=3.11, <3.15"
classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.11",
]
dependencies = [
"requests>=2.32.3,<3",
"pyright>=1.1.396,<2",
"fortls>=3.2.2",
"overrides>=7.7.0,<8",
"python-dotenv>=1.0.0, <2",
"mcp==1.23.0",
"flask>=3.0.0",
"sensai-utils>=1.5.0",
"pydantic>=2.10.6",
"types-pyyaml>=6.0.12.20241230",
"pyyaml>=6.0.2",
"requests==2.32.5",
"pyright==1.1.403",
"fortls==3.2.2",
"overrides==7.7.0",
"python-dotenv==1.2.1",
"mcp==1.26.0",
"flask==3.1.3", # bumped from 3.1.1 for CVE fix (also fixes werkzeug alert)
"sensai-utils==1.5.0",
"pydantic==2.12.5",
"types-pyyaml==6.0.12.20250516",
"pyyaml==6.0.2",
"ruamel.yaml==0.18.14",
"jinja2>=3.1.6",
"dotenv>=0.9.9",
"pathspec>=0.12.1",
"psutil>=7.0.0",
"docstring_parser>=0.16",
"joblib>=1.5.1",
"tqdm>=4.67.1",
"tiktoken>=0.9.0",
"anthropic>=0.54.0",
"beautifulsoup4>=4.14.2",
"jinja2==3.1.6",
"dotenv==0.9.9",
"pathspec==0.12.1",
"psutil==7.0.0",
"docstring_parser==0.17.0",
"joblib==1.5.1",
"tqdm==4.67.1",
"tiktoken==0.12.0",
"anthropic==0.59.0",
"beautifulsoup4==4.14.2",
# Transitive deps pinned for security (dependabot alerts).
# Exact pins because uvx installs from git, ignoring the lock file.
"urllib3==2.5.0",
"werkzeug==3.1.6",
"starlette==1.0.0",
"python-multipart==0.0.22",
"filelock==3.25.2",
"cryptography==46.0.3",
]
[[tool.uv.index]]
@@ -54,29 +62,35 @@ text = "MIT"
[project.optional-dependencies]
dev = [
"black[jupyter]>=23.7.0, <26", # black 26 is incompatible with our pathspec version
"jinja2",
"jinja2==3.1.6",
# In version 1.0.4 we get a NoneType error related to some config conversion (yml_analytics is None and should be a list)
"mypy>=1.16.1",
"poethepoet>=0.20.0",
"pytest>=8.0.2",
"pytest-xdist>=3.5.0",
"mypy==1.17.0",
"poethepoet==0.36.0",
"pytest==8.4.1",
"pytest-xdist==3.8.0",
"ruff==0.12.5",
"toml-sort>=0.24.2",
"types-pyyaml>=6.0.12.20241230",
"syrupy>=4.9.1",
"types-requests>=2.32.4.20241230",
"toml-sort==0.24.2",
"types-pyyaml==6.0.12.20250516",
"syrupy==4.9.1",
"types-requests==2.32.4.20250809",
# docs
"sphinx>=7,<8",
"sphinx_rtd_theme>=0.5.1",
"sphinx==7.4.7",
"sphinx_rtd_theme==2.0.0",
"sphinx-toolbox==3.7.0",
"jupyter-book>=1,<2",
"nbsphinx",
"pyinstrument",
"pytest-timeout>=2.4.0",
"jupyter-book==1.0.4.post1",
"nbsphinx==0.9.7",
"pyinstrument==5.1.1",
"pytest-timeout==2.4.0",
# Transitive dev deps pinned for security (dependabot alerts).
# Exact pins because uvx installs from git, ignoring the lock file.
"tornado==6.5.5",
"nbconvert==7.17.0",
"wheel==0.46.3",
"pyasn1==0.6.3",
"PyJWT==2.10.1",
]
agno = ["agno>=2.2.1", "sqlalchemy>=2.0.40"]
google = ["google-genai>=1.8.0"]
agno = ["agno==2.5.10", "sqlalchemy==2.0.41"] # agno bumped for session state overwrite CVE
google = ["google-genai==1.27.0"]
[project.urls]
Homepage = "https://github.com/oraios/serena"
@@ -84,15 +98,6 @@ Homepage = "https://github.com/oraios/serena"
[tool.hatch.build.targets.wheel]
packages = ["src/serena", "src/interprompt", "src/solidlsp"]
[tool.black]
line-length = 140
target-version = ["py311"]
exclude = '''
/(
src/solidlsp/language_servers/.*/static|src/multilspy
)/
'''
[tool.doc8]
max-line-length = 1000
@@ -135,12 +140,12 @@ type = "simple"
# For custom markers, one can either adjust the env var or just use -m option in the command line,
# as the second -m option will override the first one.
test = "pytest test -vv"
_black_check = "black --check src scripts test"
_ruff_check = "ruff check src scripts test"
_black_format = "black src scripts test"
_ruff_format = "ruff check --fix src scripts test"
lint = ["_black_check", "_ruff_check"]
format = ["_ruff_format", "_black_format"]
_ruff_format_check = "ruff format --check src scripts test"
_ruff_fix = "ruff check --fix src scripts test"
_ruff_format = "ruff format src scripts test"
lint = ["_ruff_format_check", "_ruff_check"]
format = ["_ruff_fix", "_ruff_format"]
_mypy_core = "mypy src/serena src/solidlsp"
_mypy_test = "mypy --disable-error-code no-untyped-def test"
type-check = ["_mypy_core", "_mypy_test"]
+1 -1
View File
@@ -7,7 +7,7 @@ from solidlsp.ls_config import Language
if __name__ == "__main__":
lang_strings = sorted([l.value for l in Language])
max_len = max(len(s) for s in lang_strings)
fmt = f"%-{max_len+2}s"
fmt = f"%-{max_len + 2}s"
for i, l in enumerate(lang_strings):
if i % 5 == 0:
print("\n# ", end="")
+1 -1
View File
@@ -313,7 +313,7 @@ class SerenaAgent:
# log fundamental information
log.info(
f"Starting Serena server (version={serena_version()}, process id={os.getpid()}, parent process id={os.getppid()}; "
f"language backend={self.serena_config.language_backend.name})"
f"language backend={self.serena_config.language_backend.name}); Python version={platform.python_version()}, platform={platform.platform()}"
)
log.info("Configuration file: %s", self.serena_config.config_file_path)
log.info("Available projects: {}".format(", ".join(self.serena_config.project_names)))
+1 -3
View File
@@ -85,8 +85,7 @@ class MemoriesManager:
if self._is_global(name):
if name == self.GLOBAL_TOPIC:
raise ValueError(
f'Bare "{self.GLOBAL_TOPIC}" is not a valid memory name. '
f'Use "{self.GLOBAL_TOPIC}/<name>" to address a global memory.'
f'Bare "{self.GLOBAL_TOPIC}" is not a valid memory name. Use "{self.GLOBAL_TOPIC}/<name>" to address a global memory.'
)
# Strip "global/" prefix and resolve against global dir
sub_name = name[len(self.GLOBAL_TOPIC) + 1 :]
@@ -312,7 +311,6 @@ class Project(ToStringMixin):
def _gather_ignorespec(self) -> None:
with LogTime(f"Gathering ignore spec for project {self.project_config.project_name}", logger=log):
# gather ignored paths from the global configuration, project configuration, and gitignore files
global_ignored_paths = self.serena_config.ignored_paths
ignored_patterns = list(global_ignored_paths) + list(self.project_config.ignored_paths)
+3 -3
View File
@@ -68,9 +68,9 @@ class CreateTextFileTool(Tool, ToolMarkerCanEdit):
if will_overwrite_existing:
self.project.validate_relative_path(relative_path, require_not_ignored=True)
else:
assert abs_path.is_relative_to(
self.get_project_root()
), f"Cannot create file outside of the project directory, got {relative_path=}"
assert abs_path.is_relative_to(self.get_project_root()), (
f"Cannot create file outside of the project directory, got {relative_path=}"
)
abs_path.parent.mkdir(parents=True, exist_ok=True)
abs_path.write_text(content, encoding=self.project.project_config.encoding, newline=self.project.line_ending.newline_str)
+3 -1
View File
@@ -43,7 +43,9 @@ def execute_shell_command(command: str, cwd: str | None = None, capture_stderr:
def subprocess_check_output(args: list[str], encoding: str = "utf-8", strip: bool = True, timeout: float | None = None) -> str:
output = subprocess.check_output(args, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=timeout, env=os.environ.copy(), **subprocess_kwargs()).decode(encoding) # type: ignore
output = subprocess.check_output(
args, stdin=subprocess.DEVNULL, stderr=subprocess.PIPE, timeout=timeout, env=os.environ.copy(), **subprocess_kwargs()
).decode(encoding) # type: ignore
if strip:
output = output.strip()
return output
-1
View File
@@ -16,7 +16,6 @@ T = TypeVar("T")
class ExecutionResult(Generic[T], ToStringMixin):
class Status(Enum):
SUCCESS = "success"
TIMEOUT = "timeout"
@@ -78,9 +78,9 @@ class GroovyLanguageServer(SolidLanguageServer):
platform_id = PlatformUtils.get_platform_id()
# Verify platform support
assert (
platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-")
), "Only Windows, Linux and macOS platforms are supported for Groovy in multilspy at the moment"
assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), (
"Only Windows, Linux and macOS platforms are supported for Groovy in multilspy at the moment"
)
# Check if user specified custom Java home path
java_home_path = None
@@ -76,9 +76,9 @@ class Intelephense(SolidLanguageServer):
)
deps.install(intelephense_ls_dir)
assert os.path.exists(
intelephense_executable_path
), f"intelephense executable not found at {intelephense_executable_path}, something went wrong."
assert os.path.exists(intelephense_executable_path), (
f"intelephense executable not found at {intelephense_executable_path}, something went wrong."
)
return intelephense_executable_path
@@ -93,9 +93,9 @@ class KotlinLanguageServer(SolidLanguageServer):
platform_id = PlatformUtils.get_platform_id()
# Verify platform support
assert (
platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-")
), "Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment"
assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), (
"Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment"
)
kotlin_suffix = PLATFORM_KOTLIN_SUFFIX.get(platform_id.value)
assert kotlin_suffix, f"Unsupported platform for Kotlin LSP: {platform_id.value}"
@@ -137,9 +137,7 @@ class OcamlLanguageServer(SolidLanguageServer):
)
except subprocess.CalledProcessError as e:
raise RuntimeError(
f"Failed to detect ocaml-lsp-server version: {e.stderr}\n"
"Please install ocaml-lsp-server:\n"
" opam install ocaml-lsp-server"
f"Failed to detect ocaml-lsp-server version: {e.stderr}\nPlease install ocaml-lsp-server:\n opam install ocaml-lsp-server"
) from e
except FileNotFoundError as e:
raise RuntimeError("opam not found. Please install opam:\n https://opam.ocaml.org/doc/Install.html") from e
+3 -3
View File
@@ -46,9 +46,9 @@ class PhpactorServer(SolidLanguageServer):
"""
# Verify PHP is installed
php_path = shutil.which("php")
assert (
php_path is not None
), "PHP is not installed or not found in PATH. Phpactor requires PHP 8.1+. Please install PHP and try again."
assert php_path is not None, (
"PHP is not installed or not found in PATH. Phpactor requires PHP 8.1+. Please install PHP and try again."
)
# Check PHP version (Phpactor requires PHP 8.1+)
result = subprocess.run(["php", "--version"], capture_output=True, text=True, check=False)
@@ -141,9 +141,9 @@ class TypeScriptLanguageServer(SolidLanguageServer):
PlatformId.WIN_x64,
PlatformId.WIN_arm64,
]
assert (
platform_id in valid_platforms
), f"Platform {platform_id} is not supported for multilspy javascript/typescript at the moment"
assert platform_id in valid_platforms, (
f"Platform {platform_id} is not supported for multilspy javascript/typescript at the moment"
)
# Get version settings from ls_specific_settings or use defaults
language_specific_config = self._custom_settings
@@ -272,6 +272,7 @@ class TypeScriptLanguageServer(SolidLanguageServer):
await lsp.request_references(...)
# Shutdown the LanguageServer on exit from scope
# LanguageServer has been shutdown
```
"""
def register_capability_handler(params: dict) -> None:
@@ -155,6 +155,7 @@ class VtsLanguageServer(SolidLanguageServer):
await lsp.request_references(...)
# Shutdown the LanguageServer on exit from scope
# LanguageServer has been shutdown
```
"""
def register_capability_handler(params: dict) -> None:
+1 -2
View File
@@ -70,8 +70,7 @@ class ZigLanguageServer(SolidLanguageServer):
# Check for Windows and provide error message
if platform.system() == "Windows":
raise RuntimeError(
"Windows is not supported by ZLS in this integration. "
"Cross-file references don't work reliably on Windows. Reason unknown."
"Windows is not supported by ZLS in this integration. Cross-file references don't work reliably on Windows. Reason unknown."
)
zig_version = ZigLanguageServer._get_zig_version()
+9 -9
View File
@@ -139,9 +139,9 @@ class TestSerenaAgent:
continue
symbol_info = s.get("info")
assert symbol_info, f"Expected symbol info to be present for symbol: {s}"
assert (
symbol_name in s["info"]
), f"[{serena_agent.get_active_lsp_languages()[0]}] Expected symbol info to contain symbol name {symbol_name}. Info: {s['info']}"
assert symbol_name in s["info"], (
f"[{serena_agent.get_active_lsp_languages()[0]}] Expected symbol info to contain symbol name {symbol_name}. Info: {s['info']}"
)
# special additional test for Java, since Eclipse returns hover in a complex format and we want to make sure to get it right
if s["kind"] == SymbolKind.Class.name and serena_agent.get_active_lsp_languages() == [Language.JAVA]:
assert "A simple model class" in symbol_info, f"Java class docstring not found in symbol info: {s}"
@@ -166,9 +166,9 @@ class TestSerenaAgent:
f"Expected to find Dog/greet in {sample_php} but got empty result. "
"This may indicate that find_symbol is not routing to the PHP language server for PHP files."
)
assert any(
"greet" in s["name_path"] and sample_php in s["relative_path"] for s in symbols
), f"Dog/greet not found in {sample_php}. Symbols: {symbols}"
assert any("greet" in s["name_path"] and sample_php in s["relative_path"] for s in symbols), (
f"Dog/greet not found in {sample_php}. Symbols: {symbols}"
)
@pytest.mark.parametrize(
"serena_agent,symbol_name,expected_kind,expected_file",
@@ -478,9 +478,9 @@ class TestSerenaAgent:
)
symbols = json.loads(result)
assert (
len(symbols) == num_expected
), f"Expected to find {num_expected} symbols for overloaded function {name_path}. Symbols found: {symbols}"
assert len(symbols) == num_expected, (
f"Expected to find {num_expected} symbols for overloaded function {name_path}. Symbols found: {symbols}"
)
@pytest.mark.parametrize(
"serena_agent,name_path,relative_path",
+3 -3
View File
@@ -346,9 +346,9 @@ class TestSearchFiles:
actual_matched_files = sorted([result.source_file_path for result in results if result.source_file_path])
# Assert that the matched files are exactly the ones expected
assert actual_matched_files == sorted(
expected_matched_files
), f"Pattern '{paths_include_glob}' failed: expected {sorted(expected_matched_files)}, got {actual_matched_files}"
assert actual_matched_files == sorted(expected_matched_files), (
f"Pattern '{paths_include_glob}' failed: expected {sorted(expected_matched_files)}, got {actual_matched_files}"
)
# Basic check on results structure if files were expected
if expected_matched_files:
+21 -21
View File
@@ -124,9 +124,9 @@ class TestALLanguageServer:
# Check for codeunit symbols
assert SymbolUtils.symbol_tree_contains_name(symbols, "CustomerMgt"), "CustomerMgt codeunit not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(
symbols, "PaymentProcessorImpl"
), "PaymentProcessorImpl codeunit not found in symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, "PaymentProcessorImpl"), (
"PaymentProcessorImpl codeunit not found in symbol tree"
)
# Check for enum symbol
assert SymbolUtils.symbol_tree_contains_name(symbols, "CustomerType"), "CustomerType enum not found in symbol tree"
@@ -209,14 +209,14 @@ class TestALLanguageServer:
refs = language_server.request_references(table_file, sel_start["line"], sel_start["character"])
# The Customer table should be referenced in CustomerMgt.Codeunit.al
assert any(
"CustomerMgt.Codeunit.al" in ref.get("relativePath", "") for ref in refs
), "Customer table should be referenced in CustomerMgt.Codeunit.al"
assert any("CustomerMgt.Codeunit.al" in ref.get("relativePath", "") for ref in refs), (
"Customer table should be referenced in CustomerMgt.Codeunit.al"
)
# It should also be referenced in CustomerCard.Page.al
assert any(
"CustomerCard.Page.al" in ref.get("relativePath", "") for ref in refs
), "Customer table should be referenced in CustomerCard.Page.al"
assert any("CustomerCard.Page.al" in ref.get("relativePath", "") for ref in refs), (
"Customer table should be referenced in CustomerCard.Page.al"
)
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True)
def test_cross_file_symbols(self, language_server: SolidLanguageServer) -> None:
@@ -392,9 +392,9 @@ class TestALHoverInjection:
assert hover is not None, f"Hover should return a result for {symbol_name}"
assert value is not None, f"Hover should have content for {symbol_name}"
assert (
f"**{expected_full_name}**" in value
), f"Hover for {symbol_name} should contain '{expected_full_name}'. Got: {value[:200]}"
assert f"**{expected_full_name}**" in value, (
f"Hover for {symbol_name} should contain '{expected_full_name}'. Got: {value[:200]}"
)
@pytest.mark.parametrize("language_server", [Language.AL], indirect=True)
def test_hover_contains_separator_after_injection(self, language_server: SolidLanguageServer) -> None:
@@ -490,9 +490,9 @@ class TestALPathNormalization:
hover = language_server.request_hover(file_path_forward, line, char)
assert hover is not None, "Hover should return a result"
value = hover.get("contents", {}).get("value", "")
assert (
'**Table 50000 "TEST Customer"**' in value
), f"Hover injection should work with mixed path formats. Got: {value[:200]}"
assert '**Table 50000 "TEST Customer"**' in value, (
f"Hover injection should work with mixed path formats. Got: {value[:200]}"
)
return
pytest.fail("Could not find TEST Customer symbol")
@@ -517,9 +517,9 @@ class TestALPathNormalization:
hover = language_server.request_hover(file_path_backslash, line, char)
assert hover is not None, "Hover should return a result"
value = hover.get("contents", {}).get("value", "")
assert (
'**Table 50000 "TEST Customer"**' in value
), f"Hover injection should work with mixed path formats. Got: {value[:200]}"
assert '**Table 50000 "TEST Customer"**' in value, (
f"Hover injection should work with mixed path formats. Got: {value[:200]}"
)
return
pytest.fail("Could not find TEST Customer symbol")
@@ -552,7 +552,7 @@ class TestALPathNormalization:
hover = language_server.request_hover(hover_path, line, char)
assert hover is not None, f"Hover should return a result for {symbol_name}"
value = hover.get("contents", {}).get("value", "")
assert (
f"**{expected_injection}**" in value
), f"Hover for {symbol_name} should have injection with mixed paths. Got: {value[:200]}"
assert f"**{expected_injection}**" in value, (
f"Hover for {symbol_name} should have injection with mixed paths. Got: {value[:200]}"
)
break
+3 -3
View File
@@ -135,9 +135,9 @@ int use_add() {
ref_files = [ref.get("relativePath", "") for ref in refs]
# Should find reference in the newly written file
assert any(
"temp_new_file.cpp" in ref_file for ref_file in ref_files
), f"Should find reference in newly written temp_new_file.cpp, {ref_files=}"
assert any("temp_new_file.cpp" in ref_file for ref_file in ref_files), (
f"Should find reference in newly written temp_new_file.cpp, {ref_files=}"
)
finally:
# Clean up the new file
if os.path.exists(new_file_abs_path):
+6 -6
View File
@@ -63,9 +63,9 @@ class TestCSharpLanguageServer:
assert add_symbol is not None, "Could not find 'Add' method symbol in Program.cs"
sel_start = add_symbol["selectionRange"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"] + 1)
assert any(
"Program.cs" in ref.get("relativePath", "") for ref in refs
), "Program.cs should reference Add method (tried all positions in selectionRange)"
assert any("Program.cs" in ref.get("relativePath", "") for ref in refs), (
"Program.cs should reference Add method (tried all positions in selectionRange)"
)
@pytest.mark.parametrize("language_server", [Language.CSHARP], indirect=True)
def test_nested_namespace_symbols(self, language_server: SolidLanguageServer) -> None:
@@ -121,9 +121,9 @@ class TestCSharpLanguageServer:
# Check that we have reference in Models/Person.cs where Calculator.Subtract is called
# Note: New Roslyn version doesn't include the definition itself as a reference (more correct behavior)
assert any(
os.path.join("Models", "Person.cs") in ref_file for ref_file in ref_files
), "Should find reference in Models/Person.cs where Calculator.Subtract is called"
assert any(os.path.join("Models", "Person.cs") in ref_file for ref_file in ref_files), (
"Should find reference in Models/Person.cs where Calculator.Subtract is called"
)
assert len(refs) > 0, "Should find at least one reference"
# check for a second time, since the first call may trigger initialization and change the state of the LS
@@ -69,9 +69,9 @@ class TestNuGetOrgDownload:
for dep in lang_server_deps:
# Verify package name uses roslyn-language-server
assert dep.package_name is not None, f"Package name should be set for {dep.platform_id}"
assert dep.package_name.startswith(
"roslyn-language-server."
), f"Package name should start with 'roslyn-language-server.' but got: {dep.package_name}"
assert dep.package_name.startswith("roslyn-language-server."), (
f"Package name should start with 'roslyn-language-server.' but got: {dep.package_name}"
)
# Verify version is the newer NuGet.org version
assert dep.package_version == "5.5.0-2.26078.4", f"Should use NuGet.org version 5.5.0-2.26078.4, got: {dep.package_version}"
+3 -3
View File
@@ -196,9 +196,9 @@ class TestDartLanguageServer:
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
# Check that we found references - at least one should be in main.dart
assert any(
"main.dart" in ref.get("relativePath", "") or "main.dart" in ref.get("uri", "") for ref in refs
), "main.dart should reference add method (tried all positions in selectionRange)"
assert any("main.dart" in ref.get("relativePath", "") or "main.dart" in ref.get("uri", "") for ref in refs), (
"main.dart should reference add method (tried all positions in selectionRange)"
)
@pytest.mark.parametrize("language_server", [Language.DART], indirect=True)
def test_request_containing_symbol_method(self, language_server: SolidLanguageServer) -> None:
@@ -92,7 +92,6 @@ def test_refs_and_symbols_with_glob_patterns(repo_path: Path) -> None:
"""
ignored_paths = ["*cripts", "ignored_*"] # codespell:ignore cripts
with start_ls_context(language=Language.ELIXIR, repo_path=str(repo_path), ignored_paths=ignored_paths) as ls:
# Same as in the above tests
root = ls.request_full_symbol_tree()[0]
root_children = root["children"]
@@ -308,8 +308,7 @@ class TestErlangLanguageServerSymbols:
@pytest.mark.timeout(60) # Add 60 second timeout
@pytest.mark.xfail(
reason="Known intermittent timeout issue in Erlang LS in CI environments. "
"May pass locally but can timeout on slower CI systems.",
reason="Known intermittent timeout issue in Erlang LS in CI environments. May pass locally but can timeout on slower CI systems.",
strict=False,
)
@pytest.mark.parametrize("language_server", [Language.ERLANG], indirect=True)
+18 -18
View File
@@ -83,9 +83,9 @@ class TestFortranLanguageServer:
# Verify that main.f90 references the function
main_refs = [ref for ref in refs if "main.f90" in ref.get("relativePath", "")]
assert (
len(main_refs) > 0
), f"Expected to find reference in main.f90, but found references in: {[ref.get('relativePath') for ref in refs]}"
assert len(main_refs) > 0, (
f"Expected to find reference in main.f90, but found references in: {[ref.get('relativePath') for ref in refs]}"
)
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True)
def test_find_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
@@ -104,14 +104,14 @@ class TestFortranLanguageServer:
definition_location = definition_location_list[0]
# The definition should be in modules/math_utils.f90
assert "math_utils.f90" in definition_location.get(
"uri", ""
), f"Expected definition to be in math_utils.f90, but found in: {definition_location.get('uri')}"
assert "math_utils.f90" in definition_location.get("uri", ""), (
f"Expected definition to be in math_utils.f90, but found in: {definition_location.get('uri')}"
)
# Verify the definition is around the correct line (line 4, 0-indexed)
assert (
definition_location["range"]["start"]["line"] == 4
), f"Expected definition at line 4, but found at line {definition_location['range']['start']['line']}"
assert definition_location["range"]["start"]["line"] == 4, (
f"Expected definition at line 4, but found at line {definition_location['range']['start']['line']}"
)
@pytest.mark.parametrize("language_server", [Language.FORTRAN], indirect=True)
def test_request_referencing_symbols(self, language_server: SolidLanguageServer) -> None:
@@ -202,14 +202,14 @@ class TestFortranLanguageServer:
pytest.skip("fortls does not support request_containing_symbol or couldn't find the containing symbol")
# Should find the add_numbers function as the containing symbol
assert (
containing_symbol.get("name") == "add_numbers"
), f"Expected containing symbol 'add_numbers', got '{containing_symbol.get('name')}'"
assert containing_symbol.get("name") == "add_numbers", (
f"Expected containing symbol 'add_numbers', got '{containing_symbol.get('name')}'"
)
# Verify the symbol kind is Function
assert (
containing_symbol.get("kind") == SymbolKind.Function.value
), f"Expected Function kind ({SymbolKind.Function.value}), got {containing_symbol.get('kind')}"
assert containing_symbol.get("kind") == SymbolKind.Function.value, (
f"Expected Function kind ({SymbolKind.Function.value}), got {containing_symbol.get('kind')}"
)
# Verify location information exists
assert "location" in containing_symbol, "Containing symbol should have location information"
@@ -264,9 +264,9 @@ class TestFortranLanguageServer:
# Verify selectionRange points to identifier name, not line start
# Line for "type, extends(Point2D) :: Point3D" has Point3D at position > 0
assert (
sel_start["character"] > 0
), f"selectionRange should point to identifier, not line start. Got character: {sel_start['character']}"
assert sel_start["character"] > 0, (
f"selectionRange should point to identifier, not line start. Got character: {sel_start['character']}"
)
# Test that we can find references using the corrected position
_refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
+3 -3
View File
@@ -154,9 +154,9 @@ class TestGoBuildTags:
assert versioned_cache_files, f"Expected at least one SolidLSP cache file with a __cache_version under {cache_dir}"
saved_versions = {v for _, v in versioned_cache_files}
assert (
default_raw_cache_version in saved_versions or default_doc_cache_version in saved_versions
), "Expected at least one persisted cache to match the default-context cache version"
assert default_raw_cache_version in saved_versions or default_doc_cache_version in saved_versions, (
"Expected at least one persisted cache to match the default-context cache version"
)
# Run 2 (default context again): prove that persisted caches are actually loaded and used.
with start_ls_context(Language.GO, repo_path=str(repo_path), solidlsp_dir=tmp_path) as ls_default_again:
+12 -12
View File
@@ -145,9 +145,9 @@ class TestHaskellLanguageServer:
# All references should be in Helper.hs
reference_paths = [ref["relativePath"] for ref in references]
assert all(
"Helper.hs" in path for path in reference_paths
), f"All isNegative references should be in Helper.hs, got: {reference_paths}"
assert all("Helper.hs" in path for path in reference_paths), (
f"All isNegative references should be in Helper.hs, got: {reference_paths}"
)
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
def test_function_references_from_main(self, language_server: SolidLanguageServer):
@@ -168,9 +168,9 @@ class TestHaskellLanguageServer:
add_ref_paths = [ref["relativePath"] for ref in add_refs]
# Should have at least one reference in Main.hs or Calculator.hs
assert any(
"Main.hs" in path or "Calculator.hs" in path for path in add_ref_paths
), f"Expected 'add' to be referenced in Main.hs or Calculator.hs, got: {add_ref_paths}"
assert any("Main.hs" in path or "Calculator.hs" in path for path in add_ref_paths), (
f"Expected 'add' to be referenced in Main.hs or Calculator.hs, got: {add_ref_paths}"
)
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
def test_multiply_function_usage_in_calculate(self, language_server: SolidLanguageServer):
@@ -191,9 +191,9 @@ class TestHaskellLanguageServer:
# Should have reference in Calculator.hs (calculate function)
multiply_ref_paths = [ref["relativePath"] for ref in multiply_refs]
assert any(
"Calculator.hs" in path for path in multiply_ref_paths
), f"Expected 'multiply' to be referenced in Calculator.hs, got: {multiply_ref_paths}"
assert any("Calculator.hs" in path for path in multiply_ref_paths), (
f"Expected 'multiply' to be referenced in Calculator.hs, got: {multiply_ref_paths}"
)
@pytest.mark.parametrize("language_server", [Language.HASKELL], indirect=True)
def test_data_type_constructor_references(self, language_server: SolidLanguageServer):
@@ -214,6 +214,6 @@ class TestHaskellLanguageServer:
# Should have at least one reference in Main.hs or Calculator.hs
calc_ref_paths = [ref["relativePath"] for ref in calculator_refs]
assert any(
"Main.hs" in path or "Calculator.hs" in path for path in calc_ref_paths
), f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {calc_ref_paths}"
assert any("Main.hs" in path or "Calculator.hs" in path for path in calc_ref_paths), (
f"Expected Calculator to be referenced in Main.hs or Calculator.hs, got: {calc_ref_paths}"
)
+3 -3
View File
@@ -49,9 +49,9 @@ class TestHlslFullIndex:
"""Symbols unique to terrain/terrain_sdf.hlsl must appear in the full tree."""
symbols = language_server.request_full_symbol_tree()
for name in TERRAIN_SDF_UNIQUE_SYMBOLS:
assert SymbolUtils.symbol_tree_contains_name(
symbols, name
), f"Expected '{name}' from terrain/terrain_sdf.hlsl in full symbol tree"
assert SymbolUtils.symbol_tree_contains_name(symbols, name), (
f"Expected '{name}' from terrain/terrain_sdf.hlsl in full symbol tree"
)
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
def test_include_file_document_symbols_directly(self, language_server: SolidLanguageServer) -> None:
+3 -3
View File
@@ -40,9 +40,9 @@ class TestJavaLanguageServer:
else:
sel_start = model_symbol["range"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert any(
"Main.java" in ref.get("relativePath", "") for ref in refs
), "Main should reference Model (tried all positions in selectionRange)"
assert any("Main.java" in ref.get("relativePath", "") for ref in refs), (
"Main should reference Model (tried all positions in selectionRange)"
)
@pytest.mark.parametrize("language_server", [Language.JAVA], indirect=True)
def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
+3 -3
View File
@@ -45,9 +45,9 @@ class TestKotlinLanguageServer:
else:
sel_start = model_symbol["range"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert any(
"Main.kt" in ref.get("relativePath", "") for ref in refs
), "Main should reference Model (tried all positions in selectionRange)"
assert any("Main.kt" in ref.get("relativePath", "") for ref in refs), (
"Main should reference Model (tried all positions in selectionRange)"
)
@pytest.mark.parametrize("language_server", [Language.KOTLIN], indirect=True)
def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
+9 -9
View File
@@ -82,9 +82,9 @@ class TestLean4LanguageServer:
# Check that isPositive is referenced within Helper.lean at line 15 (absolute calls isPositive)
ref_locations = [(ref["relativePath"], ref["range"]["start"]["line"]) for ref in references]
helper_refs = [(path, line) for path, line in ref_locations if "Helper.lean" in path]
assert any(
line == 15 for _, line in helper_refs
), f"Expected isPositive reference at Helper.lean:15 (in absolute), got: {ref_locations}"
assert any(line == 15 for _, line in helper_refs), (
f"Expected isPositive reference at Helper.lean:15 (in absolute), got: {ref_locations}"
)
@pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
def test_cross_file_references_add(self, language_server: SolidLanguageServer) -> None:
@@ -104,9 +104,9 @@ class TestLean4LanguageServer:
assert len(main_refs) >= 1, f"Expected at least 1 reference to add in Main.lean, got: {ref_locations}"
main_ref_lines = {line for _, line in main_refs}
# add is used in Main.lean line 7 (in calculate) and line 15 (in main)
assert (
7 in main_ref_lines or 15 in main_ref_lines
), f"Expected add references at Main.lean lines 7 or 15, got lines: {main_ref_lines}"
assert 7 in main_ref_lines or 15 in main_ref_lines, (
f"Expected add references at Main.lean lines 7 or 15, got lines: {main_ref_lines}"
)
@pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
def test_cross_file_references_calculator(self, language_server: SolidLanguageServer) -> None:
@@ -125,9 +125,9 @@ class TestLean4LanguageServer:
assert len(main_refs) >= 1, f"Expected at least 1 reference to Calculator in Main.lean, got: {ref_locations}"
main_ref_lines = {line for _, line in main_refs}
# Calculator is used in Main.lean line 5 (calculate signature) and line 13 (let c : Calculator)
assert (
5 in main_ref_lines or 13 in main_ref_lines
), f"Expected Calculator references at Main.lean lines 5 or 13, got lines: {main_ref_lines}"
assert 5 in main_ref_lines or 13 in main_ref_lines, (
f"Expected Calculator references at Main.lean lines 5 or 13, got lines: {main_ref_lines}"
)
@pytest.mark.parametrize("language_server", [Language.LEAN4], indirect=True)
def test_go_to_definition_within_file(self, language_server: SolidLanguageServer) -> None:
+12 -12
View File
@@ -146,15 +146,15 @@ class TestLuaLanguageServer:
# The declaration may or may not be included
if "calculator.lua" in ref_files:
assert (
5 in ref_files["calculator.lua"]
), f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['calculator.lua']}"
assert 5 in ref_files["calculator.lua"], (
f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['calculator.lua']}"
)
# Check main.lua has usages
assert "main.lua" in ref_files, "Should find add usages in main.lua"
assert (
15 in ref_files["main.lua"] or 70 in ref_files["main.lua"]
), f"Should find add usage in main.lua, found at lines {ref_files.get('main.lua', [])}"
assert 15 in ref_files["main.lua"] or 70 in ref_files["main.lua"], (
f"Should find add usage in main.lua, found at lines {ref_files.get('main.lua', [])}"
)
# Check for cross-file references from main.lua
main_refs = [ref for ref in refs if "main.lua" in ref.get("uri", "")]
@@ -202,15 +202,15 @@ class TestLuaLanguageServer:
# The declaration may or may not be included
if "utils.lua" in ref_files:
assert (
5 in ref_files["utils.lua"]
), f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['utils.lua']}"
assert 5 in ref_files["utils.lua"], (
f"If declaration is included, it should be at line 6 (0-indexed: 5), found at {ref_files['utils.lua']}"
)
# Check main.lua has usage
assert "main.lua" in ref_files, "Should find trim usage in main.lua"
assert (
31 in ref_files["main.lua"]
), f"Should find trim usage at line 32 (0-indexed: 31) in main.lua, found at lines {ref_files.get('main.lua', [])}"
assert 31 in ref_files["main.lua"], (
f"Should find trim usage at line 32 (0-indexed: 31) in main.lua, found at lines {ref_files.get('main.lua', [])}"
)
# Check for cross-file references from main.lua
main_refs = [ref for ref in refs if "main.lua" in ref.get("uri", "")]
@@ -35,9 +35,9 @@ class TestMarkdownLanguageServerBasics:
# Verify that markdown headings are remapped from String to Namespace
for symbol in all_symbols:
assert (
symbol["kind"] == SymbolKind.Namespace
), f"Heading '{symbol['name']}' should have kind Namespace, got {SymbolKind(symbol['kind']).name}"
assert symbol["kind"] == SymbolKind.Namespace, (
f"Heading '{symbol['name']}' should have kind Namespace, got {SymbolKind(symbol['kind']).name}"
)
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
def test_markdown_request_symbols_from_guide(self, language_server: SolidLanguageServer) -> None:
@@ -79,9 +79,9 @@ class TestMarkdownLanguageServerBasics:
for symbol in all_symbols:
ls_symbol = LanguageServerSymbol(symbol)
assert (
not ls_symbol.is_low_level()
), f"Heading '{symbol['name']}' should not be low-level (kind={SymbolKind(symbol['kind']).name})"
assert not ls_symbol.is_low_level(), (
f"Heading '{symbol['name']}' should not be low-level (kind={SymbolKind(symbol['kind']).name})"
)
@pytest.mark.parametrize("language_server", [Language.MARKDOWN], indirect=True)
def test_markdown_nested_headings_remapped(self, language_server: SolidLanguageServer) -> None:
+10 -10
View File
@@ -114,7 +114,7 @@ class TestNixLanguageServer:
if refs:
ref_lines = sorted([ref["range"]["start"]["line"] for ref in refs])
# Check if we found the inherit (line 67, 0-indexed: 66)
assert 66 in ref_lines, f"Should find makeGreeting inherit at line 67, found at lines {[l+1 for l in ref_lines]}"
assert 66 in ref_lines, f"Should find makeGreeting inherit at line 67, found at lines {[l + 1 for l in ref_lines]}"
@pytest.mark.xfail(is_ci, reason="Test is flaky") # TODO: Re-enable if the hover test becomes more stable (#1040)
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
@@ -148,9 +148,9 @@ class TestNixLanguageServer:
if default_refs:
ref_lines = sorted([ref["range"]["start"]["line"] for ref in default_refs])
# Check for key references - at least the import (line 10) or usage (line 24)
assert (
9 in ref_lines or 23 in ref_lines
), f"Should find utils import or usage, found references at lines {[l+1 for l in ref_lines]}"
assert 9 in ref_lines or 23 in ref_lines, (
f"Should find utils import or usage, found references at lines {[l + 1 for l in ref_lines]}"
)
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
def test_verify_imports_exist(self, language_server: SolidLanguageServer) -> None:
@@ -187,9 +187,9 @@ class TestNixLanguageServer:
if len(definitions) > 0:
# Should point to the import statement or utils.nix
assert any(
"utils" in def_item.get("uri", "") or "default.nix" in def_item.get("uri", "") for def_item in definitions
), "Definition should relate to utils import or utils.nix file"
assert any("utils" in def_item.get("uri", "") or "default.nix" in def_item.get("uri", "") for def_item in definitions), (
"Definition should relate to utils import or utils.nix file"
)
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
def test_definition_navigation_in_flake(self, language_server: SolidLanguageServer) -> None:
@@ -202,9 +202,9 @@ class TestNixLanguageServer:
assert isinstance(definitions, list)
# nixd should find the definition of hello-custom in the same file
if len(definitions) > 0:
assert any(
"flake.nix" in def_item.get("uri", "") for def_item in definitions
), "Should find hello-custom definition in flake.nix"
assert any("flake.nix" in def_item.get("uri", "") for def_item in definitions), (
"Should find hello-custom definition in flake.nix"
)
@pytest.mark.parametrize("language_server", [Language.NIX], indirect=True)
def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None:
+3 -3
View File
@@ -120,9 +120,9 @@ class TestOCamlLanguageServer:
# Should find at least 3 references in test_repo.ml: definition + 2 recursive calls
# On OCaml 5.2+ with cross-file refs, there may be more total refs but same-file count stays the same
assert (
len(same_file_refs) >= 3
), f"Expected at least 3 references in test_repo.ml (definition + 2 recursive), found {len(same_file_refs)}"
assert len(same_file_refs) >= 3, (
f"Expected at least 3 references in test_repo.ml (definition + 2 recursive), found {len(same_file_refs)}"
)
# Verify references are on different lines (definition + recursive calls)
ref_lines = [ref.get("range", {}).get("start", {}).get("line", -1) for ref in same_file_refs]
+3 -3
View File
@@ -225,9 +225,9 @@ class TestPhpLanguageServers:
assert dog_symbol is not None, "Dog class not found in root symbols"
dog_children = dog_symbol.get("children", [])
dog_child_names = [c.get("name") for c in dog_children]
assert (
len(dog_child_names) > 0
), f"Dog class has no children — hierarchicalDocumentSymbolSupport is not working. All root symbols: {root_names}"
assert len(dog_child_names) > 0, (
f"Dog class has no children — hierarchicalDocumentSymbolSupport is not working. All root symbols: {root_names}"
)
expected_methods = {"greet", "fetch", "getBreed", "describe"}
missing = expected_methods - set(dog_child_names)
assert not missing, f"Dog class missing expected methods: {missing}. Children found: {dog_child_names}"
@@ -149,9 +149,9 @@ class TestPowerShellLanguageServerBasics:
# Should find at least the call site in Main function
assert refs is not None and len(refs) >= 1, f"Should find references to Greet-User, got {refs}"
assert any(
"main.ps1" in ref.get("uri", ref.get("relativePath", "")) for ref in refs
), f"Should find reference in main.ps1, got {refs}"
assert any("main.ps1" in ref.get("uri", ref.get("relativePath", "")) for ref in refs), (
f"Should find reference in main.ps1, got {refs}"
)
@pytest.mark.parametrize("language_server", [Language.POWERSHELL], indirect=True)
def test_powershell_find_definition_across_files(self, language_server: SolidLanguageServer) -> None:
@@ -166,9 +166,9 @@ class TestPowerShellLanguageServerBasics:
definition_locations = language_server.request_definition(main_path, 98, 18)
# Should find the definition in utils.ps1
assert (
definition_locations is not None and len(definition_locations) >= 1
), f"Should find definition of Convert-ToUpperCase, got {definition_locations}"
assert any(
"utils.ps1" in loc.get("uri", "") for loc in definition_locations
), f"Should find definition in utils.ps1, got {definition_locations}"
assert definition_locations is not None and len(definition_locations) >= 1, (
f"Should find definition of Convert-ToUpperCase, got {definition_locations}"
)
assert any("utils.ps1" in loc.get("uri", "") for loc in definition_locations), (
f"Should find definition in utils.ps1, got {definition_locations}"
)
+3 -3
View File
@@ -76,9 +76,9 @@ class TestRLanguageServer:
assert len(utils_refs) >= 1, "Should find at least one reference in utils.R"
utils_ref = utils_refs[0]
# Should be around line 6 where calculate_mean is defined (0-indexed: line 5)
assert (
utils_ref["range"]["start"]["line"] == 5
), f"Expected reference at line 5 in utils.R, got line {utils_ref['range']['start']['line']}"
assert utils_ref["range"]["start"]["line"] == 5, (
f"Expected reference at line 5 in utils.R, got line {utils_ref['range']['start']['line']}"
)
def test_file_matching(self):
"""Test that R files are properly matched."""
+3 -3
View File
@@ -111,9 +111,9 @@ class TestRegoLanguageServer:
assert definitions is not None and len(definitions) > 0, "Should find cross-file definition for is_valid_user"
# Verify the definition points to helpers.rego (cross-file)
assert any(
"helpers.rego" in defn.get("relativePath", "") for defn in definitions
), "Definition should be in utils/helpers.rego (cross-file reference)"
assert any("helpers.rego" in defn.get("relativePath", "") for defn in definitions), (
"Definition should be in utils/helpers.rego (cross-file reference)"
)
@pytest.mark.parametrize("language_server", [Language.REGO], indirect=True)
def test_find_symbols_validation(self, language_server: SolidLanguageServer) -> None:
@@ -33,9 +33,9 @@ class TestRubyLanguageServerSymbols:
# Verify that we found the containing symbol
assert containing_symbol is not None, "Should find containing symbol for method position"
assert containing_symbol["name"] == "create_user", f"Expected 'create_user', got '{containing_symbol['name']}'"
assert (
containing_symbol["kind"] == SymbolKind.Method.value
), f"Expected Method kind ({SymbolKind.Method.value}), got {containing_symbol['kind']}"
assert containing_symbol["kind"] == SymbolKind.Method.value, (
f"Expected Method kind ({SymbolKind.Method.value}), got {containing_symbol['kind']}"
)
# Verify location information
assert "location" in containing_symbol, "Containing symbol should have location information"
@@ -68,9 +68,9 @@ class TestRubyLanguageServerSymbols:
# Verify that we found the containing symbol
assert containing_symbol is not None, "Should find containing symbol for class position"
assert containing_symbol["name"] == "UserService", f"Expected 'UserService', got '{containing_symbol['name']}'"
assert (
containing_symbol["kind"] == SymbolKind.Class.value
), f"Expected Class kind ({SymbolKind.Class.value}), got {containing_symbol['kind']}"
assert containing_symbol["kind"] == SymbolKind.Class.value, (
f"Expected Class kind ({SymbolKind.Class.value}), got {containing_symbol['kind']}"
)
# Verify location information exists
assert "location" in containing_symbol, "Class symbol should have location information"
@@ -80,9 +80,9 @@ class TestRubyLanguageServerSymbols:
# Verify the class is properly nested in the Services module
if "containerName" in containing_symbol:
assert (
containing_symbol["containerName"] == "Services"
), f"Expected 'Services' as container, got '{containing_symbol['containerName']}'"
assert containing_symbol["containerName"] == "Services", (
f"Expected 'Services' as container, got '{containing_symbol['containerName']}'"
)
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
def test_request_containing_symbol_module(self, language_server: SolidLanguageServer) -> None:
@@ -411,9 +411,9 @@ class TestRubyLanguageServerSymbols:
except Exception as e:
# Some language servers throw exceptions for invalid positions, which is acceptable
assert (
"symbol" in str(e).lower() or "position" in str(e).lower() or "reference" in str(e).lower()
), f"Exception should be related to symbol/position/reference issues, got: {e}"
assert "symbol" in str(e).lower() or "position" in str(e).lower() or "reference" in str(e).lower(), (
f"Exception should be related to symbol/position/reference issues, got: {e}"
)
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
def test_request_dir_overview(self, language_server: SolidLanguageServer) -> None:
@@ -492,9 +492,9 @@ class TestRubyLanguageServerSymbols:
assert "kind" in containing_symbol, "Containing symbol should have a kind"
# The containing symbol should be a method, class, or similar construct
expected_kinds = [SymbolKind.Method, SymbolKind.Class, SymbolKind.Function, SymbolKind.Constructor]
assert containing_symbol["kind"] in [
k.value for k in expected_kinds
], f"Expected containing symbol to be method/class/function, got kind: {containing_symbol['kind']}"
assert containing_symbol["kind"] in [k.value for k in expected_kinds], (
f"Expected containing symbol to be method/class/function, got kind: {containing_symbol['kind']}"
)
@pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True)
def test_request_containing_symbol_function(self, language_server: SolidLanguageServer) -> None:
@@ -553,9 +553,9 @@ class TestRubyLanguageServerSymbols:
child_names = [c.get("name") for c in child["children"]]
expected_names = ["UserStats", "UserManager", "process_user_data"]
found_expected = [name for name in expected_names if name in child_names]
assert (
len(found_expected) > 0
), f"Should find symbols in user_management, expected {expected_names}, found {child_names}"
assert len(found_expected) > 0, (
f"Should find symbols in user_management, expected {expected_names}, found {child_names}"
)
break
if not found_user_management:
+6 -6
View File
@@ -22,9 +22,9 @@ class TestRustLanguageServer:
assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
sel_start = add_symbol["selectionRange"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert any(
"main.rs" in ref.get("relativePath", "") for ref in refs
), "main.rs should reference add (raw, tried all positions in selectionRange)"
assert any("main.rs" in ref.get("relativePath", "") for ref in refs), (
"main.rs should reference add (raw, tried all positions in selectionRange)"
)
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
def test_find_symbol(self, language_server: SolidLanguageServer) -> None:
@@ -46,9 +46,9 @@ class TestRustLanguageServer:
assert add_symbol is not None, "Could not find 'add' function symbol in lib.rs"
sel_start = add_symbol["selectionRange"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert any(
"main.rs" in ref.get("relativePath", "") for ref in refs
), "main.rs should reference add (tried all positions in selectionRange)"
assert any("main.rs" in ref.get("relativePath", "") for ref in refs), (
"main.rs should reference add (tried all positions in selectionRange)"
)
@pytest.mark.parametrize("language_server", [Language.RUST], indirect=True)
def test_overview_methods(self, language_server: SolidLanguageServer) -> None:
@@ -139,9 +139,9 @@ class TestSolidityLanguageServerBasics:
references = language_server.request_references("contracts/Token.sol", definition_line, definition_char)
assert references is not None, "Should return references for '_transfer'"
assert (
len(references) >= 2
), f"'_transfer' should have at least 2 references (callers), found {len(references)}" # called in transfer() and transferFrom()
assert len(references) >= 2, (
f"'_transfer' should have at least 2 references (callers), found {len(references)}"
) # called in transfer() and transferFrom()
ref_files = {ref.get("uri", "") for ref in references}
assert any("Token.sol" in uri for uri in ref_files), "References should include Token.sol"
@@ -69,9 +69,9 @@ class TestSystemVerilogDefinition:
assert len(definitions) >= 1, f"Expected at least 1 definition, got {len(definitions)}"
def_in_counter = [d for d in definitions if "counter.sv" in (d.get("relativePath") or "")]
assert len(def_in_counter) >= 1, f"Expected definition in counter.sv, got: {[d.get('relativePath') for d in definitions]}"
assert (
def_in_counter[0]["range"]["start"]["line"] == 7
), f"Expected definition at line 7 (output port count), got line {def_in_counter[0]['range']['start']['line']}"
assert def_in_counter[0]["range"]["start"]["line"] == 7, (
f"Expected definition at line 7 (output port count), got line {def_in_counter[0]['range']['start']['line']}"
)
@pytest.mark.parametrize("language_server", [Language.SYSTEMVERILOG], indirect=True)
def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
@@ -87,9 +87,9 @@ class TestSystemVerilogDefinition:
def_paths = [d.get("relativePath", "") for d in definitions]
assert any("counter.sv" in p for p in def_paths), f"Expected definition in counter.sv, got: {def_paths}"
counter_defs = [d for d in definitions if "counter.sv" in (d.get("relativePath") or "")]
assert (
counter_defs[0]["range"]["start"]["line"] == 1
), f"Expected definition at line 1 (module counter), got line {counter_defs[0]['range']['start']['line']}"
assert counter_defs[0]["range"]["start"]["line"] == 1, (
f"Expected definition at line 1 (module counter), got line {counter_defs[0]['range']['start']['line']}"
)
@pytest.mark.systemverilog
@@ -129,9 +129,9 @@ class TestSystemVerilogReferences:
assert any("top.sv" in p for p in ref_paths), f"Expected reference from top.sv, got: {ref_paths}"
refs_in_top = [r for r in references if "top.sv" in (r.get("relativePath") or "")]
# top.sv line 17 (0-indexed: 16): " counter #(.WIDTH(8)) u_counter ("
assert (
refs_in_top[0]["range"]["start"]["line"] == 16
), f"Expected cross-file reference at line 16 (counter instantiation), got line {refs_in_top[0]['range']['start']['line']}"
assert refs_in_top[0]["range"]["start"]["line"] == 16, (
f"Expected cross-file reference at line 16 (counter instantiation), got line {refs_in_top[0]['range']['start']['line']}"
)
def _extract_hover_text(hover_info: dict[str, Any]) -> str:
@@ -256,9 +256,9 @@ class TestSystemVerilogRename:
edit_lines = sorted(e["range"]["start"]["line"] for e in edits)
assert 1 in edit_lines, f"Expected edit at line 1 (module declaration), got lines: {edit_lines}"
decl_edits = [e for e in edits if e["range"]["start"]["line"] == 1]
assert (
decl_edits[0]["range"]["start"]["character"] == 7
), f"Expected edit at char 7, got char {decl_edits[0]['range']['start']['character']}"
assert decl_edits[0]["range"]["start"]["character"] == 7, (
f"Expected edit at char 7, got char {decl_edits[0]['range']['start']['character']}"
)
for uri, file_edits in changes.items():
for edit in file_edits:
assert edit["newText"] == "my_counter", f"Expected 'my_counter', got {edit['newText']}"
@@ -85,9 +85,9 @@ class TestTomlSymbolRetrieval:
# Children should include TOML files
child_names = {child["name"] for child in root.get("children", [])}
# Note: File names are stripped of extension in some cases
assert (
"Cargo" in child_names or "Cargo.toml" in child_names or any("cargo" in name.lower() for name in child_names)
), f"Should find Cargo.toml in tree, got: {child_names}"
assert "Cargo" in child_names or "Cargo.toml" in child_names or any("cargo" in name.lower() for name in child_names), (
f"Should find Cargo.toml in tree, got: {child_names}"
)
@pytest.mark.parametrize("language_server", [Language.TOML], indirect=True)
@pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True)
@@ -28,6 +28,6 @@ class TestTypescriptLanguageServer:
assert helper_symbol is not None, "Could not find 'helperFunction' symbol in index.ts"
sel_start = helper_symbol["selectionRange"]["start"]
refs = language_server.request_references(file_path, sel_start["line"], sel_start["character"])
assert any(
"index.ts" in ref.get("relativePath", "") for ref in refs
), "index.ts should reference helperFunction (tried all positions in selectionRange)"
assert any("index.ts" in ref.get("relativePath", "") for ref in refs), (
"index.ts should reference helperFunction (tried all positions in selectionRange)"
)
+24 -24
View File
@@ -85,9 +85,9 @@ class TestVueDualLspArchitecture:
# Verify references include .vue files (components that import the store)
vue_refs = [ref for ref in refs if ".vue" in ref.get("uri", "")]
assert (
len(vue_refs) >= 3
), f"Should find at least 3 references in Vue components, found {len(vue_refs)}: {[ref.get('uri', '') for ref in vue_refs]}"
assert len(vue_refs) >= 3, (
f"Should find at least 3 references in Vue components, found {len(vue_refs)}: {[ref.get('uri', '') for ref in vue_refs]}"
)
# Verify specific components that use the store
expected_vue_files = ["App.vue", "CalculatorInput.vue", "CalculatorDisplay.vue"]
@@ -131,9 +131,9 @@ class TestVueDualLspArchitecture:
all_ref_uris = [ref.get("uri", "") for ref in refs]
has_ts_refs = any(".ts" in uri and "types" not in uri for uri in all_ref_uris)
assert (
has_ts_refs
), f"Operation type should be referenced in TypeScript files like calculator.ts. Found references in: {all_ref_uris}"
assert has_ts_refs, (
f"Operation type should be referenced in TypeScript files like calculator.ts. Found references in: {all_ref_uris}"
)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_reference_deduplication(self, language_server: SolidLanguageServer) -> None:
@@ -260,9 +260,9 @@ class TestVueEdgeCases:
# Vue LSP returns SFC structure (template/script/style sections) for .vue files
# This is expected behavior - overview shows the file's high-level structure
assert (
len(symbol_names) >= 1
), f"Should have at least 1 symbol name in overview (e.g., 'App' or SFC section), got {len(symbol_names)}: {symbol_names}"
assert len(symbol_names) >= 1, (
f"Should have at least 1 symbol name in overview (e.g., 'App' or SFC section), got {len(symbol_names)}: {symbol_names}"
)
# Test overview for a TypeScript file
store_file = os.path.join("src", "stores", "calculator.ts")
@@ -272,18 +272,18 @@ class TestVueEdgeCases:
assert len(store_overview) >= 1, f"calculator.ts should have at least 1 top-level symbol in overview, got {len(store_overview)}"
store_symbol_names = [s.get("name") for s in store_overview if isinstance(s, dict)]
assert (
"useCalculatorStore" in store_symbol_names
), f"useCalculatorStore should be in store file overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
assert "useCalculatorStore" in store_symbol_names, (
f"useCalculatorStore should be in store file overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
)
# Test overview for another Vue component
button_file = os.path.join("src", "components", "CalculatorButton.vue")
button_overview = language_server.request_document_overview(button_file)
assert isinstance(button_overview, list), f"Button overview should be a list, got: {type(button_overview)}"
assert (
len(button_overview) >= 1
), f"CalculatorButton.vue should have at least 1 top-level symbol in overview, got {len(button_overview)}"
assert len(button_overview) >= 1, (
f"CalculatorButton.vue should have at least 1 top-level symbol in overview, got {len(button_overview)}"
)
# For Vue files, overview provides SFC structure which is useful for navigation
# The detailed symbols are available via request_document_symbols
@@ -324,9 +324,9 @@ class TestVueEdgeCases:
stores_overview = language_server.request_dir_overview(stores_dir)
assert isinstance(stores_overview, dict), f"Stores overview should be a dict, got: {type(stores_overview)}"
assert (
len(stores_overview) == 1
), f"src/stores directory should have exactly 1 file (calculator.ts) in overview, got {len(stores_overview)}"
assert len(stores_overview) == 1, (
f"src/stores directory should have exactly 1 file (calculator.ts) in overview, got {len(stores_overview)}"
)
# Verify calculator.ts is included
calculator_files = [path for path in stores_overview.keys() if "calculator.ts" in path]
@@ -339,18 +339,18 @@ class TestVueEdgeCases:
store_path = calculator_files[0]
store_symbols = stores_overview[store_path]
store_symbol_names = [s.get("name") for s in store_symbols if isinstance(s, dict)]
assert (
"useCalculatorStore" in store_symbol_names
), f"calculator.ts should have useCalculatorStore in overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
assert "useCalculatorStore" in store_symbol_names, (
f"calculator.ts should have useCalculatorStore in overview. Found {len(store_symbol_names)} symbols: {store_symbol_names}"
)
# Test overview for composables directory
composables_dir = os.path.join("src", "composables")
composables_overview = language_server.request_dir_overview(composables_dir)
assert isinstance(composables_overview, dict), f"Composables overview should be a dict, got: {type(composables_overview)}"
assert (
len(composables_overview) == 2
), f"src/composables directory should have exactly 2 files in overview, got {len(composables_overview)}"
assert len(composables_overview) == 2, (
f"src/composables directory should have exactly 2 files in overview, got {len(composables_overview)}"
)
# Verify composable files are included
expected_composables = ["useFormatter.ts", "useTheme.ts"]
+12 -12
View File
@@ -235,9 +235,9 @@ class TestVueUndefinedSymbols:
result = language_server.request_containing_symbol(file_path, 1, 0)
# Should handle gracefully - return None, empty dict, or a valid parent symbol
assert (
result is None or result == {} or isinstance(result, dict)
), f"Whitespace line should return None, empty dict, or valid symbol. Got: {result}"
assert result is None or result == {} or isinstance(result, dict), (
f"Whitespace line should return None, empty dict, or valid symbol. Got: {result}"
)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_definition_at_keyword_position(self, language_server: SolidLanguageServer) -> None:
@@ -271,9 +271,9 @@ class TestVueEdgeCasePositions:
result = language_server.request_containing_symbol(file_path, 0, 0)
# Should handle gracefully
assert (
result is None or result == {} or isinstance(result, dict)
), f"Position 0,0 should return None, empty dict, or valid symbol. Got: {result}"
assert result is None or result == {} or isinstance(result, dict), (
f"Position 0,0 should return None, empty dict, or valid symbol. Got: {result}"
)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_references_at_file_start(self, language_server: SolidLanguageServer) -> None:
@@ -317,9 +317,9 @@ class TestVueEdgeCasePositions:
result = language_server.request_containing_symbol(file_path, 5, 10)
# Should handle gracefully - template doesn't have containing symbols in the same way
assert (
result is None or result == {} or isinstance(result, dict)
), f"Template position should return None, empty dict, or valid symbol. Got: {result}"
assert result is None or result == {} or isinstance(result, dict), (
f"Template position should return None, empty dict, or valid symbol. Got: {result}"
)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_zero_character_positions(self, language_server: SolidLanguageServer) -> None:
@@ -334,9 +334,9 @@ class TestVueEdgeCasePositions:
result = language_server.request_containing_symbol(file_path, line, 0)
# Should handle gracefully
assert (
result is None or result == {} or isinstance(result, dict)
), f"Line {line}, character 0 should return None, empty dict, or valid symbol. Got: {result}"
assert result is None or result == {} or isinstance(result, dict), (
f"Line {line}, character 0 should return None, empty dict, or valid symbol. Got: {result}"
)
class TestVueTypescriptFileErrors:
+3 -3
View File
@@ -121,9 +121,9 @@ class TestVueRename:
assert len(document_changes) > 0, "Should have at least one document change"
composable_changes = [dc for dc in document_changes if "useFormatter.ts" in dc.get("textDocument", {}).get("uri", "")]
assert (
len(composable_changes) > 0
), f"Should have edits for useFormatter.ts (definition). Found changes for: {[dc.get('textDocument', {}).get('uri', '') for dc in document_changes]}"
assert len(composable_changes) > 0, (
f"Should have edits for useFormatter.ts (definition). Found changes for: {[dc.get('textDocument', {}).get('uri', '') for dc in document_changes]}"
)
for change in document_changes:
assert "textDocument" in change, "Document change should have textDocument"
+15 -15
View File
@@ -93,9 +93,9 @@ class TestVueSymbolRetrieval:
containing_symbol = language_server.request_containing_symbol(file_path, import_line, import_character)
# Should return None or empty dictionary for positions without containing symbol
assert (
containing_symbol is None or containing_symbol == {}
), f"Expected None or empty dict for import position, got {containing_symbol}"
assert containing_symbol is None or containing_symbol == {}, (
f"Expected None or empty dict for import position, got {containing_symbol}"
)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_request_referencing_symbols_store_function(self, language_server: SolidLanguageServer) -> None:
@@ -153,9 +153,9 @@ class TestVueSymbolRetrieval:
]
# Verify we found references - useFormatter is imported and used in CalculatorInput.vue
assert (
len(ref_symbols) >= 1
), f"useFormatter should have at least 1 reference (used in CalculatorInput.vue), found {len(ref_symbols)} references"
assert len(ref_symbols) >= 1, (
f"useFormatter should have at least 1 reference (used in CalculatorInput.vue), found {len(ref_symbols)} references"
)
# Check for references in Vue components
vue_refs = [
@@ -182,9 +182,9 @@ class TestVueSymbolRetrieval:
definitions = language_server.request_definition(input_file, 4, 10)
assert len(definitions) == 1, f"Should find exactly 1 definition for CalculatorButton import, got {len(definitions)}"
assert (
"CalculatorButton.vue" in definitions[0]["relativePath"]
), f"Definition should point to CalculatorButton.vue, got {definitions[0]['relativePath']}"
assert "CalculatorButton.vue" in definitions[0]["relativePath"], (
f"Definition should point to CalculatorButton.vue, got {definitions[0]['relativePath']}"
)
refs = language_server.request_references(input_file, 4, 10)
@@ -225,9 +225,9 @@ class TestVueSymbolRetrieval:
# Verify it points to the store file
if "location" in defining_symbol and "uri" in defining_symbol["location"]:
assert (
"calculator.ts" in defining_symbol["location"]["uri"]
), f"Should point to calculator.ts, got {defining_symbol['location']['uri']}"
assert "calculator.ts" in defining_symbol["location"]["uri"], (
f"Should point to calculator.ts, got {defining_symbol['location']['uri']}"
)
@pytest.mark.parametrize("language_server", [Language.VUE], indirect=True)
def test_request_defining_symbol_component_import(self, language_server: SolidLanguageServer) -> None:
@@ -239,9 +239,9 @@ class TestVueSymbolRetrieval:
definition = definitions[0]
assert definition["relativePath"] is not None, "Definition should have a relative path"
assert (
"CalculatorButton.vue" in definition["relativePath"]
), f"Should point to CalculatorButton.vue, got {definition['relativePath']}"
assert "CalculatorButton.vue" in definition["relativePath"], (
f"Should point to CalculatorButton.vue, got {definition['relativePath']}"
)
assert definition["range"]["start"]["line"] == 0, "Definition should point to start of .vue file"
+3 -3
View File
@@ -193,9 +193,9 @@ class TestZigLanguageServer:
# Verify exact location in main.zig (line 8, 0-indexed: 7)
main_ref_line = main_refs[0]["range"]["start"]["line"]
assert (
main_ref_line == 7
), f"Calculator reference in main.zig should be at line 8 (0-indexed: 7), found at line {main_ref_line + 1}"
assert main_ref_line == 7, (
f"Calculator reference in main.zig should be at line 8 (0-indexed: 7), found at line {main_ref_line + 1}"
)
@pytest.mark.parametrize("language_server", [Language.ZIG], indirect=True)
def test_cross_file_references_within_file(self, language_server: SolidLanguageServer) -> None:
Generated
+1376 -768
View File
File diff suppressed because it is too large. Load diff