mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-19 16:16:11 +00:00
Add HLSL and related languages support through shader-language-server (#1093)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
dadf3f6a18
commit
dbd7bf7596
12 files changed
+603
-3
No files matched your search
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
"""
|
||||
Basic tests for HLSL language server integration (shader-language-server).
|
||||
|
||||
This module tests Language.HLSL using shader-language-server from antaalt/shader-sense.
|
||||
Tests are skipped if the language server is not available.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_exceptions import SolidLSPException
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
|
||||
|
||||
def _find_symbol_by_name(language_server: SolidLanguageServer, file_path: str, name: str) -> dict[str, Any] | None:
|
||||
"""Find a top-level symbol by name in a file's document symbols."""
|
||||
symbols = language_server.request_document_symbols(file_path).get_all_symbols_and_roots()
|
||||
return next((s for s in symbols[0] if s.get("name") == name), None)
|
||||
|
||||
|
||||
# ── Symbol Discovery ─────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.hlsl
|
||||
class TestHlslSymbols:
|
||||
"""Tests for document symbol extraction."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_find_struct(self, language_server: SolidLanguageServer) -> None:
|
||||
"""VertexInput struct should appear in common.hlsl symbols."""
|
||||
symbol = _find_symbol_by_name(language_server, "common.hlsl", "VertexInput")
|
||||
assert symbol is not None, "Expected 'VertexInput' struct in document symbols"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_find_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""SafeNormalize function should appear in common.hlsl."""
|
||||
symbol = _find_symbol_by_name(language_server, "common.hlsl", "SafeNormalize")
|
||||
assert symbol is not None, "Expected 'SafeNormalize' function in document symbols"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_find_cbuffer_members(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Cbuffer members should appear as variables in compute_test.hlsl.
|
||||
|
||||
Note: shader-language-server reports cbuffer members as individual
|
||||
variables (kind 13), not the cbuffer name itself as a symbol.
|
||||
"""
|
||||
symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "TextureSize")
|
||||
assert symbol is not None, "Expected 'TextureSize' cbuffer member in document symbols"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_find_compute_kernel(self, language_server: SolidLanguageServer) -> None:
|
||||
"""CSMain kernel should appear in compute_test.hlsl."""
|
||||
symbol = _find_symbol_by_name(language_server, "compute_test.hlsl", "CSMain")
|
||||
assert symbol is not None, "Expected 'CSMain' compute kernel in document symbols"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_full_symbol_tree(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Full symbol tree should contain symbols from multiple files."""
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "VertexInput"), "VertexInput not in symbol tree"
|
||||
assert SymbolUtils.symbol_tree_contains_name(symbols, "CalculateDiffuse"), "CalculateDiffuse not in symbol tree"
|
||||
|
||||
|
||||
# ── Go-to-Definition ─────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.hlsl
|
||||
class TestHlslDefinition:
|
||||
"""Tests for go-to-definition capability."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_goto_definition_cross_file(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Navigating to SafeNormalize call in lighting.hlsl should resolve to common.hlsl.
|
||||
|
||||
lighting.hlsl line 22 (0-indexed): " float3 halfVec = SafeNormalize(-lightDir + viewDir);"
|
||||
SafeNormalize starts at column 21.
|
||||
"""
|
||||
definitions = language_server.request_definition("lighting.hlsl", 22, 21)
|
||||
assert len(definitions) >= 1, f"Expected at least 1 definition, got {len(definitions)}"
|
||||
def_paths = [d.get("relativePath", d.get("uri", "")) for d in definitions]
|
||||
assert any("common.hlsl" in p for p in def_paths), f"Expected definition in common.hlsl, got: {def_paths}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_goto_definition_cross_file_remap(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Navigating to Remap call in compute_test.hlsl should resolve to common.hlsl.
|
||||
|
||||
compute_test.hlsl line 20 (0-indexed): " Remap(color.r, 0.0, 1.0, 0.2, 0.8),"
|
||||
Remap starts at column 8.
|
||||
"""
|
||||
definitions = language_server.request_definition("compute_test.hlsl", 20, 8)
|
||||
assert len(definitions) >= 1, f"Expected at least 1 definition, got {len(definitions)}"
|
||||
def_paths = [d.get("relativePath", d.get("uri", "")) for d in definitions]
|
||||
assert any("common.hlsl" in p for p in def_paths), f"Expected definition in common.hlsl, got: {def_paths}"
|
||||
|
||||
|
||||
# ── References ────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.hlsl
|
||||
class TestHlslReferences:
|
||||
"""Tests for find-references capability.
|
||||
|
||||
shader-language-server does not advertise referencesProvider, so
|
||||
request_references is expected to return an empty list.
|
||||
"""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_references_not_supported(self, language_server: SolidLanguageServer) -> None:
|
||||
"""References request should raise because shader-language-server does not support it.
|
||||
|
||||
common.hlsl line 17 (0-indexed): "float3 SafeNormalize(float3 v)"
|
||||
SafeNormalize starts at column 7.
|
||||
"""
|
||||
with pytest.raises(SolidLSPException, match="Method not found"):
|
||||
language_server.request_references("common.hlsl", 17, 7)
|
||||
|
||||
|
||||
# ── Hover ─────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
def _extract_hover_text(hover_info: dict[str, Any]) -> str:
|
||||
"""Extract the text content from an LSP hover response."""
|
||||
contents = hover_info["contents"]
|
||||
if isinstance(contents, dict):
|
||||
return contents.get("value", "")
|
||||
elif isinstance(contents, str):
|
||||
return contents
|
||||
return str(contents)
|
||||
|
||||
|
||||
@pytest.mark.hlsl
|
||||
class TestHlslHover:
|
||||
"""Tests for hover information."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_hover_on_function(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Hovering over SafeNormalize definition should return info.
|
||||
|
||||
common.hlsl line 17 (0-indexed): "float3 SafeNormalize(float3 v)"
|
||||
SafeNormalize starts at column 7.
|
||||
"""
|
||||
hover_info = language_server.request_hover("common.hlsl", 17, 7)
|
||||
assert hover_info is not None, "Hover should return information for SafeNormalize"
|
||||
assert "contents" in hover_info, "Hover should have contents"
|
||||
hover_text = _extract_hover_text(hover_info)
|
||||
assert len(hover_text) > 0, "Hover text should not be empty"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_hover_on_struct(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Hovering over VertexInput should return struct info.
|
||||
|
||||
common.hlsl line 3 (0-indexed): "struct VertexInput"
|
||||
VertexInput starts at column 7.
|
||||
"""
|
||||
hover_info = language_server.request_hover("common.hlsl", 3, 7)
|
||||
assert hover_info is not None, "Hover should return information for VertexInput"
|
||||
assert "contents" in hover_info, "Hover should have contents"
|
||||
@@ -0,0 +1,63 @@
|
||||
"""
|
||||
Regression tests for HLSL full symbol tree indexing.
|
||||
|
||||
These tests verify that request_full_symbol_tree() correctly indexes all files,
|
||||
including .hlsl includes in subdirectories. This catches bugs where files are
|
||||
silently dropped during workspace-wide indexing.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
import pytest
|
||||
|
||||
from solidlsp import SolidLanguageServer
|
||||
from solidlsp.ls_config import Language
|
||||
from solidlsp.ls_types import SymbolKind
|
||||
from solidlsp.ls_utils import SymbolUtils
|
||||
|
||||
|
||||
def _collect_file_names(symbols: list[dict[str, Any]]) -> set[str]:
|
||||
"""Recursively collect the names of all File-kind symbols in the tree."""
|
||||
names: set[str] = set()
|
||||
for sym in symbols:
|
||||
if sym.get("kind") == SymbolKind.File:
|
||||
names.add(sym["name"])
|
||||
if "children" in sym:
|
||||
names.update(_collect_file_names(sym["children"]))
|
||||
return names
|
||||
|
||||
|
||||
EXPECTED_FILES = {"common", "lighting", "compute_test", "terrain_sdf"}
|
||||
|
||||
TERRAIN_SDF_UNIQUE_SYMBOLS = {"SampleSDF", "CalculateGradient", "SDFBrickData"}
|
||||
|
||||
|
||||
@pytest.mark.hlsl
|
||||
class TestHlslFullIndex:
|
||||
"""Tests for full symbol tree indexing completeness."""
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_all_files_indexed_in_symbol_tree(self, language_server: SolidLanguageServer) -> None:
|
||||
"""Every .hlsl file in the test repo must appear as a File symbol in the tree."""
|
||||
symbols = language_server.request_full_symbol_tree()
|
||||
file_names = _collect_file_names(symbols)
|
||||
missing = EXPECTED_FILES - file_names
|
||||
assert not missing, f"Files missing from full symbol tree: {missing}. Found: {file_names}"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_subdirectory_file_symbols_present(self, language_server: SolidLanguageServer) -> None:
|
||||
"""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"
|
||||
|
||||
@pytest.mark.parametrize("language_server", [Language.HLSL], indirect=True)
|
||||
def test_include_file_document_symbols_directly(self, language_server: SolidLanguageServer) -> None:
|
||||
"""request_document_symbols on terrain/terrain_sdf.hlsl should return its symbols."""
|
||||
doc_symbols = language_server.request_document_symbols("terrain/terrain_sdf.hlsl")
|
||||
all_symbols = doc_symbols.get_all_symbols_and_roots()
|
||||
symbol_names = {s.get("name") for s in all_symbols[0]}
|
||||
for name in TERRAIN_SDF_UNIQUE_SYMBOLS:
|
||||
assert name in symbol_names, f"Expected '{name}' in document symbols for terrain/terrain_sdf.hlsl, got: {symbol_names}"
|
||||
Reference in new issue
Block a user