diff --git a/CHANGELOG.md b/CHANGELOG.md index 48beba24..cc432a74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ Status of the `main` branch. Changes prior to the next official version change w * General: - Support `serena --version` CLI command for displaying the current version #1347 - Fix: Check for ignored path ignored `.git` folder only at the top level, not in every subdirectory (`Project._is_ignored_relative_path`) #1350 + - Fix: Duplicate comments in re-saved YAML configuration files #1285 # v1.1.2 (2026-04-14) diff --git a/src/serena/config/serena_config.py b/src/serena/config/serena_config.py index a9cefd3b..26815061 100644 --- a/src/serena/config/serena_config.py +++ b/src/serena/config/serena_config.py @@ -31,7 +31,7 @@ from serena.constants import ( SERENA_MANAGED_DIR_NAME, ) from serena.util.inspection import determine_programming_language_composition -from serena.util.yaml import YamlCommentNormalisation, load_yaml, normalise_yaml_comments, save_yaml, transfer_missing_yaml_comments +from serena.util.yaml import YamlCommentNormalisation, load_yaml, normalise_yaml_comments, save_yaml, transfer_yaml_comments from solidlsp.ls_config import Language from ..analytics import RegisteredTokenCountEstimator @@ -581,7 +581,7 @@ class ProjectConfig(SharedConfig): # transfer missing comments from the template file template_config, _ = self._load_yaml_dict(PROJECT_TEMPLATE_FILE, self.YAML_COMMENT_NORMALISATION) - transfer_missing_yaml_comments(template_config, config_with_comments, self.YAML_COMMENT_NORMALISATION, force_update_all=True) + transfer_yaml_comments(template_config, config_with_comments, self.YAML_COMMENT_NORMALISATION, force_update_all=True) # save project.yml save_yaml(config_path, config_with_comments) @@ -1063,7 +1063,7 @@ class SerenaConfig(SharedConfig): # For some keys, we force updates, because old comments are problematic/misleading. normalise_yaml_comments(commented_yaml, YamlCommentNormalisation.LEADING_WITH_CONVERSION_FROM_TRAILING) template_yaml = load_yaml(SERENA_CONFIG_TEMPLATE_FILE, comment_normalisation=YamlCommentNormalisation.LEADING) - transfer_missing_yaml_comments(template_yaml, commented_yaml, YamlCommentNormalisation.LEADING, force_update_all=True) + transfer_yaml_comments(template_yaml, commented_yaml, YamlCommentNormalisation.LEADING, force_update_all=True) save_yaml(self.config_file_path, commented_yaml) diff --git a/src/serena/util/yaml.py b/src/serena/util/yaml.py index ad177dce..ca63eee7 100644 --- a/src/serena/util/yaml.py +++ b/src/serena/util/yaml.py @@ -4,7 +4,7 @@ from collections.abc import Sequence from enum import Enum from typing import Any -from ruamel.yaml import YAML, CommentToken, StreamMark +from ruamel.yaml import YAML, CommentedSeq, CommentToken, StreamMark from ruamel.yaml.comments import CommentedMap from serena.constants import SERENA_FILE_ENCODING @@ -122,6 +122,20 @@ def normalise_yaml_comments(commented_map: CommentedMap, comment_normalisation: first_token.value = first_token.value[1:] return token_list + def remove_nested_comments() -> None: + """ + Removes nested comments, particularly of sequences, which incorrectly capture comments + that are actually intended for top-level keys. + """ + for key in commented_map.keys(): + entry = commented_map[key] + if isinstance(entry, CommentedSeq): + items = entry.ca.items + if isinstance(items, dict): + items_keys = list(items.keys()) + for i in items_keys: + items[i] = [None] * 4 + match comment_normalisation: case YamlCommentNormalisation.NONE: pass @@ -176,6 +190,9 @@ def normalise_yaml_comments(commented_map: CommentedMap, comment_normalisation: preceding_comment[ITEM_COMMENT_INDEX_BEFORE] = token_list current_comment[ITEM_COMMENT_INDEX_BEFORE] = None preceding_comment = current_comment + + # remove nested comments, as we assume that only top-level keys are supposed to be commented + remove_nested_comments() case _: raise ValueError(f"Unhandled comment normalisation: {comment_normalisation}") @@ -204,7 +221,7 @@ def yaml_comment_entry_is_empty(comment_entry: Any) -> bool: return False -def transfer_missing_yaml_comments_by_index( +def transfer_yaml_comments_by_index( source: CommentedMap, target: CommentedMap, indices: list[int], forced_update_keys: Sequence[str] = (), force_update_all: bool = False ) -> None: """ @@ -231,7 +248,7 @@ def transfer_missing_yaml_comments_by_index( target_comment[index] = source_comment[index] -def transfer_missing_yaml_comments( +def transfer_yaml_comments( source: CommentedMap, target: CommentedMap, comment_normalisation: YamlCommentNormalisation, @@ -251,7 +268,7 @@ def transfer_missing_yaml_comments( case YamlCommentNormalisation.NONE: pass case YamlCommentNormalisation.LEADING | YamlCommentNormalisation.LEADING_WITH_CONVERSION_FROM_TRAILING: - transfer_missing_yaml_comments_by_index( + transfer_yaml_comments_by_index( source, target, [ITEM_COMMENT_INDEX_BEFORE], forced_update_keys=forced_update_keys, force_update_all=force_update_all ) case _: