Ban Comments: a configurable ESLint rule for comments
- My contribution
- Design and implementation.
- Scope
- A configurable ESLint rule with exceptions and a comment-removal fix.
- Evidence
- Implementation inspected at dfc963c; reconstructed examples are labelled.
Page by Julian Ramirez ·
A comment says “multiply price by quantity.” The next line says price * quantity. The reader now has two versions of the same explanation to maintain, and neither tells them anything about the decision behind the code.
That small irritation is the starting point for Ban Comments, an ESLint plugin that turns a preference about comments into an executable rule. It is useful when a team wants readable implementation code and has agreed which comments are worth keeping. It cannot make that agreement on the team’s behalf.
The distinction I care about is between removing repetition and removing understanding. Better names can eliminate the need for a comment; deleting the comment alone cannot create those names.
My contribution and design scope
I built Ban Comments to express a codebase convention as an ESLint rule. The implementation separates enforcement from policy through configurable exceptions. Its fixer has a deliberately narrow job: remove the selected comment. Naming, design and the consequences for other tooling remain separate engineering decisions.
Start with the explanation inside the code
Consider the naming change below. A developer replaces abbreviations with names that express the calculation. The expression becomes easier to read because someone did the design work, not because a lint rule erased a line above it.
const t = p * q;
const total = price * quantity;
Sources & implementation notes
That is the right order for adopting a convention like this: establish what good code looks like, then use a tool to make deviations visible. Otherwise, the rule can encourage terse code that is harder to understand than the commented version it replaced.
Decide which comments should survive
Comments serve different purposes. A redundant description of an expression is not the same as an ESLint directive, a TypeScript directive or documentation for a public interface.
The rule exposes that distinction through options. ESLint and TypeScript directives are allowed by default. JSDoc is an explicit opt-in, and configured prefixes or patterns can preserve a team’s chosen forms of explanation. The examples below show three policies rather than a single universal answer.
// eslint-disable-next-line no-console // @ts-expect-errorDefault: retained
/** Public contract */ allowJSDoc: trueOpt in: retained
// why: upstream requires this allowedPrefixes: ["why:"]Explicit prefix: retained
Sources & implementation notes
A prefix such as why: can reserve space for a constraint the code itself cannot express. The value of that exception comes from its meaning to the team. Adding a prefix to every redundant comment would satisfy the configuration while defeating the convention.
Inspect what the ESLint autofix actually changes
The fix calls fixer.remove(comment). In the standalone-comment example below, that removes the selected comment and leaves the expression in place. It does not rename variables, extract a function or improve the surrounding design.
const total = price * quantity;
const total = price * quantity;
Sources & implementation notes
The fixer calls fixer.remove(comment). It does not guarantee identical behavior for arbitrary code: comments can carry tool directives or separate tokens.
It also does not guarantee identical behavior for every possible removal. Comments can affect tooling, and some separate tokens that should not be joined. Review the diff and preserve meaningful exceptions; the safe example above is deliberately narrower than a blanket “same behavior” claim.
Add the convention to a scoped flat config
The plugin exports a ban-comments rule. Registering the plugin and enabling ban-comments/ban-comments is enough for a minimal flat configuration. A real repository can then select the files and exception policy where the convention makes sense.
import banComments from 'eslint-plugin-ban-comments';
export default [{
plugins: { 'ban-comments': banComments },
rules: {
'ban-comments/ban-comments': 'error'
}
}]; Readable expression → enforced convention Sources & implementation notes
I would start with a part of the codebase whose conventions are already clear, review the diagnostics and only then widen the scope. Documentation-heavy APIs may need a different policy from small internal modules.
The broader engineering pattern is the same one behind Chest’s explicit completion criteria: a convention becomes easier to inspect when it has a concrete representation. The tool enforces the representation; people still own the judgment behind it.