沐鳴平台註冊登錄_給 eslint 寫一個插件

eslint 是很有名的 linter,地球上每一個 JavaScript 程序員都應該知道。

linter 是一種代碼靜態分析工具,它可以幫你找到代碼中可能存在的錯誤與 bug,也能找出代碼風格的問題,不過因為只是靜態分析,對 js 這種動態類型的語言所能做的就比較有限了,畢竟在 js 中,變量的類型如果不執行就不容易知道,有些錯誤就不那麼容易被找出來,雖然如此,能做的檢查還是很多了。

安裝

安裝 eslint 本身只需要安裝 eslint 本身就夠了,而且 eslint 自帶一些規則,不安裝任何插件就做到基本的檢查,但一般還是需要安裝一些插件。

$ yarn add --dev eslint

eslint 除了可以安裝插件外,還可以安裝另外兩個東西,總共有 3 種:

  • plugin:eslint 的插件可以幫 eslint 增加規則,另外也可以通過配置文件讓程序員添加自己的規則,插件可以提供一份默認的推薦配置
  • config:可以重複使用的規則配置文件,比較有名的是 standard 和 airbnb 的規則,配置文件有可能會有依賴的插件,需要自己去安裝
  • parser:用來擴充 eslint 可以處理的語法,有用 babel 轉換 js 的 babel-eslint ,讓 eslint 可以處理實驗性的語法;@typescript-eslint/parser 可以讓 eslint 處理 Typescript;還有 vue-eslint-parser 用來處理 vue 代碼。

使用

雖然安裝很簡單,但不對 eslint 進行配置是什麼都不能做的,所以還要提供一個基本的配置,而 eslint 提供一個簡單的初始化命令,通過執行這個命令並回答幾個問題,eslint 就會產生一個基本的配置:

$ yarn eslint --init

eslint 的配置文件可以是 js、json 或 yml 的格式,在這裏我們用 js 格式,文件要取名為 .eslintrc.js,這裏就用基本的配置,即只用 eslint:recommended 這組設置,如果有其它的插件也像這樣進行基本的設置:

module.exports = {
  extends: 'eslint:recommended',
}

eslint 的配置文件有幾個基本的配置項,在這裏也順便說幾個我常用的 config 的插件,首先是 config 的部分:

  • eslint-config-standard:很有名的配置,它還需要另外安裝 4 個插件
  • eslint-config-prettier:用來關掉排版相關配置項的配置文件,因為要交給 prettier 處理,關掉就不會引發衝突了。

我還沒有列出 standard 所相依的插件:

  • eslint-plugin-simple-import-sort:能夠自動排序 import 的一個插件
  • eslint-plugin-eslint-comments:用來檢查 eslint 的特殊註解的一個插件,eslint 可以用特殊的註解開關規則,這些等下會講到,這個插件的用途是不允許關閉了規則后不再打開,以及關掉所有規則。

把上面的內容都寫到配置文件中應該是這樣:

module.exports = {
  extends: [
    'standard',
    // 加上 prettier 的配置,關掉部份樣式檢查,順序很重要
    'prettier',
    'prettier/standard',
    // 如果是插件提供的配置項需要以 `plugin:` 開始
    'plugin:eslint-comments/recommended',
  ],
  // 額外的規則,這裏也可以決定是否要關掉某些規則
  rules: {
    // 設置 plugin `eslint-plugin-simple-import-sort` 的 `sort` 規則是 `error`,也就是不符合時是會報錯的
    // 另外還可以設置為 `warn` 只警告,或是 `off` 關掉
    // 有的規則也有選項,這是就要用 ['error', {<options>}] 這種像 babel 的格式了
    'simple-import-sort/sort': 'error',
  },
  // 配置指定的環境,這會影響到判斷哪些是全局變量
  env: {
    browser: true
  },
  // 設置 eslint 自己的 parser 用的是哪一版本的 js ,一般設置為 eslint --init 就行了
  parserOptions: {
    ecmaVersion: 12,
  },
}

運作原理

eslint 跟 babel 很相似,都是先把文件轉成 AST,如果想查看 eslint 轉出來的 AST ,可以到 AST Explorer 選擇 espree 解析器,這是 eslint 內置的解析器,它和 babel 的解析器不太一樣,應該說是 babel 的解析器和別人不一樣才對,ECMAScript 定義了一套 js 的 AST 該怎樣定義的規則,是 babel 和別人不同,另外 eslint 的解析器需要很詳細的信息,不能只有代碼的同步而已,而這樣才能做好 lint 的工作。

它的運作方式也像 babel 一樣,讓 plugin XML visitor 對特定的節點進行檢查,如果發現有問題就通過它的 API 來報告,也可以通過它的 API 提供修正的程序。

寫一個自己的 eslint 插件

接下來寫一個 eslint 插件,雖說是寫插件,但實際上寫的是 eslint 的規則,假設我們希望 js 的對象是這樣的(比如 vue 的 object):

export default {
  name: 'Foo',

  props: {},

  data: () => ({}),
}

像上面這樣中間都有個空行,可以用兩種很簡單的方法來判斷是不是 vue 的對象:

  • 在 export default 之後
  • 包含在 Vue.extend 中

eslint 的規則大致分為meta 和 create 兩個部分:

  • meta:這個規則的描述,如果這個規則可以被自動修復,也必須要定義在這裏
  • create:建立規則的 AST visitor,規則的檢查是在這裏做的

與 babel 插件很像,第一步是先打開 AST Explorer,選 eslint 用的解析器 espree,這裏要替換的是 ObjectExpress

module.exports = {
  meta: {
    // 可以被修復的規則一定要定義,這裏除了 'whitespace' 外還有 'code' ,不過知識分類上的問題
    // 這裏因為是要加換行,所以選 'whitespace'
    fixable: 'whitespace',
    // 可以定義可能會出現的信息,這就可以進行統一管理。這是 eslint 的推薦做法
    // 你也可以直接把數據寫在 context.report
    messages: {
      requireNewline: 'require newline between',
    },
  },
  create: function (context) {
    return {
      ObjectExpression(node) {
        if (
          // 判斷副節點是否為 export default
          node.parent.type === 'ExportDefaultDeclaration' ||
          // 或父節點是 `Vue.extend`
          (node.parent.type === 'CallExpression' && isVueExtend(node.parent.callee))
        ) {
          // 得到 source code 對象,後面的 fixer 需要用到
          const sourceCode = context.getSourceCode()
          // 用 for 循環把對象的屬性每兩個氛圍一組,檢查中間有沒有加空行
          for (let i = 0; i < node.properties.length - 1; ++i) {
            // 這裏的判斷方法很簡單,上一個屬性結尾的行號必須與下一個屬性結尾的行號相差 2 以上
            // 也就是中間有兩個以上的空行
            if (node.properties[i + 1].loc.start.line - node.properties[i].loc.end.line < 2) {
              context.report({
                // 用預先定義的數據
                messageId: 'requireNewline',
                // 或是你可以直接把數據寫進來
                // message: 'require newline between',
                // 指定出錯的位置,因為是在兩個屬性之間,所以就用上一個的 end 與后一個的 start 來指定
                loc: {
                  start: node.properties[i].loc.end,
                  end: node.properties[i + 1].loc.start,
                },
                // 如果出錯的位置正好是某個 AST 的節點,那也可以傳入節點
                // node: node
                fix(fixer) {
                  // 這裡是自動修復的部分稍後再加上
                },
              })
            }
          }
        }
      },
    }
  },
}

正常來說 eslint 的插件需要照着 eslint 的命名規則才能加載,不過為了方便測試,就直接調用 eslint 的函數把自定義的規則加入:

// eslint 的 Linter
const { Linter } = require('eslint')
// 我們要定義的規則
const rule = require('./space-between-properties')

const linter = new Linter()

// 為規則設定一個 id
const id = 'space-between-properties'
// 加入規則定義,也就是上面的那個東西
linter.defineRule(id, rule)

// 執行規則
const res = linter.verify(
  ` export default { name: 'Foo', props: {}, } `,

  {
    rules: {
      [id]: 'error',
    },
    parserOptions: {
      sourceType: 'module',
      ecmaVersion: 2015,
    },
  }
)

// 如果有錯誤的話就打印出來
if (res.length) {
  console.log(res)
}

不出意外的話應該會看到有內容輸出,接着要加上自動修復的部分:

// 接上面的 fix 部份
fix(fixer) {
  // 取得兩個節點中間的 token
  const tokens = sourceCode.getTokensBetween(node.properties[i], node.properties[i + 1])
  // “通常”中間只會有逗號,所以唯一的節點就是逗號
  const comma = tokens[0]
  // 要求 eslint 在都好後面加上換行
  return fixer.insertTextAfterRange(comma.range, '\n')
}

修復也很簡單,就是在逗號後面加上換行而已,不過上面也特別說了是“通常”,其實這個插件你只要在 , 後面加上註解就會出現問題了

eslint 會在最後一次把修復加上去,然後再跑一次所有規則,如果還是有可以修復的問題就再跑一次,直到沒有可以自動修復的問題為止,所以也不用擔心會破壞其他插件所提供的規則。不過如果出現了規則互相衝突會怎樣呢,如果有興趣的話可以自己來試試。

    站長推薦

    1.雲服務推薦: 國內主流雲服務商,各類雲產品的最新活動,優惠券領取。地址:阿里雲騰訊雲華為雲

    2.廣告聯盟: 整理了目前主流的廣告聯盟平台,如果你有流量,可以作為參考選擇適合你的平台點擊進入

    鏈接: http://www.fly63.com/article/detial/9959