ESLint no-unused-vars: _ ignore prefix
I'm a longtime user of TypeScripts noUnusedLocals
and noUnusedParameters
settings. I like to avoid leaving unused variables in my code; these compiler options help me do that.
I use ESLint alongside TypeScript. The no-unused-vars
rule provides similar functionality to TypeScripts noUnusedLocals
and noUnusedParameters
settings, but has more power and more flexibility. For instance, no-unused-vars
can catch unused error variables; TypeScript's noUnusedLocals
and noUnusedParameters
cannot.
One thing that I missed when switching to the ESLint option is that, with noUnusedLocals
and noUnusedParameters
, you can simply ignore unused variables by prefixing a variable with the _
character. That's right, sometimes I want to declare a variable that I know I'm not going to use, and I want to do that without getting shouted at by the linter.
It turns out you can get ESLint to respect the TypeScript default of ignoring variables prefixed with _
; it's just not the default configuration for no-unused-vars
. But with a little configuration we can have it. This post is a quick guide to how to implement that configuration.
When would you want to ignore unused variables?
There are various scenarios when I want to ignore unused variables. Here are a few:
- I'm writing a function but I'm not using all of the parameters yet. I plan to use them later, but I want to declare them now so I don't forget about them.
- An ignored variable can be a form of documentation. It can be a way to say "I know this is here, but I'm not using it intentionally".
Not everyone will agree with these reasons, but they work for me in certain situations.
Just to offer the counterpoint, let me quote Brad Zacher who works on TypeScript ESLint:
On the one hand it is nice to have a short-hand to ignore things.
On the other hand it is terrible having a short-hand to ignore things - it's a single character that's easy to miss in code review - so it's easy to sneak into a commit.
For example I recently reviewed a PR where someone innocently did something like
import { promisify } from 'node:util';
import { exec as _exec } from 'node:child_process';
const exec = promisify(_exec);And they didn't realise that doing this would define a variable that would never get flagged if it's unused! Really bad!
Brad has a valid point, but let's say you've decided to --ignore-pattern 'brad'
, and want to make use of the _
prefix anyway. (Sorry Brad!) Here's how you can do it.
The TypeScript settings
I mentioned that I like to use the TypeScript noUnusedLocals
and noUnusedParameters
settings. Here's how they would be configured in a tsconfig.json
:
{
"compilerOptions": {
"noUnusedLocals": true,
"noUnusedParameters": true
}
}
Given we're moving to ESLint so we'll explicitly turn these off in our tsconfig.json
so we can use ESLint to do the same job:
{
"compilerOptions": {
"noUnusedLocals": false,
"noUnusedParameters": false
}
}
The ESLint settings
With those off in TypeScript, we can now configure ESLint to respect the _
prefix. Here's how you can do that in your .eslintrc.json
:
{
"rules": {
"@typescript-eslint/no-unused-vars": [
"error",
{
"args": "all",
"argsIgnorePattern": "^_",
"caughtErrors": "all",
"caughtErrorsIgnorePattern": "^_",
"destructuredArrayIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"ignoreRestSiblings": true
}
]
}
}
The argsIgnorePattern
, caughtErrorsIgnorePattern
, destructuredArrayIgnorePattern
, and varsIgnorePattern
settings are the ones that respect the _
prefix. You have to set them all to ^_
to make it work. ^_
is a regular expression that matches any string that starts with an underscore. So if you actually had a different convention for ignoring variables, you could change this to match your convention.
Incidentally, you have to explicitly set args
to "all"
and caughtErrors
to "all"
to make the argsIgnorePattern
/caughtErrorsIgnorePattern
settings work. If you don't, the settings are ignored.
There's an ignoreRestSiblings
setting specified above that we'll get to in a minute. First of all, let's see how the linting we've activated works in practice. Here's some code that demonstrates the settings in action:
export function demoTheProblems(
unusedAndReportedArg: boolean,
_unusedButIgnoredArg: boolean, // argsIgnorePattern
someArray: string[],
) {
try {
const unusedAndReportedVar = true;
const _unusedAndButIgnoredVar = false; // varsIgnorePattern
const [
unusedAndReportedDestructuredArray,
_unusedButIgnoredDestructuredArray, // destructuredArrayIgnorePattern
] = someArray;
// caughtErrors
} catch (unusedAndReportedErr) {
// ...
}
try {
// caughtErrorsIgnorePattern
} catch (_unusedButIgnoredErr) {
// ...
}
}
In this code, the unusedAndReportedArg
, unusedAndReportedVar
, unusedAndReportedDestructuredArray
, and unusedAndReportedErr
variables are all reported as unused. ESLint considers them errors and shouts about them.
By contrast, the _unusedButIgnoredArg
, _unusedAndButIgnoredVar
, _unusedButIgnoredDestructuredArray
, and _unusedButIgnoredErr
variables are all ignored, because they are prefixed with an underscore. ESLint notices them but lets them past.
If we run ESLint on this code, we get the following output:
2:3 error 'unusedAndReportedArg' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
7:11 error 'unusedAndReportedVar' is assigned a value but never used. Allowed unused vars must match /^_/u @typescript-eslint/no-unused-vars
11:7 error 'unusedAndReportedDestructuredArray' is assigned a value but never used. Allowed unused elements of array destructuring patterns must match /^_/u @typescript-eslint/no-unused-vars
15:12 error 'unusedAndReportedErr' is defined but never used. Allowed unused args must match /^_/u @typescript-eslint/no-unused-vars
Perfect! This is exactly what we wanted. You can see this in action in the playground.
The ignoreRestSiblings
setting
The ignoreRestSiblings
setting is also useful. You may find the need to use the rest operator in a destructuring assignment to omit properties from an object and hold onto the rest. Here's an example:
const { formattedDate, date, ...totals } = payload;
In this case I don't want to use formattedDate
or date
but I do want to use totals
. I can use the ignoreRestSiblings
setting to ignore the unused variables without even needing a _
prefix or similar. So I do.
Conclusion
I hope this post has been helpful. I've shown you how to configure ESLint to respect the TypeScript default of ignoring variables prefixed with _
.
Many thanks to Brad Zacher for his input on this post. You can read our discussion on the TypeScript ESLint GitHub repo here.