Pablo Estrada

How I use linters to check my writing

I installed textlint, a pluggable linter for text. This post describes how I configured it, along with initial results and observations.

At work, my team is developing Bento, a free program analysis toolkit for finding bugs that matter in your code. Our program analysis team writes code checks that find problems in projects using specific frameworks such as Flask and Requests. We’ve also added curated checks from some tools such as Flake 8 and ESLint, and characterized the performance of checks that Bento enables or disables.

As we fine-tuned Bento’s configuration, it got less noisy, had a higher signal-to-noise ratio, and became more useful. Since I spend a lot of time writing, I wanted to have similar software analyze and improve my writing, like Bento analyzes and improves source code.

Inspiration

I found helpful articles like Chris Ward’s Customizing Visual Studio Code for Writing. That and others kept pointing to textlint, so I checked it out.

textlint is described as, “the pluggable natural language linter for text and markdown.” It’s written in JavaScript and inspired by ESLint. Like ESLint, textlint’s pluggable design brings flexibility, extensibility, some initial configuration requirements, and collaboration through contribution.

Installation

textlint suggests per-project installation, but I installed it globally since I wasn’t sure how it would perform, and I didn’t want to check in textlint configs or plugin code to any repository while I tinkered with it. I’m using textlint to check my private files more so than files on which I collaborate with others, so global installation has worked well so far. I saved my configuration file to /usr/local/lib/node_modules/textlint/.textlintrc.

I’m using textlint through Visual Studio Code and the vscode-textlint extension, and added this to my settings.json:

"textlint.configPath": "/usr/local/lib/node_modules/textlint/.textlintrc",
"textlint.nodePath": "/usr/local/lib/node_modules/textlint",

Once install, textlint will do nothing straight out of the box since it has no default rules. The user must select and install rules, and this is where the configuration tasks begin. Note that if you install textlint globally then you must also install each reference rule globally.

The textlint project page lists several dozen rules, about half for English and half for Japanese. It’s not too hard to go through each rule and decide if it’s worth trying. Each rule must be installed (in my case globally) and then configured in .textlintrc. Some rules have more options, and hence need more configuration, than others.

Configuration

I decided to install and configure a bunch of rules, and then observe their behavior. Then I’ll individually turning off rules that don’t seem helpful. I chose these rules:

textlint-rule-no-todo
textlint-rule-alex
textlint-rule-abbr-within-parentheses
textlint-rule-stop-words
textlint-rule-write-good
textlint-rule-en-max-word-count
textlint-rule-en-capitalization
textlint-rule-rousseau
textlint-plugin-html
check-ends-with-period
textlint-rule-terminology
textlint-rule-max-comma
textlint-rule-no-start-duplicated-conjunction
textlint-rule-period-in-list-item
textlint-rule-en-max-word-count
textlint-rule-diacritics
textlint-rule-spellchecker

You can find links to these on the textlint collection of rules page. It took a while to install and configure all the above rules, and I’m not sure I’ve configured them optimally. Here’s a GitHub Gist with my configuration.

Consuming output

Results appear inline through squiggly underlines and in Visual Studio Code’s Problems panel, like this: Problems panel

textlint’s output is also available through the command line and looks like this: textlint output in command line

Results and observations

With these checks enabled and configured, textlint can product a lot of output. I’m still working through which textlint rules to fine-tune and which to mute. I’ve configured some redundant checks, and occasionally a useful comparison of rules appears: Check comparison

In other cases the rules are entirely redundant, such as this case in which I’ve enabled Write Good’s “weasel” and Rousseau’s “weasel” rules: Redundant check output

I also installed Michael Vernier’s SpellChecker extension for Visual Studio Code and configured it in Visual Studio Code’s settings.json:

"spellchecker.ignoreWordsList": [
        "list",
        "of",
        "ignored",
        "words",
    ],
    "spellchecker.ignoreRegExp": [
        "/\\\\(.*\\\\.(jpg|jpeg|png|md|gif|svg|pdf|PDF|SVG|JPG|JPEG|PNG|MD|GIF)\\\\)/g", //remove links to image and markdown files
        "/((http|https|ftp|git)\\\\S*)/g", //remove hyperlinks
        "/^(```\\\\s*)(\\\\w+)?(\\\\s*[\\\\w\\\\W]+?\\\\n*)(```\\\\s*)\\\\n*$/gm", //remove code blocks
        "/^>[\\\\s\\\\S]*$/gm", //remove quoted text
    ],
    "spellchecker.ignoreFileExtensions": [".textlintrc", ".yml", ".json"],
    "spellchecker.checkInterval": 1500,

and Street Side Software’s Spelling Checker for Visual Studio Code, though I haven’t spent enough time with them to form a solid opinion.

Future wishes

I wish each rule included test files, so I could run each rule and understand its behavior at installation time. This way I could better tune my config from the start. As a workaround I’ve tested rules on the command line using this structure: textlint --rule [RULE NAME] [FILENAME]. It’s rather nice to see textlint’s output in the command line instead of at the bottom of my IDE.

Some of the rules have “auto-fix” functionality, but I haven’t tested this enough yet.