gogocode

The simplest tool to parse/transform/generate code on ast

Usage no npm install needed!

<script type="module">
  import gogocode from 'https://cdn.skypack.dev/gogocode';
</script>

README

GOGOCODE

全网最简单易上手,可读性最强的 AST 处理工具!

官网:https://gogocode.io

简介:阿里妈妈出的新工具,给批量修改项目代码减轻了痛苦

Install

    npm install gogocode

快速开始

对于下面的代码

const code = `
  const moment = require('moment');
  var a = 1;
  const b = 2;
  function log (x, y = 'World') {
    console.log('a')
    console.log(a, x, y);
  }
`;

创建一个 AST 实例

const $ = require('gogocode');
const AST = $(code);

  • 小明想将 所有的 a 变量名替换为 c,只需要
$(code).replace('a', 'c')

  • 小明改主意了,只想把 var a = 1 里的变量名改为 c,需要两步:

    • 取变量 a 的定义赋值,
    $(code).find('var a = 1');
    
    • a 变量名替换为 c,并输出整体代码
    $(code)
      .find('var a = 1')
      .attr('declarations.0.id.name', 'c')
      .root()
      .generate();
    

这是直接操作AST的方式,有没有更简单的方法呢?有!

$(code).replace(`var a = 1`, `var c = 1`)

replace确实用起来爽,但当你在分析转换代码时遇到replace覆盖不到的场景时,请用GoGoCode提供的其他api来精准操作AST吧!


  • 小明又改主意了,想把所有定义语句的 a 都改成 c,只需要将目标语句改一下写成:
$(code).replace(`var a = $_

	
		
		
		
		
		
		
		
	npm:gogocode | Skypack
	
		
		
		
		, `var c = $_

	
		
		
		
		
		
		
		
	npm:gogocode | Skypack
	
		
		
		
		)

看到这里,你应该已经理解 findreplace 的第一参有点类似‘jquery 选择器’,而这里的选择器是你需要查找的代码片段,无论想要匹配多么复杂的代码都可以匹配到,其中 $_$ 通配符可以匹配任意确定代码,代码选择器及通配符详细介绍 看这里


  • 小明想试试将代码里的 var 改为 letrequire 改为 import,他发现用 GoGoCode 真的可以像字符串的 replace 一样简单!
$(code)
.replace('var $_$1 = $_$2', 'let $_$1 = $_$2');
.replace('const $_$1 = require($_$2)', 'import $_$1 from $_$2')

关于如何书写选择器,以及replace详解,请见GoGoCode详细文档


API

获取节点

所有的节点获取操作都会返回一个新的AST实例,实例中可能包含多个AST节点路径,如find()siblings()等,某些api返回的实例只会存在一个AST节点路径,如next()

AST.find(selector, options)

入参 说明 类型 默认值
selector 代码选择器,可以是代码也可以将代码中的部分内容挖空替换为通配符 string
options ignoreSequence 匹配时是否忽略顺序
忽略顺序的情况:{a:$_$}匹配{b:1, a:2}
需要严格按照顺序匹配的情况:function($_$, b){} 匹配function(a, b){}
boolean false
parseOptions 同构造函数的parseOptions

当selector中存在 $_$ 通配符时,返回的AST实例中存在 match 属性,也就是被 $_$ 匹配到的AST节点,按照$_$紧接着的key做聚合 如:$('const a = { key: 1, value: "gogo" }').find('const $_$1 = $_$2')


下图是选择器通过find匹配到的整句代码对应的AST节点:

下图是是 $_$1$_$2 分别匹配到的节点以及对应的输出

.parent(level)

获取某个父节点

入参 说明 类型 默认值
level 自内向外第n层父元素 number 0

.parents()

获取所有父节

.root()

获取根节点,对于js来说是type为'File'的节点,对于html来说是nodeType为'document'的节点 通常对AST进行操作之后需要获取root元素之后再输出

.siblings()

获取所有兄弟节点

.prev()

获取前一个节点

.prevAll()

获取当前节点之前的同级节点

.next()

获取后一个节点

.nextAll()

获取当前节点之后的同级节点

.each(callback)

以每一个匹配的元素作为上下文来执行一个函数。

入参 说明 类型 默认值
callback 对于每个匹配的元素所要执行的函数
执行函数时,会给函数传递当前节点nodeindex
function

.eq(index)

获取当前链式操作中第N个AST对象

入参 说明 类型 默认值
index 需要获取的AST对象的位置 number 0

操作节点

.attr()

获取或修改AST节点的属性,入参可分三种情况:

  1. 返回属性名称对应的节点或属性值
入参 说明 类型 默认值 举例
attrName ast节点的属性名称,支持多层属性,通过.连接 string declarations
declarations.0.id.name
  1. 修改属性名称对应的节点或属性值
入参 说明 类型 举例
attrName ast节点的属性名称,支持多层属性,通过.连接 string declarations
declarations.0.id.name
attrValue 将第一个入参获取到的节点或属性修改为该入参
注意:字符串不会被解析为ast节点而是直接替换原有属性
node string
  1. 修改多个属性名称对应的节点或属性值
入参 类型 默认值 举例
attrMap attrName string declarations
declarations.0.id.name
attrValue node string
AST.attr('init', initNode)

AST.attr({ 
  init: initNode,
  'program.body.0.params.0.name': 'a'
})

AST.attr('program.body.0.params.0.name')

.has(selector, options)

判断是否有某个子节点,返回值为boolean类型 入参同.find()

.clone()

返回由当前节点深度复制的新节点

.replace(selector, replacer)

在当前节点内部用replacer替换selector匹配到的代码,返回当前节点

入参 解释 类型
selector 代码选择器,可以是代码也可以将代码中的部分内容挖空替换为通配符 string var $_$1 = $_$2
replacer 替换代码,同代码选择器通配符顺序与selector的保持一致
也可以是确定的ast节点
string node
options ignoreSequence 匹配时是否忽略顺序 object
parseOptions 解析入参 object

AST.replace(`Component`, `module.exports = Magix.View.extend`);

AST.replace(
  `export default function calculateData($_$1){$_$2}`, 
  `function calculateData($_$1){$_$2}`
)

AST.replace(
  `navigateToOutside({url: $_$})`, 
  `jm.attachUrlParams($_$)`, 
  options: { ignoreSequence: true }
)

.replaceBy(replacerAST)

用replacerAST替换当前节点,返回新节点

入参 类型
replacerAST AST
node
string

.after(ast)

在当前节点后面插入一个同级别的节点,返回当前节点

入参 类型
ast AST
node
string

.before()

在当前节点前面插入一个同级别的节点,返回当前节点

入参 类型
ast AST
node
string

.append(attr, ast)

在当前节点内部某个数组属性的末尾插入一个子节点,返回当前节点

入参 类型
attr 当前节点的数组属性名称
ast AST
node
string
  • 为什么需要传入attr

因为某些节点中多个属性都为数组,如函数,存在入参params和函数体body两个数组子节点,必须通过attr来判断插入节点的位置

AST
.find('function $_$() {}')
.append('params', 'b')
.prepend('body', 'b = b || 1;')

.prepend()

在当前节点内部某个数组属性的首位插入一个子节点,返回当前节点

.empty()

清空当前节点所有子节点,返回当前节点

.remove()

移除当前节点,返回根节点

.generate()

将AST对象输出为代码