十分鐘瞭解eslint配置 && 編寫自定義eslint規則

陽呀呀發表於2019-08-01

eslint介紹

ESLint 是一個開源的 JavaScript 程式碼檢查工具,由 Nicholas C. Zakas 於2013年6月建立。程式碼檢查是一種靜態的分析,常用於尋找有問題的模式或者程式碼,並且不依賴於具體的編碼風格。對大多數程式語言來說都會有程式碼檢查,一般來說編譯程式會內建檢查工具。

JavaScript 是一個動態的弱型別語言,在開發中比較容易出錯。因為沒有編譯程式,為了尋找 JavaScript 程式碼錯誤通常需要在執行過程中不斷除錯。像 ESLint 這樣的可以讓程式設計師在編碼的過程中發現問題而不是在執行的過程中。

ESLint 的初衷是為了讓程式設計師可以建立自己的檢測規則。ESLint 的所有規則都被設計成可插拔的。為了便於人們使用,ESLint 內建了一些規則,當然,你可以在使用過程中自定義規則。所有的規則預設都是禁用的。

ESLint 使用 Node.js 編寫。

eslint配置

配置方式
  1. 一般都採用.eslintrc.*的配置檔案進行配置, 如果放在專案的根目錄中,則會作用於整個專案。如果在專案的子目錄中也包含著.eslintrc檔案,則對於子目錄中檔案的檢查會忽略掉根目錄中的配置,而直接採用子目錄中的配置,這就能夠在不同的目錄範圍內應用不同的檢查規則,顯得比較靈活。ESLint採用逐級向上查詢的方式查詢.eslintrc.*檔案,當找到帶有"root": true配置項的.eslintrc.*檔案時,將會停止向上查詢。
  2. 在 package.json檔案裡的 eslintConfig 欄位進行配置。
具體配置規則

以使用專案為例,簡單介紹一下eslint的具體配置及作用:

module.exports = {
    parser: 'babel-eslint', // parser指定解析器,預設的為espree。babel-eslint是一個Babel parser的包裝器,這個包裝器使得 Babel parser 可以和 ESLint 協調工作
    parserOptions: {
        sourceType: 'module', // 設定為 "script" (預設) 或 "module"(ES6)。
        ecmaFeatures: { // 這是個物件,表示你想使用的額外的語言特性:
            jsx: true // 啟用 JSX
        }
    },
    extends: ['eslint:recommended'], // 使用eslint推薦的規則作為基礎配置,可以在rules中覆蓋
    plugins: ['html', 'vue', 'prettier', 'import'], // vue是eslint-plugin-vue的簡寫,此外掛的作用是可以讓eslint識別.vue中的script程式碼
    rules: { // 0或者off表示規則關閉,出錯也被忽略;1或者warn表示如果出錯會給出警告(不會導致程式退出);2或者error表示如果出錯會報出錯誤(會導致程式退出,退出碼是1)
        'no-console': 'off',
        'prefer-const': 'error',
        'prettier/prettier': 'warn',
        'prefer-arrow-callback': 'warn',
        'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0
    },
    globals: { // 允許在程式碼中使用全域性變數
        location: true,
        setTimeout: true
    }
};
複製程式碼

具體的配置文件:eslint.cn/docs/user-g… 具體的eslint:recommended支援的規則:cn.eslint.org/docs/rules/

“extends”除了可以引入推薦規則,還可以以檔案形式引入其它的自定義規則,然後在這些自定義規則的基礎上用rules去定義個別規則,從而覆蓋掉”extends”中引入的規則。

{
    "extends": [
        "./node_modules/coding-standard/eslintDefaults.js",
        // Override eslintDefaults.js
        "./node_modules/coding-standard/.eslintrc-es6",
        // Override .eslintrc-es6
        "./node_modules/coding-standard/.eslintrc-jsx",
    ],
    "rules": {
        // Override any settings from the "parent" configuration
        "eqeqeq": "warn"
    }
}
複製程式碼

除了在配置檔案中指定規則外,還可以在程式碼中指定規則,程式碼檔案內以註釋配置的規則會覆蓋配置檔案裡的規則,即優先順序要更高。平時我們常用的就是 eslint-disable-next-line

忽略檢查

可以通過在專案目錄下建立.eslintignore檔案,並在其中配置忽略掉對哪些檔案的檢查。需要注意的是,不管你有沒有在.eslintignore中進行配置,eslint都會預設忽略掉對/node_modules/** 的檢查。也可以在package.json檔案的 eslintIgnore 欄位進行配置。

eslint檢查原理

要實現靜態分析則需要自建一個預編譯階段對程式碼進行解析。

首先我們看看大部分編譯器工作時的三個階段:

解析:將未經處理的程式碼解析成更為抽象的表示式,通常為抽象語法樹,即 AST。 轉換:通過修改解析後的程式碼表示式,將其轉換為符合預期的新格式。 程式碼生成:將轉換後的表示式生成為新的目的碼。

對於eslint來說,規則校驗發生在將JavaScript 程式碼解析為 AST 之後,遍歷 AST 的過程中。eslint採用 Espree 來生成AST。具體的生成方法在這裡。 我們可以使用AST explorer來檢視程式碼被解析後生成的AST。

rules工作原理

首先來看看eslint原始碼中關於rules的編寫。eslint中的rules原始碼存在於lib/rules下。每一個rules都是一個node模組,用module.exports匯出一個meta物件及一個create函式。

module.exports = {
    meta: {
        type: "suggestion",

        docs: {
            description: "disallow unnecessary semicolons",
            category: "Possible Errors",
            recommended: true,
            url: "https://eslint.org/docs/rules/no-extra-semi"
        },
        fixable: "code",
        schema: [] // no options
    },
    create: function(context) {
        return {
            // callback functions
        };
    }
};

複製程式碼

meta 代表了這條規則的後設資料,如這條規則的類別,文件,可接收的引數 schema 等等。

create 返回一個物件,其中定義了一些在 AST 遍歷訪問到對應節點需要執行的方法等等。函式接受一個context物件作為引數,裡面包含了例如可以報告錯誤或者警告的context.report()、可以獲取原始碼的context.getSourceCode()等方法,可以簡化規則的編寫。

function checkLastSegment (node) {
    // report problem for function if last code path segment is reachable
}

module.exports = {
    meta: { ... },
    create: function(context) {
        // declare the state of the rule
        return {
            ReturnStatement: function(node) {
                // 在AST從上向下遍歷到ReturnStatement node 時執行
            },
            // 在AST 從下向上遍歷到 function expression node 時執行:
            "FunctionExpression:exit": checkLastSegment,
            "ArrowFunctionExpression:exit": checkLastSegment,
            onCodePathStart: function (codePath, node) {
                // 在分析程式碼路徑開始時執行
            },
            onCodePathEnd: function(codePath, node) {
                // 在分析程式碼路徑結束時執行
            }
        };
    }
};
複製程式碼

遍歷 AST 的過程中會以“從上至下”再“從下至上”的順序經過節點兩次,selector 預設會在下行的過程中執行對應的訪問函式,如果需要再上行的過程中執行,則需要新增:exit。

詳細的原理在官方文件中有說明,點這裡。 詳細的程式碼路徑分析在這裡

如何編寫一個rules

知道了rules的原理,接下來可以自定義一個rules。每一個rules需要有三個以該規則名命名的檔案,分別是:

  • 在 lib/rules 目錄下: 一個原始檔(例如,no-extra-semi.js)

  • 在 tests/lib/rules 目錄下: 一個測試檔案 (例如, no-extra-semi.js)

  • 在 docs/rules 目錄: 一個 markdown 文件檔案 (例如, no-extra-semi)

接下來我們來編寫一個簡單的rules,例如禁止塊級註釋,當程式碼中使用了塊級註釋,eslint將報錯。

rules檔案:

// lib/rules/no-block-comments.js
module.exports = {
  meta: {
    docs: {
      description: '禁止塊級註釋',
      category: 'Stylistic Issues',
      recommended: true
    }
  },

  create (context) {
    // 獲取原始碼
    const sourceCode = context.getSourceCode()

    return {
      Program () {
        // 獲取原始碼中所有的註釋
        const comments = sourceCode.getAllComments()

        const blockComments = comments.filter(({ type }) => type === 'Block')

        blockComments.length && context.report({
          node: node,
          message: 'No block comments'
        })
      }
    }
  }
}
複製程式碼

rules的測試檔案:

// tests/lib/rules/no-block-comments.js
const RuleTester = require("eslint").RuleTester;
const rule = require("../../../lib/rules/no-block-comments");

const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }); // You do have to tell eslint what js you're using

ruleTester.run("no-block-comments", rule, {
    valid: ["var a = 1; console.log(a)"],
    invalid: [
        {
            code: "var a = 1; /* block comments */ console.log(a)",
            errors: [
                {
                    messageId: "blockComments",
                    line: 1,
                    nodeType: "Block"
                }
            ]
        }
    ]
});
複製程式碼

官網的working with rules文件中有關於如何編寫一個rules的詳細介紹。

如何使用自定義的rules

編寫好的rules需要釋出到npm上,作為一個eslint-plugin,在專案中下載下來才能夠使用。例子中程式碼的npm在這裡

在專案中的配置:

// .eslintrc.js
module.exports = {
    ...
    "plugins": [
        "eslint-plugin-no-block-comments"
        // 你 publish 的 npm 包名稱,可以省略 eslint-plugin
      ],
    "rules": { // 啟用的規則及其各自的錯誤級別
        'no-console': 'off',
        "no-block-comments/no-block-comments": 2 // 引用no-block-comments外掛中的no-block-comments規則
    }
};
複製程式碼

之後就可以對程式碼進行檢查了。比如我要檢查的程式碼如下:

// src/index.js
const a = 1;
/*
    這裡是塊級註釋
*/
console.log(a);
複製程式碼

在命令列中執行eslint src,就可以看到報錯結果。 evernotecid://43E55629-1A51-495A-878E-7C9D5C55CC19/appyinxiangcom/15304103/ENResource/p297

十分鐘瞭解eslint配置 && 編寫自定義eslint規則

參考文章:

ESlint官網

ESLint 工作原理探討

開發 eslint 規則

相關文章