From 2622ee5b2ef2bc3294ee50b1a520e2d65e2f3f1f Mon Sep 17 00:00:00 2001 From: Danil <81031453+Kostenkov-2021@users.noreply.github.com> Date: Sat, 19 Sep 2026 11:19:58 +0300 Subject: [PATCH 1/2] Fix audit-ignore rule scoping Adjust the audit-translations parser so `# audit-ignore` markers immediately above a rule apply to that rule, while still supporting existing inline markers inside a rule block. This also clarifies the documented behavior and adds regression tests for leading, inline, and first-item ignore cases. --- PythonScripts/audit_translations/README.md | 3 +- PythonScripts/audit_translations/parsers.py | 17 +++++-- .../audit_translations/tests/test_parsers.py | 47 +++++++++++++++++++ 3 files changed, 61 insertions(+), 6 deletions(-) diff --git a/PythonScripts/audit_translations/README.md b/PythonScripts/audit_translations/README.md index 8aedf1265..fe391d49b 100644 --- a/PythonScripts/audit_translations/README.md +++ b/PythonScripts/audit_translations/README.md @@ -12,7 +12,8 @@ The tool analyzes rule files to detect the following issues: * **Rule Differences:** Structural changes (match expressions, conditions, variables, or test/replace layout) between the source and target translation. * **Definition Coverage:** Compares literal `definitions.yaml` entries by name and collection kind (`vector`, `set`, or `map`). -Add `# audit-ignore` to a rule block to suppress auditing that rule. +Add `# audit-ignore` on the line immediately before a rule to suppress auditing +that rule. Existing markers inside a rule block are also supported. --- diff --git a/PythonScripts/audit_translations/parsers.py b/PythonScripts/audit_translations/parsers.py index 11dfc5292..a96de8bef 100644 --- a/PythonScripts/audit_translations/parsers.py +++ b/PythonScripts/audit_translations/parsers.py @@ -149,13 +149,20 @@ def format_tag(tag_value: Any) -> str | None: def build_raw_blocks(lines: list[str], starts: list[int]) -> list[str]: - blocks = [] if not starts: - return blocks + return [] + + block_starts = starts.copy() for idx, start in enumerate(starts): - end = starts[idx + 1] if idx + 1 < len(starts) else len(lines) - blocks.append("\n".join(lines[start:end])) - return blocks + lower_bound = starts[idx - 1] + 1 if idx else 0 + while start > lower_bound and (not lines[start - 1].strip() or lines[start - 1].lstrip().startswith("#")): + start -= 1 + block_starts[idx] = start + + return [ + "\n".join(lines[start : block_starts[idx + 1] if idx + 1 < len(block_starts) else len(lines)]) + for idx, start in enumerate(block_starts) + ] def _extract_item_fields(item: Any, is_unicode: bool) -> tuple[str, str | None, str | None, Any] | None: diff --git a/PythonScripts/audit_translations/tests/test_parsers.py b/PythonScripts/audit_translations/tests/test_parsers.py index db7f31e1f..a8d1c36db 100644 --- a/PythonScripts/audit_translations/tests/test_parsers.py +++ b/PythonScripts/audit_translations/tests/test_parsers.py @@ -174,6 +174,53 @@ def test_detects_audit_ignore(self): rules = parse_rules_file(content, data) assert rules[0].audit_ignore + def test_assigns_leading_audit_ignore_to_following_rule(self): + """A marker directly above a rule suppresses that rule, not its neighbour.""" + content = """- name: first + tag: mo + match: "." + +# audit-ignore: the following placeholder intentionally differs +# from the source-language rule. +- name: second + tag: mi + match: "false()" +""" + yaml = YAML() + data = yaml.load(content) + rules = parse_rules_file(content, data) + assert not rules[0].audit_ignore + assert rules[1].audit_ignore + + def test_keeps_audit_ignore_inside_rule(self): + """Existing markers within a rule block remain supported.""" + content = """- name: first + tag: mo + # audit-ignore + match: "." + +- name: second + tag: mi + match: "x" +""" + yaml = YAML() + data = yaml.load(content) + rules = parse_rules_file(content, data) + assert rules[0].audit_ignore + assert not rules[1].audit_ignore + + def test_assigns_file_header_to_first_rule(self): + """A leading marker before the first item is included in its raw block.""" + content = """# audit-ignore +- name: first + tag: mo + match: "." +""" + yaml = YAML() + data = yaml.load(content) + rules = parse_rules_file(content, data) + assert rules[0].audit_ignore + def test_handles_array_tag(self): """Ensure handles array tag.""" content = """- name: multi-tag From 600180b4291f0f5a5797d443e84dfb41337d29d5 Mon Sep 17 00:00:00 2001 From: Danil <81031453+Kostenkov-2021@users.noreply.github.com> Date: Sun, 20 Sep 2026 10:41:20 +0300 Subject: [PATCH 2/2] Fix audit-ignore parsing around comments This change fixes how audit_translations YAML blocks are split when comments or blank lines appear around list items. It keeps introductory comments with the correct rule, preserves explicit audit-ignore markers inside a rule, and avoids incorrectly attaching comment-only lines to neighboring items. --- PythonScripts/audit_translations/README.md | 4 +- PythonScripts/audit_translations/parsers.py | 35 ++++++++++++--- .../audit_translations/tests/test_parsers.py | 44 +++++++++++++++++-- 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/PythonScripts/audit_translations/README.md b/PythonScripts/audit_translations/README.md index fe391d49b..241a500d2 100644 --- a/PythonScripts/audit_translations/README.md +++ b/PythonScripts/audit_translations/README.md @@ -12,8 +12,8 @@ The tool analyzes rule files to detect the following issues: * **Rule Differences:** Structural changes (match expressions, conditions, variables, or test/replace layout) between the source and target translation. * **Definition Coverage:** Compares literal `definitions.yaml` entries by name and collection kind (`vector`, `set`, or `map`). -Add `# audit-ignore` on the line immediately before a rule to suppress auditing -that rule. Existing markers inside a rule block are also supported. +After a blank line, add `# audit-ignore` immediately before a rule to suppress +auditing that rule. Existing markers inside a rule block are also supported. --- diff --git a/PythonScripts/audit_translations/parsers.py b/PythonScripts/audit_translations/parsers.py index a96de8bef..bcbdd0027 100644 --- a/PythonScripts/audit_translations/parsers.py +++ b/PythonScripts/audit_translations/parsers.py @@ -154,15 +154,36 @@ def build_raw_blocks(lines: list[str], starts: list[int]) -> list[str]: block_starts = starts.copy() for idx, start in enumerate(starts): - lower_bound = starts[idx - 1] + 1 if idx else 0 - while start > lower_bound and (not lines[start - 1].strip() or lines[start - 1].lstrip().startswith("#")): + if idx == 0: + while start > 0 and (not lines[start - 1].strip() or lines[start - 1].lstrip().startswith("#")): + start -= 1 + block_starts[idx] = start + continue + + lower_bound = starts[idx - 1] + 1 + dash_line = re.fullmatch(r"\s*-\s*(?:#.*)?", lines[start - 1]) if start > lower_bound else None + if dash_line: start -= 1 - block_starts[idx] = start + block_starts[idx] = start + continue - return [ - "\n".join(lines[start : block_starts[idx + 1] if idx + 1 < len(block_starts) else len(lines)]) - for idx, start in enumerate(block_starts) - ] + comment_start = start + while comment_start > lower_bound and lines[comment_start - 1].lstrip().startswith("#"): + comment_start -= 1 + + # A separating blank makes the comment run introductory text for this + # item. Without one, leave the run with the preceding rule. + if comment_start > lower_bound and not lines[comment_start - 1].strip(): + start = comment_start - 1 + while start > lower_bound and not lines[start - 1].strip(): + start -= 1 + block_starts[idx] = start + + blocks = [] + for idx, start in enumerate(block_starts): + end = block_starts[idx + 1] if idx + 1 < len(block_starts) else len(lines) + blocks.append("\n".join(lines[start:end])) + return blocks def _extract_item_fields(item: Any, is_unicode: bool) -> tuple[str, str | None, str | None, Any] | None: diff --git a/PythonScripts/audit_translations/tests/test_parsers.py b/PythonScripts/audit_translations/tests/test_parsers.py index a8d1c36db..6714f3581 100644 --- a/PythonScripts/audit_translations/tests/test_parsers.py +++ b/PythonScripts/audit_translations/tests/test_parsers.py @@ -176,7 +176,8 @@ def test_detects_audit_ignore(self): def test_assigns_leading_audit_ignore_to_following_rule(self): """A marker directly above a rule suppresses that rule, not its neighbour.""" - content = """- name: first + content = """ +- name: first tag: mo match: "." @@ -194,7 +195,8 @@ def test_assigns_leading_audit_ignore_to_following_rule(self): def test_keeps_audit_ignore_inside_rule(self): """Existing markers within a rule block remain supported.""" - content = """- name: first + content = """ +- name: first tag: mo # audit-ignore match: "." @@ -209,9 +211,45 @@ def test_keeps_audit_ignore_inside_rule(self): assert rules[0].audit_ignore assert not rules[1].audit_ignore + def test_keeps_touching_comment_with_preceding_rule(self): + """A comment without a separating blank remains part of the preceding rule.""" + content = """ +- name: first + tag: mo + match: "." +# audit-ignore: this comment documents the first rule +- name: second + tag: mi + match: "x" +""" + yaml = YAML() + data = yaml.load(content) + rules = parse_rules_file(content, data) + assert rules[0].audit_ignore + assert not rules[1].audit_ignore + + def test_includes_comment_on_item_dash_line(self): + """A comment attached to an item's dash belongs to that item.""" + content = """ +- name: first + tag: mo + match: "." + +- # second rule introduction + name: second + tag: mi + match: "x" +""" + yaml = YAML() + data = yaml.load(content) + rules = parse_rules_file(content, data) + assert "second rule introduction" not in rules[0].raw_content + assert "second rule introduction" in rules[1].raw_content + def test_assigns_file_header_to_first_rule(self): """A leading marker before the first item is included in its raw block.""" - content = """# audit-ignore + content = """ +# audit-ignore - name: first tag: mo match: "."