Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 22 additions & 4 deletions src/solidlsp/language_servers/ruby_lsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
- ruby_lsp_version: Override the pinned ruby-lsp gem version installed by
Serena when no project-local or global ruby-lsp is already available
(default: the bundled Serena version).
- vendor_include_paths: List of top-level paths under ``vendor/`` that
should remain indexed. Example: ``["vendor/engines"]``.
"""

import json
Expand Down Expand Up @@ -55,7 +57,6 @@ def __init__(self, config: LanguageServerConfig, repository_root_path: str, soli
def is_ignored_dirname(self, dirname: str) -> bool:
"""Override to ignore Ruby-specific directories that cause performance issues."""
ruby_ignored_dirs = [
"vendor", # Ruby vendor directory
".bundle", # Bundler cache
"tmp", # Temporary files
"log", # Log files
Expand All @@ -71,6 +72,16 @@ def is_ignored_dirname(self, dirname: str) -> bool:
]
return super().is_ignored_dirname(dirname) or dirname in ruby_ignored_dirs

def is_ignored_path(self, relative_path: str, ignore_unsupported_files: bool = True) -> bool:
"""Override to keep only configured vendor subtrees visible to Serena."""
parts = pathlib.PurePosixPath(pathlib.Path(relative_path).as_posix()).parts
vendor_include_roots = {pathlib.PurePosixPath(path).parts[1] for path in self._custom_settings.get("vendor_include_paths", [])}
if "vendor" in parts:
if len(parts) < 2 or parts[:1] != ("vendor",) or parts[1] not in vendor_include_roots or "vendor" in parts[2:]:
return True

return super().is_ignored_path(relative_path, ignore_unsupported_files)

@override
def _get_wait_time_for_cross_file_referencing(self) -> float:
"""Override to provide optimal wait time for ruby-lsp cross-file reference resolution.
Expand Down Expand Up @@ -298,13 +309,11 @@ def _detect_rails_project(repository_root_path: str) -> bool:

return False

@staticmethod
def _get_ruby_exclude_patterns(repository_root_path: str) -> list[str]:
def _get_ruby_exclude_patterns(self, repository_root_path: str) -> list[str]:
"""
Get Ruby and Rails-specific exclude patterns for better performance.
"""
base_patterns = [
"**/vendor/**", # Ruby vendor directory
"**/.bundle/**", # Bundler cache
"**/tmp/**", # Temporary files
"**/log/**", # Log files
Expand All @@ -316,6 +325,15 @@ def _get_ruby_exclude_patterns(repository_root_path: str) -> list[str]:
"**/public/assets/**", # Rails compiled assets
]

vendor_include_roots = {pathlib.PurePosixPath(path).parts[1] for path in self._custom_settings.get("vendor_include_paths", [])}
if vendor_include_roots:
vendor_dir = pathlib.Path(repository_root_path) / "vendor"
if vendor_dir.is_dir():
base_patterns.extend(f"vendor/{child.name}/**" for child in vendor_dir.iterdir() if child.name not in vendor_include_roots)
base_patterns.extend(f"vendor/{root}/**/vendor/**" for root in vendor_include_roots)
else:
base_patterns.append("**/vendor/**")

# Add Rails-specific patterns if this is a Rails project
if RubyLsp._detect_rails_project(repository_root_path):
base_patterns.extend(
Expand Down
67 changes: 67 additions & 0 deletions test/solidlsp/ruby/test_ruby_lsp_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
from pathlib import Path

from solidlsp.language_servers.ruby_lsp import RubyLsp
from solidlsp.ls_config import Language
from solidlsp.settings import SolidLSPSettings


def _build_ruby_lsp(settings: SolidLSPSettings) -> RubyLsp:
language_server = RubyLsp.__new__(RubyLsp)
language_server._solidlsp_settings = settings
language_server.repository_root_path = ""
return language_server


def test_ruby_lsp_excludes_vendor_by_default(tmp_path: Path) -> None:
language_server = _build_ruby_lsp(SolidLSPSettings())

patterns = language_server._get_ruby_exclude_patterns(str(tmp_path))

assert "**/vendor/**" in patterns


def test_ruby_lsp_can_keep_vendor_engines_indexed(tmp_path: Path) -> None:
(tmp_path / "vendor" / "engines").mkdir(parents=True)
(tmp_path / "vendor" / "bundle").mkdir(parents=True)
(tmp_path / "vendor" / "cache").mkdir(parents=True)
(tmp_path / "vendor" / "engines" / "blog" / "vendor" / "bundle").mkdir(parents=True)

settings = SolidLSPSettings(ls_specific_settings={Language.RUBY: {"vendor_include_paths": ["vendor/engines"]}})
language_server = _build_ruby_lsp(settings)

patterns = language_server._get_ruby_exclude_patterns(str(tmp_path))

assert "**/vendor/**" not in patterns
assert "vendor/bundle/**" in patterns
assert "vendor/cache/**" in patterns
assert "vendor/engines/**" not in patterns
assert "vendor/engines/**/vendor/**" in patterns


def test_ruby_lsp_ignores_non_allowlisted_vendor_paths(tmp_path: Path) -> None:
bundle_file = tmp_path / "vendor" / "bundle" / "tool.rb"
bundle_file.parent.mkdir(parents=True)
bundle_file.write_text("class Tool; end\n")

nested_vendor_file = tmp_path / "vendor" / "engines" / "blog" / "vendor" / "cache" / "tool.rb"
nested_vendor_file.parent.mkdir(parents=True)
nested_vendor_file.write_text("class Tool; end\n")

settings = SolidLSPSettings(ls_specific_settings={Language.RUBY: {"vendor_include_paths": ["vendor/engines"]}})
language_server = _build_ruby_lsp(settings)
language_server.repository_root_path = str(tmp_path)

assert language_server.is_ignored_path("vendor/bundle/tool.rb")
assert language_server.is_ignored_path("vendor/engines/blog/vendor/cache/tool.rb")


def test_ruby_lsp_keeps_allowlisted_vendor_engines_paths(tmp_path: Path) -> None:
engine_file = tmp_path / "vendor" / "engines" / "blog" / "app" / "models" / "post.rb"
engine_file.parent.mkdir(parents=True)
engine_file.write_text("class Post; end\n")

settings = SolidLSPSettings(ls_specific_settings={Language.RUBY: {"vendor_include_paths": ["vendor/engines"]}})
language_server = _build_ruby_lsp(settings)
language_server.repository_root_path = str(tmp_path)

assert not language_server.is_ignored_path("vendor/engines/blog/app/models/post.rb", ignore_unsupported_files=False)
Loading