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
2 changes: 1 addition & 1 deletion scripts/pr_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -2941,7 +2941,7 @@ def heading_of(block: str) -> str:
# `<details>(.*?)</details>` lazily pairs each open with the *next* close, which is the innermost one once a shape nests, silently losing everything the outer wrapper still carries after it.
# CodeRabbit's outside-diff section does exactly that: a file wrapper nested inside the section heading, itself wrapping a per-finding "Prompt for AI Agents" block three levels deep.
DETAILS_TAG = re.compile(
r"<details(?:[ \t\n\f\r][^>]*|/)?>|</details[ \t]*>"
r"<details(?:[ \t\n\f\r][^<>]*|/)?>|</details[ \t]*>"
r"|(?<![^\n\r])</details(?:[ \t][^<>\n\r\v\f\x1c\x1d\x1e\x85\u2028\u2029]*|/)>",
re.IGNORECASE,
)
Expand Down
13 changes: 12 additions & 1 deletion tests/test_pr_review.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ def flowed_text(text: str) -> str:
# The shape 28 of the 333 measured bodies carry: an overview, and no count of what was read.
# A body of no text at all is not one of the shapes, and the reader now says so, correctly.
OVERVIEW = "## Pull request overview\n\nThe change is narrow.\n"
# The wall-clock ceiling the two backtracking cases read, stated once so they cannot drift apart.
# The wall-clock ceiling the backtracking cases read, stated once so they cannot drift apart.
# Each case sizes its own input so that a regression finishes and trips this rather than running long enough to hang the suite.
# Three seconds against readings of microseconds and milliseconds, which is the margin that makes a loaded runner a non-issue rather than the clock being trusted.
BACKTRACK_BOUND = 3.0
Expand Down Expand Up @@ -3347,6 +3347,7 @@ def test_a_tag_is_bounded_by_html_s_own_syntax(self) -> None:
"""A tail reaching another line or another tag swallowed the opener or heading after it."""
cases = {
"</details x <details>": ["<details>"],
"<details x <details>": ["<details>"],
"</details x\n### Suppressed comments (1)\n>": [],
"</details\v>": [],
"<details\v>": [],
Expand All @@ -3359,6 +3360,16 @@ def test_a_tag_is_bounded_by_html_s_own_syntax(self) -> None:
found = [m.group() for m in pr_review.DETAILS_TAG.finditer(text)]
self.assertEqual(tags, found)

def test_a_body_of_unclosed_openers_is_read_in_linear_time(self) -> None:
"""An opener with no closing `>` ends its scan at the next `<`, so a body of such openers reads in linear time."""
body = "<details x\n" * 9000
start = time.monotonic()
self.assertEqual([], pr_review.details_spans(body)[0])
self.assertEqual(body, pr_review.mask_narrative(body))
pr_review.unrecognized_in(body)
self.assertEqual([], pr_review.marker_blocks(body, pr_review.SUPPRESSED))
self.assertLess(time.monotonic() - start, BACKTRACK_BOUND)

def test_a_region_ends_at_a_close_in_another_html_spelling(self) -> None:
"""The section readers pair on the same tags, so their regions end where HTML ends them."""
body = "<details>\na\n</details >\nb\n<details>\nc\n</details>\n"
Expand Down
Loading