欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談開發(fā)eslint規(guī)則

 更新時(shí)間:2018年10月01日 11:53:53   作者:orangexc  
這篇文章主要介紹了淺談開發(fā)eslint規(guī)則,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧

開發(fā) eslint 規(guī)則

前端的日常開發(fā)離不開各種 lint 的支持,使用 lint 的一種誤解是:個(gè)人能力不足,必須 lint 規(guī)范才能寫出規(guī)范的代碼,實(shí)際上規(guī)范的定義主要取決于開源項(xiàng)目作者的習(xí)慣,或者公司團(tuán)隊(duì)編碼的習(xí)慣,即使兩個(gè)前端專家,寫出的代碼規(guī)范也會(huì)有差別。

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

常用的 eslint 擴(kuò)展有 standard,airbnb 等

剖析 eslint 擴(kuò)展

擴(kuò)展無非就作兩個(gè)事情

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

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

第一條不詳細(xì)介紹了,eslint 官網(wǎng)說的十分詳細(xì),基本每一條規(guī)則都支持自定義參數(shù),覆蓋面也特別廣,基本上所有的語法都有 rule

第二條的自定義 rule 才是本文的重頭戲,因?yàn)樘厥獾臉I(yè)務(wù)場(chǎng)景靠 eslint 自身配置已經(jīng)無法滿足業(yè)務(wù)需求了,如:

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

一般特殊場(chǎng)景的自定義規(guī)則都使用 eslint-plugin-* 的命名,使用時(shí)可以方便的寫成

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

當(dāng)然 eslint-config-* 同理,不過配置時(shí)需要寫成

{
 extends: 'standard'
}

下面介紹下開發(fā)流程

創(chuàng)建 eslint plugin 工程

官方推薦使用 yeoman 生成項(xiàng)目,感覺生成的項(xiàng)目比較守舊,推薦下習(xí)慣我的項(xiàng)目結(jié)構(gòu)

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

整體看下來發(fā)現(xiàn)多了 jest 配置文件,是的 yeoman 生成的項(xiàng)目默認(rèn)采用 Mocha 作為測(cè)試框架,個(gè)人感覺調(diào)試起來麻煩,沒有 jest 靈活,vscode 輕松搞定調(diào)試

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

關(guān)于 jest 的 config 文件也po出來一下,都是些基本的配置,復(fù)雜的用不到,下面會(huì)詳細(xì)介紹測(cè)試部分

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

自定義的規(guī)則全部在 lib/rules 下面,每條規(guī)則單獨(dú)一個(gè)文件足以

下面一個(gè)簡(jiǎn)單的例子打通任督二脈

開發(fā)一個(gè)規(guī)則

前期準(zhǔn)備

這個(gè)官方文檔寫的密密麻麻,好幾十個(gè)屬性,其實(shí)只是冰山一角,有很多復(fù)雜場(chǎng)景需要考慮

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

我的回答是當(dāng)然不需要,簡(jiǎn)單了解便是,最起碼知道解析出來的語法樹大體結(jié)構(gòu)長(zhǎng)什么樣子

那就隨便給自己一個(gè)命題寫吧!寫個(gè)超級(jí)簡(jiǎn)單的

module.exports = {
 meta: {
 docs: {
  description: '禁止塊級(jí)注釋',
  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'
  })
  }
 }
 }
}

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

稍微復(fù)雜點(diǎn)的場(chǎng)景

如需要 lint bar 對(duì)象中屬性的順序,如下假設(shè)一個(gè)規(guī)則

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

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

這個(gè)第一次些會(huì)有些蒙,官網(wǎng)沒有提供具體的例子,解決辦法很簡(jiǎn)單,推薦一個(gè)利器astexplorer

點(diǎn)進(jìn)去別著急復(fù)制代碼查看 AST 結(jié)果,首先選擇 espree(eslint 使用的語法解析庫),如下

這短短的四行代碼會(huì)對(duì)應(yīng)著一個(gè)抽象語法樹,如下圖:

由于全展開太長(zhǎng)了哈,感興趣的自行嘗試,會(huì)發(fā)現(xiàn)層級(jí)嵌套的特別深,找到 bar 的屬性需要 Program.body[0].declarations[0].init.properties

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

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 類型作為檢察目標(biāo),從下面的解析樹可以分析出篩選條件

VariableDeclarator 對(duì)象作為當(dāng)前的 node

當(dāng)變量名為 bar ,即 node.id.name === 'bar' ,且值為對(duì)象,即 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 對(duì)象后就可以檢測(cè)屬性的順序了,排序算法一大把,挑一個(gè)喜歡的用就行了,這里不啰嗦了,直接上結(jié)果:

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)
  }
 }
 }
}

這里代碼有點(diǎn)多,耐心看完其實(shí)挺簡(jiǎn)單的,大致解釋下

getOrderMap 方法將數(shù)組轉(zhuǎn)成 Map 類型,方面通過 get 獲取下標(biāo),這里也可以處理多緯數(shù)組,例如兩個(gè) key 希望在相同的排序等級(jí),不分上下,可以寫成:

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 就擁有相同的等級(jí)了,方便后面擴(kuò)展,當(dāng)然實(shí)際情況會(huì)有 n 個(gè)屬性的排序規(guī)則,也可以在這個(gè)規(guī)則上輕松擴(kuò)展,內(nèi)部的 sort 邏輯就不贅述了。

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

如果 rule 比較復(fù)雜,就需要大量的 utils 支持,不然每個(gè) rule 都會(huì)顯得一團(tuán)糟,比較考驗(yàn)公共代碼提取的能力

測(cè)試

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

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

上面的屬性排序 rule,測(cè)試如下:

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 中是不希望通過的代碼和錯(cuò)誤信息,到這里一個(gè) rule 算是真正完成了。

打包輸出

最后寫好的 rules 需要發(fā)一個(gè) npm 包,以便于在項(xiàng)目中使用,這里就不贅述怎么發(fā)包了,簡(jiǎn)單聊聊怎么優(yōu)雅的把 rules 導(dǎo)出來。

直接上代碼:

const requireIndex = require('requireindex')

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

這里使用了三方依賴 requireindex ,對(duì)于批量的導(dǎo)出一個(gè)文件夾內(nèi)的所有文件顯得簡(jiǎn)潔很多。

當(dāng)然前提是保證 rules 文件夾下都是 rule 文件,不要把 utils 寫進(jìn)去哈

總結(jié)

行文目的是國(guó)內(nèi)外對(duì)于自定義 eslint rule 的相關(guān)資源較少,希望分享一些寫自定義規(guī)則的經(jīng)驗(yàn)。

千萬不要在學(xué)習(xí) AST 上浪費(fèi)時(shí)間,不同的庫對(duì) AST 的實(shí)現(xiàn)是不同的,下次寫 babel 插件又要學(xué)其它的 AST 規(guī)則,再次安利一下 AST 神器astexplorer,只要把需要驗(yàn)證的代碼放到 astexplorer 里跑一遍,然后總結(jié)出規(guī)律,邏輯其實(shí)十分簡(jiǎn)單,對(duì) AST 結(jié)果進(jìn)行判斷就行了。

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

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論