Writing your own ESLint Plugin: Autofix code
July 02, 2018
This is the second post in “Writing your own ESLint plugin” series. Checkout Part 1
In part 1, we finished writing our very own ESLint plugin. However, manual code changes were required to fix the code that violated our custom rule. Let’s tap into one of the best features of ESLint - auto fix!
Recap: Our rule aims to prevent usage of
_.isNull
when checking fornull
.
Autofixing all the things!
If you’d like ESLint to attempt to fix your code, you must do the following two steps:
-
Set the value of
meta.fixable
to"code"
(defaults tonull
) -
Pass in a
fix
function tocontext.report
. Thefix
function receives afixer
object as it’s argument.
context.report({
node,
message: "Use native JavaScript to check for null",
fix: function(fixer) {
// autofix logic goes here
}
});
The fixer
object has a bunch of helpful methods on it that allow us to manipulate the AST. At this point, I recommend that you head over to ESLint - Applying fixes section to familiarize yourself with all the methods that are available on the fixer
object.
Updating tests
Let’s take a look at an example of what we’re aiming for.
_.isNull(obj) // Autofix to obj === null;
Let’s add this to our test suite. Each test can take an optional output
parameter which is used to determine the correctness of the auto-fix logic.
invalid: [
{
code: "_.isNull(obj);",
errors: [ errorObject ],
output : "obj === null;",
},
]
Run the tests after this & you should have a failing test.
Get fixing!
We have to do two things to get our example fix working.
- Grab the argument passed to
_.isNull
- Replace the
_.isNull
call with=== null
The first step is actually quite easy to do. node
inside context.report
refers to MemberExpression
i.e _.isNull
. Its parent is the actual function call that we want to the argument from.
fix: function(fixer) {
/**
* node is MemberExpression (_.isNull)
* node.parent is CallExpression (_.isNull()))
*/
let scope = node.parent;
// Grab the argument that was passed to the isNull function
const arg = scope.arguments[0];
}
That’s step one out of the way. Now all we have to do is append === null
to it, right?
const fixedCode = `${arg} === null`;
return fixer.replaceText(scope, fixedCode);
The test will continue to fail after this change because arg
isn’t the actual argument that was passed to the isNull
, it’s an object representation of that.
The context
object has a nifty little method on it that lets us access the source code associated with any node. Let’s use that instead!
fix: function(fixer) {
/**
* node is MemberExpression (_.isNull or _.isUndefined)
* node.parent is CallExpression (_.isNull() or _.isUndefined())
*/
let scope = node.parent;
// Grab the argument that was passed to the isNull function
const arg = scope.arguments[0];
// Get an instance of `SourceCode` so we can convert the argument to source code
// & append the fix to it
const sourceCode = context.getSourceCode();
let fixedCode = sourceCode.getText(arg) + ' === null';
return fixer.replaceText(scope, fixedCode);
}
Voila! With that our tests are passing. There you have it! You now have the necessary knowledge to start implementing your own custom ESLint plugins with auto-fixing enabled!