乐趣区

关于javascript:GoGoCode-实战一口气学会-30-个-AST-代码替换小诀窍

自从发了上篇文章《阿里妈妈出的新工具,给批量批改我的项目代码加重了苦楚》之后,咱们收到大家在用 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 变量,将其变量名改为 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 类且有 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…
起源:掘金
著作权归作者所有。商业转载请分割作者取得受权,非商业转载请注明出处。

退出移动版