Dryer Lint: A Do-Regex-Yourself (DRY) Linter Extension for VS Code

Dryer Lint is a language- and framework-agnostic linter for VS Code.
It is designed to
- allow syntax checking for programming languages that do not yet have their own specialized linters
- define user-specific lint rules for a given project or code style.
- Enable automated fixes to rule violations.
Dryer Link is available in the VS Code Extension Marketplace and GitHub.
Dryer Lint is based on the relint extension by Ryan Blonna (GitHub user n0bra1n3r).
Features
Dryer Lint produces configurable diagnostics for rule violations, each of which are described by a regular expression.
Rules can also be assigned fixes that define replacements for rule violations.
The behavior similar to VS Code's built-in regex find-and-replace functionality.
Usage Guide
Dryer Lint rules are created by adding them to .vscode/settings.json within your workspace.
Rules are grouped into one or more "rule sets".
For each rule set, you can set one or more languages where the rule applies and a file glob (such as **/settings.json).
Here is an example:
"dryerLint.ruleSets": {
"Example Rule Set 1": {
// Set the name of the languages where your rules apply
"language": "c++",
// Set a file glob (optional)
"glob": "**/filename.c",
// Set a list of rules.
"rules": {
"No apples or oranges. Only bananas": {
// "pattern" is a JavaScript regular expression that shows diagnostic when matched
"pattern": "(apple|orange)",
// "message" is a string to show in the "Problems" VS Code panel or other places that diagnostics are shown.
"message": "Don't use apples or oranges. Only bananas!",
// "fix" (optional) is a string to replace the matched pattern.
"fix": "banana",
// "severity" (optional) is a string that must contain one of these values: "Hint", "Information", "Warning", or "Error". The default is "Warning".
"severity": "Error",
// "maxLines" (optional) is a positive integer that sets the max number of lines that the pattern is checked against at one time. The default is 1.
"maxLines": 2,
// "caseInsensitive" (optional) is a boolean value that sets whether the regular expression uses the case insensitive flag "i". Default is false.
"caseInsensitive": true
},
}
},
"Example Rule Set 2": {
"language": ["javascript", "typescript"],
"rules": [
{
"name": "Rule 1",
// ...
},
{
"name": "Rule 2",
// ...
}
]
},
// ...
}
The following animation shows errors diagnostics added by Dryer Lint, matching the above rule, and quick actions used to apply the replacement “fix”.
In the first step, an individual rule violation is selected and fixed.
In the second step, multiple rule violations are selected in the text and a “Fix all” option is used to fix all of them in a single step.

In this animation, the highlighting and inline display of error messages is achieved with Error Lens extension.
We highly recommend the use of Error Lens with Dryer Lint.
To apply all of the fixes in the active editor at once (or, as many as possible that don't overlap), run "Dryer Lint: Fix All" from the VS Code command pallet.

Writing Regular Expressions for Dryer Lint
Dryer Lint uses JavaScript regular expressions the Regex+ package for extended functionality and the following flags are always enabled:
g flag: Global matching (rules will match all violations in a line, instead only the first).
m flag: Multiline mode.
v flag: Enables "upgraded Unicode features", improved character classes, and stricter syntax. Can be disabled for backward compatibility by switching to the "legacy" regex engine.
The following flags are used if enabled in a rule's settings:
i flag: Ignore case. Off by default.
x flag: Ignore white space in pattern string. Off by default. (Simulated by Regex+)
More details are in the sections below.
A great tool for learning regex and writing and testing rules is https://regex101.com/.
Regex101.com allows you to save a regular expression.
You can then place a link to the saved regex so you can return to it in the future.
"\"l\" instead of \"\\ell\"": {
// Link: https://regex101.com/r/QsDziM/latest
// Match any single letter "l" that is not part of a word or wrapped in "\textsc{...}".
"pattern": "(?<![a-zA-Z]|\\\\textsc\\{)l(?=[ _\\r\\n]|\\W)",
"message": "Avoid \"l\" as a symbol. Use \"\\ell\" instead.",
"severity": "Warning",
"fix": "\\ell"
}
(See the section “Escaping Regular Expressions”, below, regarding copying expression from regex101.com into settings JSON file.)
Regex Flags
In Dryer Lint, all regex searches use the g flag (to find multiple matches instead of only the first) and the m flag (so that ^ matches the start of each line and $ matches the end of each line).
If "caseInsensitive": true for a given rule, then the i flag is also used.
Dryer Lint also uses the v regex flag for "upgraded Unicode support".
Regex+: Expanded Regular Expression Syntax
Dryer Lint uses the Regex+ JavaScript package as its regex processing engine.
Regex+ requires stricter syntax for regular expressions while adding many features to the built-in JavaScript engine.
In particular, Regex+ uses the
for expanded regular expression pattern definitions in JavaScript.
which provides many new regex features, including
- Possessive quantifiers (
"*+", "++", and "?") to block a quantifier from backtracking (or "returning characters"). Thus, "a*+" means
- Ignores white space in patterns and allows for comments.
- Subroutines (using a group subpattern using
\g<groupname>)
- Subroutine definition groups (i.e., using
(?(DEFINE) ... ))
- Atomic groups to block backtracking to before the start of the group (can improve performance).
Matching Line Breaks
By default, each regex rule is applied to a single line at a time, in which case ^ matches the start of a line and $ matches the end.
When maxLines is more than 1 (the default), however, each regex searches over the given number lines.
Since the m flag is used, ^ matches the start of each line and $ matches the end of each line.
Group Replacements in "fix" and "message" Properties
In each rule definition, the "fix" and "message" fields can use replacements from the matched Regex groups.
The string $0 is replaced by the entire match, $1 is replaced with the contents of the first group capture, and "$2" is replaced with the second, and so on.
The following is an example of a rule for LaTeX, where the first group (cref|eqref|ref|cite) is substituted into the error message.
"Empty Reference or Citation": {
// Check for \cref{}, \cite{}, \ref{}, or \eqref{} occurring without arguments.
"message": "Empty \\$1{}.",
"pattern": "\\\\(cref|eqref|ref|cite)\\{\\s*\\}",
"severity": "Error"
},
The resulting error message from
\cite{}
is “Empty \cite{}."
Escaping Regular Expressions
In JSON, the backslash character \ is used to escape other characters, so, for example, \t is a tab character, \n is a new line character, and (critically) \\ is a backslash.
Backslashes are used extensively in regular expressions.
To write regex in JSON, replace every occurrence of \ with \\.
There are several VS Code extensions that provide this functionality.
I use vscode-json.
You can disable Dryer Lint for portions of a file using an inline comment such as (in C++):
// dryer-lint: disable
The following comments also disable Dryer Lint:
// dryer-lint: disabled
// dryer-lint: enable=false
// dryer-lint: enabled=false
To re-enable Dryer Lint, use any of the following:
// dryer-lint: enable
// dryer-lint: enabled
// dryer-lint: enable=true
// dryer-lint: enabled=true
The inline comment must be the only contents of the line except for empty space.
The inline comment characters for the following languages are recognized:
c: "//",
cpp: "//",
java: "//",
javascript: "//",
latex: "%",
python: "#",
ruby: "#",
shellscript: "#",
typescript: "//",
If a language is not recognized, then lines starting with // or # are treated as comments for the purpose of toggling Dryer Lint on and off.
In addition to enabling or disabling Dryer Lint completely, you can also turn specific rule sets on and off.
The comment syntax is identical, except it is followed by the name of a rule set in quotes.
E.g., to disable a rule set named "my cool rulz", use
// dryer-lint: disable "my cool rulz"
NOTE: If you have disabled Dryer Lint using
// dryer-lint: disabled
then all rules are disabled until
// dryer-lint: enabled
occurs (or an equivalent).
Placing
// dryer-lint: enabled "my cool rulz"
in a section of code where Dryer Lint is disabled will not cause "my cool rulz" rules to become active again until Dryer Lint is reenabled.
More examples
The following is a simple configuration that issues diagnostics for maximum characters exceeded in a line:
{
...
"dryerLint.ruleSets": {
"Max column width": {
"language": ["python", "javascript", "typescript"],
"rules": {
"format-line": {
"message": "format: 80 columns exceeded",
"name": ,
"pattern": "^.{81,120}$",
"severity": "Warning"
},
"format-line": {
"message": "format: 120 columns exceeded",
"pattern": "^.{121,}$",
"severity": "Error"
}
}
}
}
...
}
I am trying to make Dryer Lint backwards compatible while also making changes that dramatically improve the extension.
This means that I have defined a new format for the "dryerLint.ruleSets" setting in v2.0 while also continuing to support the v1.4 format (at least for now).
The two formats are incompatible, so you need to modify your settings before you can start taking advantage of the new v2.0 features.
Transition to v2.0 ownward
In v2.0, the list of rule sets is given as
"dryerLint.ruleSets": {
"rule set 1": { // First rule set
"name": "Example Rule Set 1",
"language": "c++",
"rules": {
"Rule 1": {
"name": "No apples or oranges. Only bananas",
"pattern": "(apple|orange)",
"message": "Don't use apples or oranges. Only bananas!",
},
}
},
"rule set 1": {
"name": "Example Rule Set 2",
"language": ["javascript", "typescript"],
"rules": [
//
]
},
}
This is different from v1.4, which uses a list of dictionaries to store the list of rule sets in "dryerLint.ruleSets", and the list of rules in "rules".
For instance
"dryerLint.ruleSets": [
{ // First rule set
"name": "Example Rule Set 1",
"language": "c++",
"rules": [
{
"name": "No apples or oranges. Only bananas",
"pattern": "(apple|orange)",
"message": "Don't use apples or oranges. Only bananas!",
},
]
},
{ // Second rule set.
"name": "Example Rule Set 2",
"language": ["javascript", "typescript"],
"rules": [
//
]
},
// ...
]
To keep existing v1.4 rules while allowing you to start updating to the v2.0 format, change "dryerLint.ruleSets" to `"dryerLint.ruleSets-legacy" for the v1.4 list of rule sets:
"dryerLint.ruleSets-legacy": [
{ // First rule set
"name": "Example Rule Set 1",
"language": "c++",
"rules": [
{
"name": "No apples or oranges. Only bananas",
"pattern": "(apple|orange)",
"message": "Don't use apples or oranges. Only bananas!",
},
]
},
{ // Second rule set.
"name": "Example Rule Set 2",
"language": ["javascript", "typescript"],
"rules": [
//
]
},
// ...
]
Define new rule sets using the v2.0 format in the "dryerLint.ruleSets" setting.
The advantage of the new format is that VSCode will merge settings defined as dictionaries, but not lists, so the new format allows you to define rule sets in user settings and workspace settings.
Dryer Lint was forked from relint extension by Ryan Blonna.
The relint project has been dormant since 2021 aside from updates merged from Dryer Lint.
ast-grep is command line tool for custom linting, which also has a VS Code Extension.
The setup of ast-grep is somewhat more involved, whereas Dryer Lint only requires installing the VS Code extension, but ast-grep.
Semgrep: AI-based static analysis.
comby
Structural Search for JetBrains IntelliJ IDEA
Development
This section describes how to set up Dryer Lint for development.
Install Node.js and the Node Package Manager:
- Download and install Node.js (LTS version recommended).
- The Node Package Manager (
npm) comes bundled with Node.js.
After installing NPM, clone this repository and open it in VS Code.
Then, to install the Node.js dependencies listed in package.json, run the following command within the root directory of the repo.
npm install
Once you have all of the dependencies, you should be able to build this extension using
npm run compile
in the root of this repository.
To see the console output of Dryer Lint, open the VS Code “Output” panel and select “Dryer Lint” from the dropdown.
Test Extension in another Workspace
To run this extension in a workspace—without building and installing a VS Code extension package globally—follow these steps:
- Open the workspace where you want to test Dryer Lint.
- Create
.vscode/extension as a directory relative to the root of your workspace (if it does not already exist).
- Clone Dryer Lint into
.vscode/extension. The resulting path should be .vscode/extension/dryer-lint.
- Change your working directory to
.vscode/extension/dryer-lint and run npm install (as described in the previous section) to install all of Dryer Lint's dependencies.
- Run
npm run compile to compile the project.
- Open the Extensions panel and select “Dryer Lint” from the “Recommended” subpanel. Click “Install Workspace Extension.”
- Run
Developer: Restart Extension Host in the VS Code Command window (CTRL+SHIFT+P, by default on Windows).
To update the extension after changing the code, repeat steps 4 and 6 (npm run compile and run Developer: Restart Extension Host).
Packaging the Extension
Dryer Lint is packaged and published used the VS Code Extension Manager tool, vsce.
To install vsce, run,
npm install -g @vscode/vsce
Then, within the root of this project, create a package with
vsce package
and publish a new major, minor, or patch version with
vsce publish [major/minor/path]
Development notes
When change the "contributes"/"configuration" in package.json, you need to reload the VS Code window for IntelliCode to update its autocompletion in the settings.json file.
To print trace and debug logging statements, you need to change the log from its default value ("Info"). To change the log level, select "Developer: Set Log Level..." from the command palette.