mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-11 03:13:51 +00:00
Merge pull request #1212 from oraios/broken-uvx
This commit is contained in:
52 files changed
+1716
-1107
No files matched your search
@@ -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
@@ -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"]
|
||||
|
||||
@@ -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
@@ -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)))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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):
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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}"
|
||||
)
|
||||
@@ -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."""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)"
|
||||
)
|
||||
@@ -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"]
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user