Handling Idempotency in ESLint Fixes
ESLint's internal fix mechanism does not provide a configuration option to skip writes based on a content comparison of the final output versus the original source. When you run eslint --fix, the engine applies all suggested fixes and writes the resulting buffer to disk. If a custom rule issues a fix that results in the exact same text as the current source, ESLint may still trigger a write operation depending on the environment and the specific way the fixer is invoked.
The Fix Mechanism and Write Behavior
By design, ESLint's --fix is a single-pass operation. It does not natively "retry" until convergence; rather, it applies fixes and exits. If you are experiencing duplicate writes, it is likely because you are wrapping the ESLint CLI in an external loop or using a tool that triggers multiple passes. In these scenarios, if a rule is not idempotent—meaning it suggests a fix even when the code already matches the desired state—ESLint will continue to write to the file on every pass.
Recommended Pattern for Idempotent Rules
To prevent redundant writes, the logic for detecting the violation must be strictly decoupled from the logic for applying the fix. A rule should only return a fix function if the current source code actually requires a change.
Follow this pattern within your custom rule implementation:
- Verify the Violation: Use the AST or the
sourceCode.getText() method to check if the code already conforms to the desired state.
- Conditional Fix: Only provide the
fix object in the report call if the verification step confirms a discrepancy.
- Avoid Blanket Replacements: Do not replace a range of text with the same text that already exists in that range.
// Example of an idempotent check
context.report({
node,
message: 'Incorrect formatting',
fix(fixer) {
const currentText = sourceCode.getText(node);
const desiredText = transform(currentText);
// Only issue the fix if the transformation actually changes the text
if (currentText === desiredText) {
return null;
}
return fixer.replaceText(node, desiredText);
}
});
Verification and Diagnostics
To verify if your rules are causing redundant writes, use a version control system to track changes between passes:
- Run
eslint --fix .
- Check for changes:
git diff
- Run
eslint --fix . a second time. If git diff still shows modifications or if the file's "last modified" timestamp updates without content changes, the rule is not idempotent.
Missing Diagnostic Detail: Are you using a custom wrapper script to loop the --fix command, or is this behavior occurring within a single execution of the ESLint CLI?