diff --git a/src/serena/cli.py b/src/serena/cli.py index 3a1b968e..ee36be22 100644 --- a/src/serena/cli.py +++ b/src/serena/cli.py @@ -567,7 +567,7 @@ class ProjectCommands(AutoRegisteringGroup): serena_config = SerenaConfig.from_config_file() registered_project = serena_config.get_registered_project(str(project_root)) if registered_project is None: - registered_project = RegisteredProject(str(project_root), generated_conf, global_ignored_paths=serena_config.ignored_paths) + registered_project = RegisteredProject(str(project_root), generated_conf, serena_config=serena_config) serena_config.add_registered_project(registered_project) return registered_project @@ -691,7 +691,7 @@ class ProjectCommands(AutoRegisteringGroup): :param project: The path to the project directory, defaults to the current working directory. """ serena_config = SerenaConfig.from_config_file() - proj = Project.load(os.path.abspath(project), global_ignored_paths=serena_config.ignored_paths) + proj = Project.load(os.path.abspath(project), serena_config=serena_config) if os.path.isabs(path): path = os.path.relpath(path, start=proj.project_root) is_ignored = proj.is_ignored_path(path) @@ -714,7 +714,7 @@ class ProjectCommands(AutoRegisteringGroup): :param verbose: if set, prints detailed information about the indexed symbols. """ serena_config = SerenaConfig.from_config_file() - proj = Project.load(os.path.abspath(project), global_ignored_paths=serena_config.ignored_paths) + proj = Project.load(os.path.abspath(project), serena_config=serena_config) if os.path.isabs(file): file = os.path.relpath(file, start=proj.project_root) if proj.is_ignored_path(file, ignore_non_source_files=True): @@ -752,7 +752,7 @@ class ProjectCommands(AutoRegisteringGroup): logging.configure(level=logging.INFO) project_path = os.path.abspath(project) serena_config = SerenaConfig.from_config_file() - proj = Project.load(project_path, global_ignored_paths=serena_config.ignored_paths) + proj = Project.load(project_path, serena_config=serena_config) # Create log file with timestamp timestamp = datetime_tag() diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index ecfb3a2b..79b8dab0 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -402,7 +402,7 @@ class RegisteredProject(ToStringMixin): project_root: str, project_config: "ProjectConfig", project_instance: Optional["Project"] = None, - global_ignored_paths: Sequence[str] = (), + serena_config: "SerenaConfig | None" = None, ) -> None: """ Represents a registered project in the Serena configuration. @@ -410,12 +410,12 @@ class RegisteredProject(ToStringMixin): :param project_root: the root directory of the project :param project_config: the configuration of the project :param project_instance: an existing project instance (if already loaded) - :param global_ignored_paths: global ignored paths from SerenaConfig, merged additively with project-level patterns + :param serena_config: the SerenaConfig instance for global settings """ self.project_root = Path(project_root).resolve() self.project_config = project_config self._project_instance = project_instance - self._global_ignored_paths = global_ignored_paths + self._serena_config = serena_config def _tostring_exclude_private(self) -> bool: return True @@ -425,21 +425,21 @@ class RegisteredProject(ToStringMixin): return self.project_config.project_name @classmethod - def from_project_instance(cls, project_instance: "Project", global_ignored_paths: Sequence[str] = ()) -> "RegisteredProject": + def from_project_instance(cls, project_instance: "Project", serena_config: "SerenaConfig | None" = None) -> "RegisteredProject": return RegisteredProject( project_root=project_instance.project_root, project_config=project_instance.project_config, project_instance=project_instance, - global_ignored_paths=global_ignored_paths, + serena_config=serena_config, ) @classmethod - def from_project_root(cls, project_root: str | Path, global_ignored_paths: Sequence[str] = ()) -> "RegisteredProject": + def from_project_root(cls, project_root: str | Path, serena_config: "SerenaConfig | None" = None) -> "RegisteredProject": project_config = ProjectConfig.load(project_root) return RegisteredProject( project_root=str(project_root), project_config=project_config, - global_ignored_paths=global_ignored_paths, + serena_config=serena_config, ) def matches_root_path(self, path: str | Path) -> bool: @@ -462,7 +462,7 @@ class RegisteredProject(ToStringMixin): self._project_instance = Project( project_root=str(self.project_root), project_config=self.project_config, - global_ignored_paths=self._global_ignored_paths, + serena_config=self._serena_config, ) return self._project_instance @@ -629,7 +629,7 @@ class SerenaConfig(ToolInclusionDefinition, ModeSelectionDefinition, ToStringMix project = RegisteredProject( project_root=str(path), project_config=project_config, - global_ignored_paths=instance.ignored_paths, + serena_config=instance, ) instance.projects.append(project) @@ -720,7 +720,7 @@ class SerenaConfig(ToolInclusionDefinition, ModeSelectionDefinition, ToStringMix if autoregister: config_path = ProjectConfig.path_to_project_yml(project_root_or_name) if os.path.isfile(config_path): - registered_project = RegisteredProject.from_project_root(project_root_or_name, global_ignored_paths=self.ignored_paths) + registered_project = RegisteredProject.from_project_root(project_root_or_name, serena_config=self) self.add_registered_project(registered_project) return registered_project # nothing found @@ -769,9 +769,9 @@ class SerenaConfig(ToolInclusionDefinition, ModeSelectionDefinition, ToStringMix project_root=str(project_root), project_config=project_config, is_newly_created=True, - global_ignored_paths=self.ignored_paths, + serena_config=self, ) - self.add_registered_project(RegisteredProject.from_project_instance(new_project, global_ignored_paths=self.ignored_paths)) + self.add_registered_project(RegisteredProject.from_project_instance(new_project, serena_config=self)) return new_project diff --git a/src/serena/project.py b/src/serena/project.py index ae7bcc0f..c0d4203b 100644 --- a/src/serena/project.py +++ b/src/serena/project.py @@ -2,9 +2,8 @@ import json import logging import os import threading -from collections.abc import Sequence from pathlib import Path -from typing import Any +from typing import TYPE_CHECKING, Any import pathspec from sensai.util.logging import LogTime @@ -19,6 +18,9 @@ from solidlsp import SolidLanguageServer from solidlsp.ls_config import Language from solidlsp.ls_utils import FileUtils +if TYPE_CHECKING: + from serena.config.serena_config import SerenaConfig + log = logging.getLogger(__name__) @@ -62,7 +64,7 @@ class Project(ToStringMixin): project_root: str, project_config: ProjectConfig, is_newly_created: bool = False, - global_ignored_paths: Sequence[str] = (), + serena_config: "SerenaConfig | None" = None, ): self.project_root = project_root self.project_config = project_config @@ -79,7 +81,7 @@ class Project(ToStringMixin): f.write(f"/{SolidLanguageServer.CACHE_FOLDER_NAME}\n") # prepare ignore spec asynchronously, ensuring immediate project activation. - self._global_ignored_paths = global_ignored_paths + self._serena_config = serena_config self.__ignored_patterns: list[str] self.__ignore_spec: pathspec.PathSpec self._ignore_spec_available = threading.Event() @@ -89,10 +91,11 @@ class Project(ToStringMixin): 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 - ignored_patterns = list(self._global_ignored_paths) + list(self.project_config.ignored_paths) - if len(self._global_ignored_paths) > 0: - log.info(f"Using {len(self._global_ignored_paths)} ignored paths from the global configuration.") - log.debug(f"Global ignored paths: {list(self._global_ignored_paths)}") + global_ignored_paths = self._serena_config.ignored_paths if self._serena_config else [] + ignored_patterns = list(global_ignored_paths) + list(self.project_config.ignored_paths) + if len(global_ignored_paths) > 0: + log.info(f"Using {len(global_ignored_paths)} ignored paths from the global configuration.") + log.debug(f"Global ignored paths: {list(global_ignored_paths)}") if len(self.project_config.ignored_paths) > 0: log.info(f"Using {len(self.project_config.ignored_paths)} ignored paths from the project configuration.") log.debug(f"Project ignored paths: {self.project_config.ignored_paths}") @@ -131,13 +134,13 @@ class Project(ToStringMixin): cls, project_root: str | Path, autogenerate: bool = True, - global_ignored_paths: Sequence[str] = (), + serena_config: "SerenaConfig | None" = None, ) -> "Project": project_root = Path(project_root).resolve() if not project_root.exists(): raise FileNotFoundError(f"Project root not found: {project_root}") project_config = ProjectConfig.load(project_root, autogenerate=autogenerate) - return Project(project_root=str(project_root), project_config=project_config, global_ignored_paths=global_ignored_paths) + return Project(project_root=str(project_root), project_config=project_config, serena_config=serena_config) def save_config(self) -> None: """ diff --git a/test/serena/config/test_global_ignored_paths.py b/test/serena/config/test_global_ignored_paths.py index f699915d..230a950d 100644 --- a/test/serena/config/test_global_ignored_paths.py +++ b/test/serena/config/test_global_ignored_paths.py @@ -20,10 +20,13 @@ def _create_test_project( ignored_paths=project_ignored_paths or [], ignore_all_files_in_gitignore=False, ) + serena_config: SerenaConfig | None = None + if global_ignored_paths: + serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=global_ignored_paths) return Project( project_root=str(project_root), project_config=config, - global_ignored_paths=global_ignored_paths or [], + serena_config=serena_config, ) @@ -138,10 +141,11 @@ class TestRegisteredProjectGlobalIgnoredPaths: ignored_paths=[], ignore_all_files_in_gitignore=False, ) + serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=["node_modules"]) registered = RegisteredProject( project_root=str(self.project_path), project_config=config, - global_ignored_paths=["node_modules"], + serena_config=serena_config, ) project = registered.get_project_instance() assert project.is_ignored_path(str(self.project_path / "node_modules" / "pkg.js")) @@ -171,7 +175,7 @@ class TestRegisteredProjectGlobalIgnoredPaths: ) registered = RegisteredProject.from_project_root( str(self.project_path), - global_ignored_paths=["node_modules"], + serena_config=SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=["node_modules"]), ) project = registered.get_project_instance() assert project.is_ignored_path(str(self.project_path / "node_modules" / "pkg.js")) @@ -184,12 +188,13 @@ class TestRegisteredProjectGlobalIgnoredPaths: ignored_paths=[], ignore_all_files_in_gitignore=False, ) + serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=["node_modules"]) project = Project( project_root=str(self.project_path), project_config=config, - global_ignored_paths=["node_modules"], + serena_config=serena_config, ) - registered = RegisteredProject.from_project_instance(project, global_ignored_paths=["node_modules"]) + registered = RegisteredProject.from_project_instance(project, serena_config=serena_config) # The registered project already has a project_instance, so get_project_instance() returns it directly retrieved = registered.get_project_instance() assert retrieved.is_ignored_path(str(self.project_path / "node_modules" / "pkg.js")) @@ -223,10 +228,11 @@ class TestGlobalIgnoredPathsWithGitignore: ignored_paths=["build"], ignore_all_files_in_gitignore=True, ) + serena_config = SerenaConfig(gui_log_window=False, web_dashboard=False, ignored_paths=["node_modules"]) project = Project( project_root=str(self.project_path), project_config=config, - global_ignored_paths=["node_modules"], + serena_config=serena_config, ) # Global pattern: node_modules assert project.is_ignored_path(str(self.project_path / "node_modules" / "pkg.js"))