Turn Past Bugs into Lint Rules
After fixing a bug, do your retrospectives end with "we'll be more careful"? Care fades when people rotate or deadlines tighten. If a machine stops the same mistake the moment it is typed, you no longer depend on anyone's attention. This article covers the implementation phase: catching a bug when the code is written, even before a test would run.
Which bugs make good lint rules
Not every bug can become a rule. The good candidates are those a reviewer could flag by reading the code alone.
- Good fit: calling a function in a risky way (
toLocaleString()with no locale,parseIntwithout a radix), using a banned API or import, skipping a required step (a missingawait) - Poor fit: business logic errors, bugs that appear only with certain data combinations, layout breakage. Use tests and acceptance criteria for those
One question sorts them: "Could another teammate point at the single line that caused this bug, using only the code?" If yes, it can be a rule.
Existing rule or custom rule?
Do not start by writing a custom rule. Check in this order.
- Look for an existing rule or plugin that already does it (for example
no-floating-promises) - See whether ESLint's built-in
no-restricted-syntaxorno-restricted-importscan express it - Write a custom rule only if neither works
Example 1: configuration only
Past bug: toLocaleString() with no argument used each environment's language setting, so dates looked different from screen to screen. Configuration alone can block it.
// eslint.config.js
{
rules: {
'no-restricted-syntax': ['error', {
selector: "CallExpression[callee.property.name='toLocaleString'][arguments.length=0]",
message: 'Pass a locale to toLocaleString (prevents display drift)',
}],
},
}Put the reason for the ban in the message. Whoever hits the rule then does not have to go digging for why.
Example 2: a minimal custom rule
When configuration cannot express the check, write one rule. This one reports any call to an in-house legacyFetch.
// rules/no-legacy-fetch.js
export default {
meta: {
type: 'problem',
messages: { used: 'legacyFetch is deprecated. Use apiClient' },
schema: [],
},
create(context) {
return {
CallExpression(node) {
if (node.callee.type === 'Identifier' && node.callee.name === 'legacyFetch') {
context.report({ node, messageId: 'used' });
}
},
};
},
};Register it as a local plugin under plugins in your config. Test it with ESLint's RuleTester, with one case that must be flagged and one that must not.
Keeping the rules healthy
- One rule per past bug, and cite the original issue number in the rule's description
- If existing code triggers many hits, start at warning level and raise to error in steps
- When a false positive appears, fix the rule's condition instead of scattering disable comments
- Every six months, review rules that never fire and remove them
Putting it into practice with Bugoon
The starting point for a rule is knowing which bug came from which way of writing code. With the Bugoon widget, each report records a screenshot, the steps taken and console errors, and becomes a GitHub issue. When hunting for bugs worth turning into rules, scan the causes in those issues for the same coding pattern appearing again. You can also let Claude Code or Cursor read an issue through the MCP server and ask it to draft a lint rule that detects the cause.
Streamline bug reporting for your team.
Bugoon is free to get started. Add one line of code to your site and transform how your team handles bugs.
Get Started