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:
-
Use a special class in the AsciiDoc and then tell Vale to ignore any text that uses that class.
-
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:
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.
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:
[…]
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:
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:
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?
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’s not lint this one: KVM
<!-- vale on --></p>(1)
</div>
<div class="paragraph">
<p><!-- vale off -->
Let’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:
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