中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

淺談開發eslint規則

發布時間:2020-10-24 19:31:10 來源:腳本之家 閱讀:204 作者:orangexc 欄目:web開發

開發 eslint 規則

前端的日常開發離不開各種 lint 的支持,使用 lint 的一種誤解是:個人能力不足,必須 lint 規范才能寫出規范的代碼,實際上規范的定義主要取決于開源項目作者的習慣,或者公司團隊編碼的習慣,即使兩個前端專家,寫出的代碼規范也會有差別。

今天主題聊聊 eslint,作為最主流的 JavaScript lint 工具深受大家喜愛,而 JSHint 卻逐漸淡出了大家的視線,使用的比較少了

常用的 eslint 擴展有 standard,airbnb 等

剖析 eslint 擴展

擴展無非就作兩個事情

  • 在原有的 eslint 的基礎上配置些 config(具體規則參數,全局變量,運行環境等)
  • 自定義些自己的 rule,以滿足需求

原理就是利用 eslint 的繼承模式,理論上可以無限繼承并覆蓋上一級的規則

第一條不詳細介紹了,eslint 官網說的十分詳細,基本每一條規則都支持自定義參數,覆蓋面也特別廣,基本上所有的語法都有 rule

第二條的自定義 rule 才是本文的重頭戲,因為特殊的業務場景靠 eslint 自身配置已經無法滿足業務需求了,如:

  • eslint-plugin-vue
  • eslint-plugin-react
  • eslint-plugin-jest

一般特殊場景的自定義規則都使用 eslint-plugin-* 的命名,使用時可以方便的寫成

{
 plugins: [
 'vue',
 'react',
 'jest'
 ]
}

當然 eslint-config-* 同理,不過配置時需要寫成

{
 extends: 'standard'
}

下面介紹下開發流程

創建 eslint plugin 工程

官方推薦使用 yeoman 生成項目,感覺生成的項目比較守舊,推薦下習慣我的項目結構

eslint-plugin-skr
 |- __tests__
 | |- rules
 | |- utils
 |
 |- lib
 | |- rules
 | |- utils
 | |- index.js
 |
 |- jest.config.js
 |
 |- package.json
 |
 |- README.md

整體看下來發現多了 jest 配置文件,是的 yeoman 生成的項目默認采用 Mocha 作為測試框架,個人感覺調試起來麻煩,沒有 jest 靈活,vscode 輕松搞定調試

教程一搜一大把哈,給伸手黨一個鏈接debugging-jest-tests

關于 jest 的 config 文件也po出來一下,都是些基本的配置,復雜的用不到,下面會詳細介紹測試部分

module.exports = {
 testEnvironment: 'node',
 roots: ['__tests__'],
 resetModules: true,
 clearMocks: true,
 verbose: true
}

自定義的規則全部在 lib/rules 下面,每條規則單獨一個文件足以

下面一個簡單的例子打通任督二脈

開發一個規則

前期準備

  • 官方開發文檔
  • AST 抽象語法樹

這個官方文檔寫的密密麻麻,好幾十個屬性,其實只是冰山一角,有很多復雜場景需要考慮

有人疑問:一定需要精通 AST?

我的回答是當然不需要,簡單了解便是,最起碼知道解析出來的語法樹大體結構長什么樣子

那就隨便給自己一個命題寫吧!寫個超級簡單的

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({
   message: 'No block comments'
  })
  }
 }
 }
}

具體寫法官方文檔有介紹哈,就不贅述了,例子也十分簡單,調用了環境變量 context 中的方法獲取全部注釋

稍微復雜點的場景

如需要 lint bar 對象中屬性的順序,如下假設一個規則

// good
const bar = {
 meta: {},
 double: num => num * 2
}

// bed
const bar = {
 double: num => num * 2,
 meta: {},
}

這個第一次些會有些蒙,官網沒有提供具體的例子,解決辦法很簡單,推薦一個利器astexplorer

點進去別著急復制代碼查看 AST 結果,首先選擇 espree(eslint 使用的語法解析庫),如下

淺談開發eslint規則

這短短的四行代碼會對應著一個抽象語法樹,如下圖:

淺談開發eslint規則

由于全展開太長了哈,感興趣的自行嘗試,會發現層級嵌套的特別深,找到 bar 的屬性需要 Program.body[0].declarations[0].init.properties

當然不至于每次都從最頂級的 Program 找下來,從上面的例子可以看出 create 方法的 return 返回的是一個 object,里面可以定義很多檢測類型,如官網的例子:

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) {
  // at a ReturnStatement node while going down
  },
  // at a function expression node while going up:
  "FunctionExpression:exit": checkLastSegment,
  "ArrowFunctionExpression:exit": checkLastSegment,
  onCodePathStart: function (codePath, node) {
  // at the start of analyzing a code path
  },
  onCodePathEnd: function(codePath, node) {
  // at the end of analyzing a code path
  }
 }
 }
}

這里可以使用 VariableDeclarator 類型作為檢察目標,從下面的解析樹可以分析出篩選條件

淺談開發eslint規則

VariableDeclarator 對象作為當前的 node

當變量名為 bar ,即 node.id.name === 'bar' ,且值為對象,即 node.init.type === 'ObjectExpression' ,代碼如下:

module.exports = {
 meta: { ... },
 create (context) {
 return {
  VariableDeclarator (node) {
  const isBarObj = node.id.name === 'bar' &&
   node.init.type === 'ObjectExpression'

  if (!isBarObj) return

  // checker
  }
 }
 }
}

就這樣成功取到 bar 對象后就可以檢測屬性的順序了,排序算法一大把,挑一個喜歡的用就行了,這里不啰嗦了,直接上結果:

const ORDER = ['meta', 'double']

function getOrderMap () {
 const orderMap = new Map()

 ORDER.forEach((name, i) => {
 orderMap.set(name, i)
 })

 return orderMap
}

module.exports = {
 create (context) {
 const orderMap = getOrderMap()

 function checkOrder (propertiesNodes) {
  const properties = propertiesNodes
  .filter(property => property.type === 'Property')
  .map(property => property.key)

  properties.forEach((property, i) => {
  const propertiesAbove = properties.slice(0, i)
  const unorderedProperties = propertiesAbove
   .filter(p => orderMap.get(p.name) > orderMap.get(property.name))
   .sort((p1, p2) => orderMap.get(p1.name) > orderMap.get(p2.name))

  const firstUnorderedProperty = unorderedProperties[0]

  if (firstUnorderedProperty) {
   const line = firstUnorderedProperty.loc.start.line

   context.report({
   node: property,
   message: `The "{{name}}" property should be above the "{{firstUnorderedPropertyName}}" property on line {{line}}.`,
   data: {
    name: property.name,
    firstUnorderedPropertyName: firstUnorderedProperty.name,
    line
   }
   })
  }
  })
 }

 return {
  VariableDeclarator (node) {
  const isBarObj = node.id.name === 'bar' &&
   node.init.type === 'ObjectExpression'

  if (!isBarObj) return

  checkOrder(node.init.properties)
  }
 }
 }
}

這里代碼有點多,耐心看完其實挺簡單的,大致解釋下

getOrderMap 方法將數組轉成 Map 類型,方面通過 get 獲取下標,這里也可以處理多緯數組,例如兩個 key 希望在相同的排序等級,不分上下,可以寫成:

const order = [
 'meta'
 ['double', 'treble']
]

function getOrderMap () {
 const orderMap = new Map()

 ORDER.forEach((name, i) => {
 if (Array.isArray(property)) {
  property.forEach(p => orderMap.set(p, i))
 } else {
  orderMap.set(property, i)
 }
 })

 return orderMap
}

這樣 doubletreble 就擁有相同的等級了,方便后面擴展,當然實際情況會有 n 個屬性的排序規則,也可以在這個規則上輕松擴展,內部的 sort 邏輯就不贅述了。

開發就介紹到這里,通過上面安利的在線語法解析工具可以輕松反推出 lint 邏輯。

如果 rule 比較復雜,就需要大量的 utils 支持,不然每個 rule 都會顯得一團糟,比較考驗公共代碼提取的能力

測試

如前面所講建議使用 jest 進行測試,這里的測試和普通的單元測試還不太一樣,eslint 是基于結果的測試,什么意思呢?

lint 只有兩種情況,通過與不通過,只需要把通過和不通過的情況整理成兩個數組,剩下的工作交給 eslint 的 RuleTester 處理就行了

上面的屬性排序 rule,測試如下:

const RuleTester = require('eslint').RuleTester
const rule = require('../../lib/rules/test')

const ruleTester = new RuleTester({
 parserOptions: {
 ecmaVersion: 6
 }
})

ruleTester.run('test rule', rule, {
 valid: [
 `const bar = {
  meta: {},
  double: num => num * 2
 }`
 ],
 invalid: [
 {
  code: `const bar = {
  double: num => num * 2,
  meta: {},
  }`,
  errors: [{
  message: 'The "meta" property should be above the "double" property on line 2.'
  }]
 }
 ]
})

valid 中是希望通過的代碼, invalid 中是不希望通過的代碼和錯誤信息,到這里一個 rule 算是真正完成了。

打包輸出

最后寫好的 rules 需要發一個 npm 包,以便于在項目中使用,這里就不贅述怎么發包了,簡單聊聊怎么優雅的把 rules 導出來。

直接上代碼:

const requireIndex = require('requireindex')

// import all rules in lib/rules
module.exports.rules = requireIndex(`${__dirname}/rules`)

這里使用了三方依賴 requireindex ,對于批量的導出一個文件夾內的所有文件顯得簡潔很多。

當然前提是保證 rules 文件夾下都是 rule 文件,不要把 utils 寫進去哈

總結

行文目的是國內外對于自定義 eslint rule 的相關資源較少,希望分享一些寫自定義規則的經驗。

千萬不要在學習 AST 上浪費時間,不同的庫對 AST 的實現是不同的,下次寫 babel 插件又要學其它的 AST 規則,再次安利一下 AST 神器astexplorer,只要把需要驗證的代碼放到 astexplorer 里跑一遍,然后總結出規律,邏輯其實十分簡單,對 AST 結果進行判斷就行了。

從團隊層面講,希望所有的團隊都有自己的 eslint 規則庫,可以大大降低 code review 的成本,又能保證代碼的一致性,一勞永逸的事情。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

集安市| 永吉县| 东宁县| 徐州市| 大兴区| 成安县| 香港| 汉中市| 麻城市| 青冈县| 高碑店市| 治县。| 赤峰市| 牟定县| 蛟河市| 喜德县| 安徽省| 开远市| 平定县| 崇义县| 无棣县| 江油市| 六枝特区| 黄大仙区| 永和县| 大埔区| 拉孜县| 凤庆县| 渭源县| 怀化市| 望城县| 安达市| 车致| 泸水县| 丰城市| 安阳县| 本溪市| 阿勒泰市| 大竹县| 务川| 菏泽市|