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

相关文章

来自专栏魂祭心

原 canvas绘制clock

5184
来自专栏张善友的专栏

Miguel de Icaza 细说 Mix 07大会上的Silverlight和DLR

Mono之父Miguel de Icaza 详细报道微软Mix 07大会上的Silverlight和DLR ,上面还谈到了Mono and Silverligh...

3017
来自专栏落花落雨不落叶

canvas画简单电路图

88411
来自专栏Ceph对象存储方案

Luminous版本PG 分布调优

Luminous版本开始新增的balancer模块在PG分布优化方面效果非常明显,操作也非常简便,强烈推荐各位在集群上线之前进行这一操作,能够极大的提升整个集群...

3705
来自专栏转载gongluck的CSDN博客

cocos2dx 打灰机

#include "GamePlane.h" #include "PlaneSprite.h" #include "BulletNode.h" #include...

7356
来自专栏我和未来有约会

Silverlight第三方控件专题

这里我收集整理了目前网上silverlight第三方控件的专题,若果有所遗漏请告知我一下。 名称 简介 截图 telerik 商 RadC...

4425
来自专栏一个爱瞎折腾的程序猿

sqlserver使用存储过程跟踪SQL

USE [master] GO /****** Object: StoredProcedure [dbo].[sp_perfworkload_trace_s...

3010
来自专栏跟着阿笨一起玩NET

c#实现打印功能

3832
来自专栏张善友的专栏

Silverlight + Model-View-ViewModel (MVVM)

     早在2005年,John Gossman写了一篇关于Model-View-ViewModel模式的博文,这种模式被他所在的微软的项目组用来创建Expr...

3328
来自专栏pangguoming

Spring Boot集成JasperReports生成PDF文档

由于工作需要,要实现后端根据模板动态填充数据生成PDF文档,通过技术选型,使用Ireport5.6来设计模板,结合JasperReports5.6工具库来调用渲...

1.4K7

扫码关注云+社区