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
42 changes: 42 additions & 0 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,47 @@ def get_pr_opener_tool() -> str | None:
return AI_AGENT_BOTS.get(str(login).lower())


def lines_removed(text: str, fixed: str) -> list[str]:
"""The non-blank lines of ``text`` that ``fixed`` no longer has, in order.

A line repeated in ``text`` counts once per copy ``fixed`` dropped.
"""
kept: dict[str, int] = {}
for line in fixed.splitlines():
kept[line] = kept.get(line, 0) + 1
removed = []
for line in text.splitlines():
if kept.get(line, 0):
kept[line] -= 1
elif line.strip():
removed.append(line.strip())
return removed


def name_the_line_changes(scope: ScopeResult, body: str) -> None:
"""Name the lines to change instead of repeating the description.

For a message the CLI's ``fix`` is the corrected text, short enough to
paste. A description can run to a page, and the summary, the comment and
the annotation would each repeat it in full; the lines to delete or add
are what its author needs.
"""
for check in scope.checks:
fixed = check.get("fix") or ""
if not fixed:
continue
removed = lines_removed(body, fixed)
added = lines_removed(fixed, body)
parts = []
if removed:
parts.append("remove " + "; ".join(f'"{line}"' for line in removed))
if added:
parts.append("add " + "; ".join(f'"{line}"' for line in added))
if parts:
check["suggest"] = "In the description, " + ", and ".join(parts)
check["fix"] = ""


#: The AI tools found in the pull request description when it was read for
#: the label only (``ai-label`` without ``pr-body-ai-attribution``), so it is no scope.
PR_BODY_AI_TOOLS: list[str] = []
Expand Down Expand Up @@ -814,6 +855,7 @@ def run_commit_check() -> tuple[int, list[ScopeResult]]:
"PR description", ["--ai-attribution-only"], input_text=pr_body
)
if PR_BODY_AI_ATTRIBUTION_ENABLED:
name_the_line_changes(scope, pr_body)
results.append(scope)
else:
PR_BODY_AI_TOOLS[:] = scope.ai_tools
Expand Down
83 changes: 83 additions & 0 deletions main_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -3737,6 +3737,89 @@ def test_no_description_nothing_to_check(self):
self.assertEqual(results, [])


class TestDescriptionLineChanges(unittest.TestCase):
BODY = "## Summary\nAdds a parser.\n\nLink to Devin run: https://app.devin.ai/sessions/1\n"

def _scope(self, *checks):
return main.ScopeResult(label="PR description", checks=list(checks))

def test_lines_removed_keeps_order_and_counts_copies(self):
self.assertEqual(main.lines_removed("a\nb\na\n\nc", "a\nc"), ["b", "a"])

def test_forbid_names_the_line_to_delete(self):
scope = self._scope(
{
"rule_id": "CC013",
"suggest": "Remove the AI lines.",
"fix": "## Summary\nAdds a parser.",
}
)
main.name_the_line_changes(scope, self.BODY)
self.assertEqual(
scope.checks[0]["suggest"],
'In the description, remove "Link to Devin run: https://app.devin.ai/sessions/1"',
)
self.assertEqual(scope.checks[0]["fix"], "")

def test_disclose_names_the_line_to_add_and_the_one_it_replaces(self):
body = "Adds a parser.\n\nCo-Authored-By: Claude <noreply@anthropic.com>"
scope = self._scope(
{
"rule_id": "CC015",
"suggest": "x",
"fix": "Adds a parser.\n\nAssisted-by: Claude",
},
{"rule_id": "CC014", "suggest": "x", "fix": body + "\nAssisted-by: Claude"},
)
main.name_the_line_changes(scope, body)
self.assertEqual(
[c["suggest"] for c in scope.checks],
[
(
'In the description, remove "Co-Authored-By: Claude'
' <noreply@anthropic.com>", and add "Assisted-by: Claude"'
),
'In the description, add "Assisted-by: Claude"',
],
)

def test_a_check_without_a_fix_or_a_change_is_left_alone(self):
scope = self._scope(
{"rule_id": "CC013", "suggest": "s", "fix": ""},
{"rule_id": "CC013", "suggest": "s", "fix": "same"},
)
main.name_the_line_changes(scope, "same")
self.assertEqual(
scope.checks,
[
{"rule_id": "CC013", "suggest": "s", "fix": ""},
{"rule_id": "CC013", "suggest": "s", "fix": "same"},
],
)

def test_run_names_the_lines_for_a_reported_description(self):
scope = self._scope(
{
"rule_id": "CC013",
"status": "fail",
"suggest": "s",
"fix": "## Summary\nAdds a parser.",
}
)
with (
patch("main.PR_TITLE_ENABLED", False),
patch("main.PR_BODY_AI_ATTRIBUTION_ENABLED", True),
patch.dict(os.environ, {"AI_LABEL": "false"}),
patch("main.MESSAGE_ENABLED", False),
patch("main.is_pr_event", return_value=True),
patch("main.get_pr_body", return_value=self.BODY),
patch("main.check_scope", return_value=scope),
patch("main.run_other_checks", return_value=[]),
):
_rc, results = main.run_commit_check()
self.assertEqual(results[0].checks[0]["fix"], "")


class TestAiToolsFound(unittest.TestCase):
def setUp(self):
main.PR_BODY_AI_TOOLS.clear()
Expand Down
Loading