Recently, I found myself in a spirited debate with one of our front-end developers at Mergify. This discussion, revolving around the usage of linters, reminded me of my long and storied history with these "advisor tools." Having been confronted with linters for the past 25 years, I believe it's time to share some of that accumulated wisdom.
My first encounter with a linter was with use strict
in Perl. Although I can't recall the specifics of what it did, I do remember it being an essential tool for writing better code. Later on, I encountered the gcc -W
and -pedantic
options, which I enabled religiously in all my projects. These early experiences set the stage for my ongoing relationship with linters.
Warnings
Fast forward to today, my recent discussion centered around eslint and enabling all the checks for the Playwright plugin, treating every drift as an error rather than a warning. This distinction is crucial: an error causes the CI to fail, while a warning merely generates noise. Not all linters have this warning level, but in my experience, warnings will be disruptive if left unaddressed in your development workflow. An error should be a clear-cut issue: either ignore it or fix it.
Having unresolved warnings in your CI logs creates ambiguity and inefficiency. Make a decision. Commit to it.
Picking Errors
Despite not being an JavaScript expert, my 25 years of experience with various linters gives me some perspective on this matter. Our debate also touched on which approach to use with respect to linters, either:
stick to the recommended and default settings;
being stricter by promoting certain warnings to errors for checks we deemed useful;
enable everything to be an error and explicitly ignore checks that don't apply to our project or are considered incorrect.
Every linter, from gcc -W
flags to ruff in Python, starts with a set of "recommended" settings. These are designed to throw a manageable number of errors on a typical project, making the linter easy to adopt for teams. This doesn't mean the disabled options are bad; they are simply considered "too much for beginners" and can be enabled later.
This incremental approach is how we adopted mypy at Mergify. The default typing checks are relatively light, allowing us to enable it without much friction. We spent a few weeks fixing typing issues, caught a few bugs in the process, and were satisfied. Gradually, we enabled more checks until we reached the point of enabling strict = true
(a nostalgic nod to Perl) and caught even more (potential) bugs.
On the flip side, having a poorly calibrated set of default recommendations is why I never adopted pylint. Running pylint on our otherwise impeccable Python code, which passes ruff with most checks enabled, results in 13,000 errors for 140,000 SLOC. This is an insurmountable barrier for any developer. The prospect of ignoring all these non-critical errors, such as missing docstrings or line lengths, seems daunting.
Eslint and Playwright
Returning to eslint and Playwright, we used the following code to enable all Playwright rules as errors:
...Object.keys(playwrightPlugin.configs['flat/recommended'].plugins.playwright.rules).reduce(
(acc, rule) => {
acc[`playwright/${rule}`] = 'error';
return acc;
},
{}
),
This approach ensures we don't miss any linting recommendations from the Playwright team. With Dependabot automatically updating our dependencies, new errors introduced by updates appear in brand-new pull requests, allowing us to improve our code continuously.
In conclusion, "recommended" settings in linters are designed for ease of adoption, striking a balance between "best practices" and "practicality," but they are not guidelines for what you should follow.
Striving for perfection (assuming your linter is robust and not crazy) is always the goal. Make deliberate choices about which checks to ignore, and remember that linters are here to help you write better, more reliable code.