Skip to content

Heading checks false-positive when inline code is followed by an ++...++ passthrough (AsciiDoc) #1111

Description

@ciechanowiec

Check for existing issues

  • Completed

Environment

  • Vale 3.15.1
  • macOS (Darwin 25.5.0)
  • Format: AsciiDoc (.adoc)

Describe the bug / provide steps to reproduce it

Summary

In an AsciiDoc heading, when an inline code span is immediately followed by an inline passthrough (`code`++...++), the text Vale extracts for heading-scoped checks is corrupted: the code span is replaced by a run of *, and the passthrough content is split off as a separate, space-prefixed token. A check scoped to headings then runs against this mangled text and reports a false positive.

I hit this with a capitalization / match: $title rule, but the corruption is in the extracted text itself, so any heading-scoped check is affected.

Steps to reproduce

Three files:

.vale.ini

StylesPath = styles

[*.adoc]
BasedOnStyles = Test

styles/Test/Heading.yml

extends: capitalization
message: "'%s' should use title-style capitalization."
level: error
scope: heading
match: $title

test.adoc

= T

==== Mixing `ResourceProvider`++s++

==== One Tree of `Resource`++s++

==== Using `Foo`++s++ Here

Run:

vale test.adoc

Actual behavior

 test.adoc
 3:1  error  'Mixing **************** s' should use title-style capitalization.  Test.Heading
 7:1  error  'Using *** s Here' should use title-style capitalization.           Test.Heading

Two things are visible in the messages themselves:

  • The inline code span is masked as * (16 for ResourceProvider, 3 for Foo), which is expected - Vale skips code.
  • The passthrough ++s++ is detached as a separate s token with a leading space, instead of being attached to the code span as ResourceProviders. That stray lowercase s is what the title-case check trips over.

Expected behavior

No error. `ResourceProvider`++s++ renders as ResourceProviders, and "Mixing ResourceProviders" is valid title case. The extracted text should keep the s attached to the (masked) code span rather than splitting it into its own token.

What makes me think it's a tokenization bug, not the rule

The second heading, ==== One Tree of `Resource`++s++, uses the same idiom but does not trigger the error. Same construct, different result - so the rule behaves correctly on the text it gets, and the text it gets is what differs between the two headings. The stray s token appears for some code-span and passthrough combinations but not others.

For context, `word`++s++ is the standard AsciiDoc way to pluralize a monospaced term, since a constrained code span can't be followed directly by a word character (`word`s renders the backticks literally). So this pattern shows up in normal technical writing wherever a code identifier is pluralized in a heading.

Workaround

For anyone landing here: wrap the heading in a toggle, or reword so the heading doesn't end in a pluralized code span.

ifdef::backend-html5[]
++++
<!-- vale Test.Heading = NO -->
++++
endif::[]
==== Mixing `ResourceProvider`++s++
ifdef::backend-html5[]
++++
<!-- vale Test.Heading = YES -->
++++
endif::[]

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