来了淘宝之后各种忙,最近正好在弄自定义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>111Text>View>;
}
}
JavaScript的AST抽象语法树中具体有哪些节点,可以详见这里,需要注意的是,以上的节点信息中不包括jsx代码,具体的jsx特有的节点的详细信息可以在这里找到。
在了解了这些后,我们可以由此做一些自定义规则,创建自定义规则的步骤如下
1. 找出合法的代码并输入到astexplorer.net
中,查看对应的语法树
2. 找出非法的代码并输入astexplorer.net
中,查看对应的语法树
3. 对比两者的不同,并在关键节点上进行检查
这里我们以在rax里禁止输入dangerouslySetInnerHTML
为例讲讲应该如何自定义规则。
这里的合法代码与上一段中的代码相同,对应的语法树如下:
非法代码如下:
'First · Second'}}>View>
对应的语法树如下:
观察后可以发现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!`
});
}
}
};
}
};
下面结合ESLint所提供的功能,继续讲讲一些好玩的功能。ESLint中能够对代码路径进行分析,而代码路径是程序的执行路线。
一段程序由几段代码路径构成,而代码路径又由CodePath
和CodePathSegment
这两个对象来表示。
- CodePath
表示的是整个代码的路径,这个对象不仅在全局中存在,且存在于每一个函数中。
- CodePathSegment
是代码路径的一部分,一段代码路径由多个CodePathSegment
对象组成。
让我们以最简单的一段代码为例来分析下整个过程是如何进行的
while (a) {
a = foo();
}
bar();
对应的Code Path
如下:
1. 一开始,代码分析直接run到循环的末尾
2. 随后进入循环的路径中
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的人心中所应该保持的初心。