From f9ff5f9aeeade5cdf049f3a6ec75c8b896f90566 Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Tue, 18 Aug 2026 14:17:17 +0700 Subject: [PATCH] License a fence the source opened _FENCE was the only guard in textguard with no licence. The reason given was that the corpus has literal blocks but a protected one reaches the module as a placeholder and never as three backticks, and that is true of the translate path and not of the audit. L03 hands over the raw msgstr and the raw msgid with nothing protected, so an entry whose msgid is --- arrived as --- and was read as a model drawing a rule under its answer. P08 had a second version of the same bug. Every other rule in placeholders.py reads the pair and that one read the translation alone, so it reported the same entries for the same wrong reason. Two entries in the corpus are affected and both are correctly copied non-prose: a literal --- in c-api/call.po and the class inheritance diagram in howto/mro.po, which opens with a line of dashes because it is a picture. Four findings over two hard checks, both now 0 on the real corpus. test_a_fence_is_never_licensed asserted the old behaviour and is replaced by three tests rather than deleted: a fence the source did not open is still narration, a fence the source did open is licensed like every other phrase, and passing no source still licenses nothing. --- src/pydocvi/audit/placeholders.py | 12 ++++++++++-- src/pydocvi/textguard.py | 17 +++++++++++++---- tests/test_audit_placeholders.py | 10 ++++++++++ tests/test_textguard.py | 21 +++++++++++++++++---- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/pydocvi/audit/placeholders.py b/src/pydocvi/audit/placeholders.py index 03ab9bd..1c37ca0 100644 --- a/src/pydocvi/audit/placeholders.py +++ b/src/pydocvi/audit/placeholders.py @@ -244,13 +244,21 @@ def p07_code_is_byte_identical(corpus: Corpus) -> Iterator[Finding]: @check("P08", Group.PLACEHOLDERS, hard=True, title="no fence or horizontal rule") def p08_no_fence(corpus: Corpus) -> Iterator[Finding]: - """No ``msgstr`` opens a fence or a ``---``. + """No ``msgstr`` opens a fence or a ``---`` that its ``msgid`` did not. Both are a model formatting its answer rather than answering, and both are valid reST that renders as something the English does not. + + Unless the English opened the same way, which is the clause this check was + missing. Every other rule in this module reads the pair, and this one read + the translation alone, so a ``msgid`` of ``---`` copied through as ``---`` + was reported as a model drawing a rule under its answer. Two entries in the + corpus are that: a literal ``---`` in ``c-api/call.po`` and the inheritance + diagram in ``howto/mro.po``, which opens with a line of dashes because it is + a picture of a class hierarchy. """ for one, entry in corpus.translated(): - if _FENCE.match(entry.msgstr): + if _FENCE.match(entry.msgstr) and not _FENCE.match(entry.msgid): yield Finding( check="P08", path=corpus.relative(one.path), diff --git a/src/pydocvi/textguard.py b/src/pydocvi/textguard.py index 33078a6..68c6cda 100644 --- a/src/pydocvi/textguard.py +++ b/src/pydocvi/textguard.py @@ -78,9 +78,18 @@ class _Guard: ) #: A fence or a rule opening the answer. The model presenting its work rather -#: than doing it, which is the same failure wearing different clothes. No -#: licence: the corpus has literal blocks, but a protected one reaches this -#: module as a placeholder and never as three backticks. +#: than doing it, which is the same failure wearing different clothes. +#: +#: Licensed by the source opening the same way, which it did not used to be. The +#: reasoning for having no licence was that the corpus has literal blocks but a +#: protected one reaches this module as a placeholder and never as three +#: backticks, and that is true of the translate path and not of the audit. +#: ``L03`` hands over the raw ``msgstr`` and the raw ``msgid``, with nothing +#: protected, so an entry whose ``msgid`` is ``---`` arrived here as ``---`` and +#: was read as a model drawing a rule. Two entries in the corpus are exactly +#: that: a literal ``---`` in ``c-api/call.po`` and the inheritance diagram in +#: ``howto/mro.po``, both copied through correctly and both reported as +#: narration by the check that exists to catch a model talking. _FENCE = re.compile(r"^\s*(?:```|~~~|---)") #: A parenthetical aside about the translation itself, in either language. @@ -116,7 +125,7 @@ def find(text: str, source: str = "") -> Narration | None: without making the decision any different. """ fence = _FENCE.match(text) - if fence: + if fence and not _FENCE.match(source): return Narration(phrase=fence.group(0).strip(), where=0) hits = [ diff --git a/tests/test_audit_placeholders.py b/tests/test_audit_placeholders.py index 0499a2b..683d536 100644 --- a/tests/test_audit_placeholders.py +++ b/tests/test_audit_placeholders.py @@ -173,6 +173,16 @@ def test_an_ordinary_translation_is_clean(self) -> None: corpus = over("Return a list.", "Trả về một danh sách.") assert findings(placeholders.p08_no_fence, corpus) == [] + def test_a_rule_the_english_opened_is_not_the_model_drawing_one(self) -> None: + """Every other rule in this module reads the pair and this one read the + translation alone, so a ``msgid`` of ``---`` copied through as ``---`` + was reported. Two entries in the corpus are that.""" + assert findings(placeholders.p08_no_fence, over("---", "---")) == [] + + def test_a_diagram_that_opens_with_dashes_survives_being_copied(self) -> None: + art = " -----------\n| O |\n| / \\ |" + assert findings(placeholders.p08_no_fence, over(art, art)) == [] + class TestEveryCheckReportsWhereItLooked: def test_a_finding_carries_the_file_the_line_and_the_segment(self) -> None: diff --git a/tests/test_textguard.py b/tests/test_textguard.py index 3c062d8..d318093 100644 --- a/tests/test_textguard.py +++ b/tests/test_textguard.py @@ -116,10 +116,23 @@ def test_a_phrase_with_no_licence_fires_however_the_source_reads(self) -> None: documentation does not say "as an AI", so nothing can excuse it.""" assert not textguard.clean("As an AI, I note this.", "As an AI system would note that.") - def test_a_fence_is_never_licensed(self) -> None: - """A literal block reaches this module as a placeholder, never as three - backticks, so a fence in an answer is always the model presenting.""" - assert not textguard.clean("```\nTrả về danh sách.\n```", "```\nsorted(x)\n```") + def test_a_fence_the_source_did_not_open_is_the_model_presenting(self) -> None: + assert not textguard.clean("```\nTrả về danh sách.\n```", "Return a list.") + + def test_a_fence_the_source_opened_is_licensed_like_any_other_phrase(self) -> None: + """This asserted the opposite until August 2026, on the grounds that a + literal block reaches this module as a placeholder and never as three + backticks. True of the translate path and not of the audit, which hands + over the raw pair: ``L03`` reported the ``---`` in ``c-api/call.po`` and + the class diagram in ``howto/mro.po`` as a model drawing a rule, when + both are the English copied through exactly.""" + assert textguard.clean("---", "---") + assert textguard.clean(" ----\n| O |", " ----\n| O |") + + def test_a_fence_with_no_source_is_still_narration(self) -> None: + """Passing no source licenses nothing, which is the right answer for a + caller holding a string and no idea what it was made from.""" + assert not textguard.clean("---\nTrả về danh sách.") def test_the_leftmost_phrase_is_the_one_reported_not_the_first_listed(self) -> None: found = textguard.find("Trả về danh sách. Xin lỗi. Here is the translation.")