rmoff's random ramblings
about talks

Disabling Vale Linting Selectively in Asciidoc

Published Dec 11, 2024 by in Asciidoc, Vale at https://preview.rmoff.net/2024/12/11/disabling-vale-linting-selectively-in-asciidoc/

I’m a HUGE fan of Docs as Code in general, and specifically tools like Vale that lint your prose for adherence to style rule.

One thing that had been bugging me though was how to selectively disable Vale for particular sections of a document. Usually linting issues should be addressed at root: either fix the prose, or update the style rule. Either it’s a rule, or it’s not, right?

Sometimes though I’ve found a need to make a particular exception to a rule, or simply needed to skip linting for a particular file. I was struggling with how to do this in Asciidoc. Despite the documentation showing how to, I could never get it to work reliably. Now I’ve taken some time to dig into it, I think I’ve finally understood :)

There are two ways to do it:

  1. Use a special class in the AsciiDoc and then tell Vale to ignore any text that uses that class.

  2. Pass-through configuration to Vale using HTML comments (per the docs). It turns out line breaks are crucial in getting this to work (and why I found it to work so apparently inconsistently)

    tl;dr: Make sure you put a line break before a Vale pass-through that re-enables linting or a particular rule, otherwise it cancels out the one that preceeded it.

A quick recap of how Vale works 🔗

Vale compiles your document from its source markup (e.g. Asciidoc, Markdown, RST, etc) into HTML. It then parses the HTML and matches it to the rules you’ve defined.

This is useful to know because it helps when troubleshooting because you can compare seemingly-identical source document content to what Vale is actually parsing.

Our test document 🔗

I ended up creating a bare-bones document on which to test this. The source looks like this:

test.adoc
1
2
3
4
5
6
7
= Test doc

This line has an acronym: NAT

Let's not lint this one: KVM

But not this one: FUBAR

With a resulting Vale output of:

 3:27  suggestion  'NAT' has no definition.  Microsoft.Acronyms
 5:19  suggestion  'KVM' has no definition.  Microsoft.Acronyms

The number before the colon is the line number, so you can use this to match up the message to the source.

Option 1: Use a dedicated class 🔗

h/t to Aidan Reilly over on the WriteTheDocs slack group for this tip 👍

The idea here is that you create a dedicated CSS class that you add to Vale’s IgnoredClasses configuration, and include in your Asciidoc wherever you want Vale to skip linting.

test-option1.adoc
1
2
3
4
5
6
7
8
= Test doc

This line has an acronym: NAT

[.my-vale-ignore-class]
Let's not lint this one: KVM

But not this one: FUBAR

Resulting HTML:

[…]
<div id="content">
<div class="paragraph">
<p>This line has an acronym: NAT</p>
</div>
<div class="paragraph my-vale-ignore-class">
<p>Let's not lint this one: KVM</p>
</div>
<div class="paragraph">
<p>But not this one: FUBAR</p>
</div>
[…]

Vale config:

vale.ini
[…]
IgnoredClasses = my-vale-ignore-class
[…]

Resulting Vale output:

 3:27  suggestion  'NAT' has no definition.    Microsoft.Acronyms
 8:15  suggestion  'FUBAR' has no definition.  Microsoft.Acronyms

So—pretty simple, and effective. The only issue I see with this is that you can’t granularly target different Vale rules—it’s either on, or off.

Now the fiddly one: Pass-through config with HTML comments 🔗

The idea here is that you use Asciidoc’s inline pass macro to embed HTML comments (<!-- remember these? -→) in the generated HTML, which then passes the commands to Vale like vale off:

Here’s what I tried originally:

test-option2a.adoc
1
2
3
4
5
6
7
8
9
= Test doc

This line has an acronym: NAT

pass:[<!-- vale off -->]
Let's not lint this one: KVM
pass:[<!-- vale on -->]

But not this one: FUBAR

and got dismayed when my Vale output was:

 3:27  suggestion  'NAT' has no definition.    Microsoft.Acronyms
 6:19  suggestion  'KVM' has no definition.    Microsoft.Acronyms
 9:15  suggestion  'FUBAR' has no definition.  Microsoft.Acronyms

The generated HTML does show the comments:

[…]
<div id="content">
<div class="paragraph">
<p>This line has an acronym: NAT</p>
</div>
<div class="paragraph">
<p><!-- vale off -->
Let's not lint this one: KVM
<!-- vale on --></p>
</div>
<div class="paragraph">
<p>But not this one: FUBAR</p>
</div>
[…]

So I was stumped, until I started randomly jiggling things (and, to be fair, looking more closely at the Vale documentation itself) and noticed a difference between the effectiveness of

1
2
3
pass:[<!-- vale off -->]
Let's not lint this one: KVM
pass:[<!-- vale on -->]

compared to

1
2
3
4
pass:[<!-- vale off -->]
Let's not lint this one: KVM
(1)
pass:[<!-- vale on -->]
1 An innocuous blank line!

Putting these two into a test doc:

test-option2b.adoc
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
= Test doc

This line has an acronym: NAT

pass:[<!-- vale off -->]
Let's not lint this one: KVM
pass:[<!-- vale on -->]

pass:[<!-- vale off -->]
Let's not lint this one too: SNAFU

pass:[<!-- vale on -->]

But not this one: FUBAR

Here’s the Vale output:

 3:27   suggestion  'NAT' has no definition.    Microsoft.Acronyms
 6:26   suggestion  'KVM' has no definition.    Microsoft.Acronyms
 14:19  suggestion  'FUBAR' has no definition.  Microsoft.Acronyms

Notice how the first one doesn’t work, but the second one (SNAFU) with the line break before vale on does?

What about this?

test-option2c.adoc
1
2
3
4
5
6
7
8
= Test doc

This line has an acronym: NAT

pass:[<!-- vale off -->]
Let's not lint this one: KVM

Let's not lint this one too: SNAFU

Vale is happy with that:

 3:27  suggestion  'NAT' has no definition.  Microsoft.Acronyms

Let’s take a look at the HTML generated by test-option2b.adoc:

<div id="content">
<div class="paragraph">
<p>This line has an acronym: NAT</p>
</div>
<div class="paragraph">
<p><!-- vale off -->
Let&#8217;s not lint this one: KVM
<!-- vale on --></p>(1)
</div>
<div class="paragraph">
<p><!-- vale off -->
Let&#8217;s not lint this one too: SNAFU</p>
</div>
<div class="paragraph">
<p><!-- vale on --></p>(2)
</div>
<div class="paragraph">
<p>But not this one: FUBAR</p>
</div>
</div>
1 vale on is within the <p> tags
2 vale on is outside the <p> tags

So what seems to be happening is that Vale is parsing the whole of the paragraph (<p>) contents and applying the configuration to it—so if it has an off and then on, the two cancel out and thus the effect is nullified.

Pass-through configuration is more flexible, because rather than just turning Vale on and off, you can target individual rules. As above—don’t just ignore rules if they’re inconvenient (they’re called rules for a reason), but if you have a good reason to make an exception, you can do this:

test-option3.adoc
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
= Test doc

This line has an acronym: NAT

pass:[<!-- vale Microsoft.Acronyms = NO -->]
This should trigger one rule violation for do not, but ignore the acronym: BHAG

pass:[<!-- vale Microsoft.Acronyms = YES -->]

pass:[<!-- vale off -->]
This should not trigger a rule violation for do not, nor for the acronym: GTFO

pass:[<!-- vale on -->]

We'll catch this acronymn tho: FUBAR

Vale output is as expected:

 3:27   suggestion  'NAT' has no definition.        Microsoft.Acronyms
 6:44   error       Use 'don't' instead of 'do      Microsoft.Contractions
                    not'.
 15:32  suggestion  'FUBAR' has no definition.      Microsoft.Acronyms

Robin Moffatt

Robin Moffatt works on the DevRel team at Confluent. He likes writing about himself in the third person, eating good breakfasts, and drinking good beer.

Story logo

© 2025