Day 30: 实作个 eslint plugin

这篇的完整的程序可以到 https://github.com/DanSnow/ironman-2020/tree/master/build-tool/packages/eslint-plugin

今天要来写个 eslint plugin ,虽说是要写个 plugin ,不过实际上是写 eslint 的规则,这边用我之前写过的一个规则来示范,有时候会希望当 js 的物件是像这样的时候 (比如是 vue 的 object 时):

export default {
  name: 'Foo',

  props: {},

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

像上面这样中间都有个空行,这边用很简单的方式来判断是不是 vue 的 object ,就两种:

  • export default
  • 包在 Vue.extend

eslint 的规则大致分成两个部份 metacreate:

  • meta: 这个规则的资料,如果这个规则可以被自动修复,也必须要定义在这里
  • create: 建立规则的 AST visitor ,规则的检查是在这边做的

跟之前写 babel 的 plugin 很像,第一步是先打开 AST Explorer ,选 eslint 用的 parser 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 以上
            // 也就是中间有 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 的 plugin 需要照着 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')
}

这边修复也很简单,就是在逗号後加上换行而已,不过上面也特别说了「通常」,其实这个 plugin 你只要在 , 後加上注解就会出问题了

eslint 会在最後一次把修复加上去,然後再跑一次所有规则,如果还是有可以修复的问题就再跑一次,直到没有可以自动修复的问题,所以也不用担心会弄坏其它的 plugin 提供的规则,不过如果有规则互相冲突的话不知道会怎麽样,有兴趣的人可以自己试试

这是这系列主要的技术文章的最後一篇了


<<:  Day 30: 更多的 Vue SSR

>>:  Day 30 Review security tools and features

@Day4 | C# WixToolset + WPF 帅到不行的安装包 [Wix基本架构]

我们先拿Day3的程序码来做介绍 依目前的感觉,我把它分成三大区块 1.注册部分 2.依功能拆分出的...

多容器编排与管理 Docker Compose简介

上篇回顾 设定档格式 YAML Docker太多文章介绍了, 小弟我K8S也没那麽熟稔 就介绍自己熟...

Swift 新手-使用者介面(UX/UI/Core)

什麽是使用者介面? 使用者介面是介於使用者与硬体而设计彼此之间互动沟通相关软件,目的在使得使用者能够...

盘点资通资产 - 资讯及资通系统资产清册

不能掺在一起做成撒尿牛丸吗? 适用人员: 资安人员。 适用法规: 资通安全管理法施行细则第六条。 依...

Day 29 - 在 VyOS 上设定 GRE

那今天,我们来讲一下在 VyOS 上设定 GRE Tunnel 的指令 其实跟昨天的大同小异。 se...