diff --git a/scripts/pr_review.py b/scripts/pr_review.py index a6748eee2..df02722cd 100755 --- a/scripts/pr_review.py +++ b/scripts/pr_review.py @@ -610,9 +610,8 @@ ) LOOSE_COUNTS = re.compile(r"reviewed\D{0,40}?(\d+)\s*(?:out of|of|/)\s*(\d+)", re.IGNORECASE) EMPHASIS_MARK = re.compile(r"[*_`]") -HTML_TAG = re.compile( - r"]+))?)*[ \t]*/?>" -) +ATTRIBUTES = r"(?:[ \t]+[\w:-]+(?:[ \t]*=[ \t]*(?:\"[^\"\n]*\"|'[^'\n]*'|[^\s\"'<>]+))?)*[ \t]*/?>" +HTML_TAG = re.compile(rf"\n]*)?/?>") @@ -876,7 +875,9 @@ def tag_opener(*names: str) -> str: rf"{tag_opener('summary')}(?:[ \t\n\f\r]+[A-Za-z][^>]*)?/?>(.*?)", re.DOTALL | re.IGNORECASE, ) -TAGS = re.compile(r"", re.IGNORECASE) +TAGS = re.compile( + rf"|{tag_opener('details', 'summary')}{ATTRIBUTES}", re.IGNORECASE +) COUNT = re.compile(r"\((\d+)\)") # A count opening a line past any opener `PREVIOUSLY_MISSED` admits, which is how `marker_blocks` tells prose. OPENING_COUNT = re.compile(rf"{HEADING_OPENER}[0-9]+[ \t]") @@ -4703,8 +4704,16 @@ def unwrap(block: str) -> str: place its findings are read. The wrapper tags say nothing, emphasis around a heading says nothing, and a severity badge says one word wrapped in two versioned icon URLs that bury the title following them. The word is kept and the markup is not. + + A `
` or `` tag a finding quotes in code is the finding's text, so it is kept. + The tags are found in a copy with every quotation masked, which keeps their offsets. """ - return TAGS.sub("", reduced(block)) + text = reduced(block) + kept, end = [], 0 + for tag in TAGS.finditer(mask_quotations(text, MARKUP_MASK)): + kept.append(text[end : tag.start()]) + end = tag.end() + return "".join(kept) + text[end:] def previously_missed_blocks(body: str) -> list[str]: diff --git a/tests/test_pr_review.py b/tests/test_pr_review.py index e66acb020..4e204a288 100755 --- a/tests/test_pr_review.py +++ b/tests/test_pr_review.py @@ -1835,6 +1835,28 @@ def test_every_opener_reader_ends_a_tag_name_where_markdown_does(self) -> None: with self.subTest(pattern=pattern.pattern, line=line): self.assertIsNotNone(pattern.search(line)) + def test_a_render_strips_an_attributed_opener_as_it_strips_a_bare_one(self) -> None: + """`TAGS` matched a bare tag alone, so `
` printed raw in the digest. + + A heading with no closer reaches `entry_heading` as its raw line, so `TAGS` reads it there. + """ + tags = ("
", '', "", "", "
") + for tag in (*tags, "
"): + with self.subTest(tag=tag): + self.assertEqual("Open (1)", pr_review.unwrap(f"{tag}Open (1)
")) + for tag in ("", "
"): + with self.subTest(tag=tag): + self.assertEqual(f"{tag}Open (1)", pr_review.unwrap(f"{tag}Open (1)
")) + self.assertEqual("Open", pr_review.unwrap("Open")) + quoted = ("Keep `
` here.", "Keep `` here.", "```\n
\n```") + split = ("The pattern `
]*>` reads.", "Write ` b`.") + prose = "The
false." + for text in (*quoted, *split, prose): + with self.subTest(text=text): + self.assertEqual(text, pr_review.unwrap(text)) + self.assertEqual("A c", pr_review.unwrap("A c")) + self.assertTrue(pr_review.entry_heading(f'{BADGE} Title')) + def test_a_heading_the_summary_reader_rejects_is_not_read_from_a_nested_entry(self) -> None: """`heading_of` asked two readers one question, so a nested entry's summary became the heading.""" block = "Open (1)\n
Nit (5)x
"