Skip to content
Last updated

Prose rules

A prose rule checks the words of a document rather than its Markdown syntax. You write one in the recheck block from three parts: a scope that selects the text, an assertion that checks it, and a message that explains the finding.

recheck:
  rules:
    recheck/no-gerund-headings:
      severity: warn
      scope: heading
      message: 'Do not start a heading with a gerund.'
      link: https://example.com/style-guide#headings
      assertions:
        pattern:
          ignoreCase: true
          tokens:
            - '^\w+ing\b'

This page lists every scope and every assertion with its options. The recheck configuration reference lists the other keys of a rule, such as appliesTo, excludes, and exceptions.

Rule keys and messages

A rule key is <namespace>/<name>, in lowercase, for example recheck/us-spelling or acme/product-names. The presets use their own namespaces, such as google/, so your keys never collide with theirs. The report prints the key without the recheck/ prefix.

A prose rule needs a message. The message can hold %s placeholders, which the assertion fills in order. Each assertion lists what it fills. Most assertions fill two values; length fills three and metric fills four. A message with more placeholders than the assertion fills is a configuration error.

Scopes

The scope key selects which parts of a file the rule reads. The default is all.

ScopeSelects
allThe whole file as one segment.
rawThe raw file content, without scope segmentation.
summaryThe document's prose: paragraph, heading, list item, blockquote, and table cell text. Alias: default.
sentenceEach sentence of the prose.
paragraphEach paragraph.
headingEach heading. heading.h1 to heading.h6 select one level.
list-itemThe text of each list item.
blockquoteThe text of each blockquote.
table.headerEach table header cell.
table.cellEach table body cell.
codeEach code block.
frontmatterThe YAML front matter.
htmlEach raw HTML block.
commentEach HTML comment.
altThe alt text of each image.
linkThe text of each link.
markdoc.tagEach Markdoc tag span. Needs markdoc: true.

A list of scopes selects all of them:

scope:
  - heading.h1
  - heading.h2

A selector narrows a scope: ~ excludes a scope, and & joins conditions. For example, '~blockquote & ~heading' selects everything except blockquotes and headings. all and raw cover the whole file, so they cannot be combined with another scope.

Prose scopes never include inline code, and when Markdoc parsing is on, they do not include the tag syntax either. Set includeCode: true on swap, pattern, repetition, or consistency to match inside inline code.

Assertions

Each rule has one or more assertions under assertions, keyed by assertion id. The Markdown rules are also assertions, keyed by rule name, and a rule can combine both kinds.

AssertionChecksFixable
swapA word or phrase that should be replaced.Yes
patternA regex that should not match.No
occurrenceHow many times a regex matches in a segment.No
repetitionAn adjacent repeated word.Yes
consistencyOne spelling of a pair across the file.Yes
conditionalA pattern that requires another pattern in the same file.No
capitalizationTitle case, sentence case, or a custom pattern.Yes
lengthThe size of a segment in characters, words, or sentences.No
metricA readability score for the whole document.No
spellingWords the dictionary does not know.No
semantic-line-breaksOne sentence or phrase per line.Yes
max-image-sizeThe file size of linked images.No

swap

Reports a word or phrase and suggests a replacement. With --fix, each match is replaced, and the match's own casing is applied to the replacement: a capitalized match gets a capitalized replacement, and an all-caps match gets an all-caps replacement. When two pairs match overlapping text, the longest match wins.

assertions:
  swap:
    ignoreCase: true
    wordBoundary: true
    pairs:
      utilize: use
      in order to: to
OptionTypeRequiredDescription
pairsobjectYesText to find mapped to its replacement.
ignoreCasebooleanNoMatch without regard to case. Default false.
wordBoundarybooleanNoMatch whole words only. Default false.
keysAreRegexbooleanNoTreat each key as a regex, for example favou?rite. An invalid regex matches nothing. Default false.
includeCodebooleanNoAlso match inside inline code spans. Default false.

Message placeholders: the replacement, then the matched text. With the pair utilize: use, the message 'Use "%s" instead of "%s".' prints Use "use" instead of "utilize".

pattern

Reports each match of a regex.

assertions:
  pattern:
    ignoreCase: true
    tokens:
      - '\bvery\b'
      - '\breally\b'
OptionTypeRequiredDescription
tokens[string]YesRegex patterns. An invalid pattern matches nothing.
ignoreCasebooleanNoMatch without regard to case. Default false.
nonwordbooleanNoDo not add word boundaries around each token. Default false.
includeCodebooleanNoAlso match inside inline code spans. Default false.

Message placeholders: the matched text.

occurrence

Counts the matches of a regex in each segment and reports the segment when the count is outside min and max. min: 1 without max requires the pattern to be present.

scope: paragraph
assertions:
  occurrence:
    pattern: '[.!?]'
    max: 3
OptionTypeRequiredDescription
patternstringYesThe regex to count.
minnumberOne of min or maxFewer matches is a finding.
maxnumberOne of min or maxMore matches is a finding.
ignoreCasebooleanNoMatch without regard to case. Default false.

Message placeholders: the match count, then the bound that was crossed.

repetition

Reports an adjacent repeated word, such as "the the", including across a line break. With --fix, the pair collapses to the first word.

assertions:
  repetition: {}
OptionTypeRequiredDescription
patternstringNoThe regex that defines a word. Default \w+.
ignoreCasebooleanNoCompare without regard to case. Default true, because "The the" is the common typo.
includeCodebooleanNoAlso look for repeats inside inline code spans. Default false.

Message placeholders: the repeated word.

consistency

Each entry of either names two variants. The variant that appears first in a file wins, and later uses of the other variant are reported. With --fix, they are replaced with the winning variant as written in either.

assertions:
  consistency:
    ignoreCase: true
    either:
      behavior: behaviour
      color: colour
OptionTypeRequiredDescription
eitherobjectYesPairs of variants. Each pair has its own winner.
ignoreCasebooleanNoMatch without regard to case. Default false.
includeCodebooleanNoAlso match inside inline code spans. Default false.

Message placeholders: the later variant, then the variant that appeared first.

conditional

If first matches in the rule's scope, second must match somewhere in the whole file, code blocks included. Otherwise each first match is reported.

assertions:
  conditional:
    first: '\bTBD\b'
    second: 'https://github\.com/\S+/issues/\d+'
OptionTypeRequiredDescription
firststringYesThe regex that requires second.
secondstringYesThe regex that must appear in the file.
ignoreCasebooleanNoMatch without regard to case. Default false.

Message placeholders: the first match, then the second pattern.

capitalization

Reports a segment whose casing does not match the required style.

scope: heading
assertions:
  capitalization:
    match: $sentence
    exceptions: [Redocly, Reunite]
OptionTypeRequiredDescription
matchstringYes$title, $sentence, $lower, $upper, or a regex the whole segment must match.
stylestringNoThe stopword list for $title: ap or chicago. Default ap.
exceptions[string]NoWords and phrases that keep their casing as written, such as GitHub or VS Code.
builtinVocabularybooleanNoAdd the built-in list of technical proper nouns, such as OpenAPI, npm, and Node.js, to exceptions. Default true.
  • $title capitalizes every word except articles, conjunctions, and prepositions. ap lowercases prepositions of three letters or fewer; chicago lowercases every preposition. The first and last words are always capitalized.
  • $sentence capitalizes the first word and lowercases the rest, except exceptions and words in all caps.
  • $lower and $upper require the whole segment in that case.
  • A regex must match the whole segment. This mode is detection-only.

Words in all caps, such as API, and inline code are never changed. --fix rewrites a single-line segment for the four $ styles. A segment that spans several lines is skipped.

Message placeholders: the segment text, then the match value.

length

Measures each segment in the rule's scope and reports it when the size is outside min and max.

scope: sentence
assertions:
  length:
    unit: words
    max: 25
OptionTypeRequiredDescription
unitstringYescharacters, words, or sentences.
minnumberOne of min or maxA smaller segment is a finding.
maxnumberOne of min or maxA larger segment is a finding.

Message placeholders: the measured size, the unit, then the bound that was crossed.

metric

Scores the prose of the whole document with a readability formula and reports the file once, at line 1, when the score is outside min and max. The score reads the same prose as redocly recheck --readability: headings, code, front matter, and Markdoc tags do not count. A file with no prose is never reported.

assertions:
  metric:
    formula: flesch-reading-ease
    min: 30
OptionTypeRequiredDescription
formulastringYesflesch-reading-ease, flesch-kincaid-grade, gunning-fog, smog, coleman-liau, or automated-readability.
minnumberOne of min or maxA lower score is a finding.
maxnumberOne of min or maxA higher score is a finding.

The assertion always reads the summary scope. Setting another scope prints a warning and has no effect.

Message placeholders: the formula, the score, min, then max.

spelling

Reports words that a Hunspell dictionary does not recognize, with up to three suggestions. The English dictionary ships with Redocly CLI.

scope: summary
assertions:
  spelling:
    vocab: [Redocly, Reunite]
    ignore: ['\bAcme\w*']
OptionTypeRequiredDescription
dictionarystringNoBase path of a custom Hunspell dictionary, without the .aff and .dic extensions, relative to the working directory.
vocab[string]NoWords that are never reported, matched without regard to case.
ignore[string]NoRegex patterns. A word that matches any of them is never reported.
builtinVocabularybooleanNoAlso accept the built-in list of technical proper nouns. Default true.

Words in all caps and words next to a digit, such as utf8, are not checked. Inline code is never checked.

Message placeholders: the unknown word, then a suggestion suffix in the form — did you mean: world?, or an empty string when there is no suggestion.

semantic-line-breaks

Requires one sentence, or one phrase, per line. With --fix, long lines are split after each sentence, and list item continuation lines are indented under the item text.

assertions:
  semantic-line-breaks:
    mode: sentence
OptionTypeRequiredDescription
modestringYessentence breaks after each sentence; phrase also breaks at clauses.
maxPhrasenumberNoIn phrase mode, the longest phrase that may stay on one line.
ignoreCodeBlocksbooleanNoSkip code blocks.
ignoreTablesbooleanNoSkip tables.

max-image-size

Reports an image whose file is larger than the limit. The image path is resolved from the Markdown file and must stay inside the folder passed to the command.

assertions:
  max-image-size:
    maxSizeKB: 200
OptionTypeRequiredDescription
maxSizeKBnumberNoThe limit in kilobytes. Default 100.
extensions[string]NoFile extensions to check. Default jpg, jpeg, png, gif, webp, and svg.

Built-in proper nouns

The capitalization and spelling assertions share a list of technical proper nouns, such as OpenAPI, GraphQL, npm, Node.js, VS Code, and Kubernetes. It saves every project from listing the same names. capitalization keeps them cased as written, and spelling accepts them as words. Set builtinVocabulary: false on a rule to use only its own exceptions or vocab.

The list leaves out words that are also ordinary English, such as Chrome or Windows, because an exception for them would weaken the check. Add names like that in the rule's own exceptions or vocab. The list is exported as TECHNICAL_PROPER_NOUNS from the @redocly/recheck package.

Examples

US spelling

recheck:
  rules:
    recheck/us-spelling:
      severity: error
      scope: summary
      message: 'Use the US spelling "%s" instead of "%s".'
      assertions:
        swap:
          ignoreCase: true
          wordBoundary: true
          pairs:
            colour: color
            behaviour: behavior
            organise: organize

Sentence length with an exception for a frozen page

recheck:
  rules:
    acme/sentence-length:
      severity: warn
      scope: sentence
      message: 'Sentence is %s %s long; keep it under %s.'
      assertions:
        length:
          unit: words
          max: 25
      exceptions:
        files:
          - docs/legal/**

Readability floor for guides only

recheck:
  rules:
    acme/readability-floor:
      severity: warn
      message: 'Readability (%s) is %s; expected between %s and %s.'
      appliesTo:
        - docs/guides/**
      assertions:
        metric:
          formula: flesch-reading-ease
          min: 40