Eslint自定义规则(一)

3,572 阅读6分钟

笔者这篇文章的目的是介绍如何去创建一个Eslint插件和自定义Eslint rules以及如何去引用写好的规则,用以帮助大家深入理解Eslint的运行原理。

Eslint介绍

ESLint 是一个开源的 JavaScript 代码检查工具,由 Nicholas C. Zakas 于2013年6月创建。代码检查是一种静态的分析,常用于寻找有问题的模式或者代码,并且不依赖于具体的编码风格。对大多数编程语言来说都会有代码检查,一般来说编译程序会内置检查工具。

JavaScript 是一个动态的弱类型语言,在开发中比较容易出错。因为没有编译程序,为了寻找 JavaScript 代码错误通常需要在执行过程中不断调试。像 ESLint 这样的可以让程序员在编码的过程中发现问题而不是在执行的过程中。

ESLint 的初衷是为了让程序员可以创建自己的检测规则。ESLint 的所有规则都被设计成可插拔的。为了便于人们使用,ESLint 内置了一些规则,当然,你可以在使用过程中自定义规则。所有的规则默认都是禁用的。

ESLint 使用 Node.js 编写。

Eslint使用

创建一个空文件夹

mkdir majun-dt

初始化npm

npm init

通过npm安装eslint

npm install eslint --save-dev

生成配置文件

1、eslint --init自动生成

2、创建一个.eslintrc.js文件

//.eslintrc.js
module.exports = {
    "env": {
        "browser": true,
        "es6": true
    },
    "extends": [  // 使用eslint推荐的规则作为基础配置,可以在rules中覆盖
        "eslint:recommended",
        "plugin:vue/essential"
    ],
    "globals": {   // 允许在代码中使用全局变量
        "Atomics": "readonly",
        "SharedArrayBuffer": "readonly"
    },
    "parserOptions": {
        "ecmaVersion": 2018,
        "sourceType": "module"
    },
    "plugins": [  // vue是eslint-plugin-vue的简写,此插件的作用是可以让eslint识别.vue中的script代码
        "vue",  
        "eslint-plugin-mjlint"
    ],
    "rules": {  //0或off(关闭) 1或warn 2或error
        "mjlint/end-semi": 2,
        "mjlint/no-block-comments": 2,
        "mjlint/if-no-var": 2
    }
};

eslint具体配置

Eslint自定义规则

创建一个插件最简单的方式是使用 Yeoman generator。它将引导你完成插件框架的设置。

1、对于Yeoman generator,可以理解为一套模板,可以用于生成包含指定框架结构的工程化目录

npm install -g yo generator-eslint

2、创建一个插件文件夹

mkdir eslint-plugin-mjlint

cd eslint-plugin-mjlint

3、初始化ESLint插件的项目结构

yo eslint:plugin

? What is your name? mj
? What is the plugin ID? mjlint    // 插件ID
? Type a short description of this plugin: XXXXX // 插件描述
? Does this plugin contain custom ESLint rules? Yes // 插件是否包含自定义ESLint规则
? Does this plugin contain one or more processors? No // 插件是否包含一个或多个处理器
// 处理器用于处理js以外的文件 比如.vue文件
   create package.json
   create lib/index.js
   create README.md

4、正式创建规则

yo eslint:rule

? What is your name? mj
? Where will this rule be published? (Use arrow keys) // 这个规则将在哪里发布?
❯ ESLint Core  // 官方核心规则 (目前有200多个规则)
  ESLint Plugin  // 选择ESLint插件
? What is the rule ID? if-no-var  // 规则ID
? Type a short description of this rule: if语句中不要使用var  // 输入该规则的描述
? Type a short example of the code that will fail:  if(true){var a = 'a'}  // 输入有问题的代码
   create docs/rules/if-no-var.md
   create lib/rules/if-no-var.js
   create tests/lib/rules/if-no-var.js

最终工程目录结构为

├── README.md
├── docs // 使用文档
│   └── rules // 所有规则的文档
│       └── if-no-var.md // 具体规则文档
├── lib // eslint 规则开发
│   ├── index.js 引入+导出rules文件夹的规则
│   └── rules // 此目录下可以构建多个规则
│       └── if-no-var.js // 规则细节
├── package.json
└── tests // 单元测试
    └── lib
        └── rules
            └── if-no-var.js // 测试该规则的文件

以上是开发ESLint插件具体规则的准备工作,下面先来看看AST和EsLint原理的相关知识,为我们开发ESLint rule 打一下基础。

AST(抽象语法树)

AST(Abstract Syntax Tree)的作用

将代码抽象、解析成树形结构,方便对代码进行分析。

AST选择器

在遍历ast树时,会触发相应选择器的回调方法a,图中IfStatement、BlockStatement、VariableDeclaration都是选择器。

Eslint运行原理

在开发规则之前,我们需要ESLint是怎么运行的,了解插件为什么需要这么写。

1、 将代码解析成AST

ESLint使用JavaScript解析器Espree把JS代码解析成AST。

PS:解析器:是将代码解析成AST的工具,ES6、react、vue都开发了对应的解析器所以ESLint能检测它们的,ESLint也是因此一统前端Lint工具的。

2、深度遍历AST,监听匹配过程

在拿到AST之后,ESLint会以"从上至下"再"从下至上"的顺序遍历每个选择器两次。

3、 触发监听选择器的rule回调

在深度遍历的过程中,生效的每条规则(自己写的规则)都会对其中的某一个或多个选择器(ast树中的选择器)进行监听,每当匹配到选择器,监听该选择器的rule,都会触发对应的回调。

如何编写一个rules

大概知道了相应的原理我们来写一个简单的规则,大家都知道es5中if语句块中没有块级作用域,用var声明的变量可以全局访问,我们想在就要禁止在项目中使用。

在 lib/rules 目录下: 一个源文件(例如,if-no-var.js)

在 tests/lib/rules 目录下: 一个测试文件 (例如, if-no-var.js)

在 docs/rules 目录: 一个 markdown 文档文件 (例如, if-no-var.md)

结合第一张图ast树,我们不难发现VariableDeclaration中kind为var

// lib/rules/if-no-var.js
/**
 * @fileoverview if语句中不要使用var
 * @author mj
 */
"use strict";

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

module.exports = {
    meta: {
        docs: {
            description: "if语句中不要使用var",
            category: "Fill me in",
            recommended: false
        },
        fixable: null,  // or "code" or "whitespace"
        schema: [
            // fill in your schema
        ]
    },

    create: function(context) {

        return {
            "IfStatement > BlockStatement > VariableDeclaration" (node) {
                node.kind === 'var' &&
                    context.report({
                        node: node,
                        message: "error:var in if"
                    });
            }
        }
    }
};
// tests/lib/rules/if-no-var.js
/**
 * @fileoverview if语句中不要使用var
 * @author mj
 */
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var rule = require("../../../lib/rules/if-no-var"),

    RuleTester = require("eslint").RuleTester;


//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------

var ruleTester = new RuleTester();
ruleTester.run("if-no-var", rule, {

    valid: ["if(true){a=1}"],

    invalid: [
        {
            code: "if(true){var a = 1}",
            errors: [{
                message: "Fill me in.",
                type: "Me too"
            }]
        }
    ]
});

如何发布我们的插件

发布插件

如何使用自定义rules

majun-dt工程目录中安装包 npm install eslint-plugin-mjlint --save-dev

1、常规的方法

module.exports = {
    "plugins": [
        "vue",
        "eslint-plugin-mjlint"
    ],
    "rules": {
        "mjlint/end-semi": 2,
        "mjlint/no-block-comments": 2,
        "mjlint/if-no-var": 2
    }
};

2、extends继承插件配置

当规则比较多的时候,用户一条条去写,未免也太麻烦了,所以ESLint可以继承插件的配置

修改lib/index.js

/**
 * @fileoverview dt build eslint
 * @author mj
 */
"use strict";

//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------

var requireIndex = require("requireindex");

//------------------------------------------------------------------------------
// Plugin Definition
//------------------------------------------------------------------------------


// import all rules in lib/rules
// module.exports.rules = requireIndex(__dirname + "/rules");
moudle.export = {
  rules: requireIndex(__dirname + '/rules'), // 导出所有规则
  configs: {
    // 导出自定义规则 在项目中直接引用
    mjRule: {
      plugins: ['mjlint'], // 引入插件
      rules: {
        // 开启规则
        "mjlint/end-semi": 2,
        "mjlint/no-block-comments": 2,
        "mjlint/if-no-var": 2
      }
    }
  }
};

使用(npm的包名不能为eslint-plugin-xx-xx,只能为eslint-plugin-xx否则会有报错)

// .eslintrc.js
module.exports = {
  extends: ['plugin:mjlint/mjRule'], // 继承插件导出的配置
  "plugins": [
        "vue"
   ],
}

src/index.js

var ab = 'ab';
console.log(ab);

var flag = true
if(flag){
	var abc = 'abc'
	var abcd = 'abcd'
}
console.log(abc)
console.log(abcd)


/*npx eslint src

vscode eslint 插件

eslint-loader*/

运行结果

eslint-plugin-mjlint