自从发了上篇文章《阿里妈妈出的新工具,给批量批改我的项目代码加重了苦楚》 之后,咱们收到大家在用 GoGoCode 做 AST 代码替换时遇到的各种问题:
我该怎么获取变量?
我该怎么批量替换?
我该怎么插入空格?
我怎么替换完不失效?
我匹配哪里出了问题?
……
于是小姐姐连夜整顿了30个代码替换小窍门,让大家一口气都学会!
学不会也没关系,欢送加群手把手包教会~
钉钉群:34266233;qq群:735216094
Github:https://github.com/thx/gogocode 新我的项目求 star 反对 o(_////▽////_)q
官网:gogocode.io

根底类型获取和操作

01.获取变量

// 获取所有变量$(code)  .find('$_$')  .each(item => {    console.log(item.match)  })// 变量名list$(code)  .find('list')复制代码

02.批改某个变量值

// 获取fetch变量,将其变量名改为requestconst 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类且有colorsize属性的类定义$(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     // 箭头函数残缺节点  })复制代码

函数相干的获取和操作

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()复制代码

importexport相干获取和操作

21.获取importexport语句

// 获取所有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代码转换的专题文章,请继续关注阿里妈妈前端快爆。

作者:阿里妈妈前端快爆
链接:https://juejin.cn/post/694311...
起源:掘金
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。