From f2d81812c787fab44ac4b8c0f8539401efc839cb Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 6 Aug 2025 20:58:14 +0200 Subject: [PATCH] Apply formatter --- .../language_servers/bash_language_server.py | 110 +++++++++--------- test/conftest.py | 3 - test/solidlsp/bash/test_bash_basic.py | 82 +++++++------ test/solidlsp/ruby/test_ruby_basic.py | 12 +- 4 files changed, 107 insertions(+), 100 deletions(-) diff --git a/src/solidlsp/language_servers/bash_language_server.py b/src/solidlsp/language_servers/bash_language_server.py index 04ffe355..2b8d8549 100644 --- a/src/solidlsp/language_servers/bash_language_server.py +++ b/src/solidlsp/language_servers/bash_language_server.py @@ -9,8 +9,7 @@ import pathlib import shutil import threading -from overrides import override - +from solidlsp import ls_types from solidlsp.language_servers.common import RuntimeDependency, RuntimeDependencyCollection from solidlsp.ls import SolidLanguageServer from solidlsp.ls_config import LanguageServerConfig @@ -20,8 +19,6 @@ from solidlsp.lsp_protocol_handler.server import ProcessLaunchInfo from solidlsp.settings import SolidLSPSettings -from solidlsp import ls_types - class BashLanguageServer(SolidLanguageServer): """ Provides Bash specific instantiation of the LanguageServer class using bash-language-server. @@ -47,8 +44,6 @@ class BashLanguageServer(SolidLanguageServer): self.server_ready = threading.Event() self.initialize_searcher_command_available = threading.Event() - - @classmethod def _setup_runtime_dependencies( cls, logger: LanguageServerLogger, config: LanguageServerConfig, solidlsp_settings: SolidLSPSettings @@ -180,7 +175,7 @@ class BashLanguageServer(SolidLanguageServer): # Enhanced capability checks for bash-language-server 5.6.0 assert init_response["capabilities"]["textDocumentSync"] in [1, 2] # Full or Incremental assert "completionProvider" in init_response["capabilities"] - + # Verify document symbol support is available if "documentSymbolProvider" in init_response["capabilities"]: self.logger.log("Bash server supports document symbols", logging.INFO) @@ -204,139 +199,138 @@ class BashLanguageServer(SolidLanguageServer): ) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]: """ Enhanced document symbol request with hybrid LSP + regex-based function detection for bash files. - + This method combines both LSP-based detection and regex-based detection to provide comprehensive function discovery. This dual approach is necessary because: - + 1. bash-language-server (v5.6.0) has inconsistent function detection capabilities 2. Some bash function syntaxes are not reliably detected by the LSP server 3. Files may contain mixed function notation styles within the same file 4. Different formatting or indentation can affect LSP detection - + The hybrid approach ensures maximum compatibility and comprehensive function discovery for reliable symbolic editing operations in Serena. """ # First get symbols from the standard LSP approach lsp_all_symbols, lsp_root_symbols = super().request_document_symbols(relative_file_path, include_body) - + # Always run regex-based function detection for comprehensive coverage # This addresses bash-language-server limitations and ensures we catch all function patterns self.logger.log(f"Running hybrid function detection (LSP + regex) for {relative_file_path}", logging.DEBUG) - + regex_detected_functions = self._detect_bash_functions(relative_file_path, include_body) - + # Merge LSP and regex results, avoiding duplicates merged_all_symbols, merged_root_symbols = self._merge_function_detections( lsp_all_symbols, lsp_root_symbols, regex_detected_functions ) - + # Log detection results for debugging lsp_functions = [s for s in lsp_all_symbols if s.get("kind") == 12] total_functions = [s for s in merged_all_symbols if s.get("kind") == 12] - + self.logger.log( f"Function detection for {relative_file_path}: LSP={len(lsp_functions)}, " - f"Regex={len(regex_detected_functions)}, Total={len(total_functions)}", - logging.INFO + f"Regex={len(regex_detected_functions)}, Total={len(total_functions)}", + logging.INFO, ) - + return merged_all_symbols, merged_root_symbols def _merge_function_detections( - self, - lsp_all_symbols: list[ls_types.UnifiedSymbolInformation], + self, + lsp_all_symbols: list[ls_types.UnifiedSymbolInformation], lsp_root_symbols: list[ls_types.UnifiedSymbolInformation], - regex_detected_functions: list[ls_types.UnifiedSymbolInformation] + regex_detected_functions: list[ls_types.UnifiedSymbolInformation], ) -> tuple[list[ls_types.UnifiedSymbolInformation], list[ls_types.UnifiedSymbolInformation]]: """ Merge LSP-detected symbols with regex-detected functions, avoiding duplicates. - + This method: 1. Keeps all non-function symbols from LSP detection 2. Keeps all LSP-detected functions (they have more accurate positioning) 3. Adds regex-detected functions that weren't found by LSP 4. Uses function names to detect duplicates - + Args: lsp_all_symbols: All symbols detected by LSP lsp_root_symbols: Root-level symbols detected by LSP regex_detected_functions: Functions detected by regex (all are root-level) - + Returns: Tuple of (merged_all_symbols, merged_root_symbols) + """ # Extract function names that LSP already detected lsp_function_names = set() for symbol in lsp_all_symbols: if symbol.get("kind") == 12: # LSP Symbol Kind 12 = Function lsp_function_names.add(symbol["name"]) - + # Start with all LSP symbols (both functions and non-functions) merged_all_symbols = lsp_all_symbols.copy() merged_root_symbols = lsp_root_symbols.copy() - + # Add regex-detected functions that weren't found by LSP added_functions = 0 for regex_function in regex_detected_functions: function_name = regex_function["name"] - + # Only add if this function wasn't detected by LSP if function_name not in lsp_function_names: merged_all_symbols.append(regex_function) merged_root_symbols.append(regex_function) # All regex functions are root-level added_functions += 1 self.logger.log(f"Added regex-detected function '{function_name}' not found by LSP", logging.DEBUG) - + if added_functions > 0: self.logger.log(f"Merged {added_functions} additional functions from regex detection", logging.INFO) - + return merged_all_symbols, merged_root_symbols - + def _detect_bash_functions(self, relative_file_path: str, include_body: bool = False) -> list[ls_types.UnifiedSymbolInformation]: """ Regex-based detection of bash functions as fallback when LSP doesn't provide them. """ import re + from solidlsp import ls_types - + try: # Read the file content directly from filesystem abs_path = os.path.join(self.repository_root_path, relative_file_path) - with open(abs_path, 'r', encoding='utf-8') as f: + with open(abs_path, encoding="utf-8") as f: file_content = f.read() lines = file_content.split("\n") - + detected_functions = [] - + # Regex patterns for bash function definitions # Pattern 1: function name() { ... } # Pattern 2: name() { ... } function_patterns = [ - re.compile(r'^\s*function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*\)\s*\{'), - re.compile(r'^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*\)\s*\{') + re.compile(r"^\s*function\s+([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*\)\s*\{"), + re.compile(r"^\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\(\s*\)\s*\{"), ] - + for line_num, line in enumerate(lines): for pattern in function_patterns: match = pattern.match(line) if match: func_name = match.group(1) - + # Find the end of the function by matching braces start_line = line_num end_line = self._find_function_end(lines, start_line) - + # Create location information location = ls_types.Location( uri=pathlib.Path(abs_path).as_uri(), - range={ - "start": {"line": start_line, "character": match.start(1)}, - "end": {"line": end_line, "character": 0} - }, + range={"start": {"line": start_line, "character": match.start(1)}, "end": {"line": end_line, "character": 0}}, absolutePath=abs_path, - relativePath=relative_file_path + relativePath=relative_file_path, ) - + # Create the function symbol func_symbol = ls_types.UnifiedSymbolInformation( name=func_name, @@ -345,44 +339,44 @@ class BashLanguageServer(SolidLanguageServer): range=location["range"], selectionRange={ "start": {"line": start_line, "character": match.start(1)}, - "end": {"line": start_line, "character": match.end(1)} + "end": {"line": start_line, "character": match.end(1)}, }, children=[], - parent=None + parent=None, ) - + # Add function body if requested if include_body: - func_symbol["body"] = "\n".join(lines[start_line:end_line + 1]) - + func_symbol["body"] = "\n".join(lines[start_line : end_line + 1]) + detected_functions.append(func_symbol) break - + return detected_functions - + except Exception as e: self.logger.log(f"Error in regex-based function detection for {relative_file_path}: {e}", logging.WARNING) return [] - + def _find_function_end(self, lines: list[str], start_line: int) -> int: """ Find the end line of a bash function by matching opening and closing braces. """ brace_count = 0 in_function = False - + for i in range(start_line, len(lines)): line = lines[i] - + # Count braces, handling basic cases for char in line: - if char == '{': + if char == "{": brace_count += 1 in_function = True - elif char == '}' and in_function: + elif char == "}" and in_function: brace_count -= 1 if brace_count == 0: return i - + # Fallback: if we can't match braces properly, assume single line or small function return min(start_line + 10, len(lines) - 1) diff --git a/test/conftest.py b/test/conftest.py index da91ebdd..849af543 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -144,6 +144,3 @@ def project(request: LanguageParamRequest): language = request.param yield create_default_project(language) - - - diff --git a/test/solidlsp/bash/test_bash_basic.py b/test/solidlsp/bash/test_bash_basic.py index 3fd1f637..8db362e9 100644 --- a/test/solidlsp/bash/test_bash_basic.py +++ b/test/solidlsp/bash/test_bash_basic.py @@ -26,14 +26,14 @@ class TestBashLanguageServerBasics: """Test request_document_symbols for bash files.""" # Test getting symbols from main.sh all_symbols, root_symbols = language_server.request_document_symbols("main.sh", include_body=False) - + # Extract function symbols (LSP Symbol Kind 12) function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12] function_names = [symbol["name"] for symbol in function_symbols] - + # Should detect all 3 functions from main.sh assert "greet_user" in function_names, "Should find greet_user function" - assert "process_items" in function_names, "Should find process_items function" + assert "process_items" in function_names, "Should find process_items function" assert "main" in function_names, "Should find main function" assert len(function_symbols) >= 3, f"Should find at least 3 functions, found {len(function_symbols)}" @@ -42,13 +42,13 @@ class TestBashLanguageServerBasics: """Test request_document_symbols with body extraction.""" # Test with include_body=True all_symbols, root_symbols = language_server.request_document_symbols("main.sh", include_body=True) - + function_symbols = [symbol for symbol in all_symbols if symbol.get("kind") == 12] - + # Find greet_user function and check it has body greet_user_symbol = next((sym for sym in function_symbols if sym["name"] == "greet_user"), None) assert greet_user_symbol is not None, "Should find greet_user function" - + if "body" in greet_user_symbol: body = greet_user_symbol["body"] assert "function greet_user()" in body, "Function body should contain function definition" @@ -58,47 +58,53 @@ class TestBashLanguageServerBasics: def test_bash_utils_functions(self, language_server: SolidLanguageServer) -> None: """Test function detection in utils.sh file.""" # Test with utils.sh as well - utils_all_symbols, utils_root_symbols = language_server.request_document_symbols( - "utils.sh", include_body=False - ) - + utils_all_symbols, utils_root_symbols = language_server.request_document_symbols("utils.sh", include_body=False) + utils_function_symbols = [symbol for symbol in utils_all_symbols if symbol.get("kind") == 12] utils_function_names = [symbol["name"] for symbol in utils_function_symbols] - + # Should detect functions from utils.sh - expected_utils_functions = ["to_uppercase", "to_lowercase", "trim_whitespace", "backup_file", - "contains_element", "log_message", "is_valid_email", "is_number"] - + expected_utils_functions = [ + "to_uppercase", + "to_lowercase", + "trim_whitespace", + "backup_file", + "contains_element", + "log_message", + "is_valid_email", + "is_number", + ] + for func_name in expected_utils_functions: assert func_name in utils_function_names, f"Should find {func_name} function in utils.sh" - + assert len(utils_function_symbols) >= 8, f"Should find at least 8 functions in utils.sh, found {len(utils_function_symbols)}" - @pytest.mark.parametrize("language_server", [Language.BASH], indirect=True) + @pytest.mark.parametrize("language_server", [Language.BASH], indirect=True) def test_bash_regex_fallback_detection(self, language_server: SolidLanguageServer) -> None: """Test the regex-based fallback function detection directly.""" # Check if the method exists (it should if we have our enhanced bash server) - if not hasattr(language_server, '_detect_bash_functions'): + if not hasattr(language_server, "_detect_bash_functions"): pytest.fail("Enhanced bash function detection not available - this may be due to test environment setup") - - # Test the _detect_bash_functions method directly + + # Test the _detect_bash_functions method directly detected_functions = language_server._detect_bash_functions("main.sh", include_body=True) - + # Should detect functions even if LSP doesn't provide them assert len(detected_functions) == 3, f"Should detect exactly 3 functions, found {len(detected_functions)}" - + function_names = [func["name"] for func in detected_functions] assert "greet_user" in function_names, "Regex should detect greet_user function" assert "process_items" in function_names, "Regex should detect process_items function" assert "main" in function_names, "Regex should detect main function" - + # Check that symbols have proper structure for func in detected_functions: assert func["kind"] == 12, f"Function {func['name']} should have LSP Symbol Kind 12" assert "location" in func, f"Function {func['name']} should have location info" assert "range" in func, f"Function {func['name']} should have range info" assert "selectionRange" in func, f"Function {func['name']} should have selectionRange info" - + # Check location structure location = func["location"] assert "uri" in location, f"Function {func['name']} location should have URI" @@ -109,29 +115,37 @@ class TestBashLanguageServerBasics: def test_bash_function_syntax_patterns(self, language_server: SolidLanguageServer) -> None: """Test detection of different bash function syntax patterns.""" # Check if the method exists (it should if we have our enhanced bash server) - if not hasattr(language_server, '_detect_bash_functions'): + if not hasattr(language_server, "_detect_bash_functions"): pytest.fail("Enhanced bash function detection not available - this may be due to test environment setup") - + # Test main.sh (has 'function' keyword functions and regular function) main_functions = language_server._detect_bash_functions("main.sh", include_body=False) - + # Test utils.sh (all use 'function' keyword) utils_functions = language_server._detect_bash_functions("utils.sh", include_body=False) - + # Verify we detect both syntax patterns main_function_names = [func["name"] for func in main_functions] utils_function_names = [func["name"] for func in utils_functions] - + # main() uses regular syntax: main() { assert "main" in main_function_names, "Should detect regular function syntax" - + # Functions with 'function' keyword: function name() { assert "greet_user" in main_function_names, "Should detect function keyword syntax" assert "to_uppercase" in utils_function_names, "Should detect function keyword syntax in utils" - + # Verify all expected utils functions are detected - expected_utils = ["to_uppercase", "to_lowercase", "trim_whitespace", "backup_file", - "contains_element", "log_message", "is_valid_email", "is_number"] - + expected_utils = [ + "to_uppercase", + "to_lowercase", + "trim_whitespace", + "backup_file", + "contains_element", + "log_message", + "is_valid_email", + "is_number", + ] + for expected_func in expected_utils: - assert expected_func in utils_function_names, f"Should detect {expected_func} function" \ No newline at end of file + assert expected_func in utils_function_names, f"Should detect {expected_func} function" diff --git a/test/solidlsp/ruby/test_ruby_basic.py b/test/solidlsp/ruby/test_ruby_basic.py index 435c0538..2edf95f2 100644 --- a/test/solidlsp/ruby/test_ruby_basic.py +++ b/test/solidlsp/ruby/test_ruby_basic.py @@ -27,14 +27,16 @@ class TestRubyLanguageServer: helper_symbol = sym break print(helper_symbol) - assert helper_symbol is not None, "Could not find 'helper_function' symbol in main.rb" + assert helper_symbol is not None, "Could not find 'helper_function' symbol in main.rb" @pytest.mark.parametrize("language_server", [Language.RUBY], indirect=True) - @pytest.mark.parametrize("repo_path", [Language.RUBY], indirect=True) - def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None: + @pytest.mark.parametrize("repo_path", [Language.RUBY], indirect=True) + def test_find_definition_across_files(self, language_server: SolidLanguageServer, repo_path: Path) -> None: # Test finding Calculator.add method definition from line 17: Calculator.new.add(demo.value, 10) - definition_location_list = language_server.request_definition(str(repo_path / "main.rb"), 16,17) # add method at line 17 (0-indexed 16), position 17 - + definition_location_list = language_server.request_definition( + str(repo_path / "main.rb"), 16, 17 + ) # add method at line 17 (0-indexed 16), position 17 + assert len(definition_location_list) == 1 definition_location = definition_location_list[0] print(f"Found definition: {definition_location}")