Skip to main content

前端代码质量门禁

前端代码质量面临的问题

项目中经常会遇到由于前端代码基础质量不高造成工作反复、后期维护难的问题。 这些问题涉及HTML、 CSS/LESS/SASS、JS/TS,大致可分为 4 类:

  • 代码风格
  • 语法规范
  • 潜在语法与逻辑风险
  • 潜在安全风险

问题示例

  • HTML 风格问题
  <!--错误 table 没有使用th做表头-->
<table>
<tr>
<td>Name</td>
<td>Age</td>
</tr>
<tr>
<td>John Doe</td>
<td>24</td>
</tr>
<tr>
<td>Alice Doe</td>
<td>54</td>
</tr>
</table>

<!-- 正确 -->
<table>
<tr>
<th>Name</th>
<th>Age</th>
</tr>
<tr>
<td>John Doe</td>
<td>24</td>
</tr>
<tr>
<td>Alice Doe</td>
<td>54</td>
</tr>
</table>
  • HTML 书写问题
  <!-- href 属性应该放置链接 不是图片 -->
<a href="image.png">...</a>
<!-- 正确 -->
<a href="page.html">...</a>
  • CSS/LESS/SASS 风格问题
  /* 十六进制颜色写法不统一 */
a {
color: #f0a;
color: #FF00aa;
color: #FF00AA;
color: #ff00aa;
}

/* 嵌套层数太多 */
.a .b .c .d .e .f {
color: #fff000;
}
  • CSS/LESS/SASS 书写问题

/* 忘记书写通用字体 */
a {
font-family: Arial,
}

a {
font-family: Arial, sans-serif;
}

/* calc函数内表达式无效 */
.foo {
width: calc();
}

/* 重复的字体 */
a {
font-family: serif, serif;
}
  • JS/TS 风格问题
     // 方法排列没有顺序
// 正确排序方式
export class CustomersComponent implements OnInit, OnDestroy {

@Select(CustomersState.getItems) items$: Observable<CustomersModel[]>;

keywordControl = new FormControl(this.route.snapshot.queryParams.keyword || '');

private destroy = new Subject();

// ...
constructor(
private

route: ActivatedRoute
,
) {
//code
}

// ...
ngOnInit()
{
// code
}
// ...
private
getCurrentQueryParams()
{
// code
}
}

// 错误

export class CustomersComponent implements OnInit, OnDestroy {

@Select(CustomersState.getItems) items$: Observable<CustomersModel[]>;

keywordControl = new FormControl(this.route.snapshot.queryParams.keyword || '');

private destroy = new Subject();

// ...
constructor(
private

route: ActivatedRoute
,
) {
//code
}

// ...
private
getCurrentQueryParams()
{
// code
}

// ...
ngOnInit()
{
// code
}
}

  • JS/TS 书写问题
    // 1. 局部变量覆盖全局变量  b() 作用域中的 a 覆盖了全局环境中的 a。这会混淆读者并且在 b 中不能获取全局变量
var a = 3;

function b() {
var a = 10;
}

// 2. 回调函数未使用箭头函数造成 this 指向错误
foo(function () {
console.log(this.a);
});
foo(() => console.log(this.a;
))
;


// 3. 未使用 === 造成逻辑错误
let x = '42';
if (x === 42) {
}

前端代码质量门禁

通过建立前端代码质量门禁,提升代码质量,达到质量内建的效果,从而从根本上解决上述的问题。

质量门禁指标

定义 3 个质量门禁指标:

指标名目标值指标说明
可靠性A级根据项目bug数给与的项目评级:
A = 0 Bug 最高等级A,表示代码无bug
B = at least 1 Minor Bug 代码只要有一个次要bug,等级就为B
C = at least 1 Major Bug 只要包含一个重要bug,等级将为C
D = at least 1 Critical Bug 只要有一个严重bug,等级评估为D
E = at least 1 Blocker Bug 只要有一个最高等级的阻断级别的bug,可靠性评估为E,最低级别。
安全性A级根据项目安全漏洞给与的项目评级:
A = 0 Vulnerability 没有漏洞时,项目评估为最高级别A
B = at least 1 Minor Vulnerability 只要包含一个次要漏洞,项目评估为级别B
C = at least 1 Major Vulnerability 只要包含一个重要漏洞,项目评估为级别C
D = at least 1 Critical Vulnerability 只要包含一个严重漏洞,评估为D
E = at least 1 Blocker Vulnerability 只要包含一个阻断漏洞,项目评估为最低级别E
可维护性A级根据项目技术债务比率而给与的项目评级:
A = 0 - 0.05,即在0到5%之间
B = 0.06 - 0.1,即在6%到10%之间
C = 0.11 - 0.2,即在11%到20%之间
D = 0.21 - 0.5,即在21%到50%之间
E = 0.51 - 1,即超过50%
注:1.开发成本与修复成本的比率。该比率=修复成本/开发成本,也=修复成本/(代码行数*每行代码开发成本)
2.每行代码开发成本为0.06天。

检查规则项

4款工具共提供了约 850+ 项规则,考虑到投入产出比,我们整理了更为常用的400+ 项规则用于支撑3个质量门禁指标,并通过 stylelint、ESLint、Prettier、SonarQube 落地实施。

语言名称StylelintESlintPrettierSonarQube小计
HTML0002929
CSS/LESS/SCSS33002255
JS/TS0210298319
总计33210349403

Stylelint 检查规则项 (33项)

code规则描述AngularVueReact
color-no-invalid-hex禁止使用无效的十六进制颜色
font-family-no-duplicate-names禁止使用重复的字体名称
font-family-no-missing-generic-family-keyword禁止在字体系列名称列表中缺少通用字体
named-grid-areas-no-invalid禁止使用无效的命名网格区域
function-calc-no-invalid禁止在calc函数内使用无效的表达式
function-calc-no-unspaced-operator不允许在calc函数内使用空格运算符
function-linear-gradient-no-nonstandard-direction禁止linear-gradient()根据标准语法在无效的调用中使用 方向值
string-no-newline禁止在字符串中使用(未转义的)换行符
unit-no-unknown禁止使用未知单位
property-no-unknown禁止使用未知属性名
keyframe-declaration-no-important在关键帧动画声明中禁止使用 !important
declaration-block-no-duplicate-custom-properties禁止在声明块中使用重复定义自定义变量
declaration-block-no-duplicate-properties禁止在声明块中重复使用相同属性
declaration-block-no-shorthand-property-overrides禁止简写属性覆盖未简写属性
block-no-empty禁止空定义
selector-pseudo-class-no-unknown禁止使用未知的伪类选择器
selector-pseudo-element-no-unknown禁止使用未知的伪元素选择器
selector-type-no-unknown禁止使用未知类型选择器
media-feature-name-no-unknown禁止使用未知的媒体查询名称
at-rule-no-unknown禁止使用未知规则
comment-no-empty禁止空备注
no-descending-specificity禁止较低优先级的选择器在覆盖较高优先级的选择器之后出现
no-duplicate-at-import-rules禁止@import重复样式文件
no-duplicate-selectors禁止在样式表中使用重复的选择器
no-empty-source禁止空来源
no-extra-semicolons禁止使用多余的分号
no-invalid-double-slash-comments禁止无效的双斜杠注释
number-leading-zero对于小于1的小数允许前导零
max-nesting-depth限制嵌套的深度为5
selector-max-id限制选择器中ID选择器的数量为2
color-hex-length十六进制颜色要求使用长表示法
color-hex-case指定颜色值为小写
unit-allowed-list制定允许的单位列表为"em", "rem", "%", "px", "s", "deg"

ESlint 检查规则项 (21项)

code规则描述AngularVueReact
@typescript-eslint/no-namespace禁止使用自定义TypeScript模块和名称空间
@typescript-eslint/no-explicit-any禁止使用该any类型
@typescript-eslint/dot-notation尽可能执行点符号
@typescript-eslint/no-shadow禁止在外部范围中声明的阴影变量中声明变量
@typescript-eslint/member-ordering需要一致的成员声明顺序
@typescript-eslint/semi要求使用分号代替
@typescript-eslint/no-extra-semi禁止不必要的分号
@typescript-eslint/no-inferrable-types禁止声明显示类型
@typescript-eslint/member-delimiter-style接口和类型文字需要特定的成员定界符样式
@typescript-eslint/no-unused-expressions禁止未使用的表达式
@typescript-eslint/no-extra-parens禁止不必要的括号
@typescript-eslint/naming-convention对代码库中的所有内容强制执行命名约定
@typescript-eslint/no-empty-function禁止空函数
no-extra-boolean-cast禁止不必要的布尔类型转换
prefer-const要求使用 const 声明那些声明后不再被修改的变量
no-empty禁止空块语句
eqeqeq要求使用 === 和 !==
object-shorthand要求对象字面量简写语法
prefer-arrow-callback要求使用箭头函数作为回调
guard-for-in需要约束 for-in (guard-for-in)
no-underscore-dangle禁止标识符中有悬空下划线

Sonar 检查规则项 (349项)

SonarQube 使用每种语言的默认的 Quality Profiles 配置文件对代码进行静态分析

语言名称规则数量
HTML29
CSS/LESS/SCSS22
JS153
TS145

具体规则可以在SonarQube中的Quality Profiles菜单中查看

工具安装与配置

Stylelint 安装

1. 安装 Stylelint

npm install stylelint --save-dev
// 或
yarn add stylelint -D

2. 安装基本配置

npm install stylelint-config-recommended --save-dev
// 或
yarn add stylelint-config-recommended -D

Stylelint 配置

1. 创建 .stylelintrc.js 配置文件

2. 配置 规则

// .stylelintrc.js
module.exports = {
extends: [
// 基本推荐规则继承,继承社区推荐的规则
'stylelint-config-recommended'
],
rules: {
// 添加基于项目自定义的常见错误
}
};

3. 添加校验命令

 // package.json
"styleCheck": "yarn stylelint 'src/**/*.{html,css,less,vue,js,jsx,ts,tsx}'",

4. 编辑器安装扩展插件

  • VSCode 客户端之后,安装以下扩展插件
插件名称插件说明
vscode-stylelintVisual Studio Code扩展,可使用stylelint扩展到CSS / SCSS / Less
  • Webstorm 进行以下配置

Webstorm stylelint 配置文档

ESlint 安装

npm install eslint --save-dev
// 或
yarn add eslint -D

ESlint 配置

1. 创建 .eslintrc.js配置文件

2. 配置基础规则

// .eslintrc.js
module.exports = {
root: true,
rules: {
//配置基本规则要求
}
};

3. 添加校验命令

 // package.json
"eslint": "eslint src/ --ext .js,.jsx,.ts,.tsx",

4. 编辑器安装扩展插件

  • VSCode 客户端之后,安装以下扩展插件
插件名称插件说明
VS Code ESlint extensionVS Code ESLint扩展
  • Webstorm 进行以下配置

Webstorm ESlint 配置文档

SonarLint 配置

SonarLint是SonarSource发布的扩展,可帮助您在以JavaScript,TypeScript,Python,Java,HTML和PHP编写代码时检测并修复质量问题。

1.添加插件

插件名称插件说明
SonarLintSonarLint for Visual Studio 扩展

注意:SonarLint需要安装JDK。Java 8 or more recent is required to run. Please download and install a recent JRE. 如果您没有安装所需的JDK,则会显示一条通知,显示。单击获取Java运行时环境以下载并安装所需的JDK。

Webstorm 配置文档

2. 添加SonarQube配置

在编辑器菜单中一次点击 code > 首选项 > 设置 > 用户, 在设置中找到SonarLint点击在 setting.json中添加以下配置


"sonarlint.connectedMode.connections.sonarqube": [
{
"serverUrl": "sonarqube服务器地址",
"token": "用户token"
},
],

3.在sonarqube服务器创建项目

sonarqube sonarqube

4.配置项目绑定

在编辑器菜单中一次点击 code > 首选项 > 设置 > 工作区, 在设置中找到SonarLint点击在 setting.json中添加以下配置

projectKey 同上图2中的 Project key 的名称


"sonarlint.connectedMode.project": {
"projectKey": "项目名称"
}

5.在编辑器中执行SonarLint命令,和服务器同步错误提示

按Ctrl + Shift + P(Windows/Linux)或Shift + Command + P(Mac)打开命令面板,然后键入SonarLint以调出SonarLint命令并运行SonarLint: Update all bindings to SonarQube/SonarCloud

SonarLint 同步错误提示

SonarLint 错误提示

SonarLint 提示同步完成

SonarLint 提示同步完成

绑定后重启编辑器就可以在打开的页面上看到对应的错误提示

针对 Vue 配置 ESlint

添加eslint-plugin-vue扩展插件

vue add @vue/cli-plugin-eslint
// 或
npm install --save-dev eslint-plugin-vue
// 或
yarn add eslint-plugin-vue -D

修改 eslintrc.js

module.exports = {
root: true,
plugins: [
'@typescript-eslint' // 扩展 typescript 支持
],

/** .vue 文件需要使用 vue-eslint-parser 解析器来解析 */
parser: 'vue-eslint-parser',

env: {
es6: true,
browser: true,
node: true
},

/**
* 为了让 ESLint 在处理非 ECMAScript 5 特性时正常工作
* 配置属性 parserOptions 是必须的
*/
parserOptions: {
/**
* 如果想了解此项配置和上面的 parser,请阅读官方文档
* https://eslint.vuejs.org/user-guide/#how-to-use-a-custom-parser
*/
parser: '@typescript-eslint/parser',
sourceType: 'module',
ecmaVersion: 2020,
ecmaFeatures: {
legacyDecorators: true
}
},

/**
* ESLint 规则
* 注:因为很多插件的规则中存在特殊符号,因此 rules 下面的所有 key 用单引号来声明
*/
rules: {
// 添加自定义配置
}
};

让 ESLint 支持 Vue 单文件组件

在编辑器菜单中一次点击 code > 首选项 > 设置, 在设置中找到 ESLint 点击在 setting.json中编辑,在eslint.validate中添加vue


"eslint.validate": [
//...
"vue"
]

针对 Angular 配置 ESLint

添加 angular-eslint 扩展插件

ng add @angular-eslint/schematics

TSLint 转换为 ESLint

ng g @angular-eslint/schematics:convert-tslint-to-eslint {{项目名称}}

修改 eslintrc.json (angular 会自动生成的为.json文件)

module.exports = {
root: true,
overrides: [
{
files: [
'*.ts'
],
parser: "@typescript-eslint/parser",
// 添加解析器
parserOptions: {
ecmaVersion: 2020,
sourceType: "module",
project: [
"tsconfig.app.json"
],
createDefaultProgram: true
},
plugins: [
"@typescript-eslint"
],
// 添加typescript 支持
rules: {
// 添加自定义配置
}
},
{
files: [
'*.html'
],
extends: [
'plugin:@angular-eslint/template/recommended'
],
rules: {}
}
]
};

针对 React 配置 ESlint

添加 ESLint的解析器

yarn add  @typescript-eslint/parser -D
// 或
npm install --save-dev @typescript-eslint/parser

添加 Typescript 代码的规范扩展

yarn add @typescript-eslint/eslint-plugin @typescript-eslint/tslint-plugin -D
// 或
npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/tslint-plugin

修改 eslintrc.json


module.exports = {
parser: "@typescript-eslint/parser",
// Specifies the ESLint parser
parserOptions: {
ecmaVersion: 2020,
// Allows for the parsing of modern ECMAScript features
sourceType: "module"
// Allows for the use of imports
},
plugins: [
"@typescript-eslint"
],
rules: {
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/no-explicit-any': [
'warn'
],
'@typescript-eslint/dot-notation': 'off',
// 强制尽可能地使用点号
'@typescript-eslint/no-shadow': 'error',
'@typescript-eslint/member-ordering': 'error',
// 标准化类声明,类表达式,接口和类型文字的结构化和排序方式
'@typescript-eslint/semi': 'error',
// 强制使用一致的分号
'@typescript-eslint/no-extra-semi': 'error',
// 禁止不必要的分号
'@typescript-eslint/no-inferrable-types': 'error',
// 禁止声明显示类型
'@typescript-eslint/member-delimiter-style': 'error',
// 成员分隔符样式
'@typescript-eslint/no-unused-expressions': [
'error',
{
'allowShortCircuit': true,
'allowTernary': true,
'allowTaggedTemplates': true
}
],
'@typescript-eslint/no-extra-parens': 'off',
'@typescript-eslint/naming-convention': 'off',
'@typescript-eslint/no-empty-function': 'error',
'no-extra-boolean-cast': 'error',
// 禁止不必要的布尔类型转换
'prefer-const': 'error',
// 要求使用 const 声明那些声明后不再被修改的变量
'no-empty': 'error',
// 禁止出现空语句块
'eqeqeq': 'error',
// 要求使用 === 和 !==
'object-shorthand': 'error',
// 要求或禁止对象字面量中方法和属性使用简写语法
'no-shadow': 'off',
'prefer-arrow-callback': 'error',
// 单行箭头函数
'guard-for-in': 'off',
'no-underscore-dangle': [
'error',
{
allowAfterThis: true,
allowAfterSuper: true
}
]
}
};

作业流程

1.安装编辑器插件

安装 Stylelint、ESlint、SonarLint等插件并配置相关参数(参考工具安装与配置), 安装后编辑器会在开发途中用波浪线提示代码中存在的错误。

Stylelint 错误提示效果

StyleLint 错误提示

ESlint 错误提示效果

Eslint 错误提示

SonarLint 错误提示效果

SonarLint 错误提示

注:sonarLint需要和服务器同步错误提示(同步方法参考SonarLint配置)

2.在提交前代对代码进行检查

在提交代码前可以使用以下的命令行检测代码。 如果代码中存在错误, 命令行则会直接提示错误所在文件和所在行数还有错误类型,此时需要对其逐一修改,直至不在提示错误方可提交代码等待自动发布。


// CSS/LESS/SCSS 检查

yarn run styleCheck

// 或

npm run styleCheck

// JS/TS 检查

yarn run eslint

// 或

npm run eslint

执行 styleCheck 错误提示效果

StyleLint 提示

执行 eslint 错误提示效果

EsLint 提示

3.提交后等待发布

提交代码后,sonarQube 会扫码代码,如收到标题为:FAILED: Job XXXXXX的邮件,代表发布代码发布失败,此时需要登录 sonarQube 网站, 点击对应的工程,根据提示修改文件后方可再次提交

发布失败提示邮件

发布失败提示邮件

注:收件地址为用户提交代码所使用账号对应的邮箱,以 Sourcetree 为例,可在 设置 > 高级 > 电子邮箱地址 中查看

项目整体状态

sonarQube 分析

具体错误提示

sonarQube 分析