preface

1.1 the opening

Error checking and automatic formatting are familiar to us. They accompany every line of code output and every save commit. We enjoy the convenience of automatic formatting, but occasionally there are minor issues that affect the experience.

For example, in the face of the same project, all project collaborators have different formatting schemes, leading to frequent changes in code format in file submission, which slightly affects code review; After completing a branch merge and happily committing, a bunch of ESLint errors prevented you from doing so. After verifying that it didn’t matter, –no-verify resolved everything; When autosave, find quotes jumping between odd and even…

So, who gave the editor automatic code review, where did formatting rules come from and how did they work, what problems were encountered using formatting tools, and how did they arise and how did they get solved? Here’s the main character of this article: ESLint.

1.2 What is ESLint

ESLint is a JS code checker used to find and correct syntax errors and unify code formats. That is, ESLint focuses on two things:

  • Code quality: Agree on how JS should be used to avoid problems
  • Code format: only affects the beauty of the code, does not cause problems

Why do we need code reviews? As a weakly typed dynamic language, JS can be written arbitrarily, which can easily introduce problems and increase the cost of finding problems. By writing appropriate rules to constrain our code and using code validation tools to find and fix problems, we can make our code more robust and our project more reliable. ESLint is one such tool for adding constraints to our code through various rules.

There are three milestones in the history of JS code checking tools: JSLint, JSHint, and ESLint.

JSLint is one of the first front-end engineers to benefit from how to use JS better. Its core is to use JS to implement a JS parser Pratt, but it lacks configurability.

JSHint quickly replaced JSLint with the Pratt parser at its core. With the explosion of front-end technology, JSHint’s extensibility is limited by the strong coupling of rule checking and Pratt parsers, which makes it difficult to meet rapidly updated ECMAScript rules.

ESLint resets to decouple the parser and rule checking work. The parser focuses on lexical parsing, syntactic parsing of source code, and generating AST compliance with ESTree specifications. The core part of ESLint focuses on configuration generation, rule management, context maintenance, AST traversal, report output and other main processes. ESLint has separate rules and reports in the form of convention interfaces for custom extensions. This nice architecture sets ESLint apart from other Linter tools.

The basic use

2.1 the initialization

Getting Started with ESLint The init command enters a bunch of Settings, including the code environment, whether to use the React /vue framework, whether to use TS, code style Settings, etc., to quickly initialize different configurations according to your needs, and prompts you to install the appropriate dependency packages. This allows us to take advantage of good practices in the community at very low cost.

# Download, install as development-time dependency

npm install eslint --save-dev

# initialization

npx eslint --init
Copy the code

Eslintrc.{js, yML,json} is generated in the root directory of the project.

2.2 configuration

Eslintrc.{js,yml,json}. Here is an example of json configuration:

{

    "env": {

        "browser": true."es2021": true

    },

    "parserOptions": {

        "ecmaVersion": 12."sourceType": "module"."ecmaFeatures": {

            "jsx": true}},/* Introduce a plugin similar to require, which is shortened to @typescript-eslint/eslint-plugin */

    "plugins": ["@typescript-eslint"]."extends": [  

        /* Use rules recommended by ESLint as the base configuration. */ can be overridden in rules

        "eslint:recommended"]."rules": {

        "quotes": ["error"."double"]."prefer-const":"error"./* Use the rules in the @typescript-eslint/eslint-plugin */

        "@typescript-eslint/consistent-type-definitions": [  

            "error"."interface"]},"globals": {

        "$": "readonly"}}Copy the code

2.2.1 Environment and global Variables

The no-undef rule will issue a warning when accessing an undefined variable within the current source file, which can be resolved by defining a global variable. Env provides multiple environment selection fields, and an environment defines a predefined set of global variables. Globals can customize individual global variables.

2.2.2 rules

The rules field defines rules that you need to comply with. The official website provides a List of available rules for you to choose from. Quotes and prefer-const shown above are both rule options provided by the official website.

The value of the rule can be set by string. The error level can be divided into three types: “OFF”, “WARN”, and “error”. You can also set the rule in array mode. In array mode, the first item is the error level, and the remaining items are optional parameters. Each rule provided on the official website has a detailed description document, showing how to use this rule. Includes configuration and inline configuration in.eslintrc.{js, yML,json}, and suggestions for use.

2.2.3 parser

ParserOptionsESLint allows you to specify JavaScript language options you want to support. ECMAScript 5 syntax is supported by default. You can override this setting to enable support for other versions of ECMAScript and JSX. Note that support for JSX parsing does not necessarily mean support for React parsing. Specific JSX syntax in React cannot be parsed by ESLint. You need to use the eslint-plugin-react plugin to handle React.

The Parser field specifies a different parser that parses JS code into an AST that ESLint will traverse to trigger the various checking rules. Since ESLint’s default parser ESPree only supports standard syntax features, experimental and non-standard grammars such as TypeScript cannot be properly parsed. In this case, you need to use other parsers to generate AST compatible with ESTree structures. Use “parser” for TypeScript: “@typescript-eslint/parser”.

A new ESLint compatible Parser has been specified on the official website.

2.2.4 plug-in

After all, there are only a limited number of rules available officially. When we want to customize rules, we need to define a custom ESLint plugin and write the rules to the custom ESLint plugin and import them in the plugins field in the configuration file.

Using TS as an example, the @typescript-esLint /parser simply converts syntax features that ESLint doesn’t recognize into ones that ESLint does, but it doesn’t include rules and requires “plugins”: [“@typescript-eslint/eslint-plugin”], plugin. This declaration only completes the loading of the plugin, and requires the required rules to be used in rules before the corresponding code checking rules can be executed. Of course, plugin is not limited to introducing new rules; other configurations can also be introduced via plugin.

{

    // ...

    "plugins": [

        "jquery".// eslint-plugin-jquery

        "@foo/foo".// @foo/eslint-plugin-foo

        "@bar"      // @bar/eslint-plugin]."rules": {

        "jquery/a-rule": "error"."@foo/foo/some-rule": "error"."@bar/another-rule": "error"

    },

    "env": {

        "jquery/jquery": true."@foo/foo/env-foo": true."@bar/env-bar": true,}// ...

}
Copy the code

See configuring- Plugins for more information on how to use them.

The Yeoman template generator-ESLint (Yeoman is a scaffolding tool used to generate engineered directory structures containing specified framework structures) is provided for developers to create plugins that use Mocha as the test framework by default.

2.2.5 extension

Manual configuration is a lot of work, so it’s common to use the extends extension pack to preset configuration. Extends can integrate a variety of popular best practices at a surprisingly low cost.

Once a Configuration file is extended, it inherits all properties from another Configuration file, including rules, plug-ins, and language parsing options Extending Configuration Files.

The principle of

3.1 about the AST

Lint is analysis based on static code, and for ESLint, the core of our input is rules and their configuration as well as the source code to be analyzed by Lint. Source code that needs to be used with Lint varies, and it is much easier to analyze the source code if you can Abstract the commonness of JS source code. The Abstract code structure is called the AST (Abstract Syntax Tree).

AST itself is not a new topic; it is the cornerstone of the implementation of front-end tools such as Babel, Webpack, and is likely to be used wherever compilation principles are involved. For more details on AST, see Cao Cheng’s previous article :AST from start to Start guide.

ESLint uses espree by default to parse our JS statements to generate an AST. You can use the AST Explorer to view the structure of a piece of code parsed into the AST.

3.2 How does rule Take effect

Once you have the AST of your code, the next thing you need to do is to iterate through the AST to trigger rule checks and determine if the code complies with the ESLint specification based on the rule rules.

A no-debugger rule source code:

module.exports = {

    meta: {

        type: "problem".docs: {

            description: "disallow the use of `debugger`".category: "Possible Errors".recommended: true.url: "https://eslint.org/docs/rules/no-debugger"

        },

        fixable: null.schema: [].messages: {

            unexpected: "Unexpected 'debugger' statement."}},create(context) {

        return {

            DebuggerStatement(node) {

                context.report({

                    node,

                    messageId: "unexpected"}); }}; }Copy the code

As you can see, a rule is a Node module consisting of two parts: meta and Create.

  • metaMetadata that represents this rule, such as categories, documents, fixable options,schema, etc.The official documentationIt is described in detail.
  • createThis is how the rule will parse the code. Create returns an object that stores a series of callback functions that will be fired as the AST is traversed.
  • Create returns three types of object keys:

    • AST selector, triggered when traversing down the AST. The selector is used to match AST nodes. The most common selector name is the AST node type. Through the selector, you can obtain the corresponding selected node content, and then you can make certain judgments about the selected content.
  • AST selector plus:exit, such as:Program:exitIs emitted when the node is traversed and the node is returned upwards.
  • The event name corresponding to code path analysis, which is used to process code in conditional statements, loop statements, etc., which can help us find unreachable code.

Going back to the rule example given, the DebuggerStatement in the above code corresponds to the node of the debugger in the AST in the previous screenshot. The above code indicates that when a debugger statement is matched, Throw “Unexpected ‘Debugger’ Statement.”

Rules come from two sources: 1. Rules involved in configuration files 2. Rules in comments; Together constitute configuredRules objects.

Once you have the AST and rules, the next thing to do is walk through all the Rules to add listening events, and then walk through the entire AST to trigger all listening events for code checking. Here’s how rules works:

function runRules(sourceCode, configuredRules, ruleMapper, parserOptions, parserName, settings, filename) {

    const emitter = createEmitter();

    const nodeQueue = [];

    let currentNode = sourceCode.ast;



  // 1. Iterate through the AST to generate a nodeQueue. Each node is passed in twice

  // nodeQueue 形似 [ { isEntering: true, node1 } , { isEntering: true, node2 } , { isEntering: false, node2 }, { isEntering: false, node1 } ]

    Traverser.traverse(sourceCode.ast, {

        // 1.1 Is triggered when a node is entered

        enter(node, parent) {

            node.parent = parent;

            nodeQueue.push({ isEntering: true, node });

        },

        // 1.2 Triggered when leaving the node

        leave(node) {

            nodeQueue.push({ isEntering: false, node });

        },

        visitorKeys: sourceCode.visitorKeys

    });



    const lintingProblems = [];



  // 2. Run through all rules and add a listener

    Object.keys(configuredRules).forEach(ruleId= > {

        const rule = ruleMapper(ruleId);

        const messageIds = rule.meta && rule.meta.messages;

        let reportTranslator = null;

        2.1 Generate context objects for each rule,

        const ruleContext = Object.freeze(

            Object.assign(

                Object.create(sharedTraversalContext),

                {

                    id: ruleId,

                    options: getRuleOptions(configuredRules[ruleId]),

                    report(. args) {

                        // If the rule check fails, the report method is called and lintingProblems is added with an error message

                        if (reportTranslator === null) {... }constproblem = reportTranslator(... args);if(problem.fix && rule.meta && ! rule.meta.fixable) {throw new Error("Fixable rules should export a `meta.fixable` property."); } lintingProblems.push(problem); }}));Create (ruleContext) to get the selector callback object returned by CREATE

        const ruleListeners = createRuleListeners(rule, ruleContext);



        // 2.3 Mounts listener events to emitter objects for all selectors corresponding to a single rule

        Object.keys(ruleListeners).forEach(selector= > {

            const ruleListener = timing.enabled

                ? timing.time(ruleId, ruleListeners[selector])

                : ruleListeners[selector];



            emitter.on(

                selector,

                addRuleErrorHandler(ruleListener)

            );

        });

    });



    // 3. Generate eventGenerator according to emiter

    // Do code path analysis only when the top-level node is "Program"

    const eventGenerator = nodeQueue[0].node.type === "Program"

      ? new CodePathAnalyzer(new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys }))

      : new NodeEventGenerator(emitter, { visitorKeys: sourceCode.visitorKeys, fallback: Traverser.getKeys });



    // 4. Pass through nodeQueue,

    Calling eventGenerator contains two event-triggering methods, enterNode and leaveNode, to trigger the event contained in each node

    nodeQueue.forEach(traversalInfo= > {

        currentNode = traversalInfo.node;

        if (traversalInfo.isEntering) {

            eventGenerator.enterNode(currentNode);

        } else{ eventGenerator.leaveNode(currentNode); }});// 5. Return the problem set

    return lintingProblems;

}
Copy the code

The overall code main flow is as above, the key steps are annotated, omitting the detailed implementation part. The principles of ESLint are much more than that, including the implementation principles of specific rules for us to implement custom rules of high reference value, interested students can look at the official source code.

Collocation is used

4.1 VSCode

In general, we don’t use the NPX esLint command frequently to perform code checks during development, but instead use the IDE to automatically alert ESLint to errors. In VSCode, you need to install the ESLint plug-in.

Set settings.json’s save auto-formatting Settings:

  "editor.codeActionsOnSave": {

    "source.fixAll.eslint": true

  },

  "editor.formatOnSave": false.Copy the code

4.2 prettier

When using ESLint, we tend to use it in conjunction with Prettier. Prettier, an ‘attitude’ code formatting tool that automatically formats code ESLint solves formatting problems when Prettier is an ‘attitude’ code formatting tool, ESLint solves formatting problems by itself

  • Before the –fix parameter, ESLint did not format code automatically, whereas Prettier does.
  • While ESLint could also validate code formats, Prettier was better at it.

Used together, ESLint looks at code quality while Prettier looks at code format. But there is some overlap in formatting, so when Prettier and ESLint are used together there is a conflict that requires some configuration:

  1. Use eslint-config-prettier to disable all ESLint configurations where prettier conflicts with prettier by making prettier the last extends in.eslintrc, which requires installationeslint-config-prettier
// .eslintrc    

{      

    "extends": ["prettier"] // prettier must be the last prettier

}
Copy the code
  1. (Optional) Then install iteslint-plugin-prettierprettierPrettier’s rules into ESLint as a plug-in

When Prettier + ESLint used Prettier + ESLint, the format problem involves both; after Disable ESLint, Prettier takes over the format problem entirely. So why do we need this plugin? Because we still expect errors to come from ESLint, using that would be like writing the configuration for Prettier’s recommended formatting problem in ESLint rules, which would unify the source of the code problem.

// .eslintrc    

{      

    "plugins": ["prettier"]."rules": {        

        "prettier/prettier": "error"}}Copy the code

The source of the error can be seen below:

Put the above two steps together to create the following configuration, which is the official recommendation

// .eslintrc

{

  "extends": ["plugin:prettier/recommended"]}Copy the code

4.3 husky

It is now possible to detect errors at development time and fix them easily, but it is up to developers to be aware that code that does not pass esLint code can still be submitted to the repository. At this point, we need husky to intercept git operations and do another code check before git operations.

The new version of Husky uses some changes and is no longer configured directly in package.json.

// package.json

{  

  "husky": {

    "hooks": {

      "pre-commit": "eslint src/** --fix"}}}Copy the code

New version:

npm install -D husky

# husky initializes, creating the. Husky/directory and specifying it as the directory where git hooks reside

husky install 

#. Husky/will add a pre-commit shell script

# Run NPX eslint SRC /** check before git commit

npx husky add .husky/pre-commit "npx eslint src/**"
Copy the code

Json. The prepare script is automatically executed after NPM install (with no parameters).

{

  "scripts": {

    "prepare": "husky install"}}Copy the code

The.husky directory structure is as follows

The.husky/pre-commit file is generated as follows

#! /bin/sh . "$(dirname "$0")/_/husky.sh" npx eslint src/** --fixCopy the code

4.4 lint – staged

For a single commit, it may not be necessary to check all files under SRC each time, especially for older projects with historical baggage that may not be able to fix a nonconforming script all at once. So we need to use Lint-staged tools to test only the parts that are currently modified.

// package.json

{

  "lint-staged": {

    "*.{js,ts}": [

      "npx eslint --fix"]}}Copy the code

The configuration in 🌰 indicates that.js and.ts files currently modified will be detected and automatically repaired when they are submitted. After automatic repair is completed, Lint-staged files will be added to the staging area again by default, and an error message will be reported if there are unfixable errors.

Husky /pre-commit Run NPX Lint-passage validates files submitted into staged storage before COMMIT. Husky /pre-commit

#! /bin/sh

. "$(dirname "$0")/_/husky.sh"



npx lint-staged
Copy the code

summary

This article introduced the basic configuration of ESLint, along with specific rules, introduced the main process for implementing Rules, and then combined with other tools to make ESLint more useful in teamwork.

reference

  • The evolution of JS Linter
  • Discusses how ESLint works
  • Front-end Science Series (5):ESLint – Guarding the elegant moat – Digging gold
  • AST in Modern JavaScript
  • The most complete Eslint configuration template to unify team programming habits – nuggets
  • Learn how to write an ESLint plugin and how ESLint works
  • Husky uses summary

❤️ Thank you

That is all the content of this sharing. I hope it will help you

Don’t forget to share, like and bookmark your favorite things.

Welcome to pay attention to the public number ELab team receiving factory good article ~

We are from the front end department of Bytedance, responsible for the front end development of all bytedance education products.

We focus on product quality improvement, development efficiency, creativity and cutting-edge technology and other aspects of precipitation and dissemination of professional knowledge and cases, to contribute experience value to the industry. Including but not limited to performance monitoring, component library, multi-terminal technology, Serverless, visual construction, audio and video, artificial intelligence, product design and marketing, etc.

Bytedance calibration/social recruitment internal promotion code: HV1D54F

Post links: job.toutiao.com/s/RC1r5ny