diff --git a/.github/actions/prose-gate/prose_lint.py b/.github/actions/prose-gate/prose_lint.py index 4e2896d18..d6802fd79 100755 --- a/.github/actions/prose-gate/prose_lint.py +++ b/.github/actions/prose-gate/prose_lint.py @@ -2371,8 +2371,9 @@ def check_file(path: Path, rules: set[str], root: Path | None = None) -> list[tu # Both prose rules are Markdown-only until a comment can be told from code. # A shell script carries 78 statement separators that are not prose at all. if path.suffix == ".md": + unquoted = QUOTE_PREFIX.sub(lambda m: " " * len(m.group()), prose, count=1) if "semicolon" in rules: - for span in list_spans(prose): + for span in list_spans(unquoted): # The sentence is the unit, since the list an exemption protects lives in one. # Judged over a whole bullet, one colon exempted every semicolon after it. for sentence in sentences(span): @@ -2388,7 +2389,6 @@ def check_file(path: Path, rules: set[str], root: Path | None = None) -> list[tu (i, "semicolon", "semicolon in prose -> a comma or two sentences") ) if "dash" in rules: - unquoted = QUOTE_PREFIX.sub(lambda m: " " * len(m.group()), prose, count=1) skip = LABEL_DASH.match(unquoted) for m in DASH.finditer(unquoted): if skip and m.start() < skip.end(): diff --git a/tests/test_prose_lint.py b/tests/test_prose_lint.py index 809686bb7..fd46df6c5 100755 --- a/tests/test_prose_lint.py +++ b/tests/test_prose_lint.py @@ -519,6 +519,36 @@ def test_a_bullet_label_colon_inside_the_emphasis_is_the_same_opener(self) -> No ), ) + def test_a_blockquoted_bullet_label_colon_does_not_announce_a_list(self) -> None: + """A callout's `>` is structure, so its bullet is judged as the same bullet unquoted.""" + for text in ( + "- **Label**: one, two; three\n", + "> - **Label**: one, two; three\n", + ">- **Label**: one, two; three\n", + "> > - **Label**: one, two; three\n", + "> 1. **Label:** one, two; three\n", + ): + with self.subTest(text=text.strip()): + self.assertEqual(["semicolon"], self.kinds(text, {"semicolon"})) + + def test_a_blockquoted_line_keeps_the_exemptions_it_has_unquoted(self) -> None: + """Only the prefix is blanked, so a list in a callout keeps its semicolons.""" + self.assertEqual( + [], self.kinds("> Inputs: a, b, and c; outputs: d and e.\n", {"semicolon"}) + ) + + def test_a_blockquoted_table_row_judges_each_cell_alone(self) -> None: + """A row inside a callout is still a record of fields rather than one sentence. + + Read whole, the first cell's colon and the last cell's comma made the row a list. + """ + for text in ( + "| S1: x | it runs; it gates | D1, D2 |\n", + "> | S1: x | it runs; it gates | D1, D2 |\n", + ): + with self.subTest(text=text.strip()): + self.assertEqual(["semicolon"], self.kinds(text, {"semicolon"})) + def test_an_abbreviation_does_not_end_a_sentence(self) -> None: """Splitting at `e.g.` cuts a list in half and flags the separator the exemption protects.""" self.assertEqual(