首发于不止前端

ESLint学习

一句话介绍,ESLint是用来检查代码的工具,类似于之前JSHintJSHint,或者对标其他语言的FindbugsCodex等。

ESLint的三个特点

  • ESLint 使用ESPree解析JavaScript
  • ESLint 使用 AST 去分析代码中的模式
  • ESLint是完全插件化的。每一个规则都是一个插件并且你可以在运行时添加更多的规则

Hello World

安装

 npm install eslint --save-dev

初始化配置

 npx eslint --init

会出来一个交互式对话来帮助生成配置文件

完成后会在根目录下生成一个.eslintrc.js文件,看看里面的内容

 module.exports = {
     "env": {
         "browser": true,
         "es6": true
     },
     "extends": [
         "eslint:recommended",
         "plugin:react/recommended"
     ],
     "globals": {
         "Atomics": "readonly",
         "SharedArrayBuffer": "readonly"
     },
     "parserOptions": {
         "ecmaFeatures": {
             "jsx": true
         },
         "ecmaVersion": 2018,
         "sourceType": "module"
     },
     "plugins": [
         "react"
     ],
     "rules": {
     }
 };

然后可以执行检查了,假如我创建了一个index.js文件

 var abc
 function hello(name) {
     console.log('Hello ' + name)
 }

执行

 npx eslint index.js

出来结果

配置

做一下实验,刚才自动生成的配置文件里rules一项为空,现在我们来加进去一些内容

 {
     "rules": {
         "semi": ["error", "always"],
         "quotes": ["error", "double"]
     }
 }

其中semi表示检查规则的名称,数组中第一个值是错误的级别,可以取下面的值

  • off / 0
  • warn / 1
  • error / 2

此时如果我将index.js改为

 var abc = 'hello'

再运行一下npx eslint index.js

如果在.eslintrc里添加

  "extends": "eslint:recommended"

那么被标记为√的规则都会默认被开启,这些都是常见的项,随机挑几个来瞅瞅,比如

  • no-func-assing: 禁止对 function 声明重新赋值
  • no-fallthrough: 禁止 case 语句落空

详细的可以参考中文版的文档eslint.bootcss.com/docs

实际项目中一个个地去配置规则项不太现实,可以去npmjs.com/search?选一个。

其中对于React项目来说,最有名的是Airbnb公司出品的配置。对于Vue生态圈,它是有朝廷(官方)认证的工具的,可以参考eslint.vuejs.org/

命令行

也可以把ESLint安装到全局,这样就可以直接使用eslint命令了。

 eslint [options] [file|dir|glob]*

比如要对于一个目录进行检查就可以

 eslint lib/**

使用eslint -h来查看帮助选项

详细解释可以参考eslint.bootcss.com/docs

输出格式

ESLint支持多种格式的结果输出,使用--format或者-f来指定,比如eslint index.js --format codeframe就是使用codeframe来输出检查结果。ESLint能支持如下格式

  • checkstyle
  • codeframe
  • compact
  • html
  • jslint-xml
  • json-with-metadata
  • json
  • junit
  • stylish
  • table
  • tap
  • unix
  • visualstudio

比如我们用npx eslint index.js --format html运行,它能输出HTML的字符串

放入一个HTML文件里,打开后显示

其他详细的效果可以参考eslint.bootcss.com/docs

集成

编辑器

就看集成到Visual Studio Code里的情况

安装后检查的效果

可以到配置里配置ESLint的规则

构建工具

构建工具就看集成到Webpack里的情况,在Webpack里使用时,要安装

 npm install eslint-loader eslint --save-dev

然后在配置里

 module.exports = {
   // ...
   module: {
     rules: [
       {
         test: /\.js$/,
         exclude: /node_modules/,
         loader: "eslint-loader",
         enforce: 'pre',
         options: {
           // eslint options (if necessary)
         }
       }
     ]
   }
   // ...
 };

注意指定enforce: 'pre'确保它在babel-loader之前运行,也可以和babel-loader放在一起

 module.exports = {
   // ...
   module: {
     rules: [
       {
         test: /\.js$/,
         exclude: /node_modules/,
         use: ["babel-loader", "eslint-loader"]
       }
     ]
   }
   // ...
 };

它支持的(Webpack里的)配置项有

  • cache
  • eslintPath
  • fix
  • formatter
  • emitError
  • emitWarning
  • failOnError
  • failOnWarning
  • quiet
  • outputReport

详情可以查看github.com/webpack-cont

命令行工具

命令行工具就介绍ESLint Watch,第一步还是安装

 npm install --save-dev eslint-watch

安装完成后执行npx esw -w就能监视文件变化

esw命令还有别的参数,可以使用npx esw -h查看或者访问github.com/rizowski/esl

Git

所谓集成到Git指的是在Git的pre-commit钩子里使用ESLint检查代码,如果检查有问题,就阻止提交代码,到.git/hooks目录,修改pre-commit文件

 #!/bin/zsh
function lintit () {
   OUTPUT=$(git diff --name-only | grep -E '(.js)$')
   a=("${(f)OUTPUT}")
   e=$(eslint -c eslint.json $a)
   echo $e
   if [[ "$e" != *"0 problems"* ]]; then
     echo "ERROR: Check eslint hints."
     exit 1 # reject
   fi
 }
 lintit

另外还可以使用husky,原理是一样的,先安装

 npm install husky --save-dev

然后在package.json或者.huskyrc里配置,.huskyrc里要少husky那一层结构

 {
   "husky": {
     "hooks": {
       "pre-commit": "eslint src/**"
     }
   }
 }

关于husky二哈(没错,它就是哈士奇)的详细用法可以参考github.com/typicode/hus

想找更多内容可以去npm官网上搜索npmjs.com/search?

详细配置(选修)

引入配置有两种方式

  • 使用JavaScript注释把配置信息直接嵌入到一个代码源文件中
  • 使用一个JavaScriptJSON或者YAML配置文件

配置文件

大部分场景都是使用配置文件,使用配置文件有两种方式: 第一种是最常见的在目录下生成配置文件,ESLint会自动查找,第二种是在命令行里使用-c指定配置文件,比如

 eslint -c --no-eslintrc myconfig.json mycode.js

ESLint支持几种格式的配置文件:

  • JavaScript - 使用 .eslintrc.js 然后输出一个配置对象。
  • YAML - 使用 .eslintrc.yaml.eslintrc.yml 去定义配置的结构。
  • JSON - 使用 .eslintrc.json 去定义配置的结构,ESLintJSON文件允许 JavaScript 风格的注释。
  • (弃用) - 使用 .eslintrc,可以是JSON也可以是YAML
  • package.json - 在 package.json 里创建一个 eslintConfig属性,在那里定义你的配置。

如果同一个目录下有多个配置文件,ESLint只会使用一个。优先级顺序如下:

  1. .eslintrc.js
  2. .eslintrc.yaml
  3. .eslintrc.yml
  4. .eslintrc.json
  5. .eslintrc
  6. package.json

配置文件的生效跟CSS类似,有层叠的关系(假设有多个配置文件,分别放在不同的目录下),ESLint总是会找最近的那个配置文件,以它覆盖父级后的配置项为准。

有时为了将ESLint限制到一个特定的项目,在项目根目录下的 package.json 文件或者 .eslintrc.* 文件里的 eslintConfig 字段下设置 "root": trueESLint一旦发现配置文件中有 "root": true,它就会停止在父级目录中寻找。类似于下图

 home
 └── user
     ├── .eslintrc <- Always skipped if other configs present
     └── projectA
         ├── .eslintrc  <- 没有使用
         └── lib
             ├── .eslintrc  <- 配置了{ "root": true }
             └── main.js

完整的配置层次结构,从最高优先级最低的优先级,如下:

1) 行内配置

    1. /*eslint-disable*//*eslint-enable*/
    2. /*global*/
    3. /*eslint*/
    4. /*eslint-env*/

2) 命令行选项(或CLIEngine等价物):

    1. --global
    2. --rule
    3. --env
    4. -c--config

3) 项目级配置:

    1. 与要检测的文件在同一目录下的 .eslintrc.*package.json 文件
    2. 继续在父级目录寻找 .eslintrcpackage.json文件,直到根目录(包括根目录)或直到发现一个有"root": true的配置。

4) 如果不是1)到3)中的任何一种情况,退回到 ~/.eslintrc 中自定义的默认配置。

扩展配置文件

一个配置文件可以被基础配置中的已启用的规则继承。

extends属性值可以是:

  • 指定配置的字符串(配置文件的路径、可共享配置的名称、eslint:recommendedeslint:all)
  • 字符串数组:每个配置继承它前面的配置

eslint:recommended 表示启用被标记为√的规则。

 module.exports = {
     "extends": "eslint:recommended",
     "rules": {
         // 启用基础配置里没有的项
         "indent": ["error", 4],
         "linebreak-style": ["error", "unix"],
         "quotes": ["error", "double"],
         "semi": ["error", "always"],
 
         // 覆盖基础配置里的若干项
         "comma-dangle": ["error", "always"],
         "no-cond-assign": ["error", "always"],
 
         // 关闭基础配置里的若干项
         "no-console": "off",
     }
 }

eslint:all表示启用当前安装的ESLint中所有的核心规则。这些规则可以在ESLint的任何版本进行更改,所以并不推荐使用all

 module.exports = {
     "extends": "eslint:all",
     "rules": {
         // 覆盖默认配置
         "comma-dangle": ["error", "always"],
         "indent": ["error", 2],
         "no-cond-assign": ["error", "always"],
 
         // 现在关闭,将来会打开
         "one-var": "off", // ["error", "never"]
 
         // 关闭
         "init-declarations": "off",
         "no-console": "off",
         "no-inline-comments": "off",
     }
 }

除了上面两个内置的配置,extends还能从一个npm包中引入配置,一般这种包都是以eslint-config为前缀,比如eslint-config-standard,使用的时候可以省略前缀。

 extends: standard
 rules:
   comma-dangle:
     - error
     - always
   no-empty: warn

也可以使用插件里的配置,插件其实也是一个npm包,使用时加上plugin:的前缀,并加上包名,比如

 {
     "plugins": [
         "react"
     ],
     "extends": [
         "eslint:recommended",
         "plugin:react/recommended"
     ],
     "rules": {
        "no-set-state": "off"
     }
 }

再狠一点就是直接指定路径了

 {
     "extends": [
         "./node_modules/coding-standard/eslintDefaults.js",
         "./node_modules/coding-standard/.eslintrc-es6",
         "./node_modules/coding-standard/.eslintrc-jsx"
     ],
     "rules": {
         "eqeqeq": "warn"
     }
 }


ESLint递归地扩展配置,因此基本配置也可以具有 extends 属性。extends 属性中的相对路径和可共享配置名从配置文件中出现的位置解析。

rules 属性可以做下面的任何事情以扩展(或覆盖)规则:

  • 启用额外的规则
  • 改变继承的规则级别而不改变它的选项:
    • 基础配置:"eqeqeq": ["error", "allow-null"]
    • 派生的配置:"eqeqeq": "warn"
    • 最后生成的配置:"eqeqeq": ["warn", "allow-null"]
  • 覆盖基础配置中的规则的选项
    • 基础配置:"quotes": ["error", "single", "avoid-escape"]
    • 派生的配置:"quotes": ["error", "single"]
    • 最后生成的配置:"quotes": ["error", "single"]

解析器设置

parseOptions用来设置语法解析的,先看它的一个例子,

 {
     "parserOptions": {
         "ecmaVersion": 6,
         "sourceType": "module",
         "ecmaFeatures": {
             "jsx": true
         }
     }
 }
  • ecmaVersion用来指定语法版本,默认是5,代表ES5,可以设置为6 ~ 10,也可以使用年份比如2015、2018等
  • sourceType设置代码类型,可以是script (默认) 或 module(如果你的代码是ECMAScript模块)
  • ecmaFeatures表示想使用的额外的语言特性,默认全是false
    • globalReturn - 允许在全局作用域下使用 return 语句
    • impliedStrict - 启用全局strict mode(如果 ecmaVersion 是 5 或更高)
    • jsx - 启用JSX
    • experimentalObjectRestSpread - 启用实验性的object rest/spread properties支持。

指定解析器

前面提到ESLint默认使用Espree作为其解析器,当然也可以指定为别的,比如

  • Babel-ESLint
  • @typescript-eslint/parser

配置方式为

 {
     "parser": "esprima"
 }

指定插件

使用plugins指定插件,比如

 {
     "plugins": [
         "plugin1",
         "eslint-plugin-plugin2"
     ]
 }

或者

 ---
   plugins:
     - plugin1
     - eslint-plugin-plugin2

指定处理器

插件可以提供处理器。处理器可以从另一种文件中提取JavaScript代码,然后让ESLint检测JavaScript代码。或者处理器可以在预处理中转换JavaScript代码。

 {
     "plugins": ["a-plugin"],
     "overrides": [
         {
             "files": ["*.md"],
             "processor": "a-plugin/markdown"
         },
         {
             "files": ["**/*.md/*.js"],
             "rules": {
                 "strict": "off"
             }
         }
     ]
 }

指定环境

可用的环境有

  • browser - 浏览器环境中的全局变量。
  • node - Node.js全局变量和 Node.js 作用域。
  • commonjs - CommonJS 全局变量和 CommonJS 作用域 (用于 Browserify/WebPack 打包的只在浏览器中运行的代码)。
  • shared-node-browser - Node.jsBrowser 通用全局变量。
  • es6 - 启用除了 modules 以外的所有 ECMAScript 6 特性(该选项会自动设置 ecmaVersion 解析器选项为 6)。
  • worker - Web Workers 全局变量。
  • amd - 将 require()define() 定义为像 amd 一样的全局变量。
  • mocha - 添加所有的 Mocha 测试全局变量。
  • jasmine - 添加所有的 Jasmine 版本 1.3 和 2.0 的测试全局变量。
  • jest - Jest 全局变量。
  • phantomjs - PhantomJS 全局变量。
  • protractor - Protractor 全局变量。
  • qunit - QUnit 全局变量。
  • jquery - jQuery 全局变量。
  • prototypejs - Prototype.js 全局变量。
  • shelljs - ShellJS 全局变量。
  • meteor - Meteor 全局变量。
  • mongo - MongoDB 全局变量。
  • applescript - AppleScript 全局变量。
  • nashorn - Java 8 Nashorn 全局变量。
  • serviceworker - Service Worker 全局变量。
  • atomtest - Atom 测试全局变量。
  • embertest - Ember 测试全局变量。
  • webextensions - WebExtensions 全局变量。
  • greasemonkey - GreaseMonkey全局变量。

可以同时定义多个环境。

有五种方式指定环境

  • 通过命令行的--env选项
  • 通过JavaScript的注释
 /* eslint-env node, mocha */
  • 通过配置文件的env
 {
     "env": {
         "browser": true,
         "node": true
     }
 }
  • 通过package.json
 {
     "name": "mypackage",
     "version": "0.0.1",
     "eslintConfig": {
         "env": {
             "browser": true,
             "node": true
         }
     }
 }
  • 通过YAML配置文件
 ---
   env:
     browser: true
     node: true

指定全局变量

指定全局变量的意义在于有时候会把一些变量定义在全局,比如jQuery$,然后在另一个文件里使用时会报变量没有定义的问题。

如果用注释指定全局变量

 /* global var1, var2 */
 // 指定变量可以被写入
 /* global var1:writable, var2:writable */

如果要在配置文件中指定全局变量

 {
     "globals": {
         "var1": "writable",
         "var2": "readonly"
     }
 }

或者在YAML文件中

 ---
   globals:
     var1: writable
     var2: readonly

使用off来禁用全局变量

 {
     "env": {
         "es6": true
     },
     "globals": {
         "Promise": "off"
     }
 }

应该要启用no-global-assign规则来禁止对只读的全局变量进行修改。

配置规则

如前所述,尽量用字符串吧,能一眼看出意义来

  • off / 0
  • warn / 1
  • error / 2

使用注释配置的方式,如果是针对插件的使用插件名字作为前缀

 /* eslint eqeqeq: "off", curly: "error" */
 
 /* eslint quotes: ["error", "double"], curly: 2 */
 
 /* eslint "plugin1/rule1": "error" */

使用配置文件的方式,如果是针对插件的使用插件名字作为前缀

 {
     "rules": {
         "eqeqeq": "off",
         "curly": "error",
         "quotes": ["error", "double"],
         "plugin1/rule1": "error"
     }
 }

或者

 ---
 plugins:
   - plugin1
 rules:
   eqeqeq: 0
   curly: error
   quotes:
     - error
     - "double"
   plugin1/rule1: error

使用行内注释禁用规则

这种情况用在特例上,有可能存在特殊情况,在该场景下想要禁用规则

 /* eslint-disable */
 alert('foo');
 /* eslint-enable */

对指定的规则启用或禁用

 /* eslint-disable no-alert, no-console */
 
 alert('foo');
 console.log('bar');
 
 /* eslint-enable no-alert, no-console */

针对整个文件,/* eslint-disable */放在头部

 /* eslint-disable */
 
 alert('foo');

对文件之后的部分启用或禁用警告

 /* eslint-disable no-alert */
 
 // 文件剩下的部分关闭no-alert
 alert('foo');

使用以下格式的行注释或块注释在某一特定的行上禁用所有规则

 alert('foo'); // eslint-disable-line
 
 // eslint-disable-next-line
 alert('foo');
 
 /* eslint-disable-next-line */
 alert('foo');
 
 alert('foo'); /* eslint-disable-line */

在某一特定的行上禁用某个指定的规则

 alert('foo'); // eslint-disable-line no-alert
 
 // eslint-disable-next-line no-alert
 alert('foo');
 
 alert('foo'); /* eslint-disable-line no-alert */
 
 /* eslint-disable-next-line no-alert */
 alert('foo');

在某个特定的行上禁用多个规则

 alert('foo'); // eslint-disable-line no-alert, quotes, semi
 
 // eslint-disable-next-line no-alert, quotes, semi
 alert('foo');
 
 alert('foo'); /* eslint-disable-line no-alert, quotes, semi */
 
 /* eslint-disable-next-line no-alert, quotes, semi */
 alert('foo');

针对一组文件禁用规则

 {
   "rules": {...},
   "overrides": [
     {
       "files": ["*-test.js","*.spec.js"],
       "rules": {
         "no-unused-expressions": "off"
       }
     }
   ]
 }

忽略文件或目录

这一块就跟.gitignore一样,也有.eslintignore的配置,可以忽略一些文件或者目录,这个文件也是可以分散到不同的目录下去的,生效也是按照就近规则。

如果想在命令行里指定ignore文件,可以加上--ignore-path

 eslint --ignore-path .jshintignore file.js
 # 复用.gitignore文件都是可以的
 eslint --ignore-path .gitignore file.js

如果是想放在package.json里指定

 {
   "name": "mypackage",
   "version": "0.0.1",
   "eslintConfig": {
       "env": {
           "browser": true,
           "node": true
       }
   },
   "eslintIgnore": ["hello.js", "world.js"]
 }


最详细的配置说明还是参考官方文档eslint.bootcss.com/docs

如果想更有追求的话,可以继续阅读官方文档的开发指南部分,了解ESLint背后原理。

发布于 03-21

文章被以下专栏收录