Eslint is a tool we use every day. We use its CLI or API for code error checking and formatting checking, and sometimes write rules for custom checking and fixing.

Although it is used every day, we rarely understand how it is implemented. Understanding how Eslint works can help you use it better and write plugins better.

So, in this article we’ll explore the implementation of Eslint through source code.

Linter

Linter is the core class of ESLint, and it provides the following apis:

verify / / check
verifyAndFix // Check and fix

getSourceCode / / get the AST
defineParser / / define the Parser
defineRule / / define the Rule
getRules // Get all the rules
Copy the code

The SourceCode is the AST (Abstract syntax tree), Parser parses source strings into the AST, and Rule is the Rule we configured to check the AST. These apis are relatively easy to understand.

Linter’s main functionality is implemented in verify and verifyAndFix when the command line specifies –fix or the configuration file specifies fix: True calls verifyAndFix to check and fix the code; otherwise, Verify is called to check.

So how do verify and fix work? This is the core of ESLint:

Determine the parser

We know that Eslint rules are checked based on the AST, so parse the source code into the AST first. Eslint’s parser can also be switched, so you need to find what parser to use first:

By default, Eslint comes with espree, or can be configured to switch to another parser, such as @esLint /babel-parser, @typescript/ esLint-parser, etc.

Here is the logic for resolve Parser:

Once the Parser is identified, it is time to call the parse method.

The parse into SourceCode

The parse method of parser parses SourceCode into an AST, and in eslint the AST is wrapped by SourceCode. SourceCode means AST.

With the AST, you can call Rules to check the AST

Call rule to check SourceCode for lintingProblems

Parse then calls the runRules method to check the AST and returns problems, which tell you what went wrong and how to fix them.

So how does runRules work as rules?

Much like the Babel plugin, rule is implemented by registering which AST to check for.

RunRules will iterate through the AST and emit different events for each AST it encounters. In this way, you can execute different rules while traversing the AST.

To register the listener:

Iterate through the AST, emit different events, and fire a listener:

Thus, after traversing the AST, all rules are called, which is how the rule works.

Also, when traversing, you pass in the context, which you can get in the rule, such as scope, Settings, etc.

And ruleContext, which is available when you call the AST listener:

In rule, errors are reported through the API of this report, so that all errors can be collected and printed.

What is this problem?

linting problem

Lint problem is the result of checking, i.e., what error message exists from line (column) to endLine (column).

There is also how to fix, repair is actually from that subscript to which subscript (range), what text to replace.

Why is fix a structure like range return and text? Because its implementation is a simple string substitution.

Implement automatic fix through string substitution

After traversing the AST, calling all the rules, and collecting linting problems, you are ready to fix.

The source code for the fix section looks like this:

Verify checks and then automatically fixes based on the fix information.

Fix is a string substitution:

Some of you might have noticed, why do string replacements have a while loop?

Because the range between multiple fixes, that is, the range of replacement, may overlap, if there is overlap, it will be put to the next repair, so that the while loop can repair 10 times at most, if there are still fixes, it will not repair.

This is how fix is implemented, through string substitution, if there is overlap loop fix.

The preprocess and postprocess

The core verify and fix processes are those above, but Eslint also supports before and after processing. Pre and Post processes, which are also defined in the plugin.

module.exports = {
    processors: {
        ".txt": {
            preprocess: function(text, filename) {
                return [ // return an array of code blocks to lint
                    { text: code1, filename: "0.js" },
                    { text: code2, filename: "1.js"},]; },postprocess: function(messages, filename) {
              
                return[].concat(... messages); }}}};Copy the code

The previous processing was to parse non-JS files into js files, much like webpack loaders, which allows Eslint to handle non-JS files.

What happens after that? You can filter out some of the messages or make some changes or something like that.

So how does preProcess and PostProcess work?

This one is a little bit easier, just call it before and after Verify.

Filter out some problems by comment directives

We know that ESLint also supports configuration via comments, such as /* eslint-disable */ /*eslint-enable*/.

So how does it work?

The configuration of comments is collected by scanning the AST for all configurations. This configuration is called commentDirective, which line and column Eslint is in effect.

Then, at the end of Verify, filter the linting problems you have collected.

This is how Eslint works:

Eslint and CLIEngine class

Linter is the core functionality that we’ve covered, but in a command-line scenario you need to handle some command-line arguments, so you need to wrap another layer of CLIEngine to do file reading and writing, command-line argument parsing.

It has apis such as executeOnFiles and executeOnText, and is a top-level wrapper based on the Linter class.

But instead of exposing it directly, CLIEngine wraps another layer of EsLint class, which is just a nice facade that hides irrelevant information.

Let’s take a look at the apis that esLint eventually exposed:

  • Linter is the core class that lint text directly
  • ESLint handles configuration, reading and writing files, etc., and then calls Linter for Lint (the middle layer of CLIEngine is not exposed)
  • SourceCode is used to encapsulate AST
  • RuleTester is an API for rule testing.

conclusion

The implementation of ESLint has been clarified using the source code:

The core class of ESLint is Linter, which is divided into several steps:

  • Preprocess, which processes non-JS text into JS
  • Determine parser (espree by default)
  • Call parser to parse SourceCode into SourceCode (ast)
  • Call rules to check SourceCode and return Linting Problems
  • Filter the Problems by scanning the directives in the comments
  • Postprocess, once for problems
  • Implement automatic fix based on string substitution

In addition to the core Linter class, there is the CLIEngine class for handling configuration and reading and writing files, as well as the eventually exposed Eslint class.

Here’s how Eslint works, and it’s pretty simple:

Check based on AST, fix based on string, pre and POST process before and after, support annotation to configure filtering out some problems.

With that sorted out, Eslint is even source code mastered.