[陈同学 i 前端] Eslint | 自定义插件编写

753 阅读7分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

前言

大家好,我是陈同学,一枚野生前端开发者,感谢各位的点赞、收藏、评论

在前端发展日益强大的今天,我们的前端生产项目逐渐变得强大,因此也需要大量的开发人力支持,但每一位同学的代码编写风格都不太一致,有的同学可能喜欢使用双空格缩进而有的却喜欢用四空格,有的同学习惯语句后加分号而有的同学不喜欢加分号。我们需要有一个机制用于统一同一个项目当中的代码风格与规范,对这块有了解的同学应该马上就能想到著名的Eslint

大家平时可能在项目当中已经使用过Eslint工具,当遇到一些团队内部特殊的格式规范需求,就需要开发Eslint插件,今天我们就来聊一聊如何开发Eslint插件以及编写简单的规则

本文阅读成本与收益如下:

阅读耗时:7mins

全文字数:7k+

预期效益

  • 掌握 Eslint 插件编写方法

初始化环境

ESLint有一个非常轻量级的开发环境,可以快速轻松地更新代码

安装 nodejs

若开发环境尚未安装 nodejs 环境,请前往 nodejs.org/ 下载并安装最新的稳定版本

同时确保npm包管理工具可用

安装插件项目生成器

全局安装yogenerator-eslint两个包,安装过程中会新增操作系统的全局命令yo

npm install -g yo generator-eslint

安装全局命令

创建插件项目

  • 建立一个项目文件夹(如:eslint-plugin-0)
  • 进入项目文件夹(cd eslint-plugin-0)
  • 运行yo生成项目结构
mkdir eslint-plugin-0
cd eslint-plugin-0
yo eslint:plugin

通过一系列的命令行问答交互后即可获得初始项目目录结构

企业微信截图_e86b603a-fb94-4d76-a0b8-866b9f9a07ed

|-- eslint-plugin-0
    |-- .eslintrc.js
    |-- .gitignore
    |-- README.md // 当前第三方包使用说明
    |-- package-lock.json
    |-- package.json
    |-- docs // 存放Eslint规则说明文档
    |   |-- rules
    |-- lib
    |   |-- index.js // 插件入口文件
    |   |-- rules // 存放规则定义文件
    |-- tests // 存放测试用例
        |-- lib
            |-- rules

生成规则模版文件

创建插件项目后,默认是只有简单的目录结构

需要通过生成器命令生成自定义规则模版文件(docs、lib、tests)

yo eslint:rule

wecom-temp-118829-f2dc1c8a37bd96d8abb22170b2dc1f57

|-- eslint-plugin-0
    |-- .eslintrc.js
    |-- .gitignore
    |-- README.md // 当前第三方包使用说明
    |-- package-lock.json
    |-- package.json
    |-- docs // 存放Eslint规则说明文档
    |   |-- rules
    |       |-- demo-1.md // demo-1的规则解释
    |-- lib
    |   |-- index.js // 插件入口文件
    |   |-- rules // 存放规则定义文件
    |       |-- demo-1.js // 自定义规则文件 demo-1(这里编写我们的AST检查逻辑代码)
    |-- tests // 存放测试用例
        |-- lib
            |-- rules
                |-- demo-1.js // demo-1的规则测试
  • lib/rules 目录: 源码文件 (for example, demo-1.js)
  • tests/lib/rules 目录: 测试文件 (for example, demo-1.js)
  • docs/src/rules 目录: Markdown 文档文件 (for example, demo-1.md)

以上便是我们开发前的环境初始化流程

Eslint规则定义源码文件格式

// lib/rules/demo-1.js
'use strict';

//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------

/** @type {import('eslint').Rule.RuleModule} */
module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'can not use var',
      recommended: true,
      url: 'https://eslint.org/docs/rules/no-var',
    },
    fixable: 'code',
    schema: [], // no options
  },
  create: function (context) {
    return {
      // callback functions
    };
  },
};

meta(object): 包含规则的元数据

  • type (string) :包含规则的类型, 对应的值只能是"problem", "suggestion", "layout":
    • "problem": 该规则标识的代码要么会导致错误,要么会导致令人困惑的行为。开发人员应该优先考虑解决这个问题
    • "suggestion": 该规则确定了一些可以以更好的方式完成的事情,但如果不更改代码,就不会发生错误
    • "layout": 该规则主要关注空格、分号、逗号和括号,这些程序中决定代码外观而不是执行方式的所有部分。这些规则适用于AST中没有指定的代码部分
  • docs (object) 文档是 Eslint 规则必需组成:
    • description (string) 提供一个关于规则的简短描述
    • recommended (boolean) 配置文件当中是否继承 "extends": "eslint:recommended" 属性
    • url (string) 指定一个 URL 能够看到完整的规则文档 (enabling code editors to provide a helpful link on highlighted rule violations)

在自定义规则或插件中,您可以省略文档或包含其中所需的任何属性

  • fixable(string): 要么是"code"要么是"whitespace",如果命令行上的——fix选项自动修复规则报告的问题

重要:固定属性对于固定规则是强制性的。如果没有指定这个属性,当规则试图产生一个修复时,ESLint将抛出一个错误。如果规则不可修复,则省略 fixable 属性

  • hasSuggestions(boolean): 指定规则是否可以返回建议(如果省略默认为 false)

重要:hasSuggestions 属性对于提供建议的规则是强制的。如果这个属性没有设置为 true,当规则试图产生一个建议时,ESLint 将抛出一个错误。如果规则不提供建议,则省略 hasSuggestions 属性

  • schema(array): 指定 ESLint 可以防止无效规则配置的选项

  • deprecated(boolean): 表示规则是否已被弃用。如果规则没有被弃用,可以省略 deprecated 属性

  • replacedBy(array): 对于已弃用的规则,指定替换规则

create(function): 返回一个带有方法的对象,ESLint 在遍历 JavaScript 代码的抽象语法树(由 ESTree 定义的 AST)时调用这些方法来“访问”节点

AST 结构预览器:astexplorer.net/

带方法的对象中的 key 有两种:选择器(节点类型/字符串表达式)、事件名

  • 如果一个键是一个节点类型选择器,ESLint 在向下遍历树时调用 visitor 函数((node)=>{}

  • 如果一个键是节点类型选择器:exit, ESLint 在向上遍历树时调用 visitor 函数((node)=>{}

  • 如果一个键是一个事件名,ESLint 调用 handler 函数进行代码路径分析((codePath, node)=>{}

// 官网文档示例
function checkLastSegment(node) {
  // report problem for function if last code path segment is reachable
}
module.exports = {
  meta: {
    // ...
  },
  create: function (context) {
    // declare the state of the rule
    return {
      ReturnStatement: function (node) {
        // at a ReturnStatement node while going down
      },
      // at a function expression node while going up:
      'FunctionExpression:exit': checkLastSegment,
      'ArrowFunctionExpression:exit': checkLastSegment,
      onCodePathStart: function (codePath, node) {
        // at the start of analyzing a code path
      },
      onCodePathEnd: function (codePath, node) {
        // at the end of analyzing a code path
      },
    };
  },
};

实现一个简单的规则

检测目标:不允许使用 var 定义变量

// lib/rules/demo-1.js
module.exports = {
  meta: {
    // ...
  },
  create: function (context) {
    function reportIllegalUsege(node) {
      context.report({
        node: context.getSourceCode().getFirstToken(node),
        loc: node.loc,
        message:
          'reportIllegalUsege: get a error',
      });
    }
    // declare the state of the rule
    return {
      'VariableDeclaration[kind="var"]': function (node) {
        reportIllegalUsege(node); // eslint命令后提示问题
      },
    };
  },
};

可以看到create回调函数的返回的对象中存在一个key'VariableDeclaration[kind="var"]'

  • VariableDeclaration为AST-Tree中node节点的type
  • [kind="var"]为node节点当中存在属性kind并且其value值为var

综上这里key选择器的意思是当AST遍历过程中匹配到type: VariableDeclaration的node节点且该节点包括值为varkind属性,则执行回调函数reportIllegalUsege(node);

20220823110220

对AST结构不熟悉的同学可以通过使用AST预览工具将需要检测的代码片段AST-Tree可视化:astexplorer.net/

到这里我们就已经编写好了一个Eslint插件中的规则,接下来我们根据需要进行docs/rules/下的说明文件的补充

20220822172008

发布eslint-plugin

完成上述步骤后,我们就可以将编写好的Eslint插件发布为一个npm包

发布之前记得在package.json当中修改一下包的名称,如:eslint-plugin-novar

'eslint-plugin-${diyName}': eslint插件包推荐使用这种命名格式

执行发布命令

# 在项目根目录下执行
npm login
npm publish

发布完成后便可以在需要用到这个插件的项目中安装插件依赖

npm i eslint --save-dev
npm install eslint-plugin-novar --save-dev

novar添加到项目根目录下.eslintrc配置文件当中(没有的话手动创建一个). 前提是发布的插件包必需以eslint-plugin-作为前缀:

{
    "plugins": [
        "novar"
    ]
}

执行Eslint检查

最后我们可以通过eslint命令执行代码检查

npx eslint --ext js,ts

若代码当中有使用var定义变量的行为,将会给出相应提示报错

讲到最后

本文仅以一个简单的Eslint规则定义来发布一个插件,意在使大家了解如何根据自身需要编写Eslint插件

谢谢大家,我们下节再见!!!

感谢各位看到这里,如果你觉得本节内容还不错的话,欢迎各位的点赞、收藏、评论,大家的支持是我做内容的最大动力

本文为作者原创,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接,否则保留追究法律责任的权利

补充

Eslint官网文档

Eslint规则说明文档