深入理解 Eslint (手把手教你撸一个plugin)

2019-10-12 admin

前言

虽然现在已经有很多实用的 ESHint 插件了,但随着项目不断迭代发展,你可能会遇到已有 ESHint 插件不能满足现在团队开发的情况。这时候,你需要自己来创建一个 ESHint 插件。

本文我将带你了解各种Lint工具的大致历史,然后一步一步地创建一个属于你自己的 ESHint 插件,以及教你如何利用AST抽象语法树来制定这个插件的规则。

以此来带你了解 ESHint 的实现原理。 1.jpg

课外知识:Lint 简史

Lint 是为了解决代码不严谨而导致各种问题的一种工具。比如 ===== 的混合使用会导致一些奇怪的问题。

JSLint 和 JSHint

2002年,Douglas Crockford 开发了可能是第一款针对 JavaScript 的语法检测工具 —— JSLint,并于 2010 年开源。

JSLint 面市后,确实帮助许多 JavaScript 开发者节省了不少排查代码错误的时间。但是 JSLint 的问题也很明显—— 几乎不可配置,所有的代码风格和规则都是内置好的;再加上 Douglas Crockford 推崇道系「爱用不用」的优良传统,不会向开发者妥协开放配置或者修改他觉得是对的规则。于是 Anton Kovalyov 吐槽:「JSLint 是让你的代码风格更像 Douglas Crockford 的而已」,并且在 2011 年 Fork 原项目开发了 JSHint。《Why I forked JSLint to JSHint》

JSHint 的特点就是可配置,同时文档也相对完善,而且对开发者友好。很快大家就从 JSLint 转向了 JSHint。

ESLint 的诞生

后来几年大家都将 JSHint 作为代码检测工具的首选,但转折点在2013年,Zakas 发现 JSHint 无法满足自己制定规则需求,并且和 Anton 讨论后发现这根本不可能在JShint上实现,同时 Zakas 还设想发明一个基于 AST 的 lint。于是 2013年6月份,Zakas 发布了全新 lint 工具——ESLint。《Introducing ESLint》

ESLint早期源码

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

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

return messages;

ESLint 的逆袭

ESLint 的出现并没有撼动 JSHint 的霸主地位。由于前者是利用 AST 处理规则,用 Esprima 解析代码,执行速度要比只需要一步搞定的 JSHint 慢很多;其次当时已经有许多编辑器对 JSHint 支持完善,生态足够强大。真正让 ESLint 逆袭的是 ECMAScript 6 的出现。

2015 年 6 月,ES2015 规范正式发布。但是发布后,市面上浏览器对最新标准的支持情况极其有限。如果想要提前体验最新标准的语法,就得靠 Babel 之类的工具将代码编译成 ES5 甚至更低的版本,同时一些实验性的特性也能靠 Babel 转换。 但这时候的 JSHint 短期内无法提供支持,而 ESLint 却只需要有合适的解析器就能继续去 lint 检查。Babel 团队就为 ESLint 开发了一款替代默认解析器的工具,也就是现在我们所见到的 babel-eslint,它让 ESLint 成为率先支持 ES6 语法的 lint 工具。

也是在 2015 年,React 的应用越来越广泛,诞生不久的 JSX 也愈加流行。ESLint 本身也不支持 JSX 语法。还是因为可扩展性,eslint-plugin-react 的出现让 ESLint 也能支持当时 React 特有的规则。

2016 年,JSCS 开发团队认为 ESLint 和 JSCS 实现原理太过相似,而且需要解决的问题也都一致,最终选择合并到 ESLint,并停止 JSCS 的维护。

当前市场上主流的 lint 工具以及趋势图:

从此 ESLint 一统江湖,成为替代 JSHint 的前端主流工具。

目标&涉及知识点

本文 ESHint 插件目标是在项目开发中禁用:console.time()

  • [x] AST 抽象语法树
  • [x] ESHint
  • [x] Npm 发布
  • [x] 单元测试

插件脚手架构建

这里我们利用 yeomangenerator-eslint 来构建插件的脚手架代码。安装:

npm install -g yo generator-eslint

本地新建文件夹eslint-plugin-demofortutorial:

mkdir eslint-plugin-demofortutorial
cd eslint-plugin-demofortutorial

初始化 ESHint 插件的项目结构:

yo eslint:plugin // 搭建一个初始化的目录结构

此时文件的目录结构为:

.
├── README.md
├── lib
│   ├── index.js
│   └── rules
├── package.json
└── tests
    └── lib
        └── rules

安装依赖:

npm install

至此,环境搭建完毕。

创建规则

终端执行:

yo eslint:rule // 生成默认 eslint rule 模版文件

此时项目结构为:

.
├── README.md
├── docs // 使用文档
│   └── rules
│       └── no-console-time.md
├── lib // eslint 规则开发
│   ├── index.js
│   └── rules // 此目录下可以构建多个规则,本文只拿一个规则来讲解
│       └── no-console-time.js
├── package.json
└── tests // 单元测试
    └── lib
        └── rules
            └── no-console-time.js

上面结构中,我们需要在 ./lib/ 目录下去开发 Eslint 插件,这里是定义它的规则的位置。

AST 在 ESHint 中的运用

在正式写 ESHint 插件前,你需要了解下 ESHint 的工作原理。其中 ESHint 使用方法大家应该都比较熟悉,这里不做讲解,不了解的可以点击官方文档如何在项目中配置ESHint

在公司团队项目开发中,不同开发者书写的源码是各不相同的,那么在 ESHint 中,如何去分析每个人写的源码呢?

作为开发者,面对这类问题,我们必须懂得要使用 抽象的手段 !那么 Javascript 的抽象性如何体现呢?

没错,就是 AST (Abstract Syntax Tree(抽象语法树)),再祭上那张看了几百遍的图。

ESHint 中,默认使用 esprima 来解析我们书写的 Javascript 语句,让其生成抽象语法树,然后去 拦截 检测是否符合我们规定的书写方式,最后让其展示报错、警告或正常通过。 ESHint 的核心就是规则(rules),而定义规则的核心就是利用 AST 来做校验。每条规则相互独立,可以设置禁用off、警告warn⚠️和报错error❌,当然还有正常通过不用给任何提示。

规则创建

上面讲完了 ESHintAST 的关系之后,我们可以正式进入开发具体规则。先来看之前生成的 lib/rules/no-console-time.js:

/**
 * @fileoverview no console.time()
 * @author Allan91
 */
"use strict";

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

module.exports = {
    meta: {
        docs: {
            description: "no console.time()",
            category: "Fill me in",
            recommended: false
        },
        fixable: null,  // or "code" or "whitespace"
        schema: [
            // fill in your schema
        ]
    },

    create: function(context) {

        // variables should be defined here

        //----------------------------------------------------------------------
        // Helpers
        //----------------------------------------------------------------------

        // any helper functions should go here or else delete this section

        //----------------------------------------------------------------------
        // Public
        //----------------------------------------------------------------------

        return {

            // give me methods

        };
    }
};

这个文件给出了书写规则的模版,一个规则对应一个可导出的 node 模块,它由 metacreate 两部分组成。

  • meta 代表了这条规则的元数据,如其类别,文档,可接收的参数的 schema 等等。
  • create:如果说 meta 表达了我们想做什么,那么 create 则用表达了这条 rule 具体会怎么分析代码;

Create 返回一个对象,其中最常见的键名AST抽象语法树中的选择器,在该选择器中,我们可以获取对应选中的内容,随后我们可以针对选中的内容作一定的判断,看是否满足我们的规则。如果不满足,可用 context.report 抛出问题,ESLint 会利用我们的配置对抛出的内容做不同的展示。

具体参数配置详情见官方文档

本文创建的 ESHint 插件是为了不让开发者在项目中使用 console.time(),先看看这段代码在抽象语法树中的展现:

其中,我们将会利用以下内容作为判断代码中是否含有 console.time:

那么我们根据上面的AST(抽象语法书)在 lib/rules/no-console-time.js 中这样书写规则:

/**
 * @fileoverview no console.time()
 * @author Allan91
 */
"use strict";

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

module.exports = {
    meta: {
        docs: {
            description: "no console.time()",
            category: "Fill me in",
            recommended: false
        },
        fixable: null,  // or "code" or "whitespace"
        schema: [
            // fill in your schema
        ],
        // 报错信息描述
        messages: {
            avoidMethod: "console method '{{name}}' is forbidden.",
        },
    },

    create: function(context) {
        return {
            // 键名为ast中选择器名
            'CallExpression MemberExpression': (node) => {
                // 如果在ast中满足以下条件,就用 context.report() 进行对外警告⚠️
                if (node.property.name === 'time' && node.object.name === 'console') {
                    context.report({
                        node,
                        messageId: 'avoidMethod',
                        data: {
                            name: 'time',
                        },
                    });
                }
            },
        };
    }
};

再修改 lib/index.js

"use strict";

module.exports = {
    rules: {
        'no-console-time': require('./rules/no-console-time'),
    },
    configs: {
        recommended: {
            rules: {
                'demofortutorial/no-console-time': 2, // 可以省略 eslint-plugin 前缀
            },
        },
    },
};

至此,Eslint 插件创建完成。接下去你需要做的就是将此项目发布到 npm平台。 根目录执行:

npm publish

打开npm平台,可以搜索到上面发布的 eslint-plugin-demofortutorial 这个 Node 包。

如何使用

发布完之后在你需要的项目中安装这个包:

npm install eslint-plugin-demofortutorial -D

然后在 .eslintrc.js 中配置:

"extends": [
    "eslint:recommended",
    "plugin:eslint-plugin-demofortutorial/recommended",
],
"plugins": [
    'demofortutorial'
],

如果之前没有.eslintrc.js 文件,可以执行下面命令生成:

npm install -g eslint
eslint --init

此时,如果在当前项目的 JS 文件中书写 console.time,会出现如下效果:

单元测试(完善)

对于完整的 npm 包来说,上面还只算是个“半成品”,我们需要写单元测试来保证它的完整性和安全性。

下面来完成单元测试,在目录中编写如下代码:

'use strict';

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

let rule = require('../../../lib/rules/no-console-time');

let RuleTester = require('eslint').RuleTester;

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

let ruleTester = new RuleTester({
    parserOptions: {
        ecmaVersion: 10,
    },
});

ruleTester.run('no-console-time', rule, {

    valid: [ // 合法示例
        '_.time({a:1});',
        "_.time('abc');",
        "_.time(['a', 'b', 'c']);",
        "lodash.time('abc');",
        'lodash.time({a:1});',
        'abc.time',
        "lodash.time(['a', 'b', 'c']);",
    ],

    invalid: [ // 不合法示例
        {
            code: 'console.time()',
            errors: [
                {
                    messageId: 'avoidMethod',
                },
            ],
        },
        {
            code: "console.time.call({}, 'hello')",
            errors: [
                {
                    messageId: 'avoidMethod',
                },
            ],
        },
        {
            code: "console.time.apply({}, ['hello'])",
            errors: [
                {
                    messageId: 'avoidMethod',
                },
            ],
        },
        {
            code: 'console.time.call(new Int32Array([1, 2, 3, 4, 5]));',
            errors: 1,
        },
    ],
});

上面测试代码详细介绍见官方文档

根目录执行:

npm run test

至此,这个包的开发完成。其它规则开发也是类似,比如您可以继续制定其它规范,比如 ️console.log()debugger警告等等。

其它

由于自动生成ESHint的项目中依赖的 eslint 版本还在 3.x 阶段,会对单元测试语法解析造成如下报错:

'Parsing error: Invalid ecmaVersion.'

建议将该包升级到 "eslint": "^5.16.0"

以上。

查看Github上的项目仓库

查看Npm上发布的包

参考资料: https://zhuanlan.zhihu.com/p/… https://en.wikipedia.org/wiki… https://octoverse.github.com/ http://jslint.com https://medium.com/@anton/why-i-forked-jslint-to-jshint-73a72fd3612 https://www.nczonline.net/blo… https://eslint.org http://jscs.info https://github.com/babel/babel-eslint https://github.com/yannickcr/… https://www.nczonline.net/blog/2016/02/reflections-on-eslints-success https://medium.com/@markelog/js

[转载]原文链接:https://segmentfault.com/a/1190000020663729

本站文章除注明转载外,均为本站原创或编译。欢迎任何形式的转载,但请务必注明出处。

转载请注明:文章转载自 JavaScript中文网 [https://www.javascriptcn.com]

本文地址:https://www.javascriptcn.com/read-77241.html

文章标题:深入理解 Eslint (手把手教你撸一个plugin)

相关文章
javascript教程:关于if简写语句优化的方法
UglifyJS是一个对javascript进行压缩和美化的工具,在它的文档说明中,我看到了几种关于if语句优化的方法。尽管我还没使用它去做一些尝试性的测试,但从这里可以看到它的确对js作了美化的工作。也许有人认为if语句就那么简单,能优化...
2015-11-12
面试官:谈谈你对 CSS 盒模型的认识?(你确定会?)
题目:谈谈你对 CSS 盒模型的认识 涉及知识点(层层递进): 基本概念:标准模型+ IE模型(区别) CSS如何设置这两种模型 JS如何设置获取盒子模型对应的宽和高 实例题(根据盒模型解释边距重叠) BFC(边距重叠解决方案) 1...
2018-06-09
2014年,你是不是被这5个HTML5技术刷屏了?
如今,几乎每天都有HTML5页面的推广以及小游戏、小测试在微信上传播,用户也逐渐习惯被各种HTML5轰炸。那么在刚刚过去的一年究竟有哪些HTML5技术堪称火爆,让人们的微信频频被刷屏呢? 1、2048 2048 在4x4的棋盘上,用方向键选...
2015-11-12
为你的 VS Code 搭建远程开发环境
开篇先说一下自己遇到的烦恼,介绍下写这篇文章的背景。我有一台低配的 MacBook 和 一台性能强悍的台式机。之前自己都是在 Mac 上跑前端项目的,那台台式机基本上处于闲置状态,偶尔用来看看文档。后来随着自己需要做服务端开发,有时候需要同...
2018-02-26
7个你可能不认识的CSS单位
浼楁墍鍛ㄧ煡CSS鎶€鏈�鎴戜滑铏界劧寰堢啛鎮夛紝鍦ㄤ娇鐢ㄧ殑杩囩▼鍗村緢瀹规槗琚�鍥颁綇锛岃繖璁╂垜浠�鍦ㄦ柊闂�棰樺嚭鐜扮殑鏃跺€欏彉寰楀緢涓嶅埄銆� 闅忕潃web缁х画涓嶆柇鍦板彂灞曪紝瀵逛簬鏂版妧鏈�鏂拌В鍐虫柟妗堢殑瑕佹眰涔熶細涓...
2015-11-11
js获取数组的最后一个元素
在js里面如何获取一个数组的最后一个元素呢?这里总结了两种方法,有需要的朋友可以看看。 (1)js内置pop方法 pop() 方法用于删除并返回数组的最后一个元素,注意这里在获取了数组的最后一个元素的同时也将原数组的最后一个元素给删除了。如...
2017-03-22
ajax教程之ajax使用Http请求
ajax中是如何让使用http请求的呢? 在传统的JS编程中,如果您希望从服务器上的文件或数据库中得到任何的信息,或者向服务器发送信息的话,就必须利用一个 HTML 表单向服务器 GET 或 POST 数据。而用户则需要单击“提交”按钮来发...
2015-11-12
自己参考两个仓库撸的通用的vue2.0+elementUI+多页面模板
A modern Vue.js multiple pages cli which uses Vue 2, Webpack3, and Element-UI Features Vue2 Webpack3 ElementUI Eslint(e...
2018-01-12
关于本教程
你应该已经知道什么? 本教程假定您有以下基本的知识: 有一个大体的了解互联网和万维网( www )。 良好的工作知识的超文本标记语言( html )。 一些编程经验。 ...
2015-11-12
JavaScript中文网:即将发布JavaSCript原创教程
JavaScript中文网:即将发布JavaSCript原创教程 ...
2015-11-12
回到顶部