制定企业级代码规范与检查
开篇一张图
前言
如何作出项目的亮点?
- 项目中遇到了什么问题?
- 解决问题的过程并且如何思考?
- 思考之后通过什么方式解决
- 最后这一个任务你学到了什么,给团队带来了什么价值,解决了哪些痛点。
就从我的题目说起,本篇文章告诉你针对定制代码规范和检查这个小需求如何做出亮点?看完本文后回顾上面提到的 4 点,感觉下。
本文目标
目标不是一次全部定出来的,在实践和调研过程中会添加一些
- 去掉项目中原有的
TSLint
,统一使用ESLint
,但是在ESLint
中加入TSLint
检测插件 Prettier
支持的格式化规则全部使用Prettier
,不提供的使用ESLint
,以免冲突(个人认为Prettier
提供的格式化规则可以满足开发者)。- 代码保存时,支持自动
fix
,只对自己控制范围内的fix
,范围外的内容依靠开发者配置或vscode
自动配置。 - 格式化和
ESLint
纳入项目级git
跟踪,所有开发者统一。 - 除了上面的规范与检查实现,了解一些原理,比如
rules
原理?为什么Prettier
和ESLint
冲突?Prettier
原理?
ESLint
ESLint
的原理就是一款插件化的javascript代码静态检查工具,其核心是对代码解析得到的 AST (Abstract Syntax Tree 抽象语法树
)进行模式匹配,定位不符合约定规范的代码。ESLint
是完全插件化的。每一个规则都是一个插件并且可以在运行时添加更多的规则。
社区比较知名的代码规范
- eslint-config-airbnb
- eslint-config-standard
- eslint-config-alloy
如果想降低配置成本,可以直接接入上面的开源配置方案,好多开发者是继承它们的规范,然后在原有基础进行部分修改。我们目前选择的方式不是继承,挑选出了一些适合我们的 ESLint
规则(因为是在原有代码重新建立规范,防止改动过大)。
ESLint 集成
ESLint
使用并不复杂,简单说下 ESLint
的集成。
全局安装
yarn add eslint -D
初始化
eslint --init
这个时候在项目中会出现一个 .eslintrc.js
的文件。
eslint 自定义配置文件
module.exports = {
parser: {}, //定义ESLint的解析器
extends: [], // 定义文件继承的子规范
plugins: [], // 定义了该eslint文件所依赖的插件
env: {},
rules: {} // 规则
};
parser
定义 parser
的解析器,我们常用的解析器应该是 @typescript-eslint/parser
。
env
通过 env
配置需要启动的环境
env: {
es6: true, // 支持新的 ES6 全局变量,同时自动启用 ES6 语法支持
node: true, // 启动 node 环境
mocha: true,
},
extend
extend
提供的是 eslint
现有规则的一系列预设。
这里注意的是,“extends”除了可以引入推荐规则,还可以以文件形式引入其它的自定义规则,然后在这些自定义规则的基础上用rules去定义个别规则,从而覆盖掉”extends”中引入的规则。
{
"extends": [
"./node_modules/coding-standard/eslintDefaults.js",
// Override eslintDefaults.js
"./node_modules/coding-standard/.eslintrc-es6",
// Override .eslintrc-es6
"./node_modules/coding-standard/.eslintrc-jsx",
],
"rules": {
// Override any settings from the "parent" configuration
"eqeqeq": "warn"
}
}
除了在配置文件中指定规则外,还可以在代码中指定规则,代码文件内以注释配置的规则会覆盖配置文件里的规则,即优先级要更高。平时我们常用的就是 eslint-disable-next-line
。
忽略检查可以通过在项目目录下建立 .eslintignore
文件,并在其中配置忽略掉对哪些文件的检查。需要注意的是,不管你有没有在 .eslintignore
中进行配置,eslint
都会默认忽略掉对 /node_modules/**
的检查。也可以在 package.json
文件的 eslintIgnore
字段进行配置。
plugins
plugin
则提供了除预设之外的自定义规则,当你在 ESlint
的规则里找不到合适的的时候就可以借用插件来实现了
module.exports = {
parser: '@typescript-eslint/parser', // 解析器
extends: [
"./.eslintRules.js",
'plugin:prettier/recommended',
"prettier",// 优先 prettier 中的样式规范
'prettier/@typescript-eslint',
], // 继承的规则
plugins: ['@typescript-eslint'], // 插件
ESLint 重要特性
rules
rules
对应的规则,小伙伴可以去官网查看。找到符合自己项目的规则。
ESLint 规则官网地址
注意:
- 在整理总结规则的时候有些是自动检测的规则,就可以不用总结进去了。
ESLint
规则的三种级别
- "off"或者0,不启用这个规则
- "warn"或者1,出现问题会有警告
- "error"或者2,出现问题会报错
rules 工作原理`
首先来看看 eslin
t源码中关于 rules
的编写。eslint
中的 rules
源码存在于 lib/rules
下。每一个 rules
都是一个 node
模块,用 module.exports
导出一个 meta
对象及一个create
函数。
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "disallow unnecessary semicolons",
category: "Possible Errors",
recommended: true,
url: "https://eslint.org/docs/rules/no-extra-semi"
},
fixable: "code",
schema: [] // no options
},
create: function(context) {
return {
// callback functions
};
}
};
meta
代表了这条规则的 元数据
,如这条规则的类别,文档,可接收的参数 schema
等等。create
返回一个对象,其中定义了一些在 AST
遍历访问到对应节点需要执行的方法等等。函数接受一个 context
对象作为参数,里面包含了例如可以报告错误或者警告的 context.report()
、可以获取源代码的 context.getSourceCode()
等方法,可以简化规则的编写。
function checkLastSegment (node) {
// report problem for function if last code path segment is reachable
}
module.exports = {
meta: { ... },
create: function(context) {
// declare the state of the rule
return {
ReturnStatement: function(node) {
// 在AST从上向下遍历到ReturnStatement node 时执行
},
// 在AST 从下向上遍历到 function expression node 时执行:
"FunctionExpression:exit": checkLastSegment,
"ArrowFunctionExpression:exit": checkLastSegment,
onCodePathStart: function (codePath, node) {
// 在分析代码路径开始时执行
},
onCodePathEnd: function(codePath, node) {
// 在分析代码路径结束时执行
}
};
}
};
遍历 AST
的过程中会以“从上至下”再“从下至上”的顺序经过节点两次,selector
默认会在下行的过程中执行对应的访问函数,如果需要再上行的过程中执行,则需要添加:exit
。
TSLint 迁移到 ESLint 集成
背景
在这里会有读者问有现成的 TSLint
不用,为什么要迁移到 ESLint
中集成?
解答下:由于性能问题,TypeScript
官方决定全面采用 ESLint
,甚至把仓库(Repository)
作为测试平台,而 ESLint
的 TypeScript
解析器也成为独立项目,专注解决双方兼容性问题。
JavaScript
代码检测工具 ESLint
在 TypeScript
团队发布全面采用 ESLint
之后,发布typescript-eslint
项目,以集中解决TypeScript
与 ESLint
兼容性问题。而 ESLint
不再维护 typescript-eslint-parser
,也不会在 npm
上做任何发布。TypeScript
解析器转移至 Github
的 typescript-eslint/parser
。
官方都放弃了我们也没必要太坚持,而且通过 ESLint
加上 ts
插件都可以完成检查
集成过程
首先安装依赖:
yarn add @typescript-eslint/parser @typescript-eslint/eslint-plugin -D
这两个依赖分别是:
@typescript-eslint/parser
:ESLint
的解析器,用于解析typescript
,从而检查和规范Typescript
代码。@typescript-eslint/eslint-plugin
:这是一个ESLint
插件,包含了各类定义好的检测Typescript
代码的规范。
安装好2
个依赖之后,修改之前创建的.eslintrc.js
文件,在该文件中加入 TSLint
配置。
module.exports = {
parser: '@typescript-eslint/parser', //定义ESLint的解析器
extends: ['plugin:@typescript-eslint/recommended'],//定义文件继承的子规范
plugins: ['@typescript-eslint'],//定义了该 eslint 文件所依赖的插件
env:{
browser: true,
node: true,
},
parserOptions: {
parser: '@typescript-eslint/parser', // 解析 .ts 文件
ecmaVersion: 2019,
sourceType: 'module',
ecmaFeatures: {
modules: true,
},
},
}
- 在
typescript
项目中必须执行解析器为@typescript-eslint/parser
,才能正确的检测和规范typescript
代码 env
环境变量配置,形如console
属性只有在browser
环境下才会存在,如果没有设置支持browser
,那么可能报console is undefined
的错误。- 上面的配置中
extends
中定义了了文件继承的子规范,使用的typescript-eslint
默认的推荐规范 parserOptions
解析器相关条件配置。
使用自定义的 typescript 规范
上面 extends
中 plugin:@typescript-eslint/recommended
使用的是插件默认推荐的 typescript
规范。但是会不会有同学不想使用推荐的规范,制定自己或者在推荐的规范中进行一些修改(比如一些老项目,加入规范,改动大,可能暂时忽略某些规范)
使用方式:如果想使用推荐,然后在推荐的基础上进行规范修改,可以直接在.eslintrc.js
文件中的rules对象中添加。
举个例子
rules:{
'@typescript-eslint/adjacent-overload-signatures': 2, // 要求成员重载是连续的
}
具体想修改那些自定义规范,可以去官网查看,这里给出官网地址。
- TSLint rule 官网
Prettier
无法确定一个让所有人都满意的方案,就很难执行下去!
Prettier
中文的意思是漂亮的、美丽的,是一个流行的代码格式化的工具。
我们都知道 ESLint
本身就带有格式化检查的,我们为什么要是使用它?它有什么优点?使用它要注意那些问题?
优点
Perriter
官网列出几个特点:
- An opinionated code formatter (译:固执己见的代码格式化程序)
- Supports many languages(译:支持多种语言)
- Integrates with most editors(译:与大多数编辑器集成)
- Has few options(译:没有什么选择)
其中最核心的点是 opinionated
,google
翻译过来是固执己见的,在 Pertiter
中,就是说:你必须认同我的观点,按照我说的做。否则你就别用我,硬着头皮用就会处处不爽!
要解决的问题
- 使用
Prettier
如何避免与ESLint
和TSLint
的格式化冲突? Prettier
中不提供的格式化规则,ESLint
中提供的可以兼容一起使用吗?
带着两个问题继续往下看
集成
安装模块包
我们来看如何结合 ESLint
来使用。首先我们需要安装三个依赖:
yarn add prettier eslint-config-prettier eslint-plugin-prettier -D
对每个依赖进行说明:
prettier
:Prettier
插件的核心代码。eslint-config-prettier
:解决ESLint
中的样式规范和Prettier
中样式规范的冲突,以Prettier
的样式规范为准,使ESLint
中的样式规范自动失效。eslint-plugin-prettier
:将prettier
作为ESLint
规范来使用。
创建 .prettierrc 文件
在项目的根目录下创建 .prettierrc.js
文件
module.exports = {
"printWidth": 120,
"semi": false,
"singleQuote": true,
"trailingComma": "all",
"bracketSpacing": false,
"jsxBracketSameLine": true,
"arrowParens": "avoid",
"insertPragma": true,
"tabWidth": 4,
"useTabs": false
};
每个属性的含义可以去 Prettier
中查看。
修改 .eslintrc.js 文件,引入 Prettier
在 extends
中添加
extends:[
'./.eslintRules.js',
'plugin:prettier/recommended',
'prettier', // 优先 prettier 中的样式规范
'prettier/@typescript-eslint',
// 这里可以加一些prettier不支持,eslint支持的格式化规则,但是个人认为prettier的格式化规则够用了
],
关于 Prettier 配置时特殊说明(重点看下)
网上好多在 ESLint
中加入的 Prettier
的文章,但是很少有讲清楚的,好多就是把配置文件写一下,然后很多小伙伴配置时候发现 Prettier
的格式化还是和 ESLint
中的格式化冲突,ctrl+s
保存的时候甚至出现来回切换格式的冲突,不知道小伙伴们遇到过这种情况没。
所以还是知道下原理,extends
中为什么那么写,格式冲突和顺序有什么关系没?
eslint-config-prettier
源码可以看出,它的代码很简单,它实际就是关闭了eslint
的所有格式化规则。
源码地址:https://github.com/prettier/eslint-config-prettier/tree/master/bin
- 我们
yarn add
插件的时候eslint-config-prettier
模块实际是为eslint-plugin-prettier
插件服务的,在eslint-plugin-prettier
的源码中调用了eslint-config-prettier
中相关的配置,然后执行插件中的代码。 - 看
eslint-config-prettier
中recommended
部分 的源码,源码中也有使用到eslint-config-prettier
(把已有格式化配置关掉),然后自己制定了基础的recommended
版本,讲到这应该明白为什么在eslint-plugin-prettier
中有一段最重要的话,需要把它(eslint-config-prettier)放在所有格式化配置的后面。
前面的内容,通过这个插件对前面 ESLint
的配置进行重置。如果想使用一些 Prettier
中不支持的格式化配置,我们把eslint中的格式化加在他们后面写了,也不会有冲突。
prettier/@typescript-eslint
是用来忽略typescript
中的格式化配置。
这里关于防止 Prettier
和 ESLint
冲突,画了一张
另外
eslint-plugin-prettier
和eslint-config-prettier
的源码都不是很复杂,感兴趣的同学可以去看看,下面是源码地址:
- eslint-plugin-prettier 官网
- eslint-config-prettier 官网
Prettier 原理简单说明
不管你写的代码是个什么鬼样子,Prettier
会去掉你代码里的所有样式风格,然后用统一固定的格式重新输出。输出时基本上只考虑一个参数,就是 line length
。
例如你写的这行代码:
foo(arg1, arg2, arg3, arg4);
一行装得下这么多代码,所以就不需要改。
如果你写了下面代码:
foo(reallyLongArg(), omgSoManyParameters(),IShouldRefactorThis(), isThereSeriouslyAnotherOne());
太长了,Prettier
就会重新改成这样输出:
foo(
reallyLongArg(),
omgSoManyParameters(),
IShouldRefactorThis(),
isThereSeriouslyAnotherOne()
);
咱们再仔细探究一下这个过程。不管你之前写的代码是什么样,首先必须符合语法规范。Prettier
先把你的代码转换成一种中间状态,叫 AST(Abstract Syntax Tree)
。
用 Prettier
提供的 Playground 更直观一些
上图左侧是手写代码,中间是 AST(去掉了任何代码风格),右侧是重新输出的结果。
Prettier 就是在这个 AST 上重新按照自己的风格输出代码。
这是 Prettier
也搞懂后的最终配置
module.exports = {
parser: '@typescript-eslint/parser', // 解析器
extends: [
"./.eslintRules.js",
'plugin:prettier/recommended',
"prettier",// 优先 prettier 中的样式规范
'prettier/@typescript-eslint',
], // 继承的规则
plugins: ['@typescript-eslint'], // 插件
env: {
es6: true,
node: true,
mocha: true,
},
parserOptions: {
parser: '@typescript-eslint/parser', // 解析 .ts 文件
ecmaVersion: 2019,
sourceType: 'module',
ecmaFeatures: {
modules: true,
},
},
rules: {
}, // 规则
};
注意 eslintRules
是 base
规则,单独提了出来。
VSCode 自动 fix 配置
因为终极目标是我们在使用eslint
格式化并且检查我们自己编写的 javascript
和 typescript
。除了我们要求的代码,如果开发者添加别的代码也应该进行格式化,除非忽略的文件,开发者本地安装什么插件我们管不到,在用户级别配置中 setting.json
中
{
// 此模式不能使用skipFiles特性,暂时关闭,需要调试其他进程时请在本地打开
// "debug.node.autoAttach": "on",
"editor.formatOnSave": true,
"debug.openDebug": "openOnFirstSessionStart",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"[javascript]": {
"editor.formatOnSave": false
},
"[typescript]": {
"editor.formatOnSave": false
}
}
lint 校验代码与 与 --fix 参数设置
上面的配置都做完了,如果不是一个新项目是原有的老项目,可能需要做一些改动喽!我们先 Fix一下。
npm 脚本中需要有这样的配置
"scripts": {
"lint": "eslint src",
"lint:create": "eslint --init"
}
执行命令npx run lint
会出现如下的错误:
1:7 error 'lint' is assigned a value but never used no-unused-vars
1:14 error Strings must use doublequote quotes
1:22 error Missing semicolon semi
3 problems (3 errors, 0 warnings)
2 errors, 0 warnings potentially fixable with the `--fix` option.
这里报了三个错误,分别是:
index.js
第1行第7个字符,报错编码规则为no-unused-vars
:变量lint
只定义了,但是未使用;index.js
第1行第14个字符,报错编码规则为quotes
:编码规范字符串只能使用双引号,这里却使用了单引号;index.js
第1行第22个字符,报错编码规则为semi
:编码规范每行代码结尾必须加分号,这里没有加分号。
设置 --fix
参数
说明:这里给 "lint": "eslint src --fix"
, 加上 --fix
参数,是 ESLint
提供的自动修复基础错误的功能。
此时运行 npm run lint
会看到少了两条报错信息,并不是说编码规范变了,而是 Eslint 自动修复了基础错误,打开 index.js 文件,可看到字符串自动变成了双引号,并且代码末尾也加上了分号。可惜的是 --fix
只能修复基础的不影响代码逻辑的错误,像 no-unused-vars
这种错误只能手动修改。
总结
本文主要对开篇那张图片中的本地代码检查部分进行了详细讲解,从实践到原理,另外小伙伴们也可以想下我开篇提到的如何做出亮点,希望有所帮助,最后快去制定一个属于自己项目的规范与检查吧!
如果开篇图中后面 CI/CD
部分感兴趣的可以找我讨论,后面会单独写一篇 CI/CD
文章,不然篇幅太长了,欢迎在看转发。
参考文章
- Prettier 看这一篇就行了
- ESLint 在中大型团队的应用实践
- 使用 ESLint+Prettier 规范 React+Typescript 项目
- eslint-plugin-prettier 官网
- Using ESLint and Prettier in a TypeScript Project
- 十分钟了解eslint配置 && 编写自定义eslint规则