fix(release): lint the changelog entry inside the file it lands in
The entry was linted alone, so rules that compare it with the rest of CHANGELOG.md never fired. infinito-nexus 16.0.0 passed this check with "-" bullets on top of older "*" entries, and the repository's own markdown lint then reported 400 MD004 findings and refused the push. update_changelog now also lints the merged file and reports only the findings the existing file does not already produce (lint_changelog_context), so pre-existing issues are not blamed on the entry. markdownlint-cli2 runs with --no-globs, so a target config with its own globs no longer lints the whole repository and blocks a release on unrelated files. Without markdownlint-cli2 the built-in check now requires "-" list markers (MD004 dash), the strictest style. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import shutil
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from pkgmgr.actions.release.files import changelog_lint
|
||||
from pkgmgr.actions.release.files.changelog_lint import (
|
||||
lint_changelog_context,
|
||||
lint_changelog_entry,
|
||||
transform_changelog_message,
|
||||
)
|
||||
from pkgmgr.actions.release.files.changelog_md import _insert_after_h1
|
||||
|
||||
_OLD_ASTERISK_FILE = "# Changelog\n\n## [1.0.0] - 2026-01-01\n\n* old bullet\n"
|
||||
|
||||
|
||||
class TestTransformChangelogMessage(unittest.TestCase):
|
||||
@@ -38,16 +45,42 @@ class TestTransformChangelogMessage(unittest.TestCase):
|
||||
|
||||
class TestLintChangelogEntry(unittest.TestCase):
|
||||
def test_clean_entry_has_no_findings(self) -> None:
|
||||
entry = "## [1.0.0] - 2026-01-01\n\n* a clean bullet\n\n"
|
||||
entry = "## [1.0.0] - 2026-01-01\n\n- a clean bullet\n\n"
|
||||
self.assertEqual(lint_changelog_entry("CHANGELOG.md", entry), [])
|
||||
|
||||
def test_a_structured_message_survives_its_own_transform(self) -> None:
|
||||
body = transform_changelog_message(
|
||||
"# Security\n\n* a bullet\n\n## Fixed\n\n* another bullet"
|
||||
"# Security\n\n- a bullet\n\n## Fixed\n\n- another bullet"
|
||||
)
|
||||
entry = f"## [1.0.0] - 2026-01-01\n\n{body}\n\n"
|
||||
self.assertEqual(lint_changelog_entry("CHANGELOG.md", entry), [])
|
||||
|
||||
def test_the_builtin_check_refuses_a_non_dash_list_marker(self) -> None:
|
||||
entry = "## [1.0.0] - 2026-01-01\n\n* a bullet\n\n"
|
||||
with patch.object(changelog_lint.shutil, "which", return_value=None):
|
||||
findings = lint_changelog_entry("CHANGELOG.md", entry)
|
||||
self.assertTrue(any("MD004" in finding for finding in findings), findings)
|
||||
|
||||
|
||||
@unittest.skipUnless(shutil.which("markdownlint-cli2"), "markdownlint-cli2 is not installed")
|
||||
class TestLintChangelogContext(unittest.TestCase):
|
||||
def test_a_marker_that_breaks_the_files_consistency_is_reported(self) -> None:
|
||||
entry = "## [1.1.0] - 2026-02-01\n\n- new bullet\n\n"
|
||||
merged = _insert_after_h1(_OLD_ASTERISK_FILE, entry)
|
||||
findings = lint_changelog_context("CHANGELOG.md", _OLD_ASTERISK_FILE, merged)
|
||||
self.assertTrue(any("MD004" in finding for finding in findings), findings)
|
||||
|
||||
def test_a_finding_the_file_already_has_is_not_blamed_on_the_entry(self) -> None:
|
||||
existing = "# Changelog\n\n## [1.0.0] - 2026-01-01\n\n* old bullet \n"
|
||||
entry = "## [1.1.0] - 2026-02-01\n\n* new bullet\n\n"
|
||||
merged = _insert_after_h1(existing, entry)
|
||||
self.assertEqual(lint_changelog_context("CHANGELOG.md", existing, merged), [])
|
||||
|
||||
def test_a_consistent_entry_has_no_findings(self) -> None:
|
||||
entry = "## [1.1.0] - 2026-02-01\n\n* new bullet\n\n"
|
||||
merged = _insert_after_h1(_OLD_ASTERISK_FILE, entry)
|
||||
self.assertEqual(lint_changelog_context("CHANGELOG.md", _OLD_ASTERISK_FILE, merged), [])
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
unittest.main()
|
||||
|
||||
@@ -313,13 +313,13 @@ class TestUpdateChangelog(unittest.TestCase):
|
||||
def test_update_changelog_does_not_inject_a_leading_bullet(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
path = os.path.join(tmpdir, "CHANGELOG.md")
|
||||
update_changelog(path, "1.2.3", message="* Provided bullet", preview=False)
|
||||
update_changelog(path, "1.2.3", message="- Provided bullet", preview=False)
|
||||
|
||||
with open(path, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
self.assertIn("\n\n* Provided bullet\n", content)
|
||||
self.assertNotIn("* * Provided bullet", content)
|
||||
self.assertIn("\n\n- Provided bullet\n", content)
|
||||
self.assertNotIn("- - Provided bullet", content)
|
||||
|
||||
def test_update_changelog_transforms_heading_and_inline_code(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
@@ -327,7 +327,7 @@ class TestUpdateChangelog(unittest.TestCase):
|
||||
update_changelog(
|
||||
path,
|
||||
"1.2.3",
|
||||
message="# Summary\n\n* uses `foo` tool",
|
||||
message="# Summary\n\n- uses `foo` tool",
|
||||
preview=False,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user