自从发了上篇文章《阿里妈妈出的新工具,给批量修改项目代码减轻了痛苦》 之后,我们收到大家在用 GoGoCode 做 AST 代码替换时遇到的各种问题:
我该怎么获取变量?
我该怎么批量替换?
我该怎么插入空格?
我怎么替换完不生效?
我匹配哪里出了问题?
……
于是小姐姐连夜整理了30个代码替换小诀窍,让大家一口气都学会!
学不会也没关系,欢迎加群手把手包教会~
钉钉群:34266233;qq群:735216094
Github:github.com/thx/gogocod… 新项目求 star 支持 o(////▽////)q
官网:gogocode.io
基础类型获取和操作
01.获取变量
// 获取所有变量
$(code)
.find('$_$')
.each(item => {
console.log(item.match)
})
// 变量名list
$(code)
.find('list')
02.修改某个变量值
// 获取fetch变量,将其变量名改为request
const res = $(`const fetch = () => {}; const noChange = 'fetch'`)
.find('fetch')
.each(item => {
item.attr('name', 'request') // 任意的节点属性都可以通过attr来获取或者修改
})
.root()
.generate()
03.获取字符串
// 获取所有字符串
$(code)
.find(`'$_$'`)
.each(item => {
console.log(item.match)
})
// 获取确定的字符串'list/get'
$(code)
.find(`'getList'`)
.each(item => {
console.log(item.node)
})
04.获取赋值语句
// 赋值(Assignment)与定义(Declaration)语句需要区分开
// 获取所有赋值语句
$(code)
.find(`$_$1 = $_$2`)
.each(item => {
console.log(item.match[1])
console.log(item.match[2])
})
// 获取对list的赋值语句
$(code)
.find('list = $_$')
.each(item => {
console.log(item.match[0])
})
// 获取对car对象中的color属性的赋值语句
$(code)
.find('car.color = $_$')
// 获取对任意对象中的color属性的赋值语句
$(code)
.find('$_$1.color = $_$2')
// 获取被[1, 2]赋值的变量名
$(code)
.find('$_$ = [1, 2]')
05.获取定义语句
// 获取所有定义语句,包括变量定义、函数定义
$(code)
.find(`var $_$1 = $_$2`)
// 获取对list的定义语句
$(code)
.find([`var list = $_$`, `let list = $_$`, `const list = $_$`])
06.在某作用域里面获取变量定义
// 找到create函数,在其内部获取type变量定义
$(code)
.find('function() create() {}')
.find('let type = $_$')
// 找到create函数处理之后返回全局获取type变量定义或进行其他操作
$(code)
.find('function() create() {}')
.each(item => {})
.root() // 回到全局
.find('let type = $_$')
07.获取类定义
// 获取所有类定义
$(code)
.find(`class $_$ {}`)
// 获取Car类的定义
$(code)
.find(`class Car {}`)
// 获取Car类且有color\size属性的类定义
$(code)
.find(`class Car {
color = $_$c
size = $_$s
}`)
.each(item => {
item.match['c']
item.match['s']
})
08.获取ts类型定义
// 获取类型定义为CheckBoxProps的语句
$(code)
.find('CheckBoxProps') // 找到的有可能是变量名,也有可能是类型定义
.each(item => {
if (item.parent().node.type == 'TSTypeReference') {
// 判断其父节点是类型定义TSTypeReference,就找到了
}
})
// 获取类型为CheckBoxProps的变量定义
$(code)
.find('let $_$1:CheckBoxProps = $_$2')
.each(item => {
item.match[1] // 变量名
item.match[2] // 变量值
})
// 获取带有CheckBoxProps类型入参的箭头函数 *
$(code)
.find('($_$: CheckBoxProps) => {}')
.each(item => {
item.match // 入参
item.node // 箭头函数完整节点
})
09.获取条件判断if语句
// 获取所有if条件判断
$(`if (a && 1 || 0) { b } else { c; dosth() }`)
.find(`if ($_$1) { $_$2 } else { $_$3 }`)
.each(item => {
item.node // 整个if语句对应的ast节点
item.match
/** 每个通配符匹配到的内容
a && 1 || 0
b
c; dosth()
**/
})
// 获取判断isShow的条件判断
$(code)
.find('if (isShow) {}')
函数相关的获取和操作
10.获取函数定义
$(code)
.find(`function $_$() {}`)
.each(item => {
item.match[0] // 函数名
item.node // 函数完整节点
})
// 获取箭头函数
$(code)
.find(`() => {}`)
// 获取入参包含type的箭头函数
$(code)
.find(`(type) => {}`)
11.获取函数名确定的函数内容
方法1:利用$_$通配符,AST实例的match属性获取
$(code)
.find(`function greet() { $_$ }`)
.each(item => {
item.match[0][0].value // 函数内容
item.match[0][0].node // 函数内容对应的ast节点
})
方法2:利用attr()获取函数节点的子节点
$(code)
.find(`function greet() {}`)
.each(item => {
$(item.attr('body')).generate() // 函数内容
item.attr('body') // 函数内容对应的ast节点
})
12.获取包含某个语句的函数
// 方法一:选择器获取包含this.requester的函数
$(code)
.find(`function $_$() {
this.requester
}`)
// 方法二:获取所有函数之后判断内部是否包含this.requester
$(code)
.find(`function $_$() { }`)
.each(item => {
if (item.has('this.requester')) {
// 判断节点内部是否包含this.requester
}
})
13.获取函数调用
// 获取所有函数调用
$(code)
.find('$_$()')
.each(item => {
item.match // 函数名
item.node // 函数对应的ast节点
item.attr('arguments') // 调用函数的入参
})
// 获取通过this调用的函数
$(code)
.find('this.$_$()')
// 获取对create函数的调用
$(code)
.find('create()')
14.修改函数入参
alert({
type: 'error',
content: '请填写必填项',
done: () => {}
})
// 将alert函数调用的type、content入参铺平
$(code)
.replace(`alert({ type: $_$1, done: $_$3, content: $_$2})`,
`alert( $_$1, $_$2, $_$3 )`)
// 在alert函数调用的入参最前面插入this
$(code).replace(`alert($$$)`, `alert(this, $$$)`)
15.根据原节点结构构造新节点
// 仍然是将alert函数调用的type、content入参铺平这个例子:
alert({
type: 'error',
content: '请填写必填项',
done: () => {}
})
$(code)
.find(`alert({ type: $_$1, content: $_$2, done: $_$3 })`)
.each(item => {
const typeValue = item.match[1][0].value,
contentValue = item.match[2][0].value,
doneValue = item.match[3][0].value
item.replaceBy($(`
alert( ${typeValue}, ${contentValue}, ${doneValue} )
`))
})
对象及属性相关获取和操作
16.获取对象属性
// 获取对象内部名为greet的函数
$(code)
.find(`greet() {}`)
.each(item => {
item.node // greet方法
item.parent(1).node // greet方法外层对象
})
// 获取对象内部名为greet的属性
$(code)
.find(`greet: $_$`)
17.获取对象
// 获取包含color属性的对象
$(code)
.find(`{ color: $_$ }`)
.each(item => {
item.node // 包含color属性的对象
item.match // color的值
})
// 获取包含color,且color为green的对象
$(code)
.find(`{ color: 'green' }`)
// 获取包含init() {}成员函数的对象
$(code)
.find(`{ init() {} }`)
// 获取名为car的对象
$(code)
.find(`const car = $_$`)
.each(item => {
// item.match是被通配符匹配到的节点
if (item.match[0][0].node.type == 'ObjectExpression') {
// 找到car被赋值的节点,判断是不是对象类型
}
})
18.修改对象中的属性
const code = Page({
onShow() { },
data: { }
})
// 将在Page第一个入参中的onShow函数名修改为render
// 方法1: 其中$$$1、$$$2代表的是rest含义,捕获剩余部分并且不会改变
$(code)
.replace(`Page({
onShow() {
$$$1
},
$$$2
})`, `Page({
render() {
$$$1
},
$$$2
})`)
// 方法2:使用match改变子节点属性
$(code).find(`Page({ $_$() { } })`)
.each(item => {
if (item.match[0][0].value == 'onShow') {
item.match[0][0].node.name = 'render'
}
})
19.有条件的修改对象某个属性值
const map = { input: 'textarea' } // 在map中有映射的才进行修改
const res = $(`
const componentList = [{
index: 1,
component: 'input'
}, {
index: 2,
component: 'radio'
}, {
index: 3,
component: 'checkbox'
}]`)
.replace('component: $_$', (match => {
if (map[match[0][0].value]) {
return `component: ${map[match[0][0].value]}`
} else {
return 'component: $_$'
}
}))
.generate()
20.对象中插入一个新属性
Page({
onShow() { },
data: { }
})
// 在Page第一个入参对象中插入init() { this.data = {} }
方法一:
$(code)
.replace(`Page({
$$$2
})`, `Page({
init() {
this.data = {}
},
$$$2
})`)
// 方法2:使用append
$(code).find(`Page({})`)
.each(item => {
$(item.attr('arguments.0')).append('properties', `init() {}`)
// page的arguments[0]是第一个入参对象,通过attr获取到这个节点之后用$()转为AST实例,
// 就可以链式调用进行后续操作,append第一个参数是第二个参数指定插入的位置
})
.root()
.generate()
import\export相关获取和操作
21.获取import\export语句
// 获取所有import语句
// 包括import x from 'xx' ; import { x } from 'xx' ; import 'xx'
$(code)
.find(`import $_$1 from '$_$2'`)
// 第二个通配符需要使用引号包裹,因为import语句要求source一定是字符串
// 匹配import异步,如import('@source/package/index').then()
$(code)
.find(`import($_$)`)
// 匹配 ExportNameDeclaration 语句
$(code)
.find(`export $_$ from '@path/sth'`)
// 匹配 ExportAllDeclaration 语句
$(code)
.find(`export * from '@path/sth'`)
22.获取import语句并修改source
// 获取模块路径为'bb/bb-plugin'的import语句 并改为'gogocode'
$(code)
.replace(`import $_$ from 'bb/bb-plugin'`, `import $_$ from 'gogocode'`)
// 获取模块路径包含'bb/...'的import语句 并改为bb/gogocode/...
$(code)
.find(`import $_$1 from '$_$2'`)
.each(item => {
const source = item.match[2][0].value;
item.match[2][0].node.value = source.replace('bb/', 'bb/gogocode/');
})
// 源代码:
// import { useContext, userLogger } from '@as/mdw-hk'
// 将代码中由@as/mdw-hk引入的useContext模块名改为useFContext
$(code).replace(`import { useContext, $$$ } from '@as/mdw-hk'`, `import { useFContext, $$$ } from '@as/mdw-hk'`)
// $$$ 表示rest
jsx标签相关操作
23.修改jsx标签名
// jsx内容:
<View>
<View name="1" class="active" />
<View></View>
</View>
// 将View标签名改为div
$(code)
.replace(`<View $$$1>$$$2</View>`,`<div $$$1>$$$2</div>`)
24.修改jsx属性
// jsx内容:
<View>
<View name="1" class="active" />
<View name="1" id="key">text</View>
</View>
// 将View标签的name="1"属性修改为type="input"
$(code)
.replace(`<View name="1" $$$1>$$$2</View>`,`<View type="input" $$$1>$$$2</View>`)
几个完整的case
25.批量转换一个文件夹下所有.js文件
const glob = require('glob');
const $ = require('gogocode');
glob('./code/**/*.js', function (err, files) {
files.forEach(function (file) {
rewrite(file);
})
})
function rewrite(filePath) {
const newCode = $.loadFile(filePath)
.replace(`let $_$ = console.log()`, `let $_$ = void 0`)
.generate()
$.writeFile(newCode, filePath);
}
26.某行代码后面插入一个空行
// 在所有的function定义之后插入一个空行
// 空行在ast中没有对应的节点,可以插入一个带时间戳的字符串,ast输出为字符串之后再全局将时间戳字符串替换为空
const placeholder = `placeholder${+new Date()}`;
$(code)
.find('function $_$() {}')
.after(placeholder)
.root()
.generate()
.replace(new RegExp(placeholder, 'g'), '');
27.删除一个节点
// 找到console.log()节点并删除
方法1:
$(code)
.find('console.log()')
.remove()
方法2:
$(code).replace('console.log()', '')
28.解构赋值转成 es5 写法
// 找到const {a,b = {b1},c = 3} = d; 转为const a = d.a, b = d.b || {b1}, c = d.c || 3;
const res = $(`const {a,b = {b1},c = 3} = d`)
.find('const { $_$1 = $_$2 } = $_$3')
.each(item => {
const keyList = item.match[1].filter((item, i) => i%2 == 0)
const obj = item.match[3][0].value
const newkeyList = keyList.map((key, i) => {
let dec = `${key.value} = ${obj}.${key.value}`
if (item.match[2][i].value != key.value) {
dec += ('||' + item.match[2][i].value)
}
return dec
})
item.replaceBy(`const ${newkeyList.join(', ')}`)
})
.root()
.generate()
29.插入多段代码
$(code)
.find(`function create() {}`)
.each(item => {
$(item.attr('body')).append('body', `
let type = 'success'
console.log('success')
`)
.root()
.generate()
})
30.获取表达式中的变量
$(`(a.b.c && b) || (c && d)`)
.find('$_$')
.each(item => {
if (item.parent().node.type == 'MemberExpression' && item.parent(1).node.type != 'MemberExpression') {
// 输出a.b.c整体 而不是a \ b \ c
console.log(item.parent().generate())
} else if (item.parent().node.type != 'MemberExpression') {
// 输出独立的变量
console.log(item.generate())
}
})
// 输出a.b.c \ b \ c \ d
更多小诀窍会在官网持续补充。如果对于以上case有疑问或者新的问题,请写在评论区,我们会很快回复~
接下来还会发更多AST代码转换的专题文章,请持续关注阿里妈妈前端快爆。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!