Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions scripts/pr_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"</?[A-Za-z][\w-]*(?:[ \t]+[\w:-]+(?:[ \t]*=[ \t]*(?:\"[^\"\n]*\"|'[^'\n]*'|[^\s\"'<>]+))?)*[ \t]*/?>"
)
ATTRIBUTES = r"(?:[ \t]+[\w:-]+(?:[ \t]*=[ \t]*(?:\"[^\"\n]*\"|'[^'\n]*'|[^\s\"'<>]+))?)*[ \t]*/?>"
HTML_TAG = re.compile(rf"</?[A-Za-z][\w-]*{ATTRIBUTES}")
MARKER_NAME = re.compile(r"fleet[\W_]{0,3}review", re.IGNORECASE)
MARKER_NAME_TIGHT = re.compile(r"fleet\W?review", re.IGNORECASE)
LINE_TAG = re.compile(r"</?[A-Za-z][A-Za-z0-9-]*(?:\s[^<>\n]*)?/?>")
Expand Down Expand Up @@ -876,7 +875,9 @@ def tag_opener(*names: str) -> str:
rf"{tag_opener('summary')}(?:[ \t\n\f\r]+[A-Za-z][^>]*)?/?>(.*?)</summary>",
re.DOTALL | re.IGNORECASE,
)
TAGS = re.compile(r"</?(?:details|summary)>", re.IGNORECASE)
TAGS = re.compile(
rf"</(?:details|summary)>|{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]")
Expand Down Expand Up @@ -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 `<details>` or `<summary>` 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]:
Expand Down
22 changes: 22 additions & 0 deletions tests/test_pr_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<details open>` printed raw in the digest.

A heading with no closer reaches `entry_heading` as its raw line, so `TAGS` reads it there.
"""
tags = ("<details open>", '<summary class="x">', "<summary/>", "<summary />", "<details >")
for tag in (*tags, "<DETAILS>"):
with self.subTest(tag=tag):
self.assertEqual("Open (1)", pr_review.unwrap(f"{tag}Open (1)</summary>"))
for tag in ("<summaryx>", "<details/x>"):
with self.subTest(tag=tag):
self.assertEqual(f"{tag}Open (1)", pr_review.unwrap(f"{tag}Open (1)</summary>"))
self.assertEqual("<details\nopen>Open", pr_review.unwrap("<details\nopen>Open"))
quoted = ("Keep `<details open>` here.", "Keep `<summary>` here.", "```\n<details>\n```")
split = ("The pattern `<details [^>]*>` reads.", "Write `<summary ` then `a > b`.")
prose = "The <details element's open attribute -> 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 <summary title='a>b'> c"))
self.assertTrue(pr_review.entry_heading(f'<summary class="x">{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 = "<summary >Open (1)</summary>\n<details><summary>Nit (5)</summary>x</details>"
Expand Down
Loading