Skip to content

Heading-scoped rules silently skip sections when a block anchor and an xref: macro coexist in AsciiDoc #1109

@ciechanowiec

Description

@ciechanowiec

Check for existing issues

  • Completed

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:

$ vale repro.adoc

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions