Rules

Rules are specified in rule files using YAML. Each file may define one rule at most. There are seven rule types which determine the general characteristics of the performed check and any user-defined rule must be based on one of these types.

Note

A rule is violated if the condition it defines matches at any point in the linted text.

Severity Levels

There are three severity levels: error, warning and suggestion. You may set up a threshold to filter the output and suppress undesired violations using the --severity option. By default, the threshold is set to suggestion which provides full output. If instead the severity is lowered to warning, this would suppress suggestions but still display errors and warnings.

Message Strings

When a rule is violated, a message is displayed providing the user with information about the matching text. The message string is defined in the rule file together with the other parameters and may have static and dynamic content. The dynamic content is controlled by format specifiers such as $1 and $2. These entries indicate where to insert the match’s dynamic content. Precisely what constitutes dynamic content depends on the rule type but a common field is the matching text. Consider the following string,

Please remove '$1'.

If the rule matches the word ‘foo’, the message presented to the user will be

Please remove 'foo'.

The remaining sections will demonstrate the use of message strings in the context of each rule type.

Note

Strings without quotation marks and strings enclosed in single quotation marks (') are interpreted literally, i.e. \n will print the two characters \ and n. Strings enclosed in double quotation marks (") will interpret the contents using escape sequences.

Scopes

A rule may define a scope section to specify that the rule should only be enforced in certain parts of the text. The section expects a list of scope identifiers (as strings), and the available identifiers and their meaning depends on the linter. Identifiers unknown to the active linter have no effect:

scope:
  - paragraph # Only used by the plain text linter.
  - comment # Only used by the LaTeX linter.

The sections below define the scope identifiers known to each linter.

Plain text

Label

Target

text

Entire document

paragraph

Each paragraph (see the occurrence rule)

LaTeX

There is a rule file section specific to the LaTeX linter. The identifiers in the table below can be considered shorthand for the more general way of specifying scopes in a LaTeX document.

Label

Target

text

Text between \begin{document} and \end{document}.

comment

Comments, i.e. text starting from the character % to the end of the line.

math

Math environments:

  • Inline math ($ or \(, \))

  • Displayed math ($$ or \[, \])

  • The environments equation and equation*

title

Control sequences used to define section titles:

  • \section

  • \subsection

  • \subsubsection

Exceptions

A rule may define an exceptions section, listing exceptions to the rule. The section contents should be a list of strings and a minimum of one entry is required. The list entries may be regular expressions.

exceptions:
  - this is fine
  - this too

Using a regular expression:

exceptions:
  - this (is fine|too)

If a rule matches at any point in the linted text, the match is checked against the exceptions before a violation is generated.

Linter

A rule may define a linter section to specify that the rule should only be enabled when the target linter is being used. Currently, there are two linters available, identified as plain and latex. The linter section accepts a list of these identifiers. For example,

linter:
  - latex

would cause the rule to only be used by the LaTeX linter. Conversely,

linter:
  - plain

would only enable the rule when the plain text linter is used. By default, the rule is used by all the linters.

LaTeX

Each rule may define a latex section to specify in which context the rule should be enforced when the LaTeX linter is used. The section consists of a list of scope entries where each entry accepts the following fields:

Label

Description

Default

name

The name of the document element to match. Cannot be a regular expression.

N/A

type

The type of document element to match: control sequence or environment.

N/A

leading

Regular expression with access to the raw text leading up to the scope entry (see contexts). This raw text is limited to 20 characters.

An empty string

descend

A scope entry where descend is false implies that the scope is not allowed to descend beyond this level.

For example, we could define a rule saying that a \caption should contain more than five words. But unless we set descend to false for the \caption scope entry—the contents of any nested environments or control sequences would trigger the rule as well.

true

logic

The logic field specifies how a scope entry interacts with the other entries in determining whether or not the rule should be enforced. Valid values are or, and and not.

Whether to enforce the rule or not is determined according to:

(O or A) and not N

where

  • O represents all scope entries with the or logic reduced to a single truth value with the or operation.

  • A represents all scope entries with the and logic reduced to a single truth value with the and operation.

  • N represents all scope entries with the not logic reduced to a single truth value with the or operation.

As a special case, if the list only consists of entries marked with not, then (O or A) evaluates to true.

or

Note

Fields with default values are optional.

Let us look at an example:

latex:
  - name: foo
    type: control sequence
    leading: required\s$
  - name: bar
    type: environment
    logic: and
  - name: baz
    type: control sequence
    logic: and

The scope defined by the section above will enforce the rule for

  • any text inside the \foo control sequence, provided it is preceded by the string “required” followed by a space character (note the $ character anchoring the regular expression to the end of the text), i.e.

    Some introductory text is required \foo{to cause the rule to be
    enforced in here}{and here too} but \foo{the rule is not enforced
    in here}.
    
  • any text inside both the bar environment and the baz control sequence, i.e.

    The rule will \baz{not be enforced here}
    \begin{bar}
    and not here either.
    \baz{However, this text will be targeted by the rule.}
    \end{bar}
    

Note

Additional examples of rules specific to the LaTeX linter can be found here.

Existence

The existence rule checks for the presence of any of its tokens and reports a violation if there’s a match in the linted text. The message string will be supplied the matching text as a replacement field.

extends: existence
message: "Consider removing '$1'."
ignorecase: true
level: warning
tokens:
- foo
- bar

The rule definition above translates to the regular expression (?i)\b(foo|bar)\b, where \b indicates a word boundary and (?i) is the case insensitivity modifier. The nonword field (boolean) may be specified to instead match anywhere in the text.

The raw field may be used to gain access to the regular expression directly whereby any listed item is prepended (in the order they appear) to the final expression. Consider the following example which defines a rule to catch a few uncomparables.

extends: existence
message: "'$1' is not comparable."
ignorecase: true
level: error
raw:
- \b(?:most|more|less|least|very)\b\s*
tokens:
- absolute
- adequate
- complete
- unique

Here, the resulting regular expression will be

(?i)\b(?:most|more|less|least|very)\b\s*\b(absolute|adequate|complete|unique)\b

which will catch occurrences of “very unique”, “less complete” etc.

Inverting the Matching Logic

The invert field (false by default) causes the matching logic to be inverted if set to true, i.e. if the text segment does not contain any of the listed tokens, a violation is generated. One important detail is that when invert is true, the message is not provided any text to match the replacement field $1.

Important

When invert is true, the message is not provided any text to match the replacement field $1.

Substitution

The substitution rule checks for the presence of any of the keys defined in its key-value list swap and reports a violation if there’s a match in the linted text. The message string will be provided the key and value of the matching swap entry as format specifiers $1 and $2, respectively.

extends: substitution
message: "Prefer '$2' over '$1'."
ignorecase: true
level: warning
swap:
  catch on fire: catch fire
  '(cell phone|cell-phone)': cellphone

Keys are interpreted as regular expressions and word boundaries (\b) are added unless the nonword field is set to true. If the regular expression defines multiple capture groups, the first group will be used for the message replacement text. Non-capturing groups (?: may be used to modify the behavior as needed.

Lastly, there is one additional feature to this rule: if the expression given as the key matches text which is already equal to the substitution value, the violation is ignored. This is needed to write compact key expressions which sometimes cover the ‘correct’ case in addition to all error cases. For example,

swap:
  analog[ -]to[ -]digital: analog-to-digital

covers all the error combinations with one single regular expression, but also covers the correct case. This feature prevents the latter from being reported as a violation.

Occurrence

The occurrence rule enforces a requirement on the maximum/minimum number of times a token may/should occur in a particular scope. The message string for this rule doesn’t accept a format specifier.

extends: occurrence
message: "Don't use 'however' more than once in one paragraph."
level: suggestion
ignorecase: true
scope:
  - paragraph
limit: 1
limit_kind: max
token: '\bHowever\b'

Repetition

The repetition rule checks for repetitions of its tokens. The tokens are converted to lowercase if the ignorecase field is set to true. In contrast to the occurrence rule, this rule counts unique matches. That means that while the token '\b(\w+)\b' will match both ‘foo’ and ‘bar’, a the rule is not violated until ‘foo’ or ‘bar’ is repeated again in the target scope. The matching token is provided as input to the message string.

extends: repetition
message: "'$1' is repeated."
level: warning
ignorecase: true
scope:
  - paragraph
token: '\b(\w+)\b'

Consistency

The consistency rule checks for occurrences of either the key or the value specified as key-value pairs in its either list. For each pair, the earliest match in the linted text is assumed to be the preferred version and occurrences of its undesired counterpart will generate a rule violation.

extends: consistency
message: "Inconsistent spelling of '$1'."
level: error
ignorecase: true
scope:
  - text
either:
  organize: organise
  recognize: recognise
  analog: analogue

The keys and values may be general regular expressions but unless the nonword field is set to true, word boundary tokens \b are added to the expression. The message string will be supplied the matching text as a replacement field.

We can generalize the example above as:

extends: consistency
message: "Inconsistent spelling of '$1'."
level: error
ignorecase: true
scope:
  - text
either:
  (?:\w+)nize: (?:\w+)nise
  (?:\w+)log: (?:\w+)logue

Conditional

The conditional rule checks that first occurs before second in the given scope. In the case of a violation, the match for second is provided as input to the message string.

extends: conditional
message: "'$1' found without finding 'foo'."
level: warning
ignorecase: true
scope:
  - text
first: 'foo'
second: '(bar|baz)'

Definition

Documentation coming soon.