From bd9c4c9e89e2325f739300bfb2f3f13be19b2c04 Mon Sep 17 00:00:00 2001 From: zerone0x Date: Sat, 13 Jun 2026 04:42:27 +0200 Subject: [PATCH] fix: improve ignored_paths YAML guidance Fixes #1561 Co-Authored-By: Claude --- src/serena/resources/project.template.yml | 7 +++ .../resources/serena_config.template.yml | 1 + src/serena/util/yaml.py | 12 +++++- test/serena/config/test_serena_config.py | 43 +++++++++++++++++++ 4 files changed, 62 insertions(+), 1 deletion(-) diff --git a/src/serena/resources/project.template.yml b/src/serena/resources/project.template.yml index a4dddb37..dd3a4d7b 100644 --- a/src/serena/resources/project.template.yml +++ b/src/serena/resources/project.template.yml @@ -71,6 +71,13 @@ additional_workspace_folders: [] # list of additional paths to ignore in this project. # Same syntax as gitignore, so you can use * and **. +# Important: quote patterns that start with `*`, otherwise YAML treats them as aliases. +# Example: +# ignored_paths: +# - "examples/**" +# - ".worktrees/**" +# - "**/bin/**" +# - "**/obj/**" # Note: global ignored_paths from serena_config.yml are also applied additively. ignored_paths: [] diff --git a/src/serena/resources/serena_config.template.yml b/src/serena/resources/serena_config.template.yml index fb5c1d40..a5d81d0d 100644 --- a/src/serena/resources/serena_config.template.yml +++ b/src/serena/resources/serena_config.template.yml @@ -103,6 +103,7 @@ ls_specific_settings: {} # list of paths to ignore across all projects. # Same syntax as gitignore, so you can use * and **. # These patterns are merged additively with each project's own ignored_paths. +# Quote patterns that start with `*`, e.g. `"**/bin/**"`. ignored_paths: [] # list of regex patterns which, when matched, mark a memory entry as read‑only. diff --git a/src/serena/util/yaml.py b/src/serena/util/yaml.py index ca63eee7..b1261e01 100644 --- a/src/serena/util/yaml.py +++ b/src/serena/util/yaml.py @@ -68,7 +68,17 @@ def load_yaml(path: str, comment_normalisation: YamlCommentNormalisation = YamlC """ with open(path, encoding=SERENA_FILE_ENCODING) as f: yaml = _create_yaml(preserve_comments=True) - commented_map: CommentedMap | None = yaml.load(f) + try: + commented_map: CommentedMap | None = yaml.load(f) + except Exception as e: + msg = str(e) + if "undefined alias" in msg: + raise ValueError( + f"Invalid YAML in {path}: values that start with `*` must be quoted. " + "This often happens in `ignored_paths` when using gitignore-style globs like " + '`"**/bin/**"` or `"**/obj/**"`.' + ) from e + raise if commented_map is None: # ruamel returns None for empty documents, but we want an empty CommentedMap commented_map = CommentedMap() normalise_yaml_comments(commented_map, comment_normalisation) diff --git a/test/serena/config/test_serena_config.py b/test/serena/config/test_serena_config.py index 2b0a4d52..7043ed9b 100644 --- a/test/serena/config/test_serena_config.py +++ b/test/serena/config/test_serena_config.py @@ -499,6 +499,25 @@ class TestProjectSerenaDataFolder: assert project.path_to_serena_data_folder() == str(custom_serena) +class TestProjectConfigYamlValidation: + def test_ignored_paths_globs_starting_with_star_require_quotes(self): + project_dir = Path(tempfile.mkdtemp()) + try: + serena_dir = project_dir / SERENA_MANAGED_DIR_NAME + serena_dir.mkdir(parents=True) + (serena_dir / "project.yml").write_text('project_name: "demo"\nlanguages: ["csharp"]\nignored_paths:\n- **/bin/**\n') + + with pytest.raises(ValueError) as exc_info: + ProjectConfig.load(project_dir, create_default_serena_config()) + + msg = str(exc_info.value) + assert "values that start with `*` must be quoted" in msg + assert "ignored_paths" in msg + assert '"**/bin/**"' in msg + finally: + shutil.rmtree(project_dir) + + class TestSerenaConfigFromConfigFileRobustness: """Tests that ``SerenaConfig.from_config_file`` does not abort the whole loader when a single registered project has a broken ``project.yml``. @@ -568,6 +587,30 @@ class TestSerenaConfigFromConfigFileRobustness: f"Expected a warning naming {bad_project.resolve()}, got: {caplog.messages}" ) + def test_alias_like_ignored_path_error_is_logged_with_hint(self, caplog, monkeypatch): + good_project = self._make_project_dir( + "good_project", + 'project_name: "good_project"\nlanguages: ["python"]\n', + ) + bad_project = self._make_project_dir( + "bad_project", + 'project_name: "bad_project"\nlanguages: ["csharp"]\nignored_paths:\n- **/bin/**\n', + ) + self._write_master_config([good_project, bad_project]) + + monkeypatch.setattr( + SerenaConfig, + "_determine_config_file_path", + classmethod(lambda cls: str(self.master_config_path)), + ) + + with caplog.at_level(logging.ERROR): + config = SerenaConfig.from_config_file(generate_if_missing=False) + + registered_roots = {Path(p.project_root).resolve() for p in config.projects} + assert registered_roots == {good_project.resolve()} + assert any("must be quoted" in msg and str(bad_project.resolve()) in msg for msg in caplog.messages), caplog.messages + class TestMemoriesManagerCustomPath: """Tests for MemoriesManager with a custom serena data folder."""