ESLint rules/extends/plugins

rules

Some of the core rules listed on the website start with ☑️, some with 🔧, some with ☑️ 🔧, and some with nothing at all.

☑️ : represents a rule included in extends: ‘ESLint :recommended’.

🔧 : indicates that you can use –fix to correct the modified rule.

"no-console": 0."no-debugger": 1."no-trailing-spaces": 2."quotes": [2."single"].// Indicates that characters should use single quotation marks. If the rule is violated, an error will be reported.
Copy the code
  • offOr 0: disables the rule
  • warnOr 1: turn on the rule,warnLevel of error (does not cause program exit)
  • errorOr 2: turn on the rule,errorLevel of error (the program will exit when triggered)

extends

None of the rules take effect by default, and extends specifies which rules to apply. For example extends: ‘ESLint :recommended’ means rules that are checked in applying ESLint Rules.

It can be configured as:

  • String: indicates the path or of a configuration fileShareable configurationThe name of the (eslint:recommended eslint:all).
  • Array[String] : multiple configuration combinations. The later configuration inherits and overwrites the previous configuration.

Shareable Configuration: This is an NPM package of configuration objects that is used to ensure that they are already installed in a directory that ESLint can reference. Eslint-config – can be omitted when used with extends. For example, if you need to validate React style code, extends: ‘eslint-config-react’ is also written as extends: ‘React’.

// .eslintrc.js
module.exports = {
  extends: [  // Array[String]
    'eslint:recommended'.// The name of the shareable configuration
    './path-to-config'.// Path to the configuration file
    'eslint-config-react'./ / full name
    'react'./ / abbreviation].extends: 'eslint:recommended' // String
};
Copy the code

plugins

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 TypeScript, ESLint rules start to fall into disarray.

In this case, you need to install the ESLint plugin to customize certain rules for checking. ESLint plugins, like extends, have a fixed naming format, starting with eslint-plugin-, which can also be omitted when used.

For example, if we want to use TypeScript in a project, we need to change the parser to @typescript-eslint/parser and install the @typescript-eslint/eslint-plugin to extend the rule. Rules in plugins are disabled by default, so we need to enable the rules to be used in rules. In other words, plugins should be used in conjunction with rules. As follows:

// NPM I --save-dev @typescript-eslint/eslint-plugin // Register the plugin
{
  "parser": "@typescript-eslint/parser"."plugins": ["@typescript-eslint"].// Import plug-ins
  "rules": {
    "@typescript-eslint/rule-name": "error"    // Use plug-in rules
    '@typescript-eslint/adjacent-overload-signatures': 'error'.'@typescript-eslint/ban-ts-comment': 'error'. }}Copy the code

Extends comes in handy when writing a bunch of configurations in rules to enable the @typescript-eslint/eslint-plugin plugin rules can be cumbersome.

{
  extends: 'plugin:@typescript-eslint/recommended'
}
Copy the code

Create a new ESLint plug-in

Plugin goal: Disallow the second parameter of setTimeout in a project to be a number. For example, setTimeout(() => {}, 2) is a violation, const num = 2; SetTimeout (() => {}, num) is OK.

Project initialization

  • Eslint provides the Yeoman template (generator-ESLint) for developers to build plugins.

    npm install -g yo generator-eslint
    Copy the code
  • Initialize the project directory

    mkdir eslint-plugin-irenePugin
    cd eslint-plugin-irenePugin
    yo eslint:plugin
    Copy the code

    The command line interaction flow is displayed. After that, the project directory of the customized plug-in is generated

    ? What is your name? irene ? What is the plugin ID? Irenelint // Plug-in ID? Type a short description of this plugin: for testing creating an eslint plugin? Does this plugin contain custom ESLint rules? Yes // Do you include custom ESLint rules? Does this plugin contain one or more processors? No // Whether to contain one or more processors create package.json create lib/index.js create readme.mdCopy the code
  • Create rules

    yo eslint:rule
    Copy the code

    The next step is to enter the command line interaction process. After that, a rule file template is generated

    ? What is your name? irene ? Where will this rule be published? Where will the ESLint Plugin // rules be published ❯ ESLint Core // The official ESLint Plugin // ESLint Plugin? What is the rule ID? Settimeout-no-number // Rule ID? Type a short description of this rule: The second param of setTimeout is forbidden to use number? Type a short example of the code that will fail: setTimeout(() => {}, Create docs/rules/settimeout-no-number.md create lib/rules/settimeout-no-number.js create tests/lib/rules/settimeout-no-number.jsCopy the code
  • The generated project directory is as follows

    ├ ─ ─ the README. Md ├ ─ ─ docs / / using document │ └ ─ ─ rules / / all rules of document │ └ ─ ─ settimeout - no - number. Specific rules md / / document ├ ─ ─ lib / / eslint │ rules of development ├ ─ ─ index. Introduced js + export rules folder rule │ └ ─ ─ rules / / this directory can build multiple rules │ └ ─ ─ settimeout - no - number. Js / / rules details ├ ─ ─ package. The json └ ─ ─ Tests // Unit tests└ ─ ─ lib └ ─ ─ rules └ ─ ─ settimeout - no - number. Js / / test file to the ruleCopy the code
  • Installation project dependencies

    npm install
    Copy the code

A rule template

Open lib/rules/settimeout-no-number.js and you can see the template generated after the preceding command line operations.

module.exports = {
  meta: {
    docs: {
      description: "the second param of setTimeout is forbidden to use number".category: "Fill me in".recommended: false
    },
    fixable: null.// or "code" or "whitespace"
    schema: [
      // fill in your schema]},create: function(context) {
    return {
      // give me methods}; }};Copy the code

The create method returns an object whose key is a selector and value is a callback function (with an AST node as an argument), for example: {‘CallExpression’: (node) => {}}, ESLint will collect all selectors listened by the rule and the corresponding callback function, and trigger the corresponding callback whenever a selector is matched while traversing the AST.

AST: Abstract Syntax Tree

ESLint implements code verification and formatting by parsing code into an AST and iterating through it, as discussed below. Now let’s take a look at the AST resolved by setTimeout(() => {}, 2). Online AST

Write rules

By observing the generated AST, filter out the code we want to select and judge the value of the code.

// lib/rules/settimeout-no-number.js
module.exports = {
  meta: {
    docs: {
      description: "the second param of setTimeout is forbidden to use number".category: "Fill me in".recommended: false
    },
    fixable: null.// or "code" or "whitespace"
    schema: [
      // fill in your schema]},create: function(context) {
    return {
      // give me methods
      'CallExpression': (node) = > {
        if(node.callee.name ! = ='setTimeout') return // Not setTimeout

        const timeNode = node.arguments && node.arguments[1] // Get the second argument
        if(! timeNode)return

        if (timeNode.type === 'Literal'  && typeof timeNode.value === 'number') {
          context.report({
              node,
              message: 'setTimeout the second argument is forbidden to be a number '}}}}; }};Copy the code

The test case

Provide some test code that violates and passes the rules

// tests/lib/rules/settimeout-no-number.js
var rule = require(".. /.. /.. /lib/rules/settimeout-no-number"), RuleTester = require("eslint").RuleTester;

var ruleTester = new RuleTester();
ruleTester.run("settimeout-no-number", rule, {
  valid: [{code: "let num = 1000; setTimeout(() => { console.log(2) }, num)"],},invalid: [{code: "setTimeout(() => {}, 2)".errors: [{message: "SetTimeout the second argument must not be a number".// Consistent with the error thrown by rule
          type: "CallExpression".// rule listens to the corresponding hook},],},],});Copy the code

Auto repair

  • Fixable: ‘code’ starts fixing functionality;
  • Context.report () provides a fix function;
// lib/rules/settimeout-no-number.js
module.exports = {
  meta: {
    docs: {
      description: "the second param of setTimeout is forbidden to use number".category: "Fill me in".recommended: false
    },
    fixable: 'code',},create: function(context) {
    return {
      // give me methods
      'CallExpression': (node) = > {
        if(node.callee.name ! = ='setTimeout') return // Not setTimeout

        const timeNode = node.arguments && node.arguments[1] // Get the second argument
        if(! timeNode)return

        if (timeNode.type === 'Literal'  && typeof timeNode.value === 'number') {
          context.report({
            node,
            message: 'setTimeout the second argument is forbidden to be a number '.fix(fixer) {
              const numberValue = timeNode.vlaue;
              const statementString = `const num = ${numberValue}\n`;
              return [
                fixer.replaceTextRange(node.arguments[1].range, 'num'), fixer.insertTextBeforeRange(node.range, statementString) ] } }) } } }; }};Copy the code

debugging

Click Debug, and then select the project.

Click Settings to open a launch.json file with the program field to debug.

Open the debugger in lib/rules/settimeout-no-number.js and click start program.

Release the plugin

  • NPM: NPM login

  • Publish NPM package: NPM publish

use

  • Installing a plug-in

    npm install --save-dev eslint-plugin-irenelint
    Copy the code
  • Introduce plug-ins and enable rules

    • Through the plugins

      // .eslintrc.js
      module.exports = {
        plugins: [ 'irenelint'].rules: {
          'irenelint/settimeout-no-number': 'error'}}Copy the code
    • Through the road,

      Since rules in plugins are not enabled by default, they need to be enabled one by one in rules. When rules are too numerous to write, extends is used.

      First, we need to modify lib/index.js

      // lib/index.js
      var requireIndex = require("requireindex");
      
      const output = {
        rules:  requireIndex(__dirname + "/rules"), // All rules
        configs: {
          recommended: {
            plugins: ['irenelint'].// Import plug-ins
            rules: {
              'irenelint/settimeout-no-number': 'error' // Enable the rule}}}}module.exports = output;
      Copy the code

      And then use extends

      // .eslintrc.js
      module.exports = {
        extends: [ 'plugin:irenelint/recommended']}Copy the code

test

Before repair: The first prompt is automatic repair prompt

After repair: If auto-repair on save is configured, it will be automatically corrected on save.

ESLint principle

eslint/lib/linter/lint.js

Assume that the content of the file to be verified is

console.log('irene');
Copy the code

The following AST is generated based on the file content, and each node is passed to the nodeQueue twice. Online AST

nodeQueue = [
  {
    isEntering: true.node: {
      type: 'Program'.body: [Array].sourceType: 'module'.range: [Array].loc: [Object].tokens: [Array].comments: [].parent: null}}, {isEntering: true.node: {
      type: 'ExpressionStatement'.expression: [Object].range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
      type: 'CallExpression'.callee: [Object].arguments: [Array].optional: false.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
      type: 'MemberExpression'.object: [Object].property: [Object].computed: false.optional: false.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
      type: 'Identifier'.name: 'console'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
      type: 'Identifier'.name: 'console'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
      type: 'Identifier'.name: 'log'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
      type: 'Identifier'.name: 'log'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
      type: 'MemberExpression'.object: [Object].property: [Object].computed: false.optional: false.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: true.node: {
      type: 'Literal'.raw: "'irene'".value: 'irene'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
      type: 'Literal'.raw: "'irene'".value: 'irene'.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
      type: 'CallExpression'.callee: [Object].arguments: [Array].optional: false.range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
      type: 'ExpressionStatement'.expression: [Object].range: [Array].loc: [Object].parent: [Object]}}, {isEntering: false.node: {
      type: 'Program'.body: [Array].sourceType: 'module'.range: [Array].loc: [Object].tokens: [Array].comments: [].parent: null}}]Copy the code

Iterate over all consolidated rules. If the rule is not 0 or ‘off’ (that is, if the rule is on), get the rule object for that rule and execute create to return the listener, which indicates which AST nodes the rule listens on. The corresponding callback function is executed when iterating over these nodes.

// Assume that the consolidated rules are as follows
configuredRules = {
  '@typescript-eslint/no-explicit-any': [ 0].// ruleId: [severity]
  '@typescript-eslint/explicit-module-boundary-types': [ 0].'prettier/prettier': [ 'error'].'@typescript-eslint/no-unused-vars': [ 'warn'],... } ruleObj = {meta: 
  create: (context) = > {
    return {
      'CallExpression:exit': func1,
      'Identifier': func2
    }
  }
}
Copy the code

Iterate over the listener objects for this rule, registering listener functions for each AST node

listeners: {
  'CallExpression:exit': [func1],
  Identifier: [func2]
}
Copy the code

At the end of the process, we have a Listeners object where the KEY is the AST node and the value is an array of callbacks.

Iterating through the nodeQueue of the first step triggers the listeners’ corresponding callback functions, such as CallExpression, to execute the functions in the gallilisteners.CallExpression array, which detect whether the current node is violating the rules. If it is violated, warn/error is reported and stored in lintingProblems;

Return lintingProblems

reference

  • Blog.csdn.net/obkoro1/art…
  • Juejin. Cn/post / 690978…

other

  • ESLint resolves package name
  • ESLint is used with Prettier