看我如何玩转自定义ESLint规则

前言

来了淘宝之后各种忙,最近正好在弄自定义ESLint相关的东西,写篇文章mark下。最近开通了自己的微信公共号“王和阳的航海日志”,上面记录着自己的学习、思考、实践和成长的过程,欢迎关注,欢迎交流。

公共号二维码

基本思路

关于如何使用ESLint来自定义规则,具体的开发流程详见ESLint官网。这里就不赘述,接下来我重点讲讲基本思路。

首先需要了解的是,在分析代码前,ESlint会通过Estree(ESLint所支持的一种Parser,不同的Parser有不同的适用场景)对代码进行词法分析和语法分析,并将代码解析一棵抽象语法树AST(Abstract Syntax Tree),将不同类型的代码语句分成不同类型的节点,则一份代码文件便形成了一个树状的结构,之后ESlint会依次遍历语法树上的节点。

为了便于对AST有一个直观的了解,可以在astexplorer上直接输入代码后查看对应的结构图,这里要注意是在页面的tab栏选择对应的Parser,不同的Parser解析出来的结果会有差异。我选择的Parser是babel-eslint,并输入代码

import View from "rax-view";
import Text from "rax-text";
class Mod extends Component {
  render() {
    return <View><Text>111</Text></View>;
  }
}

解析后得到的语法树如下:
实例AST

JavaScript的AST抽象语法树中具体有哪些节点,可以详见这里,需要注意的是,以上的节点信息中不包括jsx代码,具体的jsx特有的节点的详细信息可以在这里找到。

牛刀小试

在了解了这些后,我们可以由此做一些自定义规则,创建自定义规则的步骤如下
1. 找出合法的代码并输入到astexplorer.net中,查看对应的语法树
2. 找出非法的代码并输入astexplorer.net中,查看对应的语法树
3. 对比两者的不同,并在关键节点上进行检查

这里我们以在rax里禁止输入dangerouslySetInnerHTML为例讲讲应该如何自定义规则。
这里的合法代码与上一段中的代码相同,对应的语法树如下:
合法代码的AST

非法代码如下:

<View dangerouslySetInnerHTML={{__html: 'First &middot; Second'}}></View>

对应的语法树如下:
非法代码的AST
观察后可以发现dangerouslySetInnerHTML在语法树中其实就是一个JSXIdentifier下的一个key为name的值,那么我们只需要把这个特殊值给检测出来即可。具体代码如下:

const jsxUtils = require('jsx-ast-utils');

const {hasProp} = jsxUtils;

module.exports = {

  meta: {
    docs: {
      description: 'Disallow use dangerouslySetInnerHTML',
      category: 'Best Practices',
      recommended: false
    },
    fixable: null,
    schema: []
  },

// context中有很多实用的方法,比如获取注释、获取纯文本源码
  create(context) {
    return {
      JSXOpeningElement: node => {
        const dangerouslySetInnerHTML = hasProp(node.attributes, 'dangerouslySetInnerHTML');

        if (dangerouslySetInnerHTML) {
          context.report({
            node,
            message: `disallow use dangerouslySetInnerHTML!`
          });
        }
      }
    };
  }
};

Code Path

下面结合ESLint所提供的功能,继续讲讲一些好玩的功能。ESLint中能够对代码路径进行分析,而代码路径是程序的执行路线。

一段程序由几段代码路径构成,而代码路径又由CodePathCodePathSegment这两个对象来表示。
- CodePath表示的是整个代码的路径,这个对象不仅在全局中存在,且存在于每一个函数中。
- CodePathSegment是代码路径的一部分,一段代码路径由多个CodePathSegment对象组成。
让我们以最简单的一段代码为例来分析下整个过程是如何进行的

while (a) {
    a = foo();
}
bar();

对应的Code Path如下:
1. 一开始,代码分析直接run到循环的末尾
步骤1
2. 随后进入循环的路径中
步骤2
3. 到达末尾
步骤3

整个过程其实就和我们人在阅读代码的时候是类似的,只是在这个分析过程中,每一步都是以一个节点作为跳转,且有清晰的逻辑。

在了解了上述这些信息后,我们可以做一些简单的代码检查,例如检查是否一段代码是可以被访问的

var last = require("lodash").last;

function isReachable(segment) {
    return segment.reachable;
}

module.exports = function(context) {
    var codePathStack = [];

    return {
        // 存储CodePath对象
        "onCodePathStart": function(codePath) {
            codePathStack.push(codePath);
        },
        "onCodePathEnd": function(codePath) {
            codePathStack.pop();
        },

        // 检查是否可执行
        "ExpressionStatement": function(node) {
            var codePath = last(codePathStack);

            // 检查当前代码片段
            if (!codePath.currentSegments.some(isReachable)) {
                //若代码不能被访问,则报错
                context.report({message: "Unreachable!", node: node});
            }
        }
    };
};

总结

ESLint功能强大,结合代码路径、各种加强版的parser能做到很多神奇的检查规则和奇技淫巧黑魔法,但真正重要的,是结合各个业务的特点进行定制,在团队开发项目时,能又快又好地完成项目,同时让代码具有可读性和可维护性,才是每一个用ESLint的人心中所应该保持的初心。

引用资料

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值