diff --git a/src/comments.py b/src/comments.py index 8031775..6fc7355 100644 --- a/src/comments.py +++ b/src/comments.py @@ -176,19 +176,30 @@ def parse_update_instructions(comment_body): # treated as global to avoid misclassifying example filenames as per-file # instructions. global_lines = [] - in_code_fence = False + fence_char = None for line in lines: stripped = line.strip() if not stripped: global_lines.append("") continue - # Toggle code fence state on lines starting with triple backticks - # or triple tildes (handles language specifiers like ```python) - if stripped.startswith("```") or stripped.startswith("~~~"): - in_code_fence = not in_code_fence + # Track code fence state with delimiter awareness: a fence opened + # with backticks can only be closed by backticks, and likewise for + # tildes (CommonMark spec §4.5). + if stripped.startswith("```"): + if fence_char is None: + fence_char = "`" + elif fence_char == "`": + fence_char = None global_lines.append(stripped) continue - if in_code_fence: + if stripped.startswith("~~~"): + if fence_char is None: + fence_char = "~" + elif fence_char == "~": + fence_char = None + global_lines.append(stripped) + continue + if fence_char is not None: global_lines.append(stripped) continue file_match = file_pattern.match(stripped) diff --git a/tests/test_comment_parsing.py b/tests/test_comment_parsing.py index 07d43a3..77ab646 100644 --- a/tests/test_comment_parsing.py +++ b/tests/test_comment_parsing.py @@ -191,6 +191,33 @@ def test_unclosed_code_fence_treats_rest_as_global(self): assert "config.md: another example" in global_inst assert file_inst == {} + def test_cross_delimiter_fence_not_closed(self): + """A backtick-opened fence should not be closed by tilde delimiters.""" + comment = "[update-docs] Update the docs:\n```\n~~~\nfile.rst: add a new section\n```\n" + global_inst, file_inst = parse_update_instructions(comment) + # ~~~ should not close the backtick fence, so file.rst line + # remains inside the fence and is not a per-file instruction + assert "file.rst" not in file_inst + assert "file.rst: add a new section" in global_inst + + def test_cross_delimiter_tilde_opened_backtick_no_close(self): + """A tilde-opened fence should not be closed by backtick delimiters.""" + comment = ( + "[update-docs] see this:\n" + "~~~\n" + "```\n" + "config.rst: update settings\n" + "~~~\n" + "health.md: fix intro" + ) + global_inst, file_inst = parse_update_instructions(comment) + # ``` should not close the tilde fence, so config.rst stays global + assert "config.rst" not in file_inst + assert "config.rst: update settings" in global_inst + # health.md is after the tilde fence closes, so it is per-file + assert "health.md" in file_inst + assert file_inst["health.md"] == "fix intro" + # ── _resolve_file_instructions ───────────────────────────────────────────────