代码重构利器 —— 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 条评论
登录 后参与评论

相关文章

来自专栏流柯技术学院

python批量下载图片的三种方法

win32com可以获得类似js里面的document对象,但貌似是只读的(文档都没找到)。

26220
来自专栏闻道于事

Java实现word文档在线预览,读取office(word,excel,ppt)文件

11.2K70
来自专栏hbbliyong

WPF备忘录(3)如何从 Datagrid 中获得单元格的内容与 使用值转换器进行绑定数据的转换IValueConverter

一、如何从 Datagrid 中获得单元格的内容    DataGrid 属于一种 ItemsControl, 因此,它有 Items 属性并且用ItemCon...

40470
来自专栏阮一峰的网络日志

Redux 入门教程(一):基本用法

一年半前,我写了《React 入门实例教程》,介绍了 React 的基本用法。 React 只是 DOM 的一个抽象层,并不是 Web 应用的完整解决方案。有两...

48450
来自专栏大内老A

ASP.NET MVC的Razor引擎:RazorView

Razor引擎具有两个核心的类型,一个是表示View本身的类型RazorView,另一个则是获取和创建它的RazorViewEngine,我们将用两篇文章对它们...

24970
来自专栏Flutter入门

Weex是如何在Android客户端上跑起来的

Weex可以通过自己设计的DSL,书写.we文件或者.vue文件来开发界面,整个页面书写分成了3段,template、style、script,借鉴了成熟的MV...

52950
来自专栏GIS讲堂

excel中提取中文拼音

概述:在工作时,有时候会用到汉语拼音,本文讲述如何在Excel中通过vba程序提取汉字的拼音。

28630
来自专栏Java成神之路

Java企业微信开发_07_JSSDK多图上传

 所有的JS接口只能在企业微信应用的可信域名下调用(包括子域名),可在企业微信的管理后台“我的应用”里设置应用可信域名。这个域名必须要通过ICP备案,不然jss...

22420
来自专栏青蛙要fly的专栏

项目需求讨论-Vlayout来快速构建及扩展复杂界面

大家好,今天又带来了项目中具体遇到的需求。做一个首界面,该首界面有很多功能块,同时这些功能块是动态的,因为登录的人的权限的不同,会显示不同的功能块,因为功能模块...

26920
来自专栏琯琯博客

Yii2 学习笔记之助手类

29270

扫码关注云+社区

领取腾讯云代金券