前端框架系列之(eslint入門)

創建工程

我們創建一個叫eslint-demo的工程,然後執行npm初始化

https://github.com/913453448/eslint-demo

npm init

安裝使用

安裝eslint

$ npm install eslint --save-dev

創建配置文件

執行eslint的初始化

npx eslint --init

執行完畢後可以看到一個配置文件,我選的是json格式的配置文件,還有package.json中直接引用、yaml、js格式的配置文件,後面我們會講到。

.eslintrc.json

{

}

可以看到,我們這裏是一個空的配置文件。

運行命令

我們創建一個叫src的目錄,然後創建一個demo1.js的文件測試

demo1.js:

我們隨便寫點代碼,比如直接document頁面輸出一個字符串

document.write("hello eslint");

運行eslint測試:

npx eslint ./src/demo1.js

運行完畢後你會發現,沒有報錯跟提示,這是因爲我們還沒有進行任何eslint的配置,下面我們就結合demo對eslint配置逐個進行解析。

爲了更好的理解eslint,我們直接clone一份源碼,https://github.com/eslint/eslint.git

配置

我們這裏以一個前端vue+webpack+ts+es2020的工程爲demo爲例子進行eslint的配置。

env&parserOptions

ESLint 允許你指定你想要支持的 JavaScript 語言選項。默認情況下,ESLint 支持 ECMAScript 5 語法。你可以覆蓋該設置,以啓用對 ECMAScript 其它版本和 JSX 的支持,爲什麼要把env跟parserOptions放在一起講呢? 因爲env中包含了對parserOptions的配置,最終兩個參數傳入供給parse解析器使用。

env

我們的demo需要運行在es2020的瀏覽器環境中,所以我們的env配置爲:

{
  "env": {
    "browser": true,
    "es2020": true
  }
}

那麼,env到底可以爲設置爲哪些呢?我們直接找到eslint的源碼。

conf/environments.js:

...
const newGlobals2015 = getDiff(globals.es2015, globals.es5); // 19 variables such as Promise, 
const newGlobals2017 = {
    Atomics: false,
    SharedArrayBuffer: false
};
const newGlobals2020 = {
    BigInt: false,
    BigInt64Array: false,
    BigUint64Array: false,
    globalThis: false
};

//------------------------------------------------------------------------------
// Public Interface
//------------------------------------------------------------------------------

/** @type {Map<string, import("../lib/shared/types").Environment>} */
module.exports = new Map(Object.entries({

    // Language
    builtin: {
        globals: globals.es5
    },
    es6: {
        globals: newGlobals2015,
        parserOptions: {
            ecmaVersion: 6
        }
    },
    es2015: {
        globals: newGlobals2015,
        parserOptions: {
            ecmaVersion: 6
        }
    },
    es2017: {
        globals: { ...newGlobals2015, ...newGlobals2017 },
        parserOptions: {
            ecmaVersion: 8
        }
    },
    es2020: {
        globals: { ...newGlobals2015, ...newGlobals2017, ...newGlobals2020 },
        parserOptions: {
            ecmaVersion: 11
        }
    },

    // Platforms
    browser: {
        globals: globals.browser
    },
    node: {
        globals: globals.node,
        parserOptions: {
            ecmaFeatures: {
                globalReturn: true
            }
        }
    },
    "shared-node-browser": {
        globals: globals["shared-node-browser"]
    },
    worker: {
        globals: globals.worker
    },
    serviceworker: {
        globals: globals.serviceworker
    },

    // Frameworks
    commonjs: {
        globals: globals.commonjs,
        parserOptions: {
            ecmaFeatures: {
                globalReturn: true
            }
        }
    },
    amd: {
        globals: globals.amd
    },
    mocha: {
        globals: globals.mocha
    },
    jasmine: {
        globals: globals.jasmine
    },
    jest: {
        globals: globals.jest
    },
    phantomjs: {
        globals: globals.phantomjs
    },
    jquery: {
        globals: globals.jquery
    },
    qunit: {
        globals: globals.qunit
    },
    prototypejs: {
        globals: globals.prototypejs
    },
    shelljs: {
        globals: globals.shelljs
    },
    meteor: {
        globals: globals.meteor
    },
    mongo: {
        globals: globals.mongo
    },
    protractor: {
        globals: globals.protractor
    },
    applescript: {
        globals: globals.applescript
    },
    nashorn: {
        globals: globals.nashorn
    },
    atomtest: {
        globals: globals.atomtest
    },
    embertest: {
        globals: globals.embertest
    },
    webextensions: {
        globals: globals.webextensions
    },
    greasemonkey: {
        globals: globals.greasemonkey
    }
}));

可以看到,默認是“builtin” 也就是es5,我們可以看到“es6”還可以叫“es2015”,然後還有一個“parserOptions”的配置:

 es2015: {
        globals: newGlobals2015,
        parserOptions: {
            ecmaVersion: 6
        }
    },

那麼parserOptions到底是什麼呢? 其實是給解析器用的參數,告訴解析器你需要利用ecmaVersion:6的語法去解析我們的源文件,那麼globals屬性裏面又是什麼東西呢?我們直接找到newGlobals2015然後點開源碼,我們找到一個叫globals的第三方庫,然後找到了一個叫globals.json的文件:

{
  ...
  "es2015": {
		"Array": false,
		"ArrayBuffer": false,
		"Boolean": false,
		"constructor": false,
		"DataView": false,
		"Date": false,
		"decodeURI": false,
		"decodeURIComponent": false,
		"encodeURI": false,
		"encodeURIComponent": false,
		"Error": false,
		"escape": false,
		"eval": false,
		"EvalError": false,
		"Float32Array": false,
		"Float64Array": false,
		"Function": false,
		"hasOwnProperty": false,
		"Infinity": false,
		"Int16Array": false,
		"Int32Array": false,
		"Int8Array": false,
		"isFinite": false,
		"isNaN": false,
		"isPrototypeOf": false,
		"JSON": false,
		"Map": false,
		"Math": false,
		"NaN": false,
		"Number": false,
		"Object": false,
		"parseFloat": false,
		"parseInt": false,
		"Promise": false,
		"propertyIsEnumerable": false,
		"Proxy": false,
		"RangeError": false,
		"ReferenceError": false,
		"Reflect": false,
		"RegExp": false,
		"Set": false,
		"String": false,
		"Symbol": false,
		"SyntaxError": false,
		"toLocaleString": false,
		"toString": false,
		"TypeError": false,
		"Uint16Array": false,
		"Uint32Array": false,
		"Uint8Array": false,
		"Uint8ClampedArray": false,
		"undefined": false,
		"unescape": false,
		"URIError": false,
		"valueOf": false,
		"WeakMap": false,
		"WeakSet": false
	},
   ...
}

可以看到,其實就是我們es6中內置的對象、屬性、方法,所以env是提供了一個es環境,parserOptions則是負責解析es語法。我們可以看到每一個變量都是一個boolean值,false代表這個變量不允許修改,true代表可以修改。

我們繼續運行一下我們的demo:

$npx eslint ./src/demo1.js

我們可以發現,我們控制檯還是沒啥反應,這是爲什麼呢?因爲我們還沒配置我們的rules,我們繼續往下走。

parserOptions

  • sourceType - 設置爲 "script" (默認) 或 "module"(如果你的代碼是 ECMAScript 模塊)。

在看env的時候我們看到了parserOptions的一個參數“ecmaVersion”,那麼ecmaVersion還有哪些配置呢?

ecmaVersion:

  • globalReturn - 允許在全局作用域下使用 return 語句
  • impliedStrict - 啓用全局 strict mode (如果 ecmaVersion 是 5 或更高)
  • jsx - 啓用 JSX
  • experimentalObjectRestSpread - 啓用實驗性的 object rest/spread properties 支持。(**重要:**這是一個實驗性的功能,在未來可能會有明顯改變。 建議你寫的規則 不要 依賴該功能,除非當它發生改變時你願意承擔維護成本。)

我們可能會用到jsx,所以我們把jsx配置成爲true

.eslintrc.json:

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    }
  },
}

因爲我們demo需要用到webpack模塊打包,所以我們需要把sourceType設置成爲module

{
  "env": {
    "browser": true,
    "es2020": true
  },
 "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  }
}

解析器(Parser)

抽象語法樹(Abstract Syntax Tree,AST),parse會把我們的源代碼轉換成抽象語法樹,然後再對每個節點做eslint的校驗,可以說是eslint中最重要的模塊了,eslint默認使用的Esprima作爲ast工具,ast的工具還有acorn等,之前寫過一篇關於babel的文章,感興趣的小夥伴可以去看看babel源碼解析一,裏面用到的就是acorn。

{
    "parser": "esprima",
    "rules": {
        "semi": "error"
    }
}

以下解析器與 ESLint 兼容:

注意,在使用自定義解析器時,爲了讓 ESLint 在處理非 ECMAScript 5 特性時正常工作,配置屬性 parserOptions 仍然是必須的。解析器會被傳入 parserOptions,但是不一定會使用它們來決定功能特性的開關。

在線轉換工具:https://astexplorer.net/

打開esprima的文檔我們簡單看一下:

README.md

​```javascript
const espree = require("espree");

const ast = espree.parse(code, options);

​```js
const options = {
    // attach range information to each node
    range: false,

    // attach line/column location information to each node
    loc: false,

    // create a top-level comments array containing all comments
    comment: false,

    // create a top-level tokens array containing all tokens
    tokens: false,

    // Set to 3, 5 (default), 6, 7, 8, 9, or 10 to specify the version of ECMAScript syntax you want to use.
    // You can also set to 2015 (same as 6), 2016 (same as 7), 2017 (same as 8), 2018 (same as 9), 2019 (same as 10), or 2020 (same as 11) to use the year-based naming.
    ecmaVersion: 5,

    // specify which type of script you're parsing ("script" or "module")
    sourceType: "script",

    // specify additional language features
    ecmaFeatures: {

        // enable JSX parsing
        jsx: false,

        // enable return in global scope
        globalReturn: false,

        // enable implied strict mode (if ecmaVersion >= 5)
        impliedStrict: false
    }
}
​```

可以看到,esprima接受的其實就是我們傳遞的parserOptions參數,那麼在eslint的配置中我們怎麼使用parse呢?

因爲我們demo是需要加載.vue文件的,所以用默認的esprima解析肯定是不行的,所以我們安裝一個vue-eslint-parser,

因爲vue-eslint-parser直接是包含在eslint-plugin-vue中的,所以我們直接安裝一個eslint-plugin-vue:

npm install -D eslint-plugin-vue

然後直接把vue-eslint-parser配置到eslint中

.eslintrc.json:

{
  "env": {
    "browser": true,
    "es2020": true
  },
 "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser"
}

我們繼續運行eslint:

npx eslint ./src/*

運行後還是沒任何反應,這是爲什麼呢? 別慌,還沒到我們的rules,我們繼續~

處理器(Processor)

processor可以理解爲在parse解析器要解析源文件之前跟eslint的rules處理過後的構造函數,也就是說可以在parse解析之前跟eslint的rules處理過後做一些事情,processor提供了兩個鉤子函數,我們先看一眼eslint-plugin-vue中提供的processor:

xxx/node_modules/eslint-plugin-vue/lib/processor.js

/**
 * @author Toru Nagashima <https://github.com/mysticatea>
 */
'use strict'

module.exports = {
  preprocess (code) {
    console.log("code",code)
    return [code]
  },

  postprocess (messages) {
    const state = {
      block: {
        disableAll: false,
        disableRules: new Set()
      },
      line: {
        disableAll: false,
        disableRules: new Set()
      }
    }

    // Filter messages which are in disabled area.
    return messages[0].filter(message => {
      if (message.ruleId === 'vue/comment-directive') {
        const rules = message.message.split(' ')
        const type = rules.shift()
        const group = rules.shift()
        switch (type) {
          case '--':
            state[group].disableAll = true
            break
          case '++':
            state[group].disableAll = false
            break
          case '-':
            for (const rule of rules) {
              state[group].disableRules.add(rule)
            }
            break
          case '+':
            for (const rule of rules) {
              state[group].disableRules.delete(rule)
            }
            break
          case 'clear':
            state.block.disableAll = false
            state.block.disableRules.clear()
            state.line.disableAll = false
            state.line.disableRules.clear()
            break
        }
        return false
      } else {
        return !(
          state.block.disableAll ||
          state.line.disableAll ||
          state.block.disableRules.has(message.ruleId) ||
          state.line.disableRules.has(message.ruleId)
        )
      }
    })
  },

  supportsAutofix: true
}

裏面的具體代碼我們就一一解析了,我們後面在做自定義plugin的時候會詳細說明一下processor,我們可以簡單的看到兩個回調函數,preprocess跟postprocess,preprocess是在parse解析源文件之前調用的方法,postprocess則是eslint的rules處理完畢後的回調函數。

那我們用一下eslint-plugin-vue的processor.

如果插件提供了processor的話,eslint會自動根據文件後綴調用processor,比如在eslint-plugin-vue的清單文件中我們可以看到:

xxx/node_modules/eslint-plugin-vue/lib/index.js

/*
 * IMPORTANT!
 * This file has been automatically generated,
 * in order to update it's content execute "npm run update"
 */
'use strict'

module.exports = {
  rules: {
    ....
  },
  configs: {
    'base': require('./configs/base'),
    'essential': require('./configs/essential'),
    'no-layout-rules': require('./configs/no-layout-rules'),
    'recommended': require('./configs/recommended'),
    'strongly-recommended': require('./configs/strongly-recommended')
  },
  processors: {
    '.vue': require('./processor')
  }
}

我們可以看到vue提供了一個針對.vue文件的processors:

 processors: {
    '.vue': require('./processor')
  }

接下來,我們用一下eslint-plugin-vue的processors,我們直接創建一個叫demo2.vue的文件:

demo2.vue

<template>
</template>
<script>
export default{
    name: 'demo2',
};
</script>
<style lang='scss' scoped>
</style>

然後直接在eslint配置中使用eslint-plugin-vue插件:

.eslintrc.json:

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "plugins": [
    "vue"
  ]
}

爲了看到點效果,我們直接在x x/node_modules/eslint-plugin-vue/lib/processor.js源代碼中打個console:

module.exports = {
  preprocess (code) {
    console.log("code",code)
    return [code]
  },
  ...

然後運行eslint:

➜  eslint-demo git:(v0.0.1) ✗ npx eslint ./src/*
code <template>
</template>
<script>
export default{
    name: 'demo2',
};
</script>
<style lang='scss' scoped>
</style>

➜  eslint-demo git:(v0.0.1) ✗ 

可以看到,processor打印了我們的源代碼。

plugin引入的話只會針對.vue文件做處理,那如果我們需要對項目中所有的文件做處理的話該怎麼做呢?

我們直接修改一下配置:

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "plugins": [
    "vue"
  ],
  "processor": "vue/.vue"
}

我們繼續運行eslint:

➜  eslint-demo git:(v0.0.1) ✗ npx eslint ./src/*
code document.write("hello eslint");
code <template>
</template>
<script>
export default{
    name: 'demo2',
};
</script>
<style lang='scss' scoped>
</style>

➜  eslint-demo git:(v0.0.1) ✗ 


可以看到,我們demo1.js跟demo2.vue的源碼都打印了一遍。

好啦,processor就先講到這裏了,後面在自定義plugin的時候具體在對每個方法做解析。

全局變量(Globals)

這個配置比較好理解,就是我們在env配置的全局變量,如果我們需要添加、禁用某些全局變量的話,我們可以放在Globals配置中,

當訪問當前源文件內未定義的變量時,no-undef 規則將發出警告。如果你想在一個源文件裏使用全局變量,推薦你在 ESLint 中定義這些全局變量,這樣 ESLint 就不會發出警告了。你可以使用註釋或在配置文件中定義全局變量。

要在你的 JavaScript 文件中,用註釋指定全局變量,格式如下:

/* global var1, var2 */

這定義了兩個全局變量,var1var2。如果你想選擇性地指定這些全局變量可以被寫入(而不是隻被讀取),那麼你可以用一個 "writable" 的標誌來設置它們:

/* global var1:writable, var2:writable */

要在配置文件中配置全局變量,請將 globals 配置屬性設置爲一個對象,該對象包含以你希望使用的每個全局變量。對於每個全局變量鍵,將對應的值設置爲 "writable" 以允許重寫變量,或 "readonly" 不允許重寫變量。例如:

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

在 YAML 中:

---
  globals:
    var1: writable
    var2: readonly

在這些例子中 var1 允許被重寫,var2 不允許被重寫。

可以使用字符串 "off" 禁用全局變量。例如,在大多數 ES2015 全局變量可用但 Promise 不可用的環境中,你可以使用以下配置:

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

由於歷史原因,布爾值 false 和字符串值 "readable" 等價於 "readonly"。類似地,布爾值 true 和字符串值 "writeable" 等價於 "writable"。但是,不建議使用舊值。

比如我們有一個全局的fox變量,我們需要在globals聲明一下,並且爲只讀。我們創建一個demo-global-fox.js文件:

demo-global-fox.js

fox.say("hello");

修改一下我們的配置信息:

.eslintrc.json

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "plugins": [
    "vue"
  ],
  "processor": "vue/.vue",
  "globals": {
    "fox": "readonly"
  }
}

插件(Plugins)

ESLint 支持使用第三方插件。在使用插件之前,你必須使用 npm 安裝它。比如我們這裏使用了eslint-plugin-vue插件,在配置文件裏配置插件時,可以使用 plugins 關鍵字來存放插件名字的列表。插件名稱可以省略 eslint-plugin- 前綴。

{
    "plugins": [
        "vue",
        "eslint-plugin-vue"
    ]
}

兩種方式都是可以的,有些第三方插件可能還帶有命名空間,比如@typescirpt-eslint/eslint-plugin插件,每個插件是一個命名格式爲 eslint-plugin- 的 npm 模塊,比如 eslint-plugin-jquery。你也可以用這樣的格式 @/eslint-plugin- 限定在包作用域下,比如 @jquery/eslint-plugin-jquery

我們引用的時候可以直接帶上命名空間即可,比如引用@typescirpt-eslint/eslint-plugin插件和@jquery/eslint-plugin-jquery插件:

{
    "plugins": [
        "@typescirpt-eslint",
      	"@jquery/jquery`"
    ]
}

前面說了我們demo需要支持typescript,所以我們demo中裝一下@typescirpt-eslint/eslint-plugin插件:

npm install -S typescript && npm install -D @typescript-eslint/eslint-plugin && npm install -D @typescript-eslint/parser

package.json

{
  "name": "eslint-demo",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "typescript": "^3.9.5"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^3.3.0",
    "@typescript-eslint/parser": "^3.3.0",
    "eslint": "^7.3.0",
    "eslint-plugin-vue": "^6.2.2"
  }
}

.eslintrc.json:

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "plugins": [
    "vue",
    "@typescript-eslint"
  ],
  "processor": "vue/.vue",
  "globals": {
    "fox": "readonly"
  }
}

規則(Rules)

終於是講到rules了,rules其實就是eslint中定義的代碼規則,其實eslint也就是一系列的規則的組合,由於我們前面並沒有對我們的demo做任何rules配置,所以我們每次執行eslint的時候都沒有一點反應,接下里我們結合demo做一下rules的配置。

比如我們需要在代碼語句結束強制加分號“;”

我們創建一個demo-semi.js文件:

demo-semi.js

document.write("hello eslint")

然後在配置文件中配置semi規則:

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "plugins": [
    "vue",
    "@typescript-eslint"
  ],
  "processor": "vue/.vue",
  "globals": {
    "fox": "readonly"
  },
  "rules": {
    "semi": [
      "error",
      "always"
    ]
  }
}

運行eslint:

➜  eslint-demo git:(v0.0.1) ✗ npx eslint ./src/*
code fox.say("hello");
code document.write("hello eslint")
code document.write("hello eslint");
code <template>
</template>
<script>
export default{
    name: 'demo2',
};
</script>
<style lang='scss' scoped>
</style>


xx/Study/eslint-demo/src/demo-semi.js
  1:31  error  Missing semicolon  semi

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

➜  eslint-demo git:(v0.0.1) ✗ 

可以看到,我們終端中直接報錯了

xx/eslint-demo/src/demo-semi.js
  1:31  error  Missing semicolon  semi

✖ 1 problem (1 error, 0 warnings)
  1 error and 0 warnings potentially fixable with the `--fix` option.

哈哈,終於是看點東西了,不容易啊~

再來一個,比如我們需要在代碼中用雙引號“”

.eslintrc.json:

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "plugins": [
    "vue",
    "@typescript-eslint"
  ],
  "processor": "vue/.vue",
  "globals": {
    "fox": "readonly"
  },
  "rules": {
    "semi": [
      "error",
      "always"
    ],
    "quotes": ["error", "double"]
  }
}

運行eslint:

➜  eslint-demo git:(v0.0.1) ✗ npx eslint ./src/*
code fox.say("hello");
code document.write("hello eslint")
code document.write("hello eslint");
code <template>
</template>
<script>
export default{
    name: 'demo2',
};
</script>
<style lang='scss' scoped>
</style>


xx/Study/eslint-demo/src/demo-semi.js
  1:31  error  Missing semicolon  semi

xx/Study/eslint-demo/src/demo2.vue
  5:11  error  Strings must use doublequote  quotes

✖ 2 problems (2 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.

➜  eslint-demo git:(v0.0.1) ✗ 

可以看到,我們代碼中有兩處報錯了,demo-semi.js跟demo2.vue

demo-semi.js(沒有加分號):

document.write("hello eslint")

demo2.vue(沒有用雙引號):

<template>
</template>
<script>
export default{
    name: 'demo2',
};
</script>
<style lang='scss' scoped>
</style>

有小夥伴可能要疑問了,semi跟quotes從哪來的呢? 我可不可以定義一個叫xxx的規則呢?

下面一個一個解答:

semi跟quotes從哪來的呢?

我們翻開eslint的源碼:

lib/rules/index.js

/**
 * @fileoverview Collects the built-in rules into a map structure so that they can be imported all at once and without
 * using the file-system directly.
 * @author Peter (Somogyvari) Metz
 */

"use strict";

/* eslint sort-keys: ["error", "asc"] */

const { LazyLoadingRuleMap } = require("./utils/lazy-loading-rule-map");

/** @type {Map<string, import("../shared/types").Rule>} */
module.exports = new LazyLoadingRuleMap(Object.entries({
    "accessor-pairs": () => require("./accessor-pairs"),
    "array-bracket-newline": () => require("./array-bracket-newline"),
    "array-bracket-spacing": () => require("./array-bracket-spacing"),
    "array-callback-return": () => require("./array-callback-return"),
    "array-element-newline": () => require("./array-element-newline"),
    "arrow-body-style": () => require("./arrow-body-style"),
    "arrow-parens": () => require("./arrow-parens"),
    "arrow-spacing": () => require("./arrow-spacing"),
    "block-scoped-var": () => require("./block-scoped-var"),
    "block-spacing": () => require("./block-spacing"),
    "brace-style": () => require("./brace-style"),
    "callback-return": () => require("./callback-return"),
    camelcase: () => require("./camelcase"),
    "capitalized-comments": () => require("./capitalized-comments"),
    "class-methods-use-this": () => require("./class-methods-use-this"),
    "comma-dangle": () => require("./comma-dangle"),
    "comma-spacing": () => require("./comma-spacing"),
    "comma-style": () => require("./comma-style"),
    complexity: () => require("./complexity"),
    "computed-property-spacing": () => require("./computed-property-spacing"),
    "consistent-return": () => require("./consistent-return"),
    "consistent-this": () => require("./consistent-this"),
    "constructor-super": () => require("./constructor-super"),
    curly: () => require("./curly"),
    "default-case": () => require("./default-case"),
    "default-case-last": () => require("./default-case-last"),
    "default-param-last": () => require("./default-param-last"),
    "dot-location": () => require("./dot-location"),
    "dot-notation": () => require("./dot-notation"),
    "eol-last": () => require("./eol-last"),
    eqeqeq: () => require("./eqeqeq"),
    "for-direction": () => require("./for-direction"),
    "func-call-spacing": () => require("./func-call-spacing"),
    "func-name-matching": () => require("./func-name-matching"),
    "func-names": () => require("./func-names"),
    "func-style": () => require("./func-style"),
    "function-call-argument-newline": () => require("./function-call-argument-newline"),
    "function-paren-newline": () => require("./function-paren-newline"),
    "generator-star-spacing": () => require("./generator-star-spacing"),
    "getter-return": () => require("./getter-return"),
    "global-require": () => require("./global-require"),
    "grouped-accessor-pairs": () => require("./grouped-accessor-pairs"),
    "guard-for-in": () => require("./guard-for-in"),
    "handle-callback-err": () => require("./handle-callback-err"),
    "id-blacklist": () => require("./id-blacklist"),
    "id-length": () => require("./id-length"),
    "id-match": () => require("./id-match"),
    "implicit-arrow-linebreak": () => require("./implicit-arrow-linebreak"),
    indent: () => require("./indent"),
    "indent-legacy": () => require("./indent-legacy"),
    "init-declarations": () => require("./init-declarations"),
    "jsx-quotes": () => require("./jsx-quotes"),
    "key-spacing": () => require("./key-spacing"),
    "keyword-spacing": () => require("./keyword-spacing"),
    "line-comment-position": () => require("./line-comment-position"),
    "linebreak-style": () => require("./linebreak-style"),
    "lines-around-comment": () => require("./lines-around-comment"),
    "lines-around-directive": () => require("./lines-around-directive"),
    "lines-between-class-members": () => require("./lines-between-class-members"),
    "max-classes-per-file": () => require("./max-classes-per-file"),
    "max-depth": () => require("./max-depth"),
    "max-len": () => require("./max-len"),
    "max-lines": () => require("./max-lines"),
    "max-lines-per-function": () => require("./max-lines-per-function"),
    "max-nested-callbacks": () => require("./max-nested-callbacks"),
    "max-params": () => require("./max-params"),
    "max-statements": () => require("./max-statements"),
    "max-statements-per-line": () => require("./max-statements-per-line"),
    "multiline-comment-style": () => require("./multiline-comment-style"),
    "multiline-ternary": () => require("./multiline-ternary"),
    "new-cap": () => require("./new-cap"),
    "new-parens": () => require("./new-parens"),
    "newline-after-var": () => require("./newline-after-var"),
    "newline-before-return": () => require("./newline-before-return"),
    "newline-per-chained-call": () => require("./newline-per-chained-call"),
    "no-alert": () => require("./no-alert"),
    "no-array-constructor": () => require("./no-array-constructor"),
    "no-async-promise-executor": () => require("./no-async-promise-executor"),
    "no-await-in-loop": () => require("./no-await-in-loop"),
    "no-bitwise": () => require("./no-bitwise"),
    "no-buffer-constructor": () => require("./no-buffer-constructor"),
    "no-caller": () => require("./no-caller"),
    "no-case-declarations": () => require("./no-case-declarations"),
    "no-catch-shadow": () => require("./no-catch-shadow"),
    "no-class-assign": () => require("./no-class-assign"),
    "no-compare-neg-zero": () => require("./no-compare-neg-zero"),
    "no-cond-assign": () => require("./no-cond-assign"),
    "no-confusing-arrow": () => require("./no-confusing-arrow"),
    "no-console": () => require("./no-console"),
    "no-const-assign": () => require("./no-const-assign"),
    "no-constant-condition": () => require("./no-constant-condition"),
    "no-constructor-return": () => require("./no-constructor-return"),
    "no-continue": () => require("./no-continue"),
    "no-control-regex": () => require("./no-control-regex"),
    "no-debugger": () => require("./no-debugger"),
    "no-delete-var": () => require("./no-delete-var"),
    "no-div-regex": () => require("./no-div-regex"),
    "no-dupe-args": () => require("./no-dupe-args"),
    "no-dupe-class-members": () => require("./no-dupe-class-members"),
    "no-dupe-else-if": () => require("./no-dupe-else-if"),
    "no-dupe-keys": () => require("./no-dupe-keys"),
    "no-duplicate-case": () => require("./no-duplicate-case"),
    "no-duplicate-imports": () => require("./no-duplicate-imports"),
    "no-else-return": () => require("./no-else-return"),
    "no-empty": () => require("./no-empty"),
    "no-empty-character-class": () => require("./no-empty-character-class"),
    "no-empty-function": () => require("./no-empty-function"),
    "no-empty-pattern": () => require("./no-empty-pattern"),
    "no-eq-null": () => require("./no-eq-null"),
    "no-eval": () => require("./no-eval"),
    "no-ex-assign": () => require("./no-ex-assign"),
    "no-extend-native": () => require("./no-extend-native"),
    "no-extra-bind": () => require("./no-extra-bind"),
    "no-extra-boolean-cast": () => require("./no-extra-boolean-cast"),
    "no-extra-label": () => require("./no-extra-label"),
    "no-extra-parens": () => require("./no-extra-parens"),
    "no-extra-semi": () => require("./no-extra-semi"),
    "no-fallthrough": () => require("./no-fallthrough"),
    "no-floating-decimal": () => require("./no-floating-decimal"),
    "no-func-assign": () => require("./no-func-assign"),
    "no-global-assign": () => require("./no-global-assign"),
    "no-implicit-coercion": () => require("./no-implicit-coercion"),
    "no-implicit-globals": () => require("./no-implicit-globals"),
    "no-implied-eval": () => require("./no-implied-eval"),
    "no-import-assign": () => require("./no-import-assign"),
    "no-inline-comments": () => require("./no-inline-comments"),
    "no-inner-declarations": () => require("./no-inner-declarations"),
    "no-invalid-regexp": () => require("./no-invalid-regexp"),
    "no-invalid-this": () => require("./no-invalid-this"),
    "no-irregular-whitespace": () => require("./no-irregular-whitespace"),
    "no-iterator": () => require("./no-iterator"),
    "no-label-var": () => require("./no-label-var"),
    "no-labels": () => require("./no-labels"),
    "no-lone-blocks": () => require("./no-lone-blocks"),
    "no-lonely-if": () => require("./no-lonely-if"),
    "no-loop-func": () => require("./no-loop-func"),
    "no-loss-of-precision": () => require("./no-loss-of-precision"),
    "no-magic-numbers": () => require("./no-magic-numbers"),
    "no-misleading-character-class": () => require("./no-misleading-character-class"),
    "no-mixed-operators": () => require("./no-mixed-operators"),
    "no-mixed-requires": () => require("./no-mixed-requires"),
    "no-mixed-spaces-and-tabs": () => require("./no-mixed-spaces-and-tabs"),
    "no-multi-assign": () => require("./no-multi-assign"),
    "no-multi-spaces": () => require("./no-multi-spaces"),
    "no-multi-str": () => require("./no-multi-str"),
    "no-multiple-empty-lines": () => require("./no-multiple-empty-lines"),
    "no-native-reassign": () => require("./no-native-reassign"),
    "no-negated-condition": () => require("./no-negated-condition"),
    "no-negated-in-lhs": () => require("./no-negated-in-lhs"),
    "no-nested-ternary": () => require("./no-nested-ternary"),
    "no-new": () => require("./no-new"),
    "no-new-func": () => require("./no-new-func"),
    "no-new-object": () => require("./no-new-object"),
    "no-new-require": () => require("./no-new-require"),
    "no-new-symbol": () => require("./no-new-symbol"),
    "no-new-wrappers": () => require("./no-new-wrappers"),
    "no-obj-calls": () => require("./no-obj-calls"),
    "no-octal": () => require("./no-octal"),
    "no-octal-escape": () => require("./no-octal-escape"),
    "no-param-reassign": () => require("./no-param-reassign"),
    "no-path-concat": () => require("./no-path-concat"),
    "no-plusplus": () => require("./no-plusplus"),
    "no-process-env": () => require("./no-process-env"),
    "no-process-exit": () => require("./no-process-exit"),
    "no-promise-executor-return": () => require("./no-promise-executor-return"),
    "no-proto": () => require("./no-proto"),
    "no-prototype-builtins": () => require("./no-prototype-builtins"),
    "no-redeclare": () => require("./no-redeclare"),
    "no-regex-spaces": () => require("./no-regex-spaces"),
    "no-restricted-exports": () => require("./no-restricted-exports"),
    "no-restricted-globals": () => require("./no-restricted-globals"),
    "no-restricted-imports": () => require("./no-restricted-imports"),
    "no-restricted-modules": () => require("./no-restricted-modules"),
    "no-restricted-properties": () => require("./no-restricted-properties"),
    "no-restricted-syntax": () => require("./no-restricted-syntax"),
    "no-return-assign": () => require("./no-return-assign"),
    "no-return-await": () => require("./no-return-await"),
    "no-script-url": () => require("./no-script-url"),
    "no-self-assign": () => require("./no-self-assign"),
    "no-self-compare": () => require("./no-self-compare"),
    "no-sequences": () => require("./no-sequences"),
    "no-setter-return": () => require("./no-setter-return"),
    "no-shadow": () => require("./no-shadow"),
    "no-shadow-restricted-names": () => require("./no-shadow-restricted-names"),
    "no-spaced-func": () => require("./no-spaced-func"),
    "no-sparse-arrays": () => require("./no-sparse-arrays"),
    "no-sync": () => require("./no-sync"),
    "no-tabs": () => require("./no-tabs"),
    "no-template-curly-in-string": () => require("./no-template-curly-in-string"),
    "no-ternary": () => require("./no-ternary"),
    "no-this-before-super": () => require("./no-this-before-super"),
    "no-throw-literal": () => require("./no-throw-literal"),
    "no-trailing-spaces": () => require("./no-trailing-spaces"),
    "no-undef": () => require("./no-undef"),
    "no-undef-init": () => require("./no-undef-init"),
    "no-undefined": () => require("./no-undefined"),
    "no-underscore-dangle": () => require("./no-underscore-dangle"),
    "no-unexpected-multiline": () => require("./no-unexpected-multiline"),
    "no-unmodified-loop-condition": () => require("./no-unmodified-loop-condition"),
    "no-unneeded-ternary": () => require("./no-unneeded-ternary"),
    "no-unreachable": () => require("./no-unreachable"),
    "no-unreachable-loop": () => require("./no-unreachable-loop"),
    "no-unsafe-finally": () => require("./no-unsafe-finally"),
    "no-unsafe-negation": () => require("./no-unsafe-negation"),
    "no-unused-expressions": () => require("./no-unused-expressions"),
    "no-unused-labels": () => require("./no-unused-labels"),
    "no-unused-vars": () => require("./no-unused-vars"),
    "no-use-before-define": () => require("./no-use-before-define"),
    "no-useless-backreference": () => require("./no-useless-backreference"),
    "no-useless-call": () => require("./no-useless-call"),
    "no-useless-catch": () => require("./no-useless-catch"),
    "no-useless-computed-key": () => require("./no-useless-computed-key"),
    "no-useless-concat": () => require("./no-useless-concat"),
    "no-useless-constructor": () => require("./no-useless-constructor"),
    "no-useless-escape": () => require("./no-useless-escape"),
    "no-useless-rename": () => require("./no-useless-rename"),
    "no-useless-return": () => require("./no-useless-return"),
    "no-var": () => require("./no-var"),
    "no-void": () => require("./no-void"),
    "no-warning-comments": () => require("./no-warning-comments"),
    "no-whitespace-before-property": () => require("./no-whitespace-before-property"),
    "no-with": () => require("./no-with"),
    "nonblock-statement-body-position": () => require("./nonblock-statement-body-position"),
    "object-curly-newline": () => require("./object-curly-newline"),
    "object-curly-spacing": () => require("./object-curly-spacing"),
    "object-property-newline": () => require("./object-property-newline"),
    "object-shorthand": () => require("./object-shorthand"),
    "one-var": () => require("./one-var"),
    "one-var-declaration-per-line": () => require("./one-var-declaration-per-line"),
    "operator-assignment": () => require("./operator-assignment"),
    "operator-linebreak": () => require("./operator-linebreak"),
    "padded-blocks": () => require("./padded-blocks"),
    "padding-line-between-statements": () => require("./padding-line-between-statements"),
    "prefer-arrow-callback": () => require("./prefer-arrow-callback"),
    "prefer-const": () => require("./prefer-const"),
    "prefer-destructuring": () => require("./prefer-destructuring"),
    "prefer-exponentiation-operator": () => require("./prefer-exponentiation-operator"),
    "prefer-named-capture-group": () => require("./prefer-named-capture-group"),
    "prefer-numeric-literals": () => require("./prefer-numeric-literals"),
    "prefer-object-spread": () => require("./prefer-object-spread"),
    "prefer-promise-reject-errors": () => require("./prefer-promise-reject-errors"),
    "prefer-reflect": () => require("./prefer-reflect"),
    "prefer-regex-literals": () => require("./prefer-regex-literals"),
    "prefer-rest-params": () => require("./prefer-rest-params"),
    "prefer-spread": () => require("./prefer-spread"),
    "prefer-template": () => require("./prefer-template"),
    "quote-props": () => require("./quote-props"),
    quotes: () => require("./quotes"),
    radix: () => require("./radix"),
    "require-atomic-updates": () => require("./require-atomic-updates"),
    "require-await": () => require("./require-await"),
    "require-jsdoc": () => require("./require-jsdoc"),
    "require-unicode-regexp": () => require("./require-unicode-regexp"),
    "require-yield": () => require("./require-yield"),
    "rest-spread-spacing": () => require("./rest-spread-spacing"),
    semi: () => require("./semi"),
    "semi-spacing": () => require("./semi-spacing"),
    "semi-style": () => require("./semi-style"),
    "sort-imports": () => require("./sort-imports"),
    "sort-keys": () => require("./sort-keys"),
    "sort-vars": () => require("./sort-vars"),
    "space-before-blocks": () => require("./space-before-blocks"),
    "space-before-function-paren": () => require("./space-before-function-paren"),
    "space-in-parens": () => require("./space-in-parens"),
    "space-infix-ops": () => require("./space-infix-ops"),
    "space-unary-ops": () => require("./space-unary-ops"),
    "spaced-comment": () => require("./spaced-comment"),
    strict: () => require("./strict"),
    "switch-colon-spacing": () => require("./switch-colon-spacing"),
    "symbol-description": () => require("./symbol-description"),
    "template-curly-spacing": () => require("./template-curly-spacing"),
    "template-tag-spacing": () => require("./template-tag-spacing"),
    "unicode-bom": () => require("./unicode-bom"),
    "use-isnan": () => require("./use-isnan"),
    "valid-jsdoc": () => require("./valid-jsdoc"),
    "valid-typeof": () => require("./valid-typeof"),
    "vars-on-top": () => require("./vars-on-top"),
    "wrap-iife": () => require("./wrap-iife"),
    "wrap-regex": () => require("./wrap-regex"),
    "yield-star-spacing": () => require("./yield-star-spacing"),
    yoda: () => require("./yoda")
}));

我們可以找到我們的semi跟quotes,還有一些其它的rule,每個rule的具體含義小夥伴可以自己去看eslint的官網或源碼哦,裏面都有說明的。

ESLint 附帶有大量的規則。你可以使用註釋或配置文件修改你項目中要使用的規則。要改變一個規則設置,你必須將規則 ID 設置爲下列值之一:

  • "off"0 - 關閉規則
  • "warn"1 - 開啓規則,使用警告級別的錯誤:warn (不會導致程序退出)
  • "error"2 - 開啓規則,使用錯誤級別的錯誤:error (當被觸發的時候,程序會退出)

在代碼中配置(Comments)

爲了在文件註釋裏配置規則,使用以下格式的註釋:

/* eslint eqeqeq: "off", curly: "error" */

在這個例子裏,eqeqeq 規則被關閉,curly 規則被打開,定義爲錯誤級別。你也可以使用對應的數字定義規則嚴重程度:

/* eslint eqeqeq: 0, curly: 2 */

這個例子和上個例子是一樣的,只不過它是用的數字而不是字符串。eqeqeq 規則是關閉的,curly 規則被設置爲錯誤級別。

如果一個規則有額外的選項,你可以使用數組字面量指定它們,比如:

/* eslint quotes: ["error", "double"], curly: 2 */

這條註釋爲規則 quotes 指定了 “double”選項。數組的第一項總是規則的嚴重程度(數字或字符串)。

比如我們的demo2.vue文件:

<template>
</template>
<script>
/* eslint quotes: ["error", "double"] */
export default{
    name: 'demo2',
};
</script>
<style lang='scss' scoped>
</style>

我們添加了一個/* eslint quotes: [“error”, “double”] */註釋,其實跟配置文件加rules一樣的效果,只是配置文件加rules是作用於所有的文件。

在config文件中配置(rules)

這個我們已經演示過了,比如:

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

我可不可以定義一個叫xxx的規則呢?

可以! 自定義規則我們會在自定義plugin的時候詳細講解。

禁止規則(Disabling Rules with Inline Comments)

既然可以在代碼中使用規則,當然也可以在代碼中禁用規則。

比如我們創建一個demo-disable.js:

document.write("hello eslint")

如果我們現在運行一下eslint的話會直接報錯,提示我們需要加上“;”符號。

如果我們需要在demo-disable.js文件中禁用掉eslint的話,我們該怎麼做呢?

禁止掉所有規則

我們直接把/* eslint-disable */添加到文件的頭部,這樣會禁止掉所有的eslint規則

/* eslint-disable */
document.write("hello eslint")
禁止掉指定的規則

/* eslint-disable semi*/,如果有多個規則的話逗號隔開 /* eslint-disable no-alert, no-console */

/* eslint-disable semi*/
document.write("hello esslint")
單行註釋
document.write("hello esslint") // eslint-disable-line semi
document.write("hello esslint") //eslint-disable-line
document.write("hello esslint") /* eslint-disable-line no-alert */
/* eslint-disable-next-line no-alert */
document.write("hello esslint")
document.write("hello esslint") // eslint-disable-line semi,quotes  多個註釋逗號隔開
以文件分組禁止(Disabling Rules Only for a Group of Files)

比如我們要禁止demo-disable.js

.eslintrc.json:

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "plugins": [
    "vue",
    "@typescript-eslint"
  ],
  "processor": "vue/.vue",
  "globals": {
    "fox": "readonly"
  },
  "rules": {
    "semi": [
      "error",
      "always"
    ],
    "quotes": ["error", "double"]
  },
  "overrides": [
    {
      "files": ["demo-disable.js"],
      "rules": {
        "semi": "off"
      }
    }
  ]
}

共享設置(Settings)

ESLint 支持在配置文件添加共享設置。你可以添加 settings 對象到配置文件,它將提供給每一個將被執行的規則。如果你想添加的自定義規則而且使它們可以訪問到相同的信息,在每個規則的回調函數的context對象中包含settings屬性。

在 JSON配置文件中:

{
    "settings": {
        "sharedData": "Hello"
    }
}

自定義rules會在後面的自定義plugin文章中詳細介紹。

配置文件格式(Configuration File Formats)

ESLint 支持幾種格式的配置文件:

  • JavaScript - 使用 .eslintrc.js 然後輸出一個配置對象。
  • YAML - 使用 .eslintrc.yaml.eslintrc.yml 去定義配置的結構。
  • JSON - 使用 .eslintrc.json 去定義配置的結構,ESLint 的 JSON 文件允許 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

配置文件層級和嵌套(Configuration Cascading and Hierarchy)

當使用 .eslintrc.*package.json文件的配置時,你可以利用層疊配置。例如,假如你有以下結構:

your-project
├── .eslintrc
├── lib
│ └── source.js
└─┬ tests
  ├── .eslintrc
  └── test.js

層疊配置使用離要檢測的文件最近的 .eslintrc文件作爲最高優先級,然後纔是父目錄裏的配置文件,等等。當你在這個項目中允許 ESLint 時,lib/ 下面的所有文件將使用項目根目錄裏的 .eslintrc 文件作爲它的配置文件。當 ESLint 遍歷到 test/ 目錄,your-project/.eslintrc 之外,它還會用到 your-project/tests/.eslintrc。所以 your-project/tests/test.js 是基於它的目錄層次結構中的兩個.eslintrc 文件的組合,並且離的最近的一個優先。通過這種方式,你可以有項目級 ESLint 設置,也有覆蓋特定目錄的 ESLint 設置。

同樣的,如果在根目錄的 package.json 文件中有一個 eslintConfig 字段,其中的配置將使用於所有子目錄,但是當 tests 目錄下的 .eslintrc 文件中的規則與之發生衝突時,就會覆蓋它。

your-project
├── package.json
├── lib
│ └── source.js
└─┬ tests
  ├── .eslintrc
  └── test.js

如果同一目錄下 .eslintrcpackage.json 同時存在,.eslintrc 優先級高會被使用,package.json 文件將不會被使用。

**注意:**如果在你的主目錄下有一個自定義的配置文件 (~/.eslintrc) ,如果沒有其它配置文件時它纔會被使用。因爲個人配置將適用於用戶目錄下的所有目錄和文件,包括第三方的代碼,當 ESLint 運行時可能會導致問題。

默認情況下,ESLint 會在所有父級目錄裏尋找配置文件,一直到根目錄。如果你想要你所有項目都遵循一個特定的約定時,這將會很有用,但有時候會導致意想不到的結果。爲了將 ESLint 限制到一個特定的項目,在你項目根目錄下的 package.json 文件或者 .eslintrc.* 文件裏的 eslintConfig 字段下設置 "root": true。ESLint 一旦發現配置文件中有 "root": true,它就會停止在父級目錄中尋找。

{
    "root": true
}

在 YAML 中:

---
  root: true

例如,projectAlib/ 目錄下的 .eslintrc 文件中設置了 "root": true。這種情況下,當檢測 main.js 時,lib/ 下的配置將會被使用,projectA/ 下的 .eslintrc 將不會被使用。

home
└── user
    ├── .eslintrc <- Always skipped if other configs present
    └── projectA
        ├── .eslintrc  <- Not used
        └── 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 中自定義的默認配置。

繼承配置文件(Extending Configuration Files)

我們可以使用extends字段去繼承一個配置文件,這個配置文件可以是單獨的一個文件、第三方依賴、插件。

單獨的一個config文件

我們在項目根目錄創建一個config目錄,然後在config目錄中創建一個cust-config.js,然後添加一條“不允許出現console”的規則。

cust-config.js:

module.exports = {
    rules: {
        "no-console": "error", //禁止使用console
    }
};

然後創建一個demo-console.js測試

demo-console.js:

console.log("hello eslint");

然後使用extends字段繼承我們的配置信息

.eslintrc.json:

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "plugins": [
    "vue",
    "@typescript-eslint"
  ],
  "processor": "vue/.vue",
  "globals": {
    "fox": "readonly"
  },
  "rules": {
    "semi": [
      "error",
      "always"
    ],
    "quotes": ["error", "double"]
  },
  "overrides": [
    {
      "files": ["demo-disable.js"],
      "rules": {
        "semi": "off"
      }
    }
  ],
  "extends": [
    "./config/cust-config.js"
  ]
}

運行eslint:

192:eslint-demo yinqingyang$ npx eslint ./src/*

/Users/yinqingyang/doc/h5/study/高級程序設計/eslint-demo/src/demo-console.js
  1:1  error  Unexpected console statement  no-console

/Users/yinqingyang/doc/h5/study/高級程序設計/eslint-demo/src/demo-semi.js
  1:31  error  Missing semicolon  semi

/Users/yinqingyang/doc/h5/study/高級程序設計/eslint-demo/src/demo2.vue
  5:11  error  Strings must use doublequote  quotes

✖ 3 problems (3 errors, 0 warnings)
  2 errors and 0 warnings potentially fixable with the `--fix` option.

192:eslint-demo yinqingyang$ 


可以發現有報錯信息“error Unexpected console statement no-console”,說明我們的自定義config文件起作用了。

第三方依賴

其實跟我們第一種方式是一樣的,也就是把我們的config文件發佈,然後利用npm依賴,我就不演示了。

插件繼承

這種方式其實跟第二種有點像,但是插件的功能大於配置文件功能,前面我們使用了兩個插件:eslint-plugin-vue跟@t y pescript-eslint/eslint-plugin,我們也提前已經使用了插件:

.eslintrc.json

{
 ...
  "plugins": [
    "vue",
    "@typescript-eslint"
  ],
 ...
}

我們打開vue的插件看一下清單文件:

Xxx/eslint-demo/node_modules/eslint-plugin-vue/lib/index.js

/*
 * IMPORTANT!
 * This file has been automatically generated,
 * in order to update it's content execute "npm run update"
 */
'use strict'

module.exports = {
	...
  configs: {
    'base': require('./configs/base'),
    'essential': require('./configs/essential'),
    'no-layout-rules': require('./configs/no-layout-rules'),
    'recommended': require('./configs/recommended'),
    'strongly-recommended': require('./configs/strongly-recommended')
  },
  ...
}

可以看到,vue插件提供了configs字段給外部調用,裏面有“base”、“essential”、“no-layout-rules”、“recommended”

、“strongly-recommended”,我們打開base看看:

/*
 * IMPORTANT!
 * This file has been automatically generated,
 * in order to update it's content execute "npm run update"
 */
module.exports = {
  parser: require.resolve('vue-eslint-parser'),
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      jsx: true
    }
  },
  env: {
    browser: true,
    es6: true
  },
  plugins: [
    'vue'
  ],
  rules: {
    'vue/comment-directive': 'error',
    'vue/jsx-uses-vars': 'error'
  }
}

其實這就是一個eslint的配置文件,我們可以選擇繼承並使用它。比如我們使用一下vue的recommended配置:

.eslintrc.json

{
  "env": {
    "browser": true,
    "es2020": true
  },
  "parserOptions": {
    "ecmaFeatures": {
      "jsx": true
    },
    "sourceType": "module"
  },
  "parser": "vue-eslint-parser",
  "plugins": [
    "vue",
    "@typescript-eslint"
  ],
  "processor": "vue/.vue",
  "globals": {
    "fox": "readonly"
  },
  "rules": {
    "semi": [
      "error",
      "always"
    ],
    "quotes": ["error", "double"]
  },
  "overrides": [
    {
      "files": ["demo-disable.js"],
      "rules": {
        "semi": "off"
      }
    }
  ],
  "extends": [
    "./config/cust-config.js", //繼承自定義config文件
    "plugin:vue/recommended" //繼承vue的recommended配置
  ]
}

192:eslint-demo yinqingyang$ npx eslint ./src/*

xx/doc/h5/study/高級程序設計/eslint-demo/src/demo-console.js
  1:1  error  Unexpected console statement  no-console

xx/doc/h5/study/高級程序設計/eslint-demo/src/demo-semi.js
  1:31  error  Missing semicolon  semi

xx/doc/h5/study/高級程序設計/eslint-demo/src/demo2.vue
  1:1   error    The template root requires exactly one element      vue/valid-template-root
  1:1   warning  Require self-closing on HTML elements (<template>)  vue/html-self-closing
  5:11  error    Strings must use doublequote                        quotes
  5:11  warning  Property name "demo2" is not PascalCase             vue/name-property-casing

✖ 6 problems (4 errors, 2 warnings)
  2 errors and 2 warnings potentially fixable with the `--fix` option.

192:eslint-demo yinqingyang$ 

可以看到,我們的demo2.vue文件中多了很多報錯

demo2.vue:

<template>
</template>
<script>
export default{
    name: 'demo2',
};
</script>
<style lang='scss' scoped>
</style>

  1. The template root requires exactly one element vue/valid-template-root

    (模版必須包含至少一個元素)

  2. warning Require self-closing on HTML elements () vue/html-self-closing

(如果爲空元素的話使用自閉合)

  1. error Strings must use doublequote quotes

  2. warning Property name “demo2” is not PascalCase vue/name-property-casing

(組件的name屬性必須是駝峯命名)

忽略文件(Ignoring Files and Directories)

demo中我們可以看到,我們每次執行eslint的時候執行的命令:

npx eslint ./src/*

也就是說會校驗./src目錄下的所有文件,如果有些文件不需要校驗的話該怎麼辦呢?

.eslintignore

在根目錄創建一個.eslintignore文件。

.eslintignore:

src/demo-ignore.js

然後我們在src下面創建一個demo-ignore.js:

demo-ignore.js

document.write("hello eslint")

運行eslint:

192:eslint-demo yinqingyang$ npx eslint ./src/*

xx/study/高級程序設計/eslint-demo/src/demo-console.js
  1:1  error  Unexpected console statement  no-console

xx/h5/study/高級程序設計/eslint-demo/src/demo-ignore.js
  0:0  warning  File ignored because of a matching ignore pattern. Use "--no-ignore" to override

xx/study/高級程序設計/eslint-demo/src/demo-semi.js
  1:31  error  Missing semicolon  semi

xxy/高級程序設計/eslint-demo/src/demo2.vue
  2:1   warning  Expected indentation of 2 spaces but found 4 spaces  vue/html-indent
  6:11  error    Strings must use doublequote                         quotes
  6:11  warning  Property name "demo2" is not PascalCase              vue/name-property-casing

✖ 6 problems (3 errors, 3 warnings)
  2 errors and 2 warnings potentially fixable with the `--fix` option.

192:eslint-demo yinqingyang$ 

可以看到一條警告:

xx/h5/study/高級程序設計/eslint-demo/src/demo-ignore.js
  0:0  warning  File ignored because of a matching ignore pattern. Use "--no-ignore" to override

當 ESLint 運行時,在確定哪些文件要檢測之前,它會在當前工作目錄中查找一個 .eslintignore 文件。如果發現了這個文件,當遍歷目錄時,將會應用這些偏好設置。一次只有一個 .eslintignore 文件會被使用,所以,不是當前工作目錄下的 .eslintignore 文件將不會被用到。

Globs 匹配使用 node-ignore,所以大量可用的特性有:

  • # 開頭的行被當作註釋,不影響忽略模式。
  • 路徑是相對於 .eslintignore 的位置或當前工作目錄。通過 --ignore-pattern command 傳遞的路徑也是如此。
  • 忽略模式同 .gitignore 規範
  • ! 開頭的行是否定模式,它將會重新包含一個之前被忽略的模式。
  • 忽略模式依照 .gitignore 規範.

特別值得注意的是,就像 .gitignore 文件,所有用作 .eslintignore--ignore-pattern 模式的路徑必須使用前斜槓作爲它們的路徑分隔符。

# Valid
/root/src/*.js

# Invalid
\root\src\*.js

請參參閱 .gitignore 規範查看有關有效語法的更多示例。

除了 .eslintignore 文件中的模式,ESLint總是忽略 /node_modules/*/bower_components/* 中的文件。

例如:把下面 .eslintignore 文件放到當前工作目錄裏,將忽略項目根目錄下的 node_modulesbower_components 以及 build/ 目錄下除了 build/index.js 的所有文件。

# /node_modules/* and /bower_components/* in the project root are ignored by default

# Ignore built files except build/index.js
build/*
!build/index.js

**重要:**注意代碼庫的 node_modules 目錄,比如,一個 packages 目錄,默認情況下不會被忽略,需要手動添加到 .eslintignore

Using an Alternate File

如果相比於當前工作目錄下 .eslintignore 文件,你更想使用一個不同的文件,你可以在命令行使用 --ignore-path 選項指定它。例如,你可以使用 .jshintignore 文件,因爲它有相同的格式:

eslint --ignore-path .jshintignore file.js

你也可以使用你的 .gitignore 文件:

eslint --ignore-path .gitignore file.js

任何文件只要滿足標準忽略文件格式都可以用。記住,指定 --ignore-path 意味着任何現有的 .eslintignore 文件將不被使用。請注意,.eslintignore 中的匹配規則比 .gitignore 中的更嚴格。

Using eslintIgnore in package.json

如果沒有發現 .eslintignore 文件,也沒有指定替代文件,ESLint 將在 package.json 文件中查找 eslintIgnore 鍵,來檢查要忽略的文件。

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

Ignored File Warnings

當你傳遞目錄給 ESLint,文件和目錄是默默被忽略的。如果你傳遞一個指定的文件給 ESLint,你會看到一個警告,表明該文件被跳過了。例如,假如你有一個像這樣的 .eslintignore文件:

foo.js

然後你執行:

eslint foo.js

你將會看到這個警告:

foo.js
  0:0  warning  File ignored because of your .eslintignore file. Use --no-ignore to override.

✖ 1 problem (0 errors, 1 warning)

這種消息出現是因爲 ESLint 不確定你是否想檢測文件。正如這個消息表明的那樣,你可以使用 --no-ignore 覆蓋忽略的規則。

好啦,eslint的配置文件環節我們算是全部分析了一遍,文章有點長,我們算結合demo跟着官網走了一遍,也算是收穫滿滿,不得不佩服寫eslint框架的大神,使得功能這麼強大的一個工具變得這麼通俗易懂,下一節我們將重點針對eslint的plugin做解析,打造一個屬於你自己的plugin,敬請期待!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章