ESLint 支持插件,使用插件是扩展 ESLint 功能的一种方式,可以通过插件来自定义新的规则或者处理器。

使用插件能够带来的好处包括:

  • 自定义规则:用于验证代码是否满足某种预期,如果不满足该预期,应该如何处理。
  • 自定义配置:用于定义一组规则和设置,这些规则和设置可以被重复使用,不需要在每个项目中重新定义。
  • 自定义环境:用于定义一组全局变量,这些变量在特定环境下(例如浏览器、Node.js、Jest 等)是预定义的。
  • 自定义处理器:用于从其他类型的文件中提取 JavaScript 代码,或在进行语法检查之前预处理代码。

插件名称的规范

ESLint 中的插件,每一个插件是一个 npm 包,命名的格式为 eslint-plugin-<插件名称>,如:eslint-plugin-jqueryeslint-plugin-react

插件还可以使用 scope 包的形式:@<scope>/eslint-plugin-<插件名称>,如:@jquery/eslint-plugin-jquery,还可以只有前面的 scope:@jquery/eslint-plugin

npm 官网 上面搜索 eslint-plugin 能够找到很多 ESLint 相关的插件。

使用插件

使用插件的方式

使用现有的插件,方式非常简单,就分为两步:

  1. 安装插件
  2. 在配置文件中进行配置

假设要使用 eslint-plugin-react 插件,首先安装该插件:

pnpm add eslint-plugin-react -D

接下来在配置文件中进行配置:

{
  "plugins": ["react"]
}

配置完成后,就可以在配置文件添置该插件所提供的各种规则:

{
  "plugins": [
    "react"
  ],
  "rules": {
    "react/jsx-uses-react": "error",
    "react/jsx-uses-vars": "error",
  }
}

演示

这里我们来演示 eslint-plugin-vue 插件的使用。

首先初始化一个 Vue 的项目:

npm init vue@latest

接下来控制台会出现交互式选择,选择安装 ESLint 以及 Prettier。

eslint-plugin-vue 提供了一些预定义配置的选项,说明如下:

  • plugin:vue/base:提供设置和规则以启用正确的 ESLint 解析。

针对使用 Vue.js 3.x 的配置:

  • plugin:vue/vue3-essential:包括 plugin:vue/base,并添加了一些规则以防止错误或意外行为。
  • plugin:vue/vue3-strongly-recommended:在上述配置的基础上,添加了一些能显著提高代码可读性和/或开发体验的规则。
  • plugin:vue/vue3-recommended:在上述配置的基础上,添加了一些强制执行社区主观默认的规则,以确保一致性。

针对使用 Vue.js 2.x 的配置:

  • plugin:vue/essential:包括 plugin:vue/base,并添加了一些规则以防止错误或意外行为。
  • plugin:vue/strongly-recommended:在上述配置的基础上,添加了一些能显著提高代码可读性和/或开发体验的规则。
  • plugin:vue/recommended:在上述配置的基础上,添加了一些强制执行社区主观默认的规则,以确保一致性。

除了上述预设规则集外,也可以对某些规则进行自定义,如:

rules: {
  'vue/html-quotes': ['error', 'double'],
  'vue/html-self-closing': [
    'error',
    {
      html: {
        void: 'always',
        normal: 'never',
        component: 'always'
      },
      svg: 'always',
      math: 'always'
    }
  ]
},

自定义插件

ESLint 插件主要是用来扩展 ESLint 本身没有的功能,这里包括扩展规则、扩展配置、扩展解析器。

90% 的 ESLint 插件都是以扩展规则为主,所以这些插件里面会包含大量的自定义规则。

像这一类的插件,一般一条规则会对应一个 JS 文件,JS 文件里面需要导出一个对象:

官方文档

module.exports = {
  // 元数据信息
  meta: {
    
  },
  // 规则具体的实现
  create: function() {
    return {}
  }
}
  • meta:提供这条规则相应的元数据信息
    • type:描述规则的类型。可以是以下的一个:
      • problem:可能导致错误的代码问题。
      • suggestion:可能的改进,以使代码更易于阅读和/或更具可维护性。
      • layout:布局问题,即风格指南中的问题,不会影响代码的功能。
    • docs:提供关于规则的文档信息,可以包含以下字段:
      • description:规则的简短描述,通常用于生成文档。
      • recommended:一个布尔值,表示这个规则是否在配置为 “recommended”(推荐)的情况下被启用。
      • url:指向规则文档的 URL。
    • fixable:说明是否可以自动修复由此规则识别的问题,以及如何修复。如果规则可以自动修复问题,此字段应为 codewhitespace,否则应为 null 或省略。
    • deprecated:一个布尔值,表示这个规则是否已被弃用。默认为 false
  • create:一个函数,该函数会返回一个对象,对象里面又是一个一个的方法,例如有如下的方法:
    • Program:这个方法会在遍历抽象语法树开始时被调用。
    • FunctionDeclaration:这个方法会在遍历到一个函数声明时被调用。
    • VariableDeclaration:这个方法会在遍历到一个变量声明时被调用。
    • ExpressionStatement:这个方法会在遍历到一个表达式语句时被调用。
    • CallExpression:这个方法会在遍历到一个函数调用时被调用。
    • ReturnStatement:这个方法会在遍历到一个 return 语句时被调用。

源代码最终会被解析成一个 抽象语法树,这个抽象语法树就是一个树结构,里面由一个一个的节点组成,每一个节点是一个 token,工具在处理源码的时候,实际上就会去遍历这棵树,遍历到对应的节点,然后针对对应节点做出相应的处理。

上面这些方法所对应的名称实际上都来源于 ESTree 规范里面所定义的 AST 节点类型。

这些方法里面接收一个参数,该参数是当前所遍历到的 AST 节点对象,通过这个节点对象就可以拿到当前节点一些具体的信息以及该节点对应的子节点:

create: function() {
    return {
      CallExpression(node) {
        // ...
      }
    }
}

除了节点处理函数以外,create 方法还会自动传入一个 context 参数:

create: function(context) {
    return {
      CallExpression(node) {
        // ...
      }
    }
}

context 参数提供了一些方法:

  • report(descriptor):用于报告一个问题。descriptor 是一个对象,包含了问题的信息,如问题的位置、消息等。
  • getSourceCode():返回一个 SourceCode 对象,可以使用它来访问源代码的文本和 AST。
  • getAncestors():返回一个包含当前节点的所有祖先节点的数组,数组中的第一个元素是最近的祖先。
  • getScope():返回一个代表当前作用域的 Scope 对象。

实战案例

创建一个插件项目 eslint-plugin-customrules,使用 pnpm init 后在项目根目录中创建 rules 目录,该目录用于存放的自定义规则。

创建如下两条规则:

// 不允许有 alert 语句
// alert("xxx")
module.exports = {
  meta: {
    type: "problem",
    docs: {
      description: "disallow the use of alert",
      category: "Best Practices",
    },
    fixable: null,
  },
  create(context) {
    return {
      // 这个方法会在遍历到一个函数调用时被调用
      CallExpression(node) {
        if (node.callee.name === "alert") {
          // 说明当前是一个 alert 的函数调用
          context.report({
            node,
            message: "不允许出现 alert 语句",
          });
        }
      },
    };
  },
};
// 不允许有 console.log 语句
// console.log("xxx")
module.exports = {
  meta: {
    type: "problem",
    docs: {
      description: "disallow the use of console.log",
      category: "Best Practices",
    },
    fixable: null,
  },
  create(context) {
    return {
      CallExpression(node) {
        if (
          node.callee.object &&
          node.callee.object.name === "console" &&
          node.callee.property.name === "log"
        ) {
          context.report({
            node,
            message: "不允许出现 console.log 语句",
          });
        }
      },
    };
  },
};

每一条规则对应一个 JS 文件,JS 文件导出一个对象,对象里面包含了基本的 metacreate 配置项。

下一步在 package.json 中,添加 peerDependencies

"peerDependencies": {
  "eslint": "^7.0.0"
}

最后创建入口文件,用于将所有的规则导出:

// index.js
// 该文件是整个包的入口文件,用于导出所有的规则
 
module.exports = {
  rules: {
    // 规则名称: 规则文件
    "no-alert": require("./rules/no-alert"),
    "no-console-log": require("./rules/no-console-log"),
  },
};

测试

接下来需要测试这个插件。这里选择通过 npm link 的方式来进行本地链接:

来到插件的根目录,执行 npm link,这样该项目就会创建一个软链接到全局包目录里,其他项目就可以通过 link 的方式来链接这个包。

之后创建一个测试项目,初始化后安装 eslint 依赖并链接之前写的 eslint 插件:

npm i eslint -D
npm link eslint-plugin-customrules

配置文件 .eslintrc.json 中配置:

{
  "plugins": ["customrules"],
  "rules": {
    "customrules/no-console-log": "warn",
    "customrules/no-alert": "error"
  }
}

然后在 src/index.js 中书写一些测试代码:

alert("Hello");
console.log("World");

运行 npx eslint ./src 即可看到 Lint 错误信息。