零散专题16 代码检查和格式化

EditorConfig + ESLint + ESLint + Husky + Stylelint学习小结。

EditorConfig

为了保持项目中代码缩进风格的一致,可以使用.editorconfig文件,来定义和维护一致的编码风格,例如范缩进风格,缩进大小,Tab长度以及字符集等。一般情况会覆盖IDE本身的设置,所以放在项目的根目录中,提交到代码仓库进行维护。

.editorconfig中第一行用于指明.editorconfig文件的位置,[*]表明规则应用所有文件,可以针对不同文件(比如[*.md])添加不同的规则,再下面是具体对应的规则

下面是我在项目中尝试用的一份.editorconfig文件,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
root = true

[*]
# 缩进风格:空格
indent_style = space

# 缩进大小2
indent_size = 2

# 字符集utf-8
charset = utf-8

# 除去换行行首的任意空白字符
trim_trailing_whitespace = true

# 使文件以一个空白行结尾
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false

没有加入的属性几个,比如定义换行符的end_of_line,可以取值lfcrcrlf,更详细的介绍可以参考腾讯AlloyTeam的文章

ESLint

Eslint是一个用来识别ECMAScript并且按照规则给出报告的代码检测工具,使用它可以避免低级错误和统一代码的风格。ESLint被设计为完全可配置的,一般使用配置文件来配置ESLint,在项目的根目录下创建.eslintrc.js

可以被配置的信息主要分为3类:

  • Environments:你的Javascript脚本将要运行在什么环境(如:Node,浏览器等)中。
  • Globals:执行代码时脚步需要访问的额外全局变量。
  • Rules:开启某些规则,也可以设置规则的等级。

安装

安装推荐局部安装:

1
npm i -D eslint

安装完毕后,接下来新建一个配置文件.eslintrc.js,或者使用如下的命令行来自动生成。

1
2
3
4
./node_modules/.bin/eslint --init

# 如果是全局安装的话可以执行
eslint --init

配置文件中的配置规则分为三种等级:

  • off或者0:关闭规则
  • warn或者1:打开规则,并且作为一个警告
  • error或者2:打开规则,并且作为一个错误
1
2
3
4
5
6
7
// .eslintrc.js
module.exports = {
env: {
browser: true,
node: true,
},
};

具体的配置规则非常多,具体见这里

在Webstorm中使用

在Webstorm中配置ESLint,需要在Language & FrameworksJavascriptCode Quality ToolsESLint中进行配置,勾选Enable

一般情况下下面的对应的配置和地址都会自动填好。

开启之后Webstorm就会在代码中对违反ESLint规则的代码进行高亮提示。

配合Vue项目使用

Vue Cli在创建项目时,可以选择是否启用ESLint,并且可以选择使用哪种扩展,基本上不需要自己再进行配置,但是如果需要自己手动配置时,可以自己安装eslint-plugin-vue这个插件

ESLint配置中的pluginextend的区别是,extend提供的是ESLint现有规则的一些列预设,而plugin除了提供预设之外的自定义规则,当现有格式的代码(比如.vue)在ESLint的规则中没有对应的规则是,就需要借用插件来实现了

插件的安装有三种方式,推荐使用第一种:

1
2
3
4
5
vue add @vue/cli-plugin-eslint

npm install --save-dev eslint eslint-plugin-vue

yarn add -D eslint eslint-plugin-vue

对应的配置文件eslintrc.js

1
2
3
4
5
6
7
8
9
10
11
module.exports = {
extends: [
// add more generic rulesets here, such as:
// 'eslint:recommended',
'plugin:vue/recommended'
],
rules: {
// override/add rules settings here, such as:
// 'vue/no-unused-vars': 'error'
}
}

规则分为Essential、Strongly Recommended和Recommended三个等级,具体的规则可以参考官网

我在项目中使用的配置是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// ESLint 中文文档:http://eslint.cn/docs/rules/
// eslint-plugin-vue: https://eslint.vuejs.org/
// 百度 EFE 团队使用的 eslint 配置:https://github.com/ecomfe/eslint-config/blob/master/index.js
// JavaScript 编码规范(百度): http://gitlab.baidu.com/fe/spec/blob/master/javascript.md#2-1
// Vue 组件代码规范(百度): http://gitlab.baidu.com/fe/spec/blob/master/vue.md#2-1
// iCode校验规则说明:http://bugbye.baidu.com/rules

module.exports = {
'parser': 'vue-eslint-parser',
'env': {
'browser': true,
'amd': true,
'node': true
},
parserOptions: {
'ecmaVersion': 8,
'parser': 'babel-eslint',
'sourceType': 'module'
},
'extends': ['plugin:vue/essential', ],
'rules': {
/* ---------------- 禁止 ---------------- */
// 4个空格缩进
'indent': ['error', 4, {
'SwitchCase': 1, // switch 的 case 子句缩进4个空格
}],
// 只允许最大连续2行空行
'no-multiple-empty-lines': ['error'],
// 禁止不写分号
'semi': ['error', 'always', {
'omitLastInOneLineBlock': true
}],
// 使用单引号
'quotes': ['error', 'single'],
// Vue 模板标签开始、结束位置不能有空格,自闭合标签有空格
'vue/html-closing-bracket-spacing': ['error', {
'startTag': 'never',
'endTag': 'never',
'selfClosingTag': 'always',
}],
// Vue 模板结束标签
'vue/html-closing-bracket-newline': ['error', {
'singleline': 'never',
'multiline': 'never',
}],
// Vue 模板标签对齐方式
'vue/html-indent': ['error', 4, {
'attribute': 1,
'baseIndent': 1,
'closeBracket': 0,
'alignAttributesVertically': true,
'ignores': []
}],
// Vue script 标签对齐
'vue/script-indent': ['error', 4, {
'baseIndent': 1,
'switchCase': 1, // switch 的 case 子句缩进4个空格
}],
// Vue 模板标签必须成对出现
'vue/html-end-tags': 'error',
// 对象和数组字面量中,最后一个选项的逗号,多行时必须有,单行时不能用
'comma-dangle': ['error', {
'arrays': 'always-multiline',
'objects': 'always-multiline',
'imports': 'always-multiline',
'exports': 'always-multiline',
'functions': 'never',
}, ],
// 禁止在数组括号内出现空格
'array-bracket-spacing': ['error', 'never'],
// 禁止在对象的大括号内出现空格
'object-curly-spacing': ['error', 'never'],
// 禁止在对象字面量的键和冒号之间存在空格,要求在对象字面量的键和冒号之间存在至少有一个空格
'key-spacing': ['error', {
'beforeColon': false,
'afterColon': true,
}, ],
/* ---------------- 建议 ---------------- */
// 建议提交时不要出现alert, prompt 和 confirm,
'no-alert': 'warn',
// 建议提交时不要出现console
'no-console': 'warn',
// 建议提交时不要出现debugger
'no-debugger': 'error',
// 建议不要出现常量作为判断条件
'no-constant-condition': 'warn',
// 建议注释符要有空白隔开
'spaced-comment': ['warn', 'always'],
// 建议将操作符放到行首, 而不是行尾
'operator-linebreak': ['warn', 'before'],
// 建议代码列数不能超过120行
'max-len': ['warn', 120],
/* ---------------- 允许 ---------------- */
// 允许if (!!foo) 这种形式
'no-extra-boolean-cast': 'off',
// 允许对函数声明进行覆盖赋值
'no-func-assign': 'off',
// 允许函数在不同的情况下返回不同类型的值
'consistent-return': 'off',
// 允许覆盖外部变量
'no-shadow': 'off',
// 允许使用下划线开头命名变量
'no-underscore-dangle': 'off',
},
// 覆盖规则
'overrides': [{
// vue 文件的 intent 规则使用
'files': ['*.vue'],
'rules': {
'indent': 'off',
}
}]
};

配合React项目使用

需要安装ESLint-plugin-React插件

1
npm install eslint eslint-plugin-react --save-dev

详细的规则参考官网

下面是我的一份.eslintrc.js配置文件,在extends中使用这个插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
module.exports = {
extends: [
'eslint:recommended',
'plugin:react/recommended'
],
plugins: [
'reacte',
'react-hooks'
],
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
// 开启 JSX 支持
jsx: true
}
}
env: {
jest: true,
browser: true,
node: true
},
settings: {
react: {
// React version. 'detect' automatically picks the version you have installed.
// You can also use `16.0`, `16.3`, etc, if you want to override the detected value.
'version': 'detect'
}
},
globals: {
// 这里填入你的项目需要的全局变量
// 这里值为 false 表示这个全局变量不允许被重新赋值,比如:
//
// jQuery: false,
// $: false
},
rules: {
// 这里填入你的项目需要的个性化配置,比如:
//
// @fixable 一个缩进必须用两个空格替代
'indent': [
2,
2,
{
// 强制 switch 语句中的 case 子句的缩进级别
'SwitchCase': 1,
// 要求三元表达式内的三元表达式不能有缩进
'flatTernaryExpressions': true,
// 对于 JSX 属性对其不进行约束,在下面的 react/jsx-indent-props 进行约束
'ignoredNodes': ['JSXAttribute', 'JSXSpreadAttribute']
}
],
'semi': ['warn', 'always'],
// JSX 的 children 缩进必须为两个空格
'react/jsx-indent': [2, 2],
// JSX 的 props 缩进必须为两个空格
'react/jsx-indent-props': [2, 'first'],
// JSX 自闭和标签结束位置
'react/jsx-closing-bracket-location': [2, 'after-props'],
// 禁止出现超过两行的连续空行
'no-multiple-empty-lines': [2, { 'max': 2}],
// 一行长度不应该超过120个字符
'max-len': [2, 120],
// React Hooks只能应用顶级函数和React函数中
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',

'no-unused-vars': 'warn',

// no-debugger
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
}
};

也可以直接使用腾讯的AlloyTeam的ESLint规则(推荐),有详细的规则描述和示例,可以在此基础上进行定制。

使用时需要首先安装eslintbabel-eslinteslint-config-alloy,这是AlloyTeam的ESLint规则的最基础的所需要安装的依赖

1
npm install -D eslint babel-eslint eslint-config-alloy eslint-plugin-react

并将以下内容复制到.eslintrc.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
module.exports = {
extends: [
'alloy',
'alloy/react',
],
env: {
// 你的环境变量(包含多个预定义的全局变量)
//
// browser: true,
// node: true,
// mocha: true,
// jest: true,
// jquery: true
},
globals: {
// 你的全局变量(设置为 false 表示它不允许被重新赋值)
//
// myGlobal: false
},
rules: {
// 自定义你的规则
}
};

AlloyTeam也有针对Vue项目、TypeScript项目、React+TypeScript项目的各种规则,可以按需选择。

需要注意的是,如果使用的Create React App脚手架工具来搭建React项目,由于它将默认的构建配置封装了起来,而ESLint仅仅开启了最基本的规则,更重要的是默认情况下,ESLint仅仅会在IDE中对违反规则的情况进行提示,并不会在构建时在终端的输出进行终端和提示。

如果这种情况可以满足需要,而只需要开启更多的规则,那么在.eslintrc.js中的extends中添加对应的规则集即可

但是如果要起到更强制性的提示作用(中断构建、终端提示),Create React App建议使用Prettier代替ESLint。如果要使用ESLint,那么就需要使用npm run eject,将配置文件吐出,按照AlloyTeam的提示进行配置即可。

配合TypeScript项目使用

由于ESLint默认使用Espree进行语法解析,无法识别TypeScript的某些语法,需要安装@typescript-eslint/parser替代默认的解析器

1
npm install --save-dev @typescript-eslint/parser

最后安装对应的规则插件@typescript-eslint/eslint-plugin

1
npm install --save-dev @typescript-eslint/eslint-plugin

对ESLint进行配置,在根目录下创建.eslintrc.js

1
2
3
4
module.exports = {
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
};

上面提到了,AlloyTeam也有对应TypeScript的规则

安装和配置时直接按照下面的进行即可,已经包含了上面的安装和配置步骤

安装:

1
npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-config-alloy

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
module.exports = {
extends: [
'alloy',
'alloy/typescript',
],
env: {
// 您的环境变量(包含多个预定义的全局变量)
// Your environments (which contains several predefined global variables)
//
// browser: true,
// node: true,
// mocha: true,
// jest: true,
// jquery: true
},
globals: {
// 您的全局变量(设置为 false 表示它不允许被重新赋值)
// Your global variables (setting to false means it's not allowed to be reassigned)
//
// myGlobal: false
},
rules: {
// 自定义您的规则
// Customize your rules
}
};

配合TypeScript + React项目使用(.tsx文件)

要检查React的.tsc文件,首先需要安装eslint-plugin-react

1
npm install --save-dev eslint-plugin-react

然后在lint命令后面需要添加.tsx后缀:

1
2
3
4
5
{
"scripts": {
"eslint": "eslint src --ext .ts,.tsx"
}
}

也可以使用AlloyTeam的ESLint规则的TypeScript React版本

安装:

1
npm install --save-dev eslint typescript @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-react eslint-config-alloy

配置文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
module.exports = {
extends: [
'alloy',
'alloy/react',
'alloy/typescript',
],
env: {
// Your environments (which contains several predefined global variables)
//
// browser: true,
// node: true,
// mocha: true,
// jest: true,
// jquery: true
},
globals: {
// Your global variables (setting to false means it's not allowed to be reassigned)
//
// myGlobal: false
},
rules: {
// Customize your rules
}
};

ESlint对Async报错的解决方法

在ESlint配置文件中增加

1
2
3
parserOptions: {
"ecmaVersion": 8,
}

Prettier

Prettier是一个流行的代码格式化工具,Webstorm的代码格式化就使用了这个插件。

一般情况下,ESLint会包含对代码格式的检查(比如缩进、结尾分号等),但是很多ESLint的插件(比如eslint-config-alloy)将所有样式相关的规则都剔除了。这是因为相比与ESLint中的代码格式规则,它提供了更少的选项,但是却更加专业。所以让ESLint来检查它更擅长的逻辑错误,使用Prettier中检查样式错误。

使用Prettier有两种方式,一种是结合ESLint使用(推荐),首先安装:

1
npm i -D prettier eslint-config-prettier eslint-plugin-prettier

其中prettier是Prettier插件的核心代码,eslint-config-prettier用来解决ESLint中的样式规范和Prettier中样式规范的冲突,以Prettier的样式规范为准,使ESLint中的样式规范自动失效,eslint-plugin-prettier用来将Prettier作为ESLint规范来使用

然后在项目的根目录下创建.prettierrc.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 基于 AlloyTeam 使用的 .prettierrc.js 配置进行修改
module.exports = {
// max 120 characters per line
printWidth: 120,
// use 2 spaces for indentation
tabWidth: 2,
// use spaces instead of indentations
useTabs: false,
// semicolon at the end of the line
semi: true,
// use single quotes
singleQuote: true,
// object's key is quoted only when necessary
quoteProps: 'as-needed',
// use double quotes instead of single quotes in jsx
jsxSingleQuote: false,
// no comma at the end
trailingComma: 'none',
// spaces are required at the beginning and end of the braces
bracketSpacing: true,
// end tag of jsx need to wrap
jsxBracketSameLine: false,
// brackets are required for arrow function parameter, even when there is only one parameter
arrowParens: 'always',
// format the entire contents of the file
rangeStart: 0,
rangeEnd: Infinity,
// no need to write the beginning @prettier of the file
requirePragma: false,
// No need to automatically insert @prettier at the beginning of the file
insertPragma: false,
// use default break criteria
proseWrap: 'preserve',
// decide whether to break the html according to the display style
htmlWhitespaceSensitivity: 'ignore',
// lf for newline
endOfLine: 'lf',
// Whether or not to indent the code inside <script> and <style> tags in Vue files
vueIndentScriptAndStyle: false,
};

关于规则的说明可以参考官方文档

接着修改.eslintrc.js文件,引入Prettier(下面项目是TypeScript项目,其他项目配置参考eslint-config-prettier的文档

1
2
3
4
5
6
7
8
9
10
module.exports = {
parser: '@typescript-eslint/parser',
extends: [
'prettier/@typescript-eslint',
"plugin:prettier/recommended",
],
plugins: [
'@typescript-eslint'
],
};

prettier/@typescript-eslint使得@typescript-eslint中的样式规范失效,遵循Prettier中的样式规范,plugin:prettier/recommended使用Prettier中的样式规范,且如果使得ESLint会检测prettier的格式问题,同样将格式问题以Error的形式抛出

这种形式的好处是,将Prettier与ESLint配合使用,当使用Lint-Staged进行检查时,只需要配置ESLint相关的命令即可。如果不符合规则,会进行提示,让你手动修改。

另一个方式就是单独使用Preitter,不使用eslint-plugin-prettier等插件,然后同样在.prettierrc.js中进行配置:

1
npm install --save-dev --save-exact prettier

这样的缺点是需要单独配置Prettier,优点是当代码风格不符合规则时,会直接按照配置的风格重新输出代码。当ESLint的规则与Prittier冲突时,还是通过上面提到的eslint-config-prettier来解决冲突

配合FECS使用

由于百度使用了统一制定的个性化的ESLint规则,可以直接使用fecs来检查代码

常规的使用方法不能满足每次提交时只检测对应改动的文件的要求,想要结合husky使用,需要新建一个.lintstagedrc文件:

1
2
3
4
5
module.exports = {
// 使用 fecs 进行校验
'*.js': filenames = > filenames.map(filename = > `fecs check '${filename}'--level = 2--rule = true--type = js, vue`)
// '*.js': filenames => filenames.map(file => `eslint ${file}`)
};

package.jsonlint-staged的配置删除,这样每次提交时就会针对改动的文件进行配置。

它采用的JavaScript的编码规范在这里

Fecs还提供了格式化的功能fesc format

stylelint

ESLint检查的JavaScript代码,对于CSS代码的检查就需要使用stylelint,它的使用和配置与ESLint非常类似,关于它的实践安可以参考企业微信的文章

它的具体规则集在这里。预设的规则集有stylelint-config-recommendedstylelint-config-standard,前者只会检验可能导致错误的规则,后者在前者基础上还增加了60条规则,官方的选择建议是,如果使用了Prettier选择stylelint-config-recommended,如果希望统一样式的编写格式,那么可以选择stylelint-config-standard。在选择了某一种后,也可以在.stylelintrc.js中进行自定义的配置。

支持多种引入方式,一般常用的有下面两种

(1)命令行使用

安装:

1
npm install stylelint  stylelint-config-standard -D

然后在.stylelintrc.js中进行配置:

1
2
3
4
5
6
7
8
// stylelint规则集:https://stylelint.io/user-guide/rules
// stylelint-config-standard扩展规则集:https://github.com/stylelint/stylelint-config-standard
module.exports = {
'extends': 'stylelint-config-standard',
'rules': {
// add your onw rules
}
};

这样在命令行中就可以对CSS文件进行校验:

1
2
3
stylelint "foo/*.css"
# 校验HTML文件中的 style 标签内的样式内容
stylelint "bar/*.html"

(2)与Webpack等构建工具配合使用

stylelint可以与各种构建工具配合使用,例如它提供了Webpack的插件stylelint-webpack-plugin,首先安装:

1
npm install stylelint  stylelint-config-standard stylelint-webpack-plugin  -D

然后在Webpack中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// vue.config.js

const StyleLintPlugin = require('stylelint-webpack-plugin');

module.exports = {
lintOnSave: process.env.NODE_ENV !== 'production',
configureWebpack: {
plugins: [
new StyleLintPlugin({
files: ['src/**/*.{vue,htm,html,css,sss,less,scss,sass}'],
lintDirtyModulesOnly: true,
failOnError: false,
failOnWarning: false
})
]
}
};

lint-staged + husky

使用lint-staged配合Husky,可以实现在Git某种操作前(一般是commit)仅检测有改动的代码。

首先进行安装:

1
2
3
4
npm install -D lint-staged husky;

// 这一行就可以安装husky和lint-stage,并且配置好husky。
npx mrm lint-staged

然后修改package.json,增加配置:

1
2
3
4
5
6
7
8
9
10
{
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"*.{js,vue}": ["eslint", "stylelint"],
},
}

这样在Commit之前就会使用ESLint进行校验。

如果配合Prettier使用,则可以自动格式化代码:

1
2
3
4
5
6
7
8
{
"scripts": {
"precommit": "lint-staged"
},
"lint-staged": {
"src/**/*.js": ["prettier --write", "git add"]
}
}

参考