From 5b31dfffed980113ad0c8eac16f5247b92d08290 Mon Sep 17 00:00:00 2001 From: w0wl0lxd <199849635+w0wl0lxd@users.noreply.github.com> Date: Mon, 1 Dec 2025 04:01:55 -0600 Subject: [PATCH] test: Add comprehensive TOML language server tests Add 28 new tests across 4 test files for Taplo TOML language support: - test_toml_edge_cases.py: Tests for inline tables, multiline strings, arrays of tables, nested tables, datetime values, and dependency tables - test_toml_ignored_dirs.py: Tests for TOML-specific directory ignoring (target, .cargo, node_modules) - test_toml_symbol_retrieval.py: Tests for document overview, symbol tree, directory overview, and symbol hierarchy operations - __init__.py: Package initialization Also adds config.toml test fixture with comprehensive TOML features for edge case testing. Total: 35 TOML tests (7 basic + 28 new), all passing. --- .../repos/toml/test_repo/config.toml | 66 +++++ test/solidlsp/toml/__init__.py | 1 + test/solidlsp/toml/test_toml_edge_cases.py | 273 ++++++++++++++++++ test/solidlsp/toml/test_toml_ignored_dirs.py | 60 ++++ .../toml/test_toml_symbol_retrieval.py | 191 ++++++++++++ 5 files changed, 591 insertions(+) create mode 100644 test/resources/repos/toml/test_repo/config.toml create mode 100644 test/solidlsp/toml/__init__.py create mode 100644 test/solidlsp/toml/test_toml_edge_cases.py create mode 100644 test/solidlsp/toml/test_toml_ignored_dirs.py create mode 100644 test/solidlsp/toml/test_toml_symbol_retrieval.py diff --git a/test/resources/repos/toml/test_repo/config.toml b/test/resources/repos/toml/test_repo/config.toml new file mode 100644 index 00000000..912d2dc7 --- /dev/null +++ b/test/resources/repos/toml/test_repo/config.toml @@ -0,0 +1,66 @@ +# Configuration file with various TOML features for testing + +[server] +host = "localhost" +port = 8080 +debug = false +timeout = 30.5 + +# Inline table example +endpoint = { url = "https://api.example.com", version = "v1" } + +[server.ssl] +enabled = true +cert_path = "/etc/ssl/cert.pem" +key_path = "/etc/ssl/key.pem" + +[database] +connection_string = """ + postgresql://user:password@localhost:5432/mydb? + sslmode=require& + pool_size=10 +""" + +[database.pool] +min_connections = 5 +max_connections = 20 +idle_timeout = 300 + +[logging] +level = "info" +format = "json" +outputs = ["stdout", "file"] + +[logging.file] +path = "/var/log/app.log" +max_size = 10485760 # 10MB +max_backups = 5 +compress = true + +# Array of inline tables +[[endpoints]] +name = "users" +path = "/api/users" +methods = ["GET", "POST", "PUT", "DELETE"] +auth_required = true + +[[endpoints]] +name = "health" +path = "/health" +methods = ["GET"] +auth_required = false + +# Datetime values +[metadata] +created = 1979-05-27T07:32:00Z +updated = 2024-01-15T10:30:00-05:00 + +# Special characters in strings +[messages] +welcome = "Hello, World!" +multiline = ''' +This is a +multiline literal +string. +''' +with_escapes = "Line1\nLine2\tTabbed" diff --git a/test/solidlsp/toml/__init__.py b/test/solidlsp/toml/__init__.py new file mode 100644 index 00000000..00ae6163 --- /dev/null +++ b/test/solidlsp/toml/__init__.py @@ -0,0 +1 @@ +"""TOML language server tests.""" diff --git a/test/solidlsp/toml/test_toml_edge_cases.py b/test/solidlsp/toml/test_toml_edge_cases.py new file mode 100644 index 00000000..3f651682 --- /dev/null +++ b/test/solidlsp/toml/test_toml_edge_cases.py @@ -0,0 +1,273 @@ +""" +Tests for TOML language server edge cases and advanced features. + +These tests cover: +- Inline tables +- Multiline strings +- Arrays of tables +- Nested tables +- Various TOML data types +""" + +from pathlib import Path + +import pytest + +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language + +pytestmark = pytest.mark.toml + + +class TestTomlEdgeCases: + """Test TOML language server handling of edge cases and advanced features.""" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_inline_table_detection(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that inline tables are properly detected.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + assert all_symbols is not None + assert len(all_symbols) > 0 + + symbol_names = [sym.get("name") for sym in all_symbols] + + # The inline table 'endpoint' should be detected + assert "endpoint" in symbol_names, "Should detect 'endpoint' inline table" + + # Find the endpoint symbol and check its properties + endpoint_symbol = next((s for s in all_symbols if s.get("name") == "endpoint"), None) + assert endpoint_symbol is not None + # Inline tables should be kind 19 (object) + assert endpoint_symbol.get("kind") == 19, "Inline table should have kind 19 (object)" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_nested_table_detection(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that deeply nested tables are properly detected.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect nested tables like server.ssl and database.pool + has_ssl = any("ssl" in str(name).lower() for name in symbol_names if name) + has_pool = any("pool" in str(name).lower() for name in symbol_names if name) + + assert has_ssl, f"Should detect 'server.ssl' nested table, got: {symbol_names}" + assert has_pool, f"Should detect 'database.pool' nested table, got: {symbol_names}" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_array_of_tables_detection(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that [[array_of_tables]] syntax is properly detected.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect [[endpoints]] array of tables + assert "endpoints" in symbol_names, f"Should detect '[[endpoints]]' array of tables, got: {symbol_names}" + + # Find the endpoints symbol + endpoints_symbol = next((s for s in all_symbols if s.get("name") == "endpoints"), None) + assert endpoints_symbol is not None + + # Array of tables should be kind 18 (array) + assert endpoints_symbol.get("kind") == 18, "Array of tables should have kind 18 (array)" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_multiline_string_handling(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that multiline strings are handled correctly.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect connection_string and multiline fields + assert "connection_string" in symbol_names, "Should detect 'connection_string' with multiline value" + assert "multiline" in symbol_names, "Should detect 'multiline' literal string" + + # Find connection_string and verify it's a string type + conn_symbol = next((s for s in all_symbols if s.get("name") == "connection_string"), None) + assert conn_symbol is not None + # String type should be kind 15 + assert conn_symbol.get("kind") == 15, "Multiline string should have kind 15 (string)" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_array_value_detection(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that array values are properly detected.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect 'outputs' and 'methods' arrays + assert "outputs" in symbol_names, "Should detect 'outputs' array" + assert "methods" in symbol_names, "Should detect 'methods' array" + + # Find outputs array and verify kind + outputs_symbol = next((s for s in all_symbols if s.get("name") == "outputs"), None) + assert outputs_symbol is not None + # Arrays should have kind 18 + assert outputs_symbol.get("kind") == 18, "'outputs' should have kind 18 (array)" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_float_value_detection(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that float values are properly detected.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect 'timeout' which has a float value (30.5) + assert "timeout" in symbol_names, "Should detect 'timeout' float value" + + # Find timeout and verify it's a number + timeout_symbol = next((s for s in all_symbols if s.get("name") == "timeout"), None) + assert timeout_symbol is not None + # Numbers should have kind 16 + assert timeout_symbol.get("kind") == 16, "'timeout' should have kind 16 (number)" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_datetime_value_detection(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that datetime values are detected.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect metadata section with datetime values + assert "metadata" in symbol_names, "Should detect 'metadata' section" + assert "created" in symbol_names, "Should detect 'created' datetime field" + assert "updated" in symbol_names, "Should detect 'updated' datetime field" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_symbol_body_with_inline_table(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that symbol bodies include inline table content.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + # Find the endpoint symbol with body + endpoint_symbol = next((s for s in all_symbols if s.get("name") == "endpoint"), None) + assert endpoint_symbol is not None + + if "body" in endpoint_symbol: + body = endpoint_symbol["body"] + # Body should contain the inline table syntax + assert "url" in body or "version" in body, f"Body should contain inline table contents, got: {body}" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_symbol_ranges_in_config(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that symbol ranges are correct in config.toml.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + # Find the server symbol + server_symbol = next((s for s in all_symbols if s.get("name") == "server"), None) + assert server_symbol is not None + assert "range" in server_symbol + + # Server should start near the beginning (line 2 is [server], 0-indexed: line 2) + server_range = server_symbol["range"] + assert server_range["start"]["line"] >= 0, "Server should start at or near the beginning" + assert server_range["end"]["line"] > server_range["start"]["line"], "Server block should span multiple lines" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_comment_handling(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that comments don't interfere with symbol detection.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # File has comments but symbols should still be detected correctly + expected_sections = {"server", "database", "logging", "endpoints", "metadata", "messages"} + found_sections = expected_sections.intersection(set(symbol_names)) + + assert len(found_sections) >= 4, f"Should find most sections despite comments, found: {found_sections}" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_special_characters_in_strings(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that strings with escape sequences are handled.""" + all_symbols, root_symbols = language_server.request_document_symbols("config.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect the messages section with special strings + assert "messages" in symbol_names, "Should detect 'messages' section" + assert "with_escapes" in symbol_names, "Should detect 'with_escapes' field" + assert "welcome" in symbol_names, "Should detect 'welcome' field" + + +class TestTomlDependencyTables: + """Test handling of dependency-style tables common in Cargo.toml and pyproject.toml.""" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_complex_dependency_inline_table(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test detection of complex inline table dependencies like serde = { version = "1.0", features = ["derive"] }.""" + all_symbols, root_symbols = language_server.request_document_symbols("Cargo.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect serde and tokio dependencies + assert "serde" in symbol_names, "Should detect 'serde' dependency" + assert "tokio" in symbol_names, "Should detect 'tokio' dependency" + + # Find serde symbol + serde_symbol = next((s for s in all_symbols if s.get("name") == "serde"), None) + assert serde_symbol is not None + + # Dependency with inline table should be kind 19 (object) + assert serde_symbol.get("kind") == 19, "Complex dependency should have kind 19 (object)" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_simple_dependency_string(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test detection of simple string dependencies like proptest = "1.0".""" + all_symbols, root_symbols = language_server.request_document_symbols("Cargo.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect proptest dev-dependency + assert "proptest" in symbol_names, "Should detect 'proptest' dependency" + + # Find proptest symbol + proptest_symbol = next((s for s in all_symbols if s.get("name") == "proptest"), None) + assert proptest_symbol is not None + + # Simple string dependency should be kind 15 (string) + assert proptest_symbol.get("kind") == 15, "Simple string dependency should have kind 15 (string)" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_pyproject_dependencies_array(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test detection of pyproject.toml dependencies array.""" + all_symbols, root_symbols = language_server.request_document_symbols("pyproject.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect dependencies array + assert "dependencies" in symbol_names, "Should detect 'dependencies' array" + + # Find dependencies symbol + deps_symbol = next((s for s in all_symbols if s.get("name") == "dependencies"), None) + assert deps_symbol is not None + + # Dependencies array should be kind 18 (array) + assert deps_symbol.get("kind") == 18, "Dependencies array should have kind 18 (array)" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_optional_dependencies_table(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test detection of optional-dependencies in pyproject.toml.""" + all_symbols, root_symbols = language_server.request_document_symbols("pyproject.toml").get_all_symbols_and_roots() + + symbol_names = [sym.get("name") for sym in all_symbols] + + # Should detect optional-dependencies or its nested form + has_optional_deps = any("optional" in str(name).lower() for name in symbol_names if name) + has_dev = "dev" in symbol_names + + assert has_optional_deps or has_dev, f"Should detect optional-dependencies or dev group, got: {symbol_names}" diff --git a/test/solidlsp/toml/test_toml_ignored_dirs.py b/test/solidlsp/toml/test_toml_ignored_dirs.py new file mode 100644 index 00000000..e5126942 --- /dev/null +++ b/test/solidlsp/toml/test_toml_ignored_dirs.py @@ -0,0 +1,60 @@ +""" +Tests for TOML language server directory ignoring functionality. + +These tests validate that the Taplo language server correctly ignores +TOML-specific directories like target, .cargo, and node_modules. +""" + +import pytest + +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language + +pytestmark = pytest.mark.toml + + +@pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) +class TestTomlIgnoredDirectories: + """Test TOML-specific directory ignoring behavior.""" + + def test_default_ignored_directories(self, language_server: SolidLanguageServer) -> None: + """Test that default TOML directories are ignored.""" + # Test that TOML/Rust/Node-specific directories are ignored by default + assert language_server.is_ignored_dirname("target"), "target should be ignored" + assert language_server.is_ignored_dirname(".cargo"), ".cargo should be ignored" + assert language_server.is_ignored_dirname("node_modules"), "node_modules should be ignored" + + # Directories starting with . are ignored by base class + assert language_server.is_ignored_dirname(".git"), ".git should be ignored" + assert language_server.is_ignored_dirname(".venv"), ".venv should be ignored" + + def test_important_directories_not_ignored(self, language_server: SolidLanguageServer) -> None: + """Test that important directories are not ignored.""" + # Common project directories should not be ignored + assert not language_server.is_ignored_dirname("src"), "src should not be ignored" + assert not language_server.is_ignored_dirname("crates"), "crates should not be ignored" + assert not language_server.is_ignored_dirname("lib"), "lib should not be ignored" + assert not language_server.is_ignored_dirname("tests"), "tests should not be ignored" + assert not language_server.is_ignored_dirname("config"), "config should not be ignored" + + def test_cargo_related_directories(self, language_server: SolidLanguageServer) -> None: + """Test Cargo/Rust-related directory handling.""" + # Rust build directories should be ignored + assert language_server.is_ignored_dirname("target"), "target (Rust build) should be ignored" + assert language_server.is_ignored_dirname(".cargo"), ".cargo should be ignored" + + # But important Rust directories should not be ignored + assert not language_server.is_ignored_dirname("benches"), "benches should not be ignored" + assert not language_server.is_ignored_dirname("examples"), "examples should not be ignored" + + def test_various_cache_directories(self, language_server: SolidLanguageServer) -> None: + """Test various cache and temporary directories are ignored.""" + # Directories starting with . are ignored by base class + assert language_server.is_ignored_dirname(".cache"), ".cache should be ignored" + + # IDE directories (start with .) + assert language_server.is_ignored_dirname(".idea"), ".idea should be ignored" + assert language_server.is_ignored_dirname(".vscode"), ".vscode should be ignored" + + # Note: __pycache__ is NOT ignored by TOML server (only Python servers ignore it) + assert not language_server.is_ignored_dirname("__pycache__"), "__pycache__ is not TOML-specific" diff --git a/test/solidlsp/toml/test_toml_symbol_retrieval.py b/test/solidlsp/toml/test_toml_symbol_retrieval.py new file mode 100644 index 00000000..5538a4c8 --- /dev/null +++ b/test/solidlsp/toml/test_toml_symbol_retrieval.py @@ -0,0 +1,191 @@ +""" +Tests for TOML language server symbol retrieval functionality. + +These tests focus on advanced symbol operations: +- request_containing_symbol +- request_document_overview +- request_full_symbol_tree +- request_dir_overview +""" + +from pathlib import Path + +import pytest + +from solidlsp import SolidLanguageServer +from solidlsp.ls_config import Language + +pytestmark = pytest.mark.toml + + +class TestTomlSymbolRetrieval: + """Test advanced symbol retrieval functionality for TOML files.""" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_request_containing_symbol_behavior(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test request_containing_symbol behavior for TOML files. + + Note: Taplo LSP doesn't support definition/containing symbol lookups for TOML files + since TOML is a configuration format, not code. This test verifies the behavior. + """ + # Line 2 (0-indexed: 1) is inside the [package] table + containing_symbol = language_server.request_containing_symbol("Cargo.toml", 1, 5) + + # Taplo doesn't support containing symbol lookup - returns None + # This is expected behavior for a configuration file format + assert containing_symbol is None, "TOML LSP doesn't support containing symbol lookup" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_request_document_overview_cargo(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test request_document_overview for Cargo.toml.""" + overview = language_server.request_document_overview("Cargo.toml") + + assert overview is not None + assert len(overview) > 0 + + # Get symbol names from overview + symbol_names = {symbol.get("name") for symbol in overview if "name" in symbol} + + # Verify expected top-level tables appear + expected_tables = {"package", "dependencies", "dev-dependencies", "features", "workspace"} + assert expected_tables.issubset(symbol_names), f"Missing expected tables in overview: {expected_tables - symbol_names}" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_request_document_overview_pyproject(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test request_document_overview for pyproject.toml.""" + overview = language_server.request_document_overview("pyproject.toml") + + assert overview is not None + assert len(overview) > 0 + + # Get symbol names from overview + symbol_names = {symbol.get("name") for symbol in overview if "name" in symbol} + + # Verify expected top-level tables appear + assert "project" in symbol_names, "Should detect 'project' table" + assert "build-system" in symbol_names, "Should detect 'build-system' table" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_request_full_symbol_tree(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test request_full_symbol_tree returns TOML files.""" + symbol_tree = language_server.request_full_symbol_tree() + + assert symbol_tree is not None + assert len(symbol_tree) > 0 + + # The root should be test_repo + root = symbol_tree[0] + assert root["name"] == "test_repo" + assert "children" in root + + # 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}" + ) + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_request_dir_overview(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test request_dir_overview returns symbols for TOML files.""" + overview = language_server.request_dir_overview(".") + + assert overview is not None + assert len(overview) > 0 + + # Should have entries for both Cargo.toml and pyproject.toml + file_paths = list(overview.keys()) + assert any("Cargo.toml" in path for path in file_paths), f"Should find Cargo.toml in overview, got: {file_paths}" + assert any("pyproject.toml" in path for path in file_paths), f"Should find pyproject.toml in overview, got: {file_paths}" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_symbol_hierarchy_in_cargo(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that symbol hierarchy is properly preserved in Cargo.toml.""" + all_symbols, root_symbols = language_server.request_document_symbols("Cargo.toml").get_all_symbols_and_roots() + + # Find the 'package' table + package_symbol = next((s for s in root_symbols if s.get("name") == "package"), None) + assert package_symbol is not None, "Should find 'package' as root symbol" + + # Verify it has children (nested keys) + assert "children" in package_symbol, "'package' should have children" + child_names = {child.get("name") for child in package_symbol.get("children", [])} + + # Package should have name, version, edition at minimum + assert "name" in child_names, "'package' should have 'name' child" + assert "version" in child_names, "'package' should have 'version' child" + assert "edition" in child_names, "'package' should have 'edition' child" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_symbol_hierarchy_in_pyproject(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that symbol hierarchy is properly preserved in pyproject.toml.""" + all_symbols, root_symbols = language_server.request_document_symbols("pyproject.toml").get_all_symbols_and_roots() + + # Find the 'project' table + project_symbol = next((s for s in root_symbols if s.get("name") == "project"), None) + assert project_symbol is not None, "Should find 'project' as root symbol" + + # Verify it has children + assert "children" in project_symbol, "'project' should have children" + child_names = {child.get("name") for child in project_symbol.get("children", [])} + + # Project should have name, version, dependencies at minimum + assert "name" in child_names, "'project' should have 'name' child" + assert "version" in child_names, "'project' should have 'version' child" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_tool_section_hierarchy(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that tool sections in pyproject.toml are properly structured.""" + all_symbols, root_symbols = language_server.request_document_symbols("pyproject.toml").get_all_symbols_and_roots() + + # Get all symbol names + all_names = [s.get("name") for s in all_symbols] + + # Should detect tool.ruff, tool.mypy, or tool.pytest + has_ruff = any("ruff" in name.lower() for name in all_names if name) + has_mypy = any("mypy" in name.lower() for name in all_names if name) + has_pytest = any("pytest" in name.lower() for name in all_names if name) + + assert has_ruff or has_mypy or has_pytest, f"Should detect tool sections, got names: {all_names}" + + @pytest.mark.parametrize("language_server", [Language.TOML], indirect=True) + @pytest.mark.parametrize("repo_path", [Language.TOML], indirect=True) + def test_array_of_tables_symbol(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + """Test that [[bin]] array of tables is detected.""" + all_symbols, root_symbols = language_server.request_document_symbols("Cargo.toml").get_all_symbols_and_roots() + + # Get all symbol names + all_names = [s.get("name") for s in all_symbols] + + # Should detect bin array of tables + has_bin = "bin" in all_names + assert has_bin, f"Should detect [[bin]] array of tables, got names: {all_names}" + + # Find the bin symbol and verify its structure + bin_symbol = next((s for s in all_symbols if s.get("name") == "bin"), None) + assert bin_symbol is not None, "Should find bin symbol" + + # Array of tables should be kind 18 (array) + assert bin_symbol.get("kind") == 18, "[[bin]] should have kind 18 (array)" + + # Children of array of tables are indexed by position ('0', '1', etc.) + if "children" in bin_symbol: + bin_children = bin_symbol.get("children", []) + assert len(bin_children) > 0, "[[bin]] should have at least one child element" + # First child is index '0' + first_child = bin_children[0] + assert first_child.get("name") == "0", f"First array element should be named '0', got: {first_child.get('name')}" + + # The '0' element should contain name and path as grandchildren + if "children" in first_child: + grandchild_names = {gc.get("name") for gc in first_child.get("children", [])} + assert "name" in grandchild_names, f"[[bin]] element should have 'name' field, got: {grandchild_names}" + assert "path" in grandchild_names, f"[[bin]] element should have 'path' field, got: {grandchild_names}"