Eslint踩坑记
为啥写这篇文章呢
入职的第二个星期,提交代码的时候顺利提交,顺利的通过了代码审查,那时的我犹如脱缰的骏马
结果其他小伙伴拉下代码,一启动
这次的事件暴露了我对eslint的不了解 所以接着这次机会重温一下eslint的知识
那么这里开启了哲学三问 这是啥 为什么要做这个东西 这个东西有什么用?
这是个啥
让我们看看官网的说法
ESLint最初是由Nicholas C. Zakas 于2013年6月创建的开源项目。它的目标是提供一个插件化的javascript代码检测工具。
为什么要做这个东西
ESLint 的初衷是为了让程序员可以创建自己的检测规则。ESLint 的所有规则都被设计成可插入的。ESLint 的默认规则与其他的插件并没有什么区别,规则本身和测试可以依赖于同样的模式。为了便于人们使用,ESLint 内置了一些规则,当然,你可以在使用过程中自定义规则。
这个东西有什么用?
他可以规范你的代码风格同时可以进行错误检查
那么下面让我们开始从头学习eslint
第一步当然是安装了 没有npm的同学可以参考下这篇文章
npm install -g eslint
初始化一下规则配置文件
eslint --init
初始化之后有三个模式可以选择
- To check syntax only (只检查语法错误)
- To check syntax and find problems(检查语法错误和发现代码格式问题)
- To check syntax, find problems, and enforce code style(语法错误,代码格式,强制纠正)
往配置文件上配置一些规则
校验某个文件的js是否满足我们现有的规范(假设这个文件叫做xx.js)
eslint xx.js
发现报错信息看不懂 不知道怎么修改怎么办( 自动修复也可能会出问题 所以修复完之后还是要看一眼修复后的样子是否满足我们的预期)
eslint xx.js --fix
eslint规则文件的优先级
- JavaScript - 使用 .eslintrc.js 然后输出一个配置对象。
- YAML - 使用 .eslintrc.yaml 或 .eslintrc.yml 去定义配置的结构。
- JSON -使用 .eslintrc.json 去定义配置的结构,ESLint 的 JSON 文件允许 JavaScript 风格的注释。
- Deprecated -使用 .eslintrc,可以使 JSON 也可以是YAML。
- package.json - 在 package.json 里创建一个 eslintConfig属性,在那里定义你的配置。
如何配置我们的规则呢
上面的规则文件中会有一个rule的字段
需要配置哪些规则可以来这里看看
规则文档
规则的等级
- “off” 或 0 - 关闭规则
- “warn” 或 1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
- “error” 或 2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
到这里或许我们会有疑问 规则是如何检查我们的代码呢
简单描述一下过程
- 我们的写好的代码首先是被浏览器解析成为可以读懂的一个AST(抽象语法树)
- 得到AST之后 我们可以通过某些规则对语法树进行分析
- 对不符合规则的代码 报错提醒
- 对于加了fix选项的指令 就需要对AST做出修改 从不符合规则的AST->符合规则的AST 最后再把AST转成我们可以看懂的新代码
详细描述一下转化过程
在了解如何生成AST之前,有必要了解一下Parser(常见的Parser有esprima、traceur、acorn、shift等)。JS Parser其实是一个解析器,它是将js源码转化为抽象语法树(AST)的解析器。整个解析过程主要分为以下两个步骤:
分词(也就是词法分析):将整个代码字符串分割成最小语法单元数组
语法分析:在分词基础上建立分析语法单元之间的关系
1、什么是语法单元?
语法单元是被解析语法当中具备实际意义的最小单元,简单的来理解就是自然语言中的词语。举个例子来说,下面这段话:“2019年是祖国70周年”,我们可以把这句话拆分成最小单元,即:2019年、是、祖国、70、周年。
这就是我们所说的分词,也是最小单元,因为如果我们把它再拆分出去的话,那就没有什么实际意义了。
Javascript 代码中的语法单元主要包括以下这么几种:
关键字:例如 var、let、const等
标识符:没有被引号括起来的连续字符,可能是一个变量,也可能是 if、else 这些关键字,又或者是 true、false 这些内置常量
运算符: +、-、 *、/ 等
数字:像十六进制,十进制,八进制以及科学表达式等语法
字符串:因为对计算机而言,字符串的内容会参与计算或显示
空格:连续的空格,换行,缩进等
注释:行注释或块注释都是一个不可拆分的最小语法单元
其他:大括号、小括号、分号、冒号等
下面我们实战一下 假设我们写了一段这样的代码
var say = “hello world”
那么他会被转成怎样的AST呢
答案是
{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "say"
},
"init": {
"type": "Literal",
"value": "hello world",
"raw": "\"hello world\""
}
}
],
"kind": "var"
}
],
"sourceType": "script"
}
这就是 var say = “hello world” 所转换的 AST;(这里推荐一下esprima AST的可视化工具,esprima)
或者可以使用方案二(npm i esprima)
依次输入以下命令
mkdir testEsprima
cd testEsprima
touch testEsprima.js
npm init
npm i esprima
vim testEsprima.js
扔进去这一段代码
:wq
let esprima = require('esprima');
let estraverse = require('estraverse')
let code = 'let say = "hello world"'
function analyzeCode(code) {
var ast = esprima.parse(code);
console.log(ast.body)
estraverse.traverse(ast, {
enter: function (node) {
console.log(node);
}
});
}
let ast = analyzeCode(code);
console.log(ast)
node testEsprima.js
esprima.parse()方法接收两种类型的参数:字符串或Node的Buffer对象,它也可以收附加的选项作为参数。解析后返回结果即为抽象语法树(AST),AST遵守Mozilla SpiderMonkey的解析器API
VariableDeclaration {
type: 'VariableDeclaration',
declarations: [ [VariableDeclarator] ],
kind: 'let'
}
]
Script {
type: 'Program',
body: [
VariableDeclaration {
type: 'VariableDeclaration',
declarations: [Array],
kind: 'let'
}
],
sourceType: 'script'
}
VariableDeclaration {
type: 'VariableDeclaration',
declarations: [
VariableDeclarator {
type: 'VariableDeclarator',
id: [Identifier],
init: [Literal]
}
],
kind: 'let'
}
VariableDeclarator {
type: 'VariableDeclarator',
id: Identifier { type: 'Identifier', name: 'say' },
init: Literal {
type: 'Literal',
value: 'hello world',
raw: '"hello world"'
}
}
Identifier { type: 'Identifier', name: 'say' }
Literal { type: 'Literal', value: 'hello world', raw: '"hello world"' }
didi@localhost a % node a.js
[
VariableDeclaration {
type: 'VariableDeclaration',
declarations: [ [VariableDeclarator] ],
kind: 'let'
}
]
Script {
type: 'Program',
body: [
VariableDeclaration {
type: 'VariableDeclaration',
declarations: [Array],
kind: 'let'
}
],
sourceType: 'script'
}
VariableDeclaration {
type: 'VariableDeclaration',
declarations: [
VariableDeclarator {
type: 'VariableDeclarator',
id: [Identifier],
init: [Literal]
}
],
kind: 'let'
}
VariableDeclarator {
type: 'VariableDeclarator',
id: Identifier { type: 'Identifier', name: 'say' },
init: Literal {
type: 'Literal',
value: 'hello world',
raw: '"hello world"'
}
}
Identifier { type: 'Identifier', name: 'say' }
Literal { type: 'Literal', value: 'hello world', raw: '"hello world"' }
再在下面加入以下代码
作用是把定义
const estraverse = require('estraverse');
estraverse.traverse(ast, {
enter: function (node) {
node.kind = "let";
}
});
console.log(ast);
// 输出结果
Script {
type: 'Program',
body: [
VariableDeclaration {
type: 'VariableDeclaration',
declarations: [Array],
kind: 'let'
}
],
sourceType: 'script',
kind: 'let'
}
看起来ast这个树已经被我们成功的修改了
但是这个树 电脑看得懂我看的费劲呀
咋办呢
转成我们自己的函数呗
加入以下代码
const escodegen = require("escodegen");
const transformCode = escodegen.generate(ast)
console.log(transformCode);
let say = 'hello world';
到这里就转成功了 这就是我们最后获取到的更新代码
其他更复杂的规则有其他的分析办法 但是原理和流程就是按照这样子
分析代码 ->生成ast->分析ast->修改ast->生成理想ast->转化ast成为代码
篇幅有限 下一篇会讲一下eslint和prettier 学习去了 冲冲冲~