代码质量和风格化:Eslint,prettier

586 阅读17分钟

编码规范

规范的意义

每个程序员都有自己的编码习惯,最常见的莫过于:

  • 有的人写代码一行代码结尾必须加分号 ;,有的人觉得不加分号 ; 更好看;
  • 有的人写代码一行代码不会超过 80 个字符,认为这样看起来简洁明了,有的人喜欢把所有逻辑都写在一行代码上,觉得别人看不懂的代码很牛逼;
  • 有的人使用变量必然会先定义 var a = 10;,而粗心的人写变量可能没有定义过就直接使用 b = 10;

如果你写自己的项目怎么折腾都没关系,但是在公司中老板希望每个人写出的代码都要符合一个统一的规则,这样别人看源码就能够看得懂,因为源码是符合统一的编码规范制定的。

那么问题来了,总不能每个人写的代码老板都要一行行代码去检查吧,这是一件很蠢的事情。凡是重复性的工作,都应该被制作成工具来节约成本。这个工具应该做两件事情:

  • 提供编码规范;
  • 提供自动检验代码的程序,并打印检验结果:告诉你哪一个文件哪一行代码不符合哪一条编码规范,方便你去修改代码。

Lint 因此而诞生。Lint 是检验代码格式工具的一个统称,具体的工具有 JslintEslint 等等

检测方式

检测可分为两种方式

  • 使用编辑器的扩展
  • 使用脚本的方式

第一种方式其实适合个人开发,第二种方式适合团队开发。

至于为什么这么说,就要考虑到二者的优先级问题了。上面两种方式如果同时存在的话,会有优先级的问题。

.prettierrc 的优先级会高于在vscode全局配置settings.json中格式化配置的优先级(同理eslint)

也就是说,如果你在一个项目中有 .prettierrc 配置文件,然后你又在settings.json也配置了格式化规则,那么当你在vscode编辑器中对一个文件点击鼠标右键[格式化文档]的时候,格式化规则会以 .prettierrc 为准

Eslint(代码质量)

含义

ESLint 是什么呢? 是一个开源的 JavaScript 的 linting 工具,使用 espree 将 JavaScript 代码解析成抽象语法树 (AST),然后通过AST 来分析我们代码,从而给予我们两种提示:

  1. 代码质量问题
  2. 代码风格问题(ESLint 之类的 Linters 对于代码格式化的能力是有限的,不如 Prettier 那么专业。通过 Prettier 执行格式化代码的工作,而代码质量的控制由 ESLint 处理)

获得如下收益:

  • 在执行代码之前发现并修复语法错误,减少调试耗时和潜在 bug
  • 保证项目的编码风格统一,提高可维护性
  • 督促团队成员在编码时遵守约定的最佳实践,提高代码质量

ESLint 可以让程序员在编码的过程中发现问题而不是在执行的过程中,还可以让让程序员可以创建自己的检测规则。ESLint 的所有规则都被设计成可插拔的。为了便于人们使用,ESLint 内置了一些规则,当然,你可以在使用过程中自定义规则。所有的规则默认都是禁用的。

安装

生成流程:blog.csdn.net/shenxianhui…

如果你想你所有项目都使用eslint,请全局安装;如果你想当前项目使用,请局部安装。

  • 全局安装:

    • npm install -g eslint
    • eslint --init
  • 项目安装:

    • npm install eslint --save-dev
    • ./node_modules/.bin/eslint --init
? How would you like to use ESLint? (Use arrow keys) // 你想怎样使用eslint
  To check syntax only 
  //只检查语法
> To check syntax and find problems
  //检查语法、发现问题
  To check syntax, find problems, and enforce code style
  //检查语法、发现问题并执行代码样式

eslint配置

www.cnblogs.com/jiaoshou/p/…

  1. 一般都采用.eslintrc.的配置文件进行配置, 如果放在项目的根目录中,则会作用于整个项目。如果在项目的子目录中也包含着.eslintrc文件,则对于子目录中文件的检查会忽略掉根目录中的配置,而直接采用子目录中的配置,这就能够在不同的目录范围内应用不同的检查规则,显得比较灵活。ESLint采用逐级向上查找的方式查找.eslintrc.文件,当找到带有"root": true配置项的.eslintrc.文件时,将会停止向上查找。
  2. 在 package.json文件里的 eslintConfig 字段进行配置。

root

root:是否以当前目录为根目录。 告诉 ESLint 不要再往上级目录查找,利用此属性配置,项目级和目录级的配置都可以不受上级目录以及祖先目录的配置影响,通常项目根目录应该设置为 true

举个例子,比如这个项目 vue-project1,默认情况下 root 为 false,而且该项目上层目录下还有 eslint 配置文件的话,这个更上一层的配置就会对你的项目文件的代码产生作用,直到到达根目录才会停止。这是我们不愿意看到的,所以就需要我们在当前项目目录下设置 root: true,告诉 ESLint 这里就是根目录了,别再往上查找其他的配置文件了!

env

使用 env 属性来指定要启用的环境,将其设置为 true,以保证在进行代码检测时不会把这些环境预定义的全局变量识别成未定义的变量而报错

"env": {
    "browser": true,
    "commonjs": true,
    "es6": true,
    "jquery": true
}

rule

启用的规则及其各自的错误级别

在上文的配置文件中, "extends": "eslint:recommended" 选项表示启用推荐规则,在推荐规则的基础上我们还可以根据需要使用 rules 新增自定义规则,每个规则的第一个值都是代表该规则检测后显示的错误级别:

  • "off"0 - 关闭规则
  • "warn"1 - 将规则视为一个警告
  • "error"2 - 将规则视为一个错误
module.exports = {
  'root': true,
  'env': {
    'node': true
  },
  'extends': [
    'plugin:vue/essential',
    '@vue/standard',
    '@vue/typescript/recommended'
  ],
  'parserOptions': {
    'ecmaVersion': 2020
  },
  'rules': {
    "vue/multi-word-component-names": [
      "error",
      {
        ignores: ["index", '404'], //需要忽略的组件名
      },
    ],
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'vue/no-v-model-argument': 0,
    'semi': 0, // 去掉结尾的分号
    'singleQuote': 0, // 单引号替代双引号
    'trailingComma': 0, // 末尾禁止添加逗号
    'no-alert': 0,//禁止使用alert confirm prompt
    'no-array-constructor': 2,//禁止使用数组构造器
    'no-bitwise': 0,//禁止使用按位运算符
    'no-caller': 1,//禁止使用arguments.caller或arguments.callee
    'no-catch-shadow': 2,//禁止catch子句参数与外部作用域变量同名
    'no-class-assign': 2,//禁止给类赋值
    'no-cond-assign': 2,//禁止在条件表达式中使用赋值语句
    // "no-console": 2,//禁止使用console
    'no-const-assign': 2,//禁止修改const声明的变量
    'no-constant-condition': 2,//禁止在条件中使用常量表达式 if(true) if(1)
    'no-continue': 0,//禁止使用continue
    'no-control-regex': 2,//禁止在正则表达式中使用控制字符
    // "no-debugger": 2,//禁止使用debugger
    'no-delete-var': 2,//不能对var声明的变量使用delete操作符
    'no-div-regex': 1,//不能使用看起来像除法的正则表达式/=foo/
    'no-dupe-keys': 2,//在创建对象字面量时不允许键重复 {a:1,a:1}
    'no-dupe-args': 2,//函数参数不能重复
    'no-duplicate-case': 2,//switch中的case标签不能重复
    'no-else-return': 2,//如果if语句里面有return,后面不能跟else语句
    'no-empty': 2,//块语句中的内容不能为空
    'no-empty-character-class': 2,//正则表达式中的[]内容不能为空
    'no-empty-label': 0,//禁止使用空label
    'no-eq-null': 2,//禁止对null使用==或!=运算符
    'no-eval': 1,//禁止使用eval
    'no-ex-assign': 2,//禁止给catch语句中的异常参数赋值
    'no-extend-native': 2,//禁止扩展native对象
    'no-extra-bind': 2,//禁止不必要的函数绑定
    'no-extra-boolean-cast': 2,//禁止不必要的bool转换
    'no-extra-parens': 2,//禁止非必要的括号
    'no-extra-semi': 2,//禁止多余的冒号
    'no-fallthrough': 1,//禁止switch穿透
    'no-floating-decimal': 2,//禁止省略浮点数中的0 .5 3.
    'no-func-assign': 2,//禁止重复的函数声明
    'no-implicit-coercion': 1,//禁止隐式转换
    'no-implied-eval': 2,//禁止使用隐式eval
    'no-inline-comments': 0,//禁止行内备注
    'no-inner-declarations': [2, 'functions'],//禁止在块语句中使用声明(变量或函数)
    'no-invalid-regexp': 2,//禁止无效的正则表达式
    'no-invalid-this': 2,//禁止无效的this,只能用在构造器,类,对象字面量
    'no-irregular-whitespace': 2,//不能有不规则的空格
    'no-iterator': 2,//禁止使用__iterator__ 属性
    'no-label-var': 2,//label名不能与var声明的变量名相同
    'no-labels': 2,//禁止标签声明
    'no-lone-blocks': 2,//禁止不必要的嵌套块
    'no-lonely-if': 0,//禁止else语句内只有if语句
    'no-loop-func': 1,//禁止在循环中使用函数(如果没有引用外部变量不形成闭包就可以)
    'no-mixed-requires': [0, false],//声明时不能混用声明类型
    'no-mixed-spaces-and-tabs': [2, false],//禁止混用tab和空格
    'linebreak-style': [0, 'windows'],//换行风格
    'no-multi-spaces': 1,//不能用多余的空格
    'no-multi-str': 2,//字符串不能用换行
    'no-multiple-empty-lines': [1, { 'max': 2 }],//空行最多不能超过2行
    'no-native-reassign': 2,//不能重写native对象
    'no-negated-in-lhs': 2,//in 操作符的左边不能有!
    'no-nested-ternary': 0,//禁止使用嵌套的三目运算
    'no-new': 1,//禁止在使用new构造一个实例后不赋值
    'no-new-func': 1,//禁止使用new Function
    'no-new-object': 2,//禁止使用new Object()
    'no-new-require': 2,//禁止使用new require
    'no-new-wrappers': 2,//禁止使用new创建包装实例,new String new Boolean new Number
    'no-obj-calls': 2,//不能调用内置的全局对象,比如Math() JSON()
    'no-octal': 2,//禁止使用八进制数字
    'no-octal-escape': 2,//禁止使用八进制转义序列
    'no-param-reassign': 2,//禁止给参数重新赋值
    'no-path-concat': 0,//node中不能使用__dirname或__filename做路径拼接
    'no-plusplus': 0,//禁止使用++,--
    'no-process-env': 0,//禁止使用process.env
    'no-process-exit': 0,//禁止使用process.exit()
    'no-proto': 2,//禁止使用__proto__属性
    'no-redeclare': 2,//禁止重复声明变量
    'no-regex-spaces': 2,//禁止在正则表达式字面量中使用多个空格 /foo bar/
    'no-restricted-modules': 0,//如果禁用了指定模块,使用就会报错
    'no-return-assign': 1,//return 语句中不能有赋值表达式
    'no-script-url': 0,//禁止使用javascript:void(0)
    'no-self-compare': 2,//不能比较自身
    'no-sequences': 0,//禁止使用逗号运算符
    'no-shadow': 0,//外部作用域中的变量不能与它所包含的作用域中的变量或参数同名
    'no-shadow-restricted-names': 2,//严格模式中规定的限制标识符不能作为声明时的变量名使用
    'no-spaced-func': 2,//函数调用时 函数名与()之间不能有空格
    'no-sparse-arrays': 2,//禁止稀疏数组, [1,,2]
    'no-sync': 0,//nodejs 禁止同步方法
    'no-ternary': 0,//禁止使用三目运算符
    'no-trailing-spaces': 0,//一行结束后面不要有空格
    'no-this-before-super': 0,//在调用super()之前不能使用this或super
    'no-throw-literal': 2,//禁止抛出字面量错误 throw "error";
    'no-undef': 1,//不能有未定义的变量
    'no-undef-init': 2,//变量初始化时不能直接给它赋值为undefined
    'no-undefined': 2,//不能使用undefined
    'no-unexpected-multiline': 2,//避免多行表达式
    'no-underscore-dangle': 1,//标识符不能以_开头或结尾
    'no-unneeded-ternary': 2,//禁止不必要的嵌套 var isYes = answer === 1 ? true : false;
    'no-unreachable': 2,//不能有无法执行的代码
    'no-unused-expressions': 2,//禁止无用的表达式
    'no-unused-vars': [1, { 'vars': 'all', 'args': 'after-used' }],//不能有声明后未被使用的变量或参数
    'no-use-before-define': 2,//未定义前不能使用
    'no-useless-call': 2,//禁止不必要的call和apply
    'no-void': 2,//禁用void操作符
    'no-var': 0,//禁用var,用let和const代替
    'no-warning-comments': [1, { 'terms': ['todo', 'fixme', 'xxx'], 'location': 'start' }],//不能有警告备注
    'no-with': 2,//禁用with
​
    'array-bracket-spacing': [2, 'never'],//是否允许非空数组里面有多余的空格
    'arrow-parens': 0,//箭头函数用小括号括起来
    'arrow-spacing': 0,//=>的前/后括号
    'accessor-pairs': 0,//在对象中使用getter/setter
    'block-scoped-var': 0,//块语句中使用var
    'brace-style': [1, '1tbs'],//大括号风格
    'callback-return': 0,//避免多次调用回调什么的
    'camelcase': 2,//强制驼峰法命名
    'comma-dangle': [0, 'never'],//对象字面量项尾不能有逗号
    'comma-spacing': 0,//逗号前后的空格
    'comma-style': [2, 'last'],//逗号风格,换行时在行首还是行尾
    'complexity': [0, 11],//循环复杂度
    'computed-property-spacing': [0, 'never'],//是否允许计算后的键名什么的
    'consistent-return': 0,//return 后面是否允许省略
    'consistent-this': [2, 'that'],//this别名
    'constructor-super': 0,//非派生类不能调用super,派生类必须调用super
    'curly': [2, 'all'],//必须使用 if(){} 中的{}
    'default-case': 2,//switch语句最后必须有default
    'dot-location': 0,//对象访问符的位置,换行的时候在行首还是行尾
    'dot-notation': [0, { 'allowKeywords': true }],//避免不必要的方括号
    'eol-last': 0,//文件以单一的换行符结束
    'eqeqeq': 2,//必须使用全等
    'func-names': 0,//函数表达式必须有名字
    'func-style': [0, 'declaration'],//函数风格,规定只能使用函数声明/函数表达式
    'generator-star-spacing': 0,//生成器函数*的前后空格
    'guard-for-in': 0,//for in循环要用if语句过滤
    'handle-callback-err': 0,//nodejs 处理错误
    'id-length': 0,//变量名长度
    'indent': [0, 2],//缩进风格
    'init-declarations': 0,//声明时必须赋初值
    'key-spacing': [0, { 'beforeColon': false, 'afterColon': true }],//对象字面量中冒号的前后空格
    'lines-around-comment': 0,//行前/行后备注
    'max-depth': [0, 4],//嵌套块深度
    'max-len': [0, 80, 4],//字符串最大长度
    'max-nested-callbacks': [0, 2],//回调嵌套深度
    'max-params': [0, 3],//函数最多只能有3个参数
    'max-statements': [0, 10],//函数内最多有几个声明
    'new-cap': 2,//函数名首行大写必须使用new方式调用,首行小写必须用不带new方式调用
    'new-parens': 2,//new时必须加小括号
    'newline-after-var': 0,//变量声明后是否需要空一行
    'object-curly-spacing': [0, 'never'],//大括号内是否允许不必要的空格
    'object-shorthand': 0,//强制对象字面量缩写语法
    'one-var': 1,//连续声明
    'operator-assignment': [0, 'always'],//赋值运算符 += -=什么的
    'operator-linebreak': [0, 'after'],//换行时运算符在行尾还是行首
    'padded-blocks': 0,//块语句内行首行尾是否要空行
    'prefer-const': 0,//首选const
    'prefer-spread': 0,//首选展开运算
    'prefer-reflect': 0,//首选Reflect的方法
    'quotes': [0, 'single'],//引号类型 `` "" ''
    'quote-props': [0, 'always'],//对象字面量中的属性名是否强制双引号
    'radix': 2,//parseInt必须指定第二个参数
    'id-match': 0,//命名检测
    'require-yield': 0,//生成器函数必须有yield
    // 'semi': [2, 'always'],//语句强制分号结尾
    'semi-spacing': [0, { 'before': false, 'after': true }],//分号前后空格
    'sort-vars': 0,//变量声明时排序
    'space-after-keywords': [0, 'always'],//关键字后面是否要空一格
    'space-before-blocks': [0, 'always'],//不以新行开始的块{前面要不要有空格
    'space-before-function-paren': [0, 'always'],//函数定义时括号前面要不要有空格
    'space-in-parens': [0, 'never'],//小括号里面要不要有空格
    'space-infix-ops': 0,//中缀操作符周围要不要有空格
    'space-return-throw-case': 0,//return throw case后面要不要加空格
    'space-unary-ops': [0, { 'words': true, 'nonwords': false }],//一元运算符的前/后要不要加空格
    'spaced-comment': 0,//注释风格要不要有空格什么的
    'strict': 2,//使用严格模式
    'use-isnan': 2,//禁止比较时使用NaN,只能用isNaN()
    'valid-jsdoc': 0,//jsdoc规则
    'valid-typeof': 2,//必须使用合法的typeof的值
    'vars-on-top': 2,//var必须放在作用域顶部
    'wrap-iife': [2, 'inside'],//立即执行函数表达式的小括号风格
    'wrap-regex': 0,//正则表达式字面量用小括号包起来
    'yoda': [2, 'never']//禁止尤达条件
  }
};

globals

ESLint会检测未声明的额外的全局变量,并发出报错,比如node环境中的process,浏览器环境下的全局变量console,以及我们通过cdn引入的jQuery定义的$等;我们可以在globals中进行变量声明:

{
    "globals": {
        // true表示该变量可读写,false表示变量是只读
        "$": true,
        "console": false
    }
}

但是node或者浏览器中的全局变量很多,如果我们一个个进行声明显得繁琐,因此就需要用到我们的env,这是对环境定义的一组全局变量的预设。

parserOptions

parserOptions:解析器选项,允许你指定 JS 语言(包括 JSX 语法),默认为 ES5。

overrides

overrides:用指定配置覆盖指定后缀的文件的规则配置

extends和plugins

如果每条规则都需要团队协商配置还是比较繁琐的,在项目开始配置时,我们可以先使用一些业内已经成熟的、大家普遍遵循的编码规范

需要注意的是:多个扩展中有相同的规则,以后面引入的扩展中规则为准。

{
    "extends": [
        "eslint:recommended",
        "plugin:vue/essential",
        "@vue/prettier",
        "eslint-config-standard"
    ]
}

extends可以使用以下几种类型的扩展:

  • eslint:开头的ESLint官方扩展,有两个:eslint:recommended(推荐规范)和eslint:all(所有规范)。
  • plugin:开头的扩展是插件类型扩展
  • eslint-config:开头的来自npm包,使用时可以省略eslint-config-,比如上面的可以直接写成standard
  • @:开头的扩展和eslint-config一样,是在npm包上面加了一层作用域scope

Prettier(代码风格)

含义

它是代码格式化工具,用来做代码格式化,有了Prettier之后,它能去掉原始的代码风格,确保团队的代码使用统一相同的格式

安装

依赖

公共

冲突

ESLint 是用于发现并修复代码问题(包括代码风格格式化),这和 Prettier 美化代码风格显得很矛盾,因为它们存在重叠的部分,那么业界为什么要将二者结合在一起使用呢?

ESLint 之类的 Linters 对于代码格式化的能力是有限的,不如 Prettier 那么专业

使用:使用Prettier 进行格式化,使用 linter来捕捉错误!

所以,怎么处理重叠部分的冲突?Prettier 提供了两个插件:

  • eslint-config-prettier

    • github.com/prettier/es…
    • 解决 ESLint 中的样式规范(即代码风格)和 Prettier 中样式规范的冲突,以 Prettier 的样式规范为准,使 ESLint 中的样式规范自动失效。对应 .eslintrc 配置 extends-"prettier"
  • eslint-plugin-prettier

    • github.com/prettier/es…
    • 将 Prettier 样式规范作为 ESLint 代码质量规范来使用,同样将格式问题以 error 的形式抛出,即 rule-"prettier/prettier",可在 rules-"prettier/prettier" 中进行自定义配置。对应 .eslintrc 配置 plugin-"prettier"

vue

  • plugin:vue/vue3-essential:Vue.js 的官方 ESLint (规则)插件

  • @vue/eslint-config-typescript/recommended:专门给 Vue 使用的 TS 验证规则(eslint-config-typescript

    @vue/eslint-config-typescript 有两套规则集,一个是 @vue/eslint-config-typescript ,一个就是 @vue/eslint-config-typescript/recommended 。其中,后者更加严格。

vscode

VS配置扩展

vscode中打开设置输入@tag:sync,点击setting.json配置

{
  // ----------------  prettier  ----------------
  "prettier.jsxSingleQuote": true,
  "prettier.printWidth": 800, //换行数
  "prettier.vueIndentScriptAndStyle": true,
  "prettier.singleQuote": true, // 用单引号
  "prettier.semi": true,
​
  // ----------------  vetur  ----------------
  "vetur.validation.template": false,
  "vetur.format.defaultFormatterOptions": {
    "js-beautify-html": {
      "wrap_line_length": 150, //换行长度
      "wrap_attributes": "auto", //属性换行
      "end_with_newline": false
    },
    "prettyhtml": {
      "printWidth": 300,
      "singleQuote": false,
      "wrapAttributes": false,
      "sortAttributes": false
    },
    "prettier": {
      // Prettier option here
      "printWidth": 180 // 超过最大值换行
    }
  },
  "vetur.format.defaultFormatter.html": "js-beautify-html",
  //让vue中的js按编辑器prettier格式进行格式化
  "vetur.format.defaultFormatter.js": "prettier"
  // 让vue中的js按编辑器自带的ts格式进行格式化
  // "vetur.format.defaultFormatter.js": "vscode-typescript"
}

vscode扩展

eslint

安装插件:ESLint,这样代码不规范的时候底部才有波浪线出现。

Vetur和Volar

给 Vue 提供代码高亮和语法提示

vue2:Vetur

vue3:Volar