代码重构利器 —— jscodeshift

Standard Component 项目需要一个基于 AST 的 Javascript Transformer 编写工具,用于从一种类型的组件 transform 到 Standard Component。本来,想用著名的 esprima,来编写相应工具。但后来发现,Facebook 已经开发了 jscodeshift,重造一个轮子明显是多余的。

所以,jscodeshift 是什么鬼?

jscodeshift 是一个 Javscript Codemod 工具,官方对 Codemod 的解释是:

Codemod is a tool/library to assist you with large-scale codebase refactors that can be partially automated but still require human oversight and occasional intervention.

jscodeshift 也是基于 esprima 的,相比 esprima 及 estools 工具集,其通过 path 可以很容易的在 AST 上遍历 node。

OK,前戏不多言,直接上例子,项目主要依赖下面这些库:

简单重构,比如生命周期,初始化完成finished,改名成为了ready

先写好测试用用例:

import test from 'ava'
import jscodeshift from 'jscodeshift'
import testCodemod from '../test.plugin'
import transformer from '../transformer/old-component/test'

const { testChanged, testUnchanged } = testCodemod(jscodeshift, test, transformer)

testChanged(`import Base from 'base';

export default Base.extend({
  finished: () => {
    console.log('ready')
  }
});`, `import Base from 'base';

export default Base.extend({
  ready: () => {
    console.log('ready')
  }
});`)

testUnchanged(`import Base from 'base';

export default Base.extend({
  other: () => {
    console.log('other')
  }
});`)

然后我们将需要修改的代码粘贴进 AST explorer

注意红色框出来的 node,好的开始写 codemod。

function transformer(file, api) {
  const j = api.jscodeshift

  // TODO 等下要写的过滤函数

  // 把 Identifier 节点的 name 从 finished 改成 ready 就行了 
  const replaceFishined = p => {
     Object.assign(p.node, { name: 'ready' })
     return p.node
  }

  return (
    // 读取文件
    j(file.source)
      // 找到 Identifier 节点,且其名字为 finished
      .find(j.Identifier, { name: 'finished' })
      // TODO 要验证一下是不是 Base.extend 里面的
      .replaceWith(replaceFishined)
      .toSource()
  )
}

module.exports = transformer

怎么过滤呢?主要就是通过 path 找到他的父节点,然后逐一判断类型和名字是不是符合预期的。

  • 先确定是不是 Propoerty:
  const isProperty = p => {
    return (
      p.parent.node.type === 'Property' &&
      p.parent.node.key.type === 'Identifier' &&
      p.parent.node.key.name === 'finished'
    )
  }
  • 找到 CallExpression:
  const isArgument = p => {
    if (p.parent.parent.parent.node.type === 'CallExpression') {
      const call = p.parent.parent.parent.node
      return checkCallee(call.callee)
    }
    return false
  }
  • 然后看看 CallExpression是不是 Base.extend:
  const checkCallee = node => {
    const types = (
      node.type === 'MemberExpression' &&
      node.object.type === 'Identifier' &&
      node.property.type === 'Identifier'
    )

    const identifiers = (
      node.object.name === 'Base' &&
      node.property.name === 'extend'
    )

    return types && identifiers
  }

大功告成:

function transformer(file, api) {
  const j = api.jscodeshift

  const isProperty = p => {
    return (
      p.parent.node.type === 'Property' &&
      p.parent.node.key.type === 'Identifier' &&
      p.parent.node.key.name === 'finished'
    )
  }

  const checkCallee = node => {
    const types = (
      node.type === 'MemberExpression' &&
      node.object.type === 'Identifier' &&
      node.property.type === 'Identifier'
    )

    const identifiers = (
      node.object.name === 'Base' &&
      node.property.name === 'extend'
    )

    return types && identifiers
  }

  const isArgument = p => {
    if (p.parent.parent.parent.node.type === 'CallExpression') {
      const call = p.parent.parent.parent.node
      return checkCallee(call.callee)
    }
    return false
  }

  const replaceFishined = p => {
    Object.assign(p.node, { name: 'ready' })
    return p.node
  }

  return (
    j(file.source)
      .find(j.Identifier, { name: 'finished' })
      .filter(isProperty)
      .filter(isArgument)
      .replaceWith(replaceFishined)
      .toSource()
  )
}

module.exports = transformer

测试通过,搞定!

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏偏前端工程师的驿站

JS魔法堂:再识IE的内存泄露

一、前言                               IE6~8除了不遵守W3C标准和各种诡异外,我想最让人诟病的应该是内存泄露的问题了。这阵子...

1925
来自专栏向治洪

MIDlet工作原理

题记 :  现在的J2ME用户已经是日益减少 , 开发也在转型! 无奈之下也不得不写下这系列文章来别了j2me ,也是对过去的一些总结吧!         ...

17010
来自专栏林德熙的博客

WPF 插拔触摸设备触摸失效

最近使用 WPF 程序,在不停插拔触摸设备会让 WPF 程序触摸失效。通过分析 WPF 源代码可以找到 WPF 触摸失效的原因。

821
来自专栏猿人谷

Unix下c程序内存泄露检测工具

Valgrind是一款用于内存调试、内存泄漏检测以及性能分析的软件开发工具。 Valgrind的最初作者是Julian Seward,他于2006年由于在开发V...

1917
来自专栏更流畅、简洁的软件开发方式

从吉日嘎拉那里学到的……

多个列表框联动,不算是啥大问题,但是却挺麻烦,那么怎么才能够尽量方便一点呢?网上搜了一下,没发现太好用的,于是就自己写了一个。基于jQuery,无限级联动,支持...

2036
来自专栏彭湖湾的编程世界

【react】关于react框架使用的一些细节要点的思考

( _(:3 」∠)_给云友们提个建议,无论是API文档还是书籍,一定要多看几遍!特别是隔一段时间后,会有意想不到的收获的) 这篇文章主要是写关于学习react...

1898
来自专栏有趣的django

10.Flask上下文

 应用上下文和请求上下文都是存放在一个‘LocalStack’的栈中,和应用app相关的操作就必须要用到应用上下文,比如通过current_app获取当前的这个...

721
来自专栏软件开发

CSS3与页面布局学习总结(七)——前端预处理技术(Less、Sass、CoffeeScript、TypeScript)

CSS不像其它高级语言一样支持算术运算、变量、流程控制与面向对象特性,所以CSS样式较多时会引起一些问题,如修改复杂,冗余,某些别的语言很简单的功能实现不了等。...

1755
来自专栏对角另一面

读Zepto源码之Form模块

Form 模块处理的是表单提交。表单提交包含两部分,一部分是格式化表单数据,另一部分是触发 submit 事件,提交表单。 读 Zepto 源码系列文章已经放到...

2000
来自专栏水击三千

浅谈JavaScript的事件(事件对象)

  在触发DOM上的某个事件时,会产生一个事件对象event,这个对象中包含这所有与事件有关的信息。包括导致事件的元素、事件的类型和事件的相关信息。例如鼠标操作...

2266

扫码关注云+社区