From ed7f5833abb21fc6f466d18222f758dad012b0f7 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sun, 25 Oct 2026 12:10:07 +0000 Subject: [PATCH 1/4] feat(tools): tell a missing pull request template heading from an empty section The pull request template check reported 'section What is missing or empty' both when the body had no '### What' heading and when the heading was there with nothing under it, without saying which heading it looks for. Report 'add the ### What heading of the template' when the heading is absent and 'fill the ### What section' when it is empty, for What, Tested and Checklist. --- tools/check_conventions.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/check_conventions.py b/tools/check_conventions.py index 9e95eb4bf6..e31e1ee2ca 100644 --- a/tools/check_conventions.py +++ b/tools/check_conventions.py @@ -401,8 +401,10 @@ class Check: body = re.sub(r"", "", body or "", flags=re.S) sections = dict(zip(SECTION.findall(body), SECTION.split(body)[2::2])) for name in ("What", "Tested", "Checklist"): - if not sections.get(name, "").strip(): - self.report("pr-template", "", 0, f"section '{name}' is missing or empty") + if name not in sections: + self.report("pr-template", "", 0, f"add the '### {name}' heading of the template") + elif not sections[name].strip(): + self.report("pr-template", "", 0, f"fill the '### {name}' section") checklist = sections.get("Checklist", "") for item in re.findall(r"^\s*[-*+]\s*\[ \]\s*(.+)$", checklist, re.M): self.report("pr-template", "", 0, f"unchecked: {item.strip()}") From 3772994a616cd6bd76315ff54b66225170ffaaf2 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sun, 25 Oct 2026 12:11:03 +0000 Subject: [PATCH 2/4] test(tools): cover missing and empty pull request template sections Expect 'fill the ### What section' for the untouched template and for sections that are blank or only hold a comment, and 'add the ### What heading of the template' for a heading written at another level. Add a body that names its sections without headings, the shape that failed #10721 at first, and expect one finding per missing heading. --- .../tests/test_check_conventions_pr_template.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tools/tests/test_check_conventions_pr_template.py b/tools/tests/test_check_conventions_pr_template.py index 51412f21dd..007ad22f7e 100644 --- a/tools/tests/test_check_conventions_pr_template.py +++ b/tools/tests/test_check_conventions_pr_template.py @@ -71,8 +71,8 @@ class PullRequestTemplateTests(unittest.TestCase): self.assertTrue(items) result, findings = self.run_checker(template) self.assertEqual(result.returncode, 1, result.stdout) - self.assertIn("section 'What' is missing or empty", findings) - self.assertIn("section 'Tested' is missing or empty", findings) + self.assertIn("fill the '### What' section", findings) + self.assertIn("fill the '### Tested' section", findings) for item in items: self.assertIn(f"unchecked: {item.strip()}", findings) self.assertIn("answer AI-assisted with yes or no", findings) @@ -109,9 +109,15 @@ class PullRequestTemplateTests(unittest.TestCase): def test_sections_must_have_content(self): checklist = "- [x] Depends on: none\n- [x] AI-assisted: yes\n" - self.assert_findings(body(checklist, what=""), ["section 'What' is missing or empty"]) - self.assert_findings(body(checklist, tested=" \n"), ["section 'Tested' is missing or empty"]) - self.assert_findings(body(checklist).replace("### What", "#### What"), ["section 'What' is missing or empty"]) + self.assert_findings(body(checklist, what=""), ["fill the '### What' section"]) + self.assert_findings(body(checklist, tested=" \n"), ["fill the '### Tested' section"]) + self.assert_findings(body(checklist).replace("### What", "#### What"), ["add the '### What' heading of the template"]) + + def test_a_body_without_template_headings_names_each_heading(self): + text = "What\nFix x.\n\nTested\nNot tested.\n\nChecklist\n- [x] Depends on: none\n- [x] AI-assisted: yes\n" + self.assert_findings(text, ["add the '### What' heading of the template", + "add the '### Tested' heading of the template", + "add the '### Checklist' heading of the template"]) def test_title_must_be_conventional(self): text = body("- [x] Depends on: none\n- [x] AI-assisted: yes\n") From f32db390de4acf116a3a2c85affe137565596273 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sun, 25 Oct 2026 12:11:28 +0000 Subject: [PATCH 3/4] feat(tools): name a template heading written at another level or case The template check only reads '### Name' headings, so '## What', '#### Tested' or '### checklist' counted as missing, and the finding did not say why. When a section is missing, look for the same name as a heading at any level and in any case, and add it to the finding, as in "add the '### What' heading of the template; found '## What'". --- tools/check_conventions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/check_conventions.py b/tools/check_conventions.py index e31e1ee2ca..a6a32ea883 100644 --- a/tools/check_conventions.py +++ b/tools/check_conventions.py @@ -402,7 +402,9 @@ class Check: sections = dict(zip(SECTION.findall(body), SECTION.split(body)[2::2])) for name in ("What", "Tested", "Checklist"): if name not in sections: - self.report("pr-template", "", 0, f"add the '### {name}' heading of the template") + found = re.search(rf"^[ \t]*#+[ \t]*{name}[ \t]*$", body, re.M | re.I) + hint = f"; found '{found.group(0).strip()}'" if found else "" + self.report("pr-template", "", 0, f"add the '### {name}' heading of the template{hint}") elif not sections[name].strip(): self.report("pr-template", "", 0, f"fill the '### {name}' section") checklist = sections.get("Checklist", "") From cc69a95ccf98d21931885b3be3b127d41f769022 Mon Sep 17 00:00:00 2001 From: MarcZX10 <67475268+MarcZX10@users.noreply.github.com> Date: Sun, 25 Oct 2026 12:11:40 +0000 Subject: [PATCH 4/4] test(tools): cover template headings at another level or case Expect the found heading in the finding for '## What', an indented '### what', '#WHAT' and the existing '#### What' case, and no hint for a different word such as '### Whatever'. --- tools/tests/test_check_conventions_pr_template.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tools/tests/test_check_conventions_pr_template.py b/tools/tests/test_check_conventions_pr_template.py index 007ad22f7e..35ff674f59 100644 --- a/tools/tests/test_check_conventions_pr_template.py +++ b/tools/tests/test_check_conventions_pr_template.py @@ -111,7 +111,20 @@ class PullRequestTemplateTests(unittest.TestCase): checklist = "- [x] Depends on: none\n- [x] AI-assisted: yes\n" self.assert_findings(body(checklist, what=""), ["fill the '### What' section"]) self.assert_findings(body(checklist, tested=" \n"), ["fill the '### Tested' section"]) - self.assert_findings(body(checklist).replace("### What", "#### What"), ["add the '### What' heading of the template"]) + self.assert_findings(body(checklist).replace("### What", "#### What"), + ["add the '### What' heading of the template; found '#### What'"]) + + def test_a_heading_at_another_level_or_case_is_named(self): + checklist = "- [x] Depends on: none\n- [x] AI-assisted: yes\n" + cases = { + "## What": "add the '### What' heading of the template; found '## What'", + " ### what ": "add the '### What' heading of the template; found '### what'", + "#WHAT": "add the '### What' heading of the template; found '#WHAT'", + } + for heading, finding in cases.items(): + with self.subTest(heading=heading): + self.assert_findings(body(checklist).replace("### What", heading), [finding]) + self.assert_findings(body(checklist).replace("### What", "### Whatever"), ["add the '### What' heading of the template"]) def test_a_body_without_template_headings_names_each_heading(self): text = "What\nFix x.\n\nTested\nNot tested.\n\nChecklist\n- [x] Depends on: none\n- [x] AI-assisted: yes\n"