Have you ever wondered how your editor can know if you used
var instead of
let? or that you missed adding
alt to the
img tag? Yes, that is ESLint ladies and gentlemen.
In this article, we’re going to talk about how this is possible and how you can do something similar!
For you who ain’t familiar with ESLint, I recommend that you read about it in advance and perhaps try to see it in action. The writing presumes that you have at least little knowledge in configuring ESLint.
All the code snippets are available in Replit - if you can’t see the file in the caption, you’ve to manually navigate to it from the file explorer.
Table Of Content
- How ESLint Can Understand The Code
- Rule Fixer
- Rule Suggestions
- ESLint Parser
- Rule Tester
- Plugins Folder Structure
- AST selectors
- Bounce Sections
const) an after some investigation it was mandatory to adapt to the new way, your team agreed and left the office thinking you’re crazy. The next day the team have forgotten and it was like nothing happened yesterday.
New developer joined and was pretty excited to create its first PR, after the onboarding, the new hire created the PR with inline styles everywhere. The team doesn’t like inline styles and considers it a bad practice thus the PR was abandoned with one big comment “HTML files are pretty without inline styles, please consider moving them to the styles files”.
You’re refactoring a legacy class trying to simplify the logic by extracting the master method that does everything based on parameters to a rather set of simple methods. Now the master method is marked as deprecated in favour of the simpler ones.
You’re working on React SSR project, you’ve created a component that utilises the navigator object to get the user agent information, the moment the code runs an error appears from nowhere telling you that navigator is undefined because the component has been executed in the server -because of SSR nature-.
The team members often forgot to unsubscribe from RxJS observable, the memory halted, you blame Angular, and decides to move to React instead.
All these issues can be dealt with using ESLint. ESLint ain’t magic that will instantly solve the code bugs and errors, but it can be a very useful tool to warn you about potential bugs.
Throughout this article, we’ll write ESLint rules that address a variety of issues, so let’s get to the point directly.
How ESLint Can Understand The Code
ESLint has two (of many) major concepts:
- Rule: an object that describes what is about to be validated/linted (More information later).
- Plugin: Node.js package that exposes a set of rules.
An ESLint rule has a
create function with one argument (the
context object) and returns a traverse object.
The context object contains additional functionality that is helpful for rules to do their jobs. As the name implies, the context object contains information that is relevant to the context of the rule.
The traverse object keys are AST selectors and the values are functions that take a
Bear with me for a bit, things will get clearer soon! the snippet above is the basis for other definitions.
To know what is the
Node object and how ESLint can provide it, we need to talk about CS terms.
nope, I mean Compiler phases and not the definition of the Compiler.
Your code (The program) will get smashed down by several layers before it’s finally understood by the hardware, these layers are:
- There’re more but not mandatory to know now.
Would you like to read more about the compiler or read more about the compiler phases, I recommend this book
The process of reading the source code a character at a time and chunking them into
for instance, when passing this code into the scanner
will produce the following tokens
The process of reading the output of the Scanning phase (tokens) and outputting a tree data structure called AST or “Abstract syntax tree”. go down to learn more about AST
for instance, the above tokens will produce
Finally, we can answer the question what is the
Node object in the traverse function? simply put a Node is an object that holds information about a specific part of the program like the location of the variable
foo and its initializer in form of a tree structure.
Let’s start with a simple example: prevent the developer from using the
var keyword, for that, we need to handle nodes of type
VariableDeclaration - based on the AST above the selector for the
var keyword is
VariableDeclaration - and when the handler function is invoked we need to warn the developer that
var shouldn’t be used.
I guess that’s enough about how ESLint process your code, now it’s the time to get more involved in writing code.
ESLint rule structure as follows
Check ESLint rule docs for complete details
We already covered the create function in the above section, so let’s talk about
type: If the rule is for a code structure use “layout”, if it’s to warn about potential bug/error use “problem”, otherwise you might want to stick with “suggestion”.
- fixable: Indicates that the node that represents a segment of the code is fixable (can be fixed automatically) by adding
--fixoptions (More about it later).
- schema: In case you want to make the rule configurable (e.g. accept an option to what kind of variable is allowed) you’ve to add JSON schema.
- hasSuggestion: Indicates that the node that represents a segment of the code has suggestions go down.
- description: Concise yet meaningful description of what the rule does.
- url: A link where the user can get more information about the rule (docs or spec).
recommendedout of this article’s scope.
Let’s modify the rule to let the developer decides which kind of variable to dismiss, the option can be accessed using the
To specify options, we need to write a valid JSON schema. the
schema property takes an array of JSON objects. More information about the schema
Let’s see how ESLint can fix this code for us…
As we saw above the
context.report function is what warns the developer about the validity of a segment of code.
this function accepts an object that has the node (that represents the code to be reported), message (meaningful message describing why that code is being reported) and
Note: there’re more properties that the report object can have, which we might explain as we go further and others would be out of this article’s scope.
The fix function receives a single argument, a fixer object, that you can use to apply a fix, it returns a fixing object (the return value of the fixer object methods) or array of fixing objects
Completing the previous example this is how we can auto-fix the reported code. We need to adjust the
meta object to have the
fixable option and add the
fix function to the report object.
Note: you shouldn’t determine the node range that way, for the sake of not complicating the code, I’ve added 3 to the start position, for instance, if
var start position is 5 then the end is 8 because
var length is 3. Check out [the official rule] (https://github.com/eslint/eslint/blob/main/lib/rules/no-var.js)
It has a few methods, each return a
fixing object to mutate the source code, you can either pass the token/node or the range (location) of a token/node.
In case you returned an array of fixing objects, make sure that the fixes ain’t stepping on each other, otherwise, ESLint will refuse to apply the fixes for that node/token. ESLint can determine the conflicts by inspecting the node/token range of all the fix attempts, if the ranges step on each other ESLint will discard all fixes for that node.
for instance, the fixes in this sample conflicting on the range [4, 5]
Be wary to add the
fixable option to
meta object, otherwise, ESLint will refuse to do any fixes.
Fixer object methods.
insertTextAfter(nodeOrToken, text)- inserts text after the given node or token
insertTextAfterRange(range, text)- inserts text after the given range
insertTextBefore(nodeOrToken, text)- inserts text before the given node or token
insertTextBeforeRange(range, text)- inserts text before the given range
remove(nodeOrToken)- removes the given node or token
removeRange(range)- removes text in the given range
replaceText(nodeOrToken, text)- replaces the text in the given node or token
replaceTextRange(range, text)- replaces the text in the given range
Check the examples to see how the fixer object can be used.
Note: ESLint does not mutate the source code directly rather it does mutate the AST and regenerates the code from it
What if we want to tell the developer that the code is fine but there might better way to do it. In this case, we shouldn’t auto-fix the code, instead, you can provide a suggestion!
for instance, we want to suggest that instead of using the ternary operator you should use the normal
First thing first, we need to know what the AST of the above code looks like, for that we’ll use AST Explorer.
The ternary operator node type is
ConditionalExpression, so we’ll use that as an AST selector
Second, the type in
meta object needs to be
suggestion. You only need
hasSuggestions when the rule does suggest a fix or more.
Let’s take another example but with real suggestions now. the next rule will suggest that if the
var/let is not being reassigned then it should be const instead.
hasSuggestionsis true because the rule suggests fixes, also instead of adding
fixfunction as part of the report object, we added it as part of the suggestion object in the
- The context object has been used before to get the developer options and now it’s used to get the whole source code.
- The source code has plenty of useful methods that we are using to get access to the tokens
- In a variable declaration, the first token is the variable keyword token and the next one is variable identifier token hence skip the first token.
We’ve to get all tokens that reference that variable and check if any of them have equal operator as direct sibling and if so then that variable cannot be
In the fix function, get the variable keyword token (we are interested in its exact location in the source code) then replace the text with const.
Suggestions are very helpful in the editor experience where it’ll show you dropdown of fixes that you can choose from.
ESLint doesn’t process the code into the compiler phases, rather it provides an option to let you specify a Parser. By default, ESLint uses Espree which essentially converts JS source code to AST data structure, so in case you want to write a rule targeting TypeScript source code, you’ll need to specify a different parser in your
.eslintrc.json configuration file, same applies for different file extension, for HTML you might use this or creating your own parser!
Most properly you’ll find parsers for all popular extensions - A custom parser is useful in case you want to enrich the generated AST with other properties related to your project or if you’re building a custom file extension.
What about test, I know you like to write test ^^ and that’s why we’ll talk about it now…
Unit testing rules is easier and explicit than the traditional test cases that you would write.
All you have to do is to prepare the valid and invalid cases and make sure they are always as stated. Let’s go back to use-let rule and write test for it.
A sample code that use-let rule will find valid
A sample code that use-let rule will find invalid
What we need to do now is use
RuleTester to verify this requirement, it takes an object similar to the one you’d use in the in top level
This class is wrapper over mocha testing library.
invalid options take an array of cases. The easier way to write a valid test case is by using the static
only method, it takes the program to run the test against.
The invalid test case are bit more rich, you’ve to add the invalid program as well as the errors array that should present, the errors are can be as simple as message or more complex by adding the location and node type and so on
Plugins Folder Structure
This is an opinionated folder structure and nothing like what ESLint recommend
Will start with folder in the root workspace named “plugins”, each folder under this one is a plugin folder with all files related to it. A plugin is essentially an npm package that you can either publish it to a registry or link it locally. All the examples in this article are linked locally.
You do not have to use npm link for that, only add the plugin name and the path to it.
Folder structure tree for ESLint plugins.
package.json file should have at least the
name “eslint-plugin-‘plugin-name’” and
main pointing to the rules exports file (index.js).
Let’s write few more examples of ESLint rules and discover their potential.
Image alternative text (img alt)
This rule will report a message in case
img tag doesn’t have the
Here’s a sample HTML code
The AST is - some properties omitted for brevity.
- In the
urlproperty that give more info about the rule.
- The AST selector of an HTML element is
ifignores all element that are not
- If the
imgelement have an
altregardless if it does have value or not then we do not report.
This rule will report a message in case
img tag is not within
- The first
ifignores all element that are not
- We walk all the way up till we find
Wait! challenge for you, complete the rule to report a message in case
figcaption is not within the
This rule will report a message whenever console method is present.
This sample code
Will give the following AST - some properties omitted for brevity.
So in order to check if any method from the console object is used we have to use
CallExpression as AST selector and in the handler we need to inspect the callee property to make sure the method is part of the
console object and if so we report an error.
This of course won’t cover all cases, for instance, code like the following won’t be discovered by the logic we wrote above because we are no longer calling the
console.log directly thus we lost the
CallExpression signature. A proper solution is to report whenever a console method present and for that we’ve to use
MemberExpression AST selector.
The why we’ve not actually did this because I want you to be wary of such cases that can be easily forgotten.
Let’s see how we can leverage the comments and make them useful in different way.
In this example we want to report a message in case a declaration annotated with
@private tag is being used in specific directory (make sure to limit this rule to specific files in
So in order to target all nodes we need to use the
Program node in the following steps
- Extract all tokens from it.
- Create a loop that starts from the first token.
- Ignore any token that isn’t a
Keyword(afaik, you can only annotate Keywords “function”, “const”, “class” and so on).
- Ignore any keyword that doesn’t have comments.
- Ignore in case the comments doesn’t have
Once we found a keyword with comments that have
@private tag we need to find all references to it.
- From the previous sample the
indexis pointing at the keyword token.
- Get the keyword Identifier token (the identifier is always the next token after the keyword token).
- Create new loop that starts from the token after the keyword identifier to gather the references.
- Since we only need the references to the keyword token we need to ignore any token that is not Identifier or doesn’t have the same value (identifier name) as the keyword identifier.
- Once we found a reference we report the private tag message
Deprecate a function
What about you creating this to test your understanding, here’s how
- Create another folder in the plugins directory named deprecated.
- Create rules folder inside that directory and within it another folder named deprecate-function.
- In the deprecated folder create two files,
index.jsto export the rule and
package.jsonto make the plugin npm module.
- In the deprecate-function folder create two files, deprecate-function.rule.js and deprecate-function.test.js.
The folder structure should look like this
Now the rule should do the following
- Accepts an array of function names to deprecate (
- If the function name is not defined then ignore (don’t report).
- use the
Identifierselector and check if its name is exist in the functions names array (you need to use
- In case the
Ifstatement conditioned then report a message (you can make the message option as well!).
We’ve already talked about AST selector before so this is a complement to the writing.
You already know the CSS selectors like
>, …etc, you actually can use those selectors along with the AST selectors, for instance, if you want to find variables with name foo you can use the attribute selector instead of doing an extra
ESLint rules are not only a way to ensure the developer adheres to a specific style of coding, the way I see it, with ESLint you can build habits in the team members to do the things as routine and not a checklist of things they have to pass.
Following that, you can benefit from ESLint by creating rules that will help new joiners adapt easily to the codebase or folks from other teams who frequently participate in a codebase.
Final thought, ESLint rules can be developer guidelines for a codespace.
That’s specially for you ^^ to enrich your information!
Abstract Syntax Tree (AST)
We already talked about compiler phase/stages and how each of them is essentially a step towards making the computer understands the code. One of these stages is Parsing which outputs AST.
AST is the source code in tree data structure that represents the relation between the tokens, for instance, this sample code
will output the following
As you can see there’s root node
Program which is the source code (the variable foo), the body contains list of nodes because usually you’ve more than one statement 🙂. The
VariableDeclaration node is a statement that have the type of declaration and because it’s variable and the variable have different kinds, there’s
In the declarations array there’s Variable declarator that have information about the var identifier (name) and its initializer.
The node term is an abstract therefore each specific of the node have different properties related to it, for instance
BinaryExpression statement (5 + 1) will have left and right properties that have information about the operands an so on.
We’ve been utilising the AST so far for Static Code Analysis but that’s not the only usage of it. You can also create code migration tools using the AST, like changing functions names or removing parameters and more complex usage.
AST Explorer is an amazing website if you’re working with ASTs, it has plenty of parsers for the most of things that you might hear about, for instance it has an option to parse SQL Query and GraphQL and many more.
This this the toolbar