preface

Xiao Shen is a front-end intern who has just started her work. It is her first time for team development. Under the arrangement of my tutor, I got the git permission of the project and started clone.

$ git clone [email protected]:company/project.git
Copy the code

Shen began to carefully taste the code of his colleagues. Finally, with his unremitting efforts, he found a bug written by Lao Wang two years ago. After reporting it to his tutor, Shen began to modify it. Young people are easy to be impulsive, so I not only fixed the bug of Lao Wang, but also reconstructed this part of the code, using the strategy mode I just learned from the book two days ago, and removed some unnecessary if and else logic. Shen gently touched his sparse hair, proudly ready to submit the code, thinking of the first day just came to show his super coding ability. The next horrible thing happened: the code could not pass lint. He was so upset that he ran to his tutor, who told him that he only had to modify the code according to the console warning. Shen countered that the Lint tool was forcing me to remove semicolons. When I was in school, I was taught that semicolons were essential and that code without semicolons was imperfect. The tutor smiled helplessly and opened xiao Shen’s internship score sheet and ticked “poor” in team cooperation.

Unconvinced, Xiao Shen wrote a blog and published it on CSDN, which gained a lot of reading.

Q: What mistakes did Shen make on her first day on the job?

  1. Refactoring business code you don’t understand is a business development no-no.
  2. Failure to adhere to team norms, team development is too personal;
  3. I made up the above, I heard that now write the beginning of the article to make up a story.

A brief history of the Lint tool

In computer science, Lint is the name of a tool used to flag suspicious, unstructured (and potentially buggy) statements in code. It is a static program analysis tool, originally suitable for C language, developed on the UNIX platform. It has since become a generic term to describe the tools used to flag questionable statements in code in any programming language. — by wikipedia

There have been many lint tools in JavaScript’s 20-plus years of development. Here are three of the major lint tools.

  1. JSLint
  2. JSHint
  3. ESLint

JSLint

JSLint is arguably the first JavaScript lint tool developed by Douglas Crockford, author of JavaScript Language Essentials. As you can see from the style of JavaScript Language Essentials, Douglas is a man of impeccable character, so JSLint has inherited this feature. All rules for JSLint are defined by Douglas himself, This is a very Douglas style Lint tool, and if you want to use it, you have to accept all its rules. To its credit, JSLint is still being updated and a Node version is also available: Node-jsLint.

JSHint

Anton Kovalyov (who now works at Medium) developed JSHint based on JSLint, since JSLint made many people unbearable and feel oppressed by its rules. JSHint builds on JSLint to provide a rich set of configuration items that give developers a lot of freedom. JSHint has been open source from the get-go, community-driven and growing very quickly. Earlier jQuery also used JSHint for code checking, but has now moved to ESLint.

ESLint

ESLint was created in June 2013 by Nicholas C. Zakas (author of JavaScript Advanced Programming). It was created because Zakas wanted to add a custom rule using JSHint, but found that JSHint did not support it. So I developed one myself.

ESLint claims to be the next generation of JS Linter tools. Inspired by PHP Linter, ESLint parses source code into an AST and then tests the AST to determine if the code conforms to rules. ESLint uses Esprima to parse source code into an AST, and then you can use any rule to check that the AST meets expectations, which is why ESLint is so extensible.

Early source code:

var ast = esprima.parse(text, { loc: true.range: true }),
    walk = astw(ast);

walk(function(node) {
    api.emit(node.type, node);
});

return messages;
Copy the code

However, ESLint didn’t catch on at the time because it needed to convert source code to AST, which was slower than JSHint, and JSHint already had a complete ecosystem (editor support). What really took ESLint off was the introduction of ES6.

With the release of ES6, JSHint won’t be supported for a while because of the new syntax, and ESLint only needs the right parser to be able to do Lint checking. At this point, Babel provided support for ESLint, developing babel-ESLint to make ESLint the fastest lint tool to support ES6 syntax.

In 2016, ESLint incorporated JSCS, another Lint tool that was created at the same time as ESLint, because it does rule checking by generating aN AST.

Since then, ESLint has dominated the JS Linter space and become a mainstream tool in the front-end world.

Meaning of Lint tools

Consider the following question: is Lint a guarantee of code quality or a constraint for engineers?

Next, let’s look at the introduction to ESLint’s official website:

Code review is a static analysis that is often used to find problematic patterns or code and is not dependent on specific coding styles. Most programming languages have code checks, and compilers typically have built-in checks.

JavaScript is a dynamic, weakly typed language that is prone to error in development. Because there is no compiler, it is often necessary to debug during execution to find JavaScript code errors. Things like ESLint allow programmers to find problems while coding rather than during execution.

Because JavaScript is an amazing language, and while it gives us flexibility, it also opens up some holes. For example, the weak casting involved in == is really annoying, and the orientation of this is also confusing. Lint solves this problem by simply banning you from using ==, which limits the flexibility of the language but can be very beneficial.

And as a dynamic language, because lacks the compilation process, some of the errors can be found in the compilation process, can only wait to run to find, the debugging work adds some burden to us, and Lint tool is equivalent to the language compilation process, find errors before the code to run a static analysis.

So to summarize the advantages of the Lint tool:

1. Avoid low-level bugs and find out possible syntax errors

Using undeclared variables, modifying const variables…

2. Prompt to delete unnecessary code

Declared unused variables, repeated cases…

3. Make sure your code follows best practices

Please refer to Airbnb Style and javascript Standard

4. Unify the team’s code style

With or without a semicolon? TAB or space?

use

With all that said, let’s take a look at how ESLint is actually used in a practical sense.

Initialize the

If you want to introduce ESLint into an existing project, you can simply run the following command:

Install ESLint globally
$ npm install -g eslint

# Enter the project
$ cd ~/Code/ESLint-demo

Initialize package.json
$ npm init -f

# Initialize the ESLint configuration
$ eslint --init
Copy the code

After using esLint –init, there are a number of user configuration items available in the esLint CLI section of the source code.

After a series of questions and answers, you will find that an.eslintrc.js file has been generated at the root of your folder.

Configuration mode

ESLint can be configured in two ways:

1. Use comments to embed Lint rules directly into source code

This is the simplest and most crude way to define Lint rules directly in source code, using comments that ESLint can recognize.

/* eslint eqeqeq: "error" */
var num = 1
num == '1'
Copy the code

Of course we usually use comments to temporarily disable warnings for certain strict Lint rules:

/* eslint-disable */
alert('This comment goes at the top of the file and there will be no lint warnings for the entire file')

/* eslint-enable */
alert('Re-enable Lint alarms')

/* eslint-disable eqeqeq */
alert('Only disallow one or more rules')

/* eslint-disable-next-line */
alert('Current line disables Lint warnings')

alert('Current line disables Lint warnings') // eslint-disable-line
Copy the code

2. Use the configuration file to configure Lint rules

During initialization, one option is to use What file type to configure Lint (What format do you want your config file to be in?). :

{ type: "list", name: "format", message: "What format do you want your config file to be in?" , default: "JavaScript", choices: ["JavaScript", "YAML", "JSON"] }Copy the code

There are three official options:

  1. JavaScript (eslintrc.js)
  2. YAML (eslintrc.yaml)
  3. JSON (eslintrc.json)

Alternatively, you can configure it yourself by adding the eslintConfig field to the package.json file.

As you can see from the ESLint source code, the configuration file has the following priorities:

const configFilenames = [
  ".eslintrc.js".".eslintrc.yaml".".eslintrc.yml".".eslintrc.json".".eslintrc"."package.json"
];
Copy the code
.eslintrc.js > .eslintrc.yaml  > .eslintrc.yml > .eslintrc.json > .eslintrc > package.json
Copy the code

Of course, you can also use cli to specify the configuration file path:

Project level and directory level configuration

We have the following directory structure, and running ESLint in the root directory gives us two configuration files.eslintrc.js (project-level configuration) and SRC /.eslintrc.js (directory-level configuration), which are merged. But SRC /.eslintrc.js has a higher priority.

However, as long as we set “root”: true in SRC /.eslintrc.js, ESLint will consider the SRC directory as the root directory and will no longer look up configurations.

{
  "root": true
}
Copy the code

Configuration parameters

Let’s take a closer look at ESLinte’s configuration rules.

Parser configuration

{
  // Parser type
  // espima(default), babel-eslint, @typescript-eslint/parse
  "parse": "esprima".// Parser configuration parameters
  "parseOptions": {
    // Code type: script(default), module
    "sourceType": "script".// Es version number, default is 5, can also use year, such as 2015 (same as 6)
    "ecamVersion": 6.// Es feature configuration
    "ecmaFeatures": {
        "globalReturn": true.// Allow the use of return statements in the global scope
        "impliedStrict": true.// Enable strict mode globally
        "jsx": true / / enable JSX}}},Copy the code

For the @typescript-esLint /parse parser, which was primarily intended to replace the existing TSLint, the TS team has thrived as the ESLint ecosystem has expanded and esLint has more configuration items, You had to ditch TSLint and implement an ESLint parser instead. In the meantime, the parser has different configurations:

{
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "useJSXTextNode": true."project": "./tsconfig.json"."tsconfigRootDir": ".. /.. /"."extraFileExtensions": [".vue"]}}Copy the code

Environment and global variables

ESLint will detect undeclared variables and issue warnings, but some variables are declared by the library we introduced and need to be declared in the configuration ahead of time.

{
  "globals": {
    // Declare the jQuery object as a global variable
    "$": false // true indicates that the variable is writeable, and false indicates readonly}}Copy the code

It’s a bit cumbersome to declare one by one in Globals, so use env, which is the default of a set of global variables defined by an environment (similar to Babel’s presets).

{
  "env": {
    "amd": true."commonjs": true."jquery": true}}Copy the code

There are many different environments to choose from, and the default values are defined in this file. If you look at the source code, you can see that the default variables are referenced from the Globals package.

The rules set

ESLint comes with a large number of rules, and you can configure the rules you want in the rules property of your configuration file. Each rule takes one parameter, which has the following value:

  • “Off” or 0: turns off the rule
  • “Warn” or 1: Open rules, warn level errors (do not cause program exit)
  • “Error” or 2: enable rule, error level error (when triggered, the program will exit)

For example, let’s write a piece of code that uses equality, and then configure the eqeqeq rules differently.

// demo.js
var num = 1
num == '1'
Copy the code

The command line configuration is used if you only want to check a single file for a certain rule.

However, things are often not as simple as you might think. ESLint’s rules are not just off and on; each rule has its own configuration item. If you need to configure a rule, you need to use an array of parameters.

Take a look at the Quotes rule. According to the website, it supports both strings and objects.

{
  "rules": {
    // Use an array to configure the rules
    // The first parameter is whether to enable the rule
    // The following parameters are the configuration items of the rule
    "quotes": [
      "error"."single",
      {
        "avoidEscape": true}}}]Copy the code

According to the rules above:

// bad
var str = "test 'ESLint' rule"

// good
var str = 'test "ESLint" rule'
Copy the code

extension

An extension is a quick and easy way to use lint rules already written by someone else. Extensions generally support three types:

{
  "extends": [
    "eslint:recommended"."plugin:react/recommended"."eslint-config-standard"]},Copy the code
  • eslint:There are two official extensions to ESLint:eslint:recommendedeslint:all.
  • plugin:The extension that starts is a plug-in type, which can also be directly inpluginsProperty, which is covered in more detail in a later section.
  • The last type of extension comes from the NPM package, which officially must be extended witheslint-config-This header can be omitted when used, as in the example aboveeslint-config-standardI could just write it asstandard.

If you are satisfied with your configuration, you can also publish your Lint configuration to the NPM package by naming the package eslint-config-xxx, and in the meantime, You need to declare the version number of ESLint you depend on in the peerDependencies field of package.json.

The plug-in

The use of plug-in

There are hundreds of rules to choose from, but that’s not enough, because the official rules only check standard JavaScript syntax, and if you’re writing JSX or Vue single-file components, ESLint’s rules start to fall into place.

In this case, you need to install an ESLint plug-in to customize certain rules for checking. Plugins for ESLint have the same naming format as extensions, starting with eslint-plugin-, which can also be omitted when used.

npm install --save-dev eslint-plugin-vue eslint-plugin-react
Copy the code
{
  "plugins": [
    "react".// eslint-plugin-react
    "vue".// eslint-plugin-vue]}Copy the code

An extension is a plugin that loads the plugin.

{
  "extends": [
    "plugin:react/recommended"]},Copy the code

The rules for loading plug-ins by extension are as follows:

extPlugin = `plugin:${pluginName}/${configName}`
Copy the code

Install the eslint-plugin-react package. Install the eslint-plugin-react package. Install the eslint-plugin-react package. So where does this configuration name come from?

You can see the source code for eslint-plugin-react.

module.exports = {
  // Custom rule
  rules: allRules,
  // Available extensions
  configs: {
    // plugin:react/recommended
    recomended: {
      plugins: [ 'react' ]
      rules: {...}
    },
    // plugin:react/all
    all: {
      plugins: [ 'react' ]
      rules: {... }}}}Copy the code

The configuration name is defined by the configs property of the plugin configuration, where the configuration is actually an extension to ESLint. This way plugins and extensions can be loaded.

Developing a plug-in

ESLint officially provides a Yeoman template (generator-ESLint) for developers’ convenience.

# Install module
npm install -g yo generator-eslint

Create a directory
mkdir eslint-plugin-demo
cd eslint-plugin-demo

# create template
yo eslint:plugin
Copy the code

Once the project is created, you can start creating a rule, and fortunately generator-ESLint has template code for creating rules in addition to the template code for the plug-in. Open the previously created eslint-plugin-demo folder and add a rule to that directory. I want this rule to detect whether my code has a console, so I call it disable-console.

yo eslint:rule
Copy the code

Let’s look at how to specify a rule for ESLinte:

Open lib/rules/disable-console.js and you can see the default template code as follows:

module.exports = {
  meta: {
    docs: {
      description: "disable console".category: "Fill me in".recommended: false
    },
    schema: []},create: function (context) {
    // variables should be defined here
    return {
      // give me methods}; }};Copy the code

A brief description of the parameters (see the official documentation for more details) :

  • Meta: Some description of the rule
    • Docs: description object of the rule
      • Descrition (string) : Short description of a rule
      • Category (string) : Category of the rule (see the official website for specific categories)
      • Recommended (Boolean) : Join or noteslint:recommended
    • Schema (array) : configuration items accepted by a rule
  • Create: Returns an object containing a set of event hooks that ESLint fires when iterating through the JavaScript code AST.

Before going into the details of how to create a rule, let’s talk about the AST (Abstract Syntax tree). ESLint uses a JavaScript parser called Espree to parse JavaScript code into an AST. The AST is then deeply traversed, with each rule listening for the matching process and checking for each type matched. To facilitate viewing the various node types of the AST, here is a site that provides a very clear view of what a piece of code looks like after being parsed into the AST: AstExplorer. If you want to find the types of all AST nodes, look at ESTree.

You can see that console.log() belongs to the CallExpression(calling statements) in ExpressionStatement.

{
  "type": "ExpressionStatement"."expression": {
    "type": "CallExpression"."callee": {
      "type": "MemberExpression"."object": {
        "type": "Identifier"."name": "console"
      },
      "property": {
        "type": "Identifier"."name": "log"
      },
      "computed": false}}}Copy the code

So, to determine if console is called in our code, we can write a CallExpression method in the object returned by the create method that listens for calling statements as ESLint iterated through the AST. Then check whether the call statement is a console call.

module.exports = {
  create: function(context) {
    return {
      CallExpression(node) {
        // Get the calling object of the calling statement
        const callObj = node.callee.object
        if(! callObj) {return
        }
        if (callObj.name === 'console') {
          // Notify ESLint if the calling object is console
          context.report({
            node,
            message: 'error: should remove console'})}},}}}Copy the code

As you can see, we end up using the context.report method to tell ESLint that this is a bad piece of code, depending on which rule [OFF, WARN, error] is in the ESLint configuration.

Before introducing rules, we mentioned that rules can accept configuration. Now let’s see how to accept configuration items when we make our own rules. It is easy to define the type of the parameter in the schema of the mate object, and then get it in the create method using context.options. The next change to disable-console is to add a parameter that is an array of console methods that are allowed to be called.

module.exports = {
  meta: {
    docs: {
      description: "disable console".// Rule description
      category: "Possible Errors".// Rule category
      recommended: false
    },
    schema: [ // Accept an argument
      {
        type: 'array'.// Accept arguments of type array
        items: {
          type: 'string' // Each entry in the array is a string}}},create: function(context) {
    const logs = [ // All methods of console
        "debug"."error"."info"."log"."warn"."dir"."dirxml"."table"."trace"."group"."groupCollapsed"."groupEnd"."clear"."count"."countReset"."assert"."profile"."profileEnd"."time"."timeLog"."timeEnd"."timeStamp"."context"."memory"
    ]
    return {
      CallExpression(node) {
         // The accepted parameters
        const allowLogs = context.options[0]
        const disableLogs = Array.isArray(allowLogs)
          // Filter out methods that are allowed to be called
          ? logs.filter(log= >! allowLogs.includes(log)) : logsconst callObj = node.callee.object
        const callProp = node.callee.property
        if(! callObj || ! callProp) {return
        }
        if(callObj.name ! = ='console') {
          return
        }
        // Check for disallowed console methods
        if (disableLogs.includes(callProp.name)) {
          context.report({
            node,
            message: 'error: should remove console'})}},}}}Copy the code

After the rule is written, open tests/rules/disable-console.js and write the test case.

var rule = require(".. /.. /.. /lib/rules/disable-console")
var RuleTester = require("eslint").RuleTester

var ruleTester = new RuleTester()
ruleTester.run("disable-console", rule, {
  valid: [{
    code: "console.info(test)".options: [['info']]}],invalid: [{
    code: "console.log(test)".errors: [{ message: "error: should remove console"}}}]]);Copy the code

Finally, just import the plug-in and turn on the rules.

// eslintrc.js
module.exports = {
  plugins: [ 'demo'].rules: {
    'demo/disable-console': [
      'error'['info']].}}Copy the code

The best configuration

There are a number of recommended coding specifications for JavaScript, of which the following two are well known:

  1. airbnb style
  2. javascript standard

AlloyTeam’s eslint-config-alloy is also recommended here.

But the code specification is something that is best worked out between team members to make sure that everyone can accept it, and if there is a disagreement, the majority rule. Although the title of this section is Optimal configuration, there is no optimal solution in the software industry, and even javascript Standard is not a javascript standard coding specification. It is simply called what is best.

Finally, ESLint and Prettier could be used together to unify not only coding conventions but code styles as well. For a practical approach, see my article: Using ESLint+Prettier to unify front-end code styles.

conclusion

JSLint and JSHint parse code in a top-down manner, but ESLint and JSHint parse code in a top-down manner. In addition, the early JavaScript syntax is not updated for thousands of years, so it can be quickly parsed to find possible syntax errors and irregularities in the code. However, since the release of ES6, JavaScript syntax has changed a lot, such as arrow functions, template strings, extension operators… JSLint and JSHint cannot detect ES6 code without updating the parser. ESLint, on the other hand, takes the AST approach to statically analyzing code, while retaining great extensibility and flexible configuration capabilities. This also tells us that in the daily coding process, it is important to consider the subsequent scalability.

Because of this powerful extension capability, many of the industry’s JavaScript coding specifications can be quickly implemented across teams, and the team’s own custom code specifications can also be shared.

Finally, hopefully you’ve understood the benefits of ESLint, learned how to use ESLint, and been able to introduce ESLint to existing projects to improve the quality of your code.

reference

  • ESLint website
  • The evolution of JS Linter
  • Discusses how ESLint works