Check for existing issues
Environment
- Vale 3.15.1
- Asciidoctor 2.0.26, resolved at
/opt/homebrew/bin/asciidoctor
- Ruby 2.6.10p210
- macOS 26.5 on arm64
- Observed on 2026-06-19
Describe the bug / provide steps to reproduce it
Rules carrying scope: heading stop matching most section titles in an AsciiDoc file once that file combines explicit block anchors with xref: inline macros. The result is a silent false negative: a heading that violates the rule passes, because Vale never applies the rule to it.
Below is a self-contained reproduction, the expected and actual output, and two controls that each remove one half of the trigger and restore correct matching. The closing section relates this to the earlier issue #1101.
Summary
A heading-scoped rule matches only a subset of section titles when both of these constructs are present in the same document:
- an explicit block anchor
[[id]] on the line directly above a section title, and
- an
xref: inline macro anywhere in the section bodies.
Removing either construct restores matching on every heading. Asciidoctor parses the same file with zero warnings, so the markup is valid and the defect sits in Vale's source-line mapping rather than in the document. A clean Vale run therefore does not prove that the headings conform.
Minimal reproduction
Create three files in an empty directory.
.vale.ini:
StylesPath = .vale/styles
[*.adoc]
BasedOnStyles = English
.vale/styles/English/TitleCase.yml:
extends: capitalization
message: "'%s' should use title-style capitalization."
level: error
scope: heading
match: $title
repro.adoc:
= Document
[[intro]]
== first heading lowercase
This paragraph links to xref:#intro[].
[[middle]]
== second heading lowercase
This paragraph links to xref:#intro[].
[[final]]
== third heading lowercase
This paragraph links to xref:#intro[].
Run:
Expected behavior
The rule carries scope: heading and each section title is lowercased, so Vale flags all three section titles. The expected output names lines 4, 9, and 14:
4:4 error 'first heading lowercase' should use title-style capitalization. English.TitleCase
9:4 error 'second heading lowercase' should use title-style capitalization. English.TitleCase
14:4 error 'third heading lowercase' should use title-style capitalization. English.TitleCase
Actual behavior
Vale flags only the first section title and drops the other two:
4:4 error 'first heading lowercase' should use title-style capitalization. English.TitleCase
✖ 1 error, 0 warnings and 0 suggestions in 1 file.
The section titles at lines 9 and 14 receive no heading-scoped match. Vale reports no error and exits as if those titles conform.
The trigger is the pair of constructs
Two controls each remove one half of the trigger from repro.adoc. Each control restores matching on all three section titles.
Control one removes the xref: macros and keeps the anchors. Write the result to ctrl_noxref.adoc and lint it. All three section titles are flagged:
$ sed 's/ to xref:#[a-z]*\[\]//' repro.adoc > ctrl_noxref.adoc
$ vale ctrl_noxref.adoc
4:4 error 'first heading lowercase' should use title-style capitalization. English.TitleCase
9:4 error 'second heading lowercase' should use title-style capitalization. English.TitleCase
14:4 error 'third heading lowercase' should use title-style capitalization. English.TitleCase
✖ 3 errors, 0 warnings and 0 suggestions in 1 file.
Control two removes the anchors and keeps the xref: macros. Write the result to ctrl_noanchor.adoc and lint it. All three section titles are flagged:
$ grep -v '^\[\[' repro.adoc > ctrl_noanchor.adoc
$ vale ctrl_noanchor.adoc
3:4 error 'first heading lowercase' should use title-style capitalization. English.TitleCase
7:4 error 'second heading lowercase' should use title-style capitalization. English.TitleCase
11:4 error 'third heading lowercase' should use title-style capitalization. English.TitleCase
✖ 3 errors, 0 warnings and 0 suggestions in 1 file.
So neither construct alone causes the defect. The defect appears only when both are present.
The document is valid AsciiDoc
Asciidoctor renders repro.adoc with no warnings and no errors:
$ asciidoctor -o /dev/null repro.adoc
$ echo $?
0
The section titles, anchors, and cross-references are well-formed. The mismatch arises after the render, when Vale maps rendered spans back to source line numbers.
Impact
The defect is a silent false negative on every heading-scoped rule, not Title Case alone. Any rule with scope: heading is suppressed on the dropped titles. A team that relies on Vale to enforce heading conventions receives a passing run while violations remain in the dropped titles.
The two triggering constructs are common in long technical documents. Explicit block anchors and xref: cross-references are the standard AsciiDoc mechanism for stable internal links, and some house style guides require them. Such documents are the ones most likely to depend on automated heading checks, and they are the ones where the checks fail.
Related issue
This defect resembles issue #1101, in which heading-scoped rules did not match AsciiDoc headings at all. That issue is marked fixed as of release 3.15.0. The reproduction in this report runs on 3.15.1, which is past that fix. Title Case matches a heading in a trivial file, which confirms the 3.15.0 fix is active, yet the pairing of block anchors with xref: macros still desynchronizes the source-line mapping and drops the heading scope. This report therefore documents a distinct manifestation that survives the 3.15.0 fix rather than a regression of it.
Check for existing issues
Environment
/opt/homebrew/bin/asciidoctorDescribe the bug / provide steps to reproduce it
Rules carrying
scope: headingstop matching most section titles in an AsciiDoc file once that file combines explicit block anchors withxref:inline macros. The result is a silent false negative: a heading that violates the rule passes, because Vale never applies the rule to it.Below is a self-contained reproduction, the expected and actual output, and two controls that each remove one half of the trigger and restore correct matching. The closing section relates this to the earlier issue #1101.
Summary
A heading-scoped rule matches only a subset of section titles when both of these constructs are present in the same document:
[[id]]on the line directly above a section title, andxref:inline macro anywhere in the section bodies.Removing either construct restores matching on every heading. Asciidoctor parses the same file with zero warnings, so the markup is valid and the defect sits in Vale's source-line mapping rather than in the document. A clean Vale run therefore does not prove that the headings conform.
Minimal reproduction
Create three files in an empty directory.
.vale.ini:.vale/styles/English/TitleCase.yml:repro.adoc:Run:
$ vale repro.adocExpected behavior
The rule carries
scope: headingand each section title is lowercased, so Vale flags all three section titles. The expected output names lines 4, 9, and 14:Actual behavior
Vale flags only the first section title and drops the other two:
The section titles at lines 9 and 14 receive no
heading-scoped match. Vale reports no error and exits as if those titles conform.The trigger is the pair of constructs
Two controls each remove one half of the trigger from
repro.adoc. Each control restores matching on all three section titles.Control one removes the
xref:macros and keeps the anchors. Write the result toctrl_noxref.adocand lint it. All three section titles are flagged:Control two removes the anchors and keeps the
xref:macros. Write the result toctrl_noanchor.adocand lint it. All three section titles are flagged:So neither construct alone causes the defect. The defect appears only when both are present.
The document is valid AsciiDoc
Asciidoctor renders
repro.adocwith no warnings and no errors:The section titles, anchors, and cross-references are well-formed. The mismatch arises after the render, when Vale maps rendered spans back to source line numbers.
Impact
The defect is a silent false negative on every heading-scoped rule, not Title Case alone. Any rule with
scope: headingis suppressed on the dropped titles. A team that relies on Vale to enforce heading conventions receives a passing run while violations remain in the dropped titles.The two triggering constructs are common in long technical documents. Explicit block anchors and
xref:cross-references are the standard AsciiDoc mechanism for stable internal links, and some house style guides require them. Such documents are the ones most likely to depend on automated heading checks, and they are the ones where the checks fail.Related issue
This defect resembles issue #1101, in which heading-scoped rules did not match AsciiDoc headings at all. That issue is marked fixed as of release 3.15.0. The reproduction in this report runs on 3.15.1, which is past that fix. Title Case matches a heading in a trivial file, which confirms the 3.15.0 fix is active, yet the pairing of block anchors with
xref:macros still desynchronizes the source-line mapping and drops the heading scope. This report therefore documents a distinct manifestation that survives the 3.15.0 fix rather than a regression of it.