Skip to content

Commit b1614fa

Browse files
committed
fix: keep pipes and backticks in checked values from breaking the table
A commit subject is arbitrary text: a | inside a table cell ended the cell, and a backtick ended the single-backtick code span and rendered the rest of the row as markdown. The value cell now escapes pipes and grows the span delimiter past the longest backtick run in the value, with padding spaces when it starts or ends with one.
1 parent dc04ee4 commit b1614fa

2 files changed

Lines changed: 52 additions & 2 deletions

File tree

main.py

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -558,12 +558,32 @@ def _skip_count(results: list[ScopeResult]) -> int:
558558
return sum(1 for scope in results if scope.status == "skip")
559559

560560

561+
def _table_cell_code(text: str) -> str:
562+
"""Render a checked value as a code span safe for a Markdown table cell.
563+
564+
A commit subject is arbitrary text. A ``|`` inside a cell ends the cell,
565+
and a backtick would end a fixed single-backtick span, so the delimiter
566+
grows past the longest backtick run in the value instead. A value that
567+
starts or ends with a backtick additionally needs the padding spaces —
568+
CommonMark strips exactly one of each.
569+
"""
570+
escaped = text.replace("|", "\\|")
571+
delimiter = "`"
572+
while delimiter in escaped:
573+
delimiter += "`"
574+
if escaped.startswith("`") or escaped.endswith("`"):
575+
escaped = f" {escaped} "
576+
return f"{delimiter}{escaped}{delimiter}"
577+
578+
561579
def _markdown_table(results: list[ScopeResult]) -> str:
562580
"""Render the failure table shared by summary and PR comment.
563581
564582
Only failed scopes appear, so a per-row result column would read ``\u274c`` on
565583
every row and carry no information; the pass/fail picture for everything
566-
else lives in the details block.
584+
else lives in the details block. Listing every scope was tried and
585+
reverted: the table grew with the pull request, and it duplicated the
586+
details block line for line.
567587
"""
568588
rows = [
569589
"| Scope | Checked value | Failed checks |",
@@ -576,7 +596,7 @@ def _markdown_table(results: list[ScopeResult]) -> str:
576596
if scope.status != "fail":
577597
continue
578598
value = _scope_value(scope)
579-
value_display = f"`{value}`" if value else "\u2014"
599+
value_display = _table_cell_code(value) if value else "\u2014"
580600
if scope.raw_text and not scope.checks:
581601
links = "_output could not be parsed \u2014 see details_"
582602
else:
@@ -714,6 +734,10 @@ def _scope_value(scope: ScopeResult, max_len: int = 60) -> str:
714734
# the number of commits in the pull request or rules in the config.
715735
# - The table lists only failed scopes; there is no per-row result column
716736
# because it would read ❌ on every row. Passing scopes live in the details.
737+
# An all-scope table with a verdict column was tried and reverted: it grew
738+
# with the pull request and duplicated the details block line for line.
739+
# - A checked value renders as a code span with pipes escaped and the span
740+
# delimiter grown past any backtick run, so a subject cannot break the table.
717741
# - Values are capped at 60 characters with a literal "..." suffix, except on a
718742
# failing scope, where the details block prints the value in full — it is the
719743
# one value the reader has to act on and the cap can hide the reason.

main_test.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,6 +803,32 @@ def test_table_has_no_constant_result_column(self):
803803
self.assertIn("| Scope | Checked value | Failed checks |", body)
804804
self.assertNotIn("| Result |", body)
805805

806+
@staticmethod
807+
def _failing_scope_with_value(value: str) -> main.ScopeResult:
808+
return main.ScopeResult(
809+
label="Commit 1/1",
810+
checks=[make_check("message", status="fail", value=value)],
811+
)
812+
813+
def test_pipe_in_a_value_does_not_break_the_table(self):
814+
table = main._markdown_table([self._failing_scope_with_value("feat: a|b")])
815+
self.assertIn("`feat: a\\|b`", table)
816+
817+
def test_backtick_in_a_value_does_not_end_the_code_span(self):
818+
"""The span delimiter grows past the longest backtick run inside."""
819+
table = main._markdown_table(
820+
[self._failing_scope_with_value("fix: escape `quoted` text")]
821+
)
822+
self.assertIn("``fix: escape `quoted` text``", table)
823+
824+
def test_value_ending_in_a_backtick_gets_padding_spaces(self):
825+
"""CommonMark reads a backtick against the delimiter as part of it;
826+
the padding spaces keep the content and the fence apart."""
827+
table = main._markdown_table(
828+
[self._failing_scope_with_value("fix: dangling `")]
829+
)
830+
self.assertIn("`` fix: dangling ` ``", table)
831+
806832
def test_body_opens_with_hidden_marker(self):
807833
body = main.render_job_summary([pass_scope("Branch")])
808834
self.assertTrue(body.startswith(main.COMMENT_MARKER))

0 commit comments

Comments
 (0)