Include autolink text in Markdown heading anchors

This commit is contained in:
2026-10-08 01:31:18 +00:00
parent 1eb1cc6491
commit 95599e96f6
4 changed files with 21 additions and 5 deletions

View File

@@ -1,7 +1,6 @@
# ReleaseKit implementation remediation # ReleaseKit implementation remediation
Status: Three open v0.2.0 findings (7–9). The six foundation findings (1–6) Status: Two open v0.2.0 findings (8–9). Findings 1–7 are addressed.
are addressed.
The v0.2.0 review covers committed state `edd670e` and the implementation The v0.2.0 review covers committed state `edd670e` and the implementation
commits after `89981ab`, against the [implementation plan](implementation.md), commits after `89981ab`, against the [implementation plan](implementation.md),
@@ -295,6 +294,11 @@ check, arbitrary inspection limit, or change to source-publication completion.
## 7. P2: Heading anchors omit autolink display text ## 7. P2: Heading anchors omit autolink display text
**Resolution:** Addressed. Heading extraction includes autolink display labels.
Parser coverage includes URL/email headings and surrounding text; committed
fragment checks cover both forms. The new cases failed before the fix, and
`go test ./internal/validation` passed afterward.
**Owner:** [Markdown heading extraction](../../internal/validation/markdown.go), **Owner:** [Markdown heading extraction](../../internal/validation/markdown.go),
`markdownHeadingText`, lines 115–139. `markdownHeadingText`, lines 115–139.

View File

@@ -128,6 +128,9 @@ func markdownHeadingText(heading *ast.Heading, source []byte) string {
case *ast.CodeSpan: case *ast.CodeSpan:
result.WriteString(node.Value.Value(source)) result.WriteString(node.Value.Value(source))
return return
case *ast.AutoLink:
result.WriteString(node.Label.Value(source))
return
} }
for child := node.FirstChild(); child != nil; child = child.NextSibling() { for child := node.FirstChild(); child != nil; child = child.NextSibling() {
appendNode(child) appendNode(child)

View File

@@ -16,6 +16,8 @@ func TestCheckMarkdownLinksResolvesCommittedDestinationsAndFragments(t *testing.
"# Start\n" + "# Start\n" +
"[target](target.md?view=1#TARGET)\n" + "[target](target.md?view=1#TARGET)\n" +
"[same](#START)\n" + "[same](#START)\n" +
"[url heading](autolinks.md#httpsexampleinvalid)\n" +
"[email heading](autolinks.md#maintainerexampleinvalid)\n" +
"[top](#)\n" + "[top](#)\n" +
"[directory](assets/?view=1)\n" + "[directory](assets/?view=1)\n" +
"[binary](image.bin#not-an-anchor)\n" + "[binary](image.bin#not-an-anchor)\n" +
@@ -26,6 +28,7 @@ func TestCheckMarkdownLinksResolvesCommittedDestinationsAndFragments(t *testing.
repository, tree := committedMarkdownTree(t, map[string][]byte{ repository, tree := committedMarkdownTree(t, map[string][]byte{
"guide.md": guide, "guide.md": guide,
"target.md": []byte("# Target\n"), "target.md": []byte("# Target\n"),
"autolinks.md": []byte("# <https://example.invalid>\n# <maintainer@example.invalid>\n"),
"docs/a b.md": []byte("# Café\n"), "docs/a b.md": []byte("# Café\n"),
"assets/file.md": []byte("# Asset\n"), "assets/file.md": []byte("# Asset\n"),
"nested/guide.md": []byte("# Nested\n[same](#NESTED)\n[query](?view=1#nested)\n"), "nested/guide.md": []byte("# Nested\n[same](#NESTED)\n[query](?view=1#nested)\n"),

View File

@@ -61,7 +61,10 @@ func TestParseMarkdownHeadingAnchors(t *testing.T) {
"# Foo\n" + "# Foo\n" +
"# Foo-1\n" + "# Foo-1\n" +
"# Foo\n" + "# Foo\n" +
"# Foo-1\n") "# Foo-1\n" +
"# <https://example.invalid>\n" +
"# <maintainer@example.invalid>\n" +
"# Contact <maintainer@example.invalid> Today\n")
document := parseMarkdown(source) document := parseMarkdown(source)
for _, anchor := range []string{ for _, anchor := range []string{
"café--東京-code-here-bold", "café--東京-code-here-bold",
@@ -71,13 +74,16 @@ func TestParseMarkdownHeadingAnchors(t *testing.T) {
"foo-1-1", "foo-1-1",
"foo-2", "foo-2",
"foo-1-2", "foo-1-2",
"httpsexampleinvalid",
"maintainerexampleinvalid",
"contact-maintainerexampleinvalid-today",
} { } {
if _, ok := document.anchors[anchor]; !ok { if _, ok := document.anchors[anchor]; !ok {
t.Errorf("missing anchor %q in %#v", anchor, document.anchors) t.Errorf("missing anchor %q in %#v", anchor, document.anchors)
} }
} }
if len(document.anchors) != 7 { if len(document.anchors) != 10 {
t.Fatalf("found %d anchors, want 7: %#v", len(document.anchors), document.anchors) t.Fatalf("found %d anchors, want 10: %#v", len(document.anchors), document.anchors)
} }
} }