最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 「GoGoCode 实战」一口气学会 30 个 AST 代码替换小诀窍

    正文概述 掘金(阿里妈妈前端快爆)   2021-03-24   868

    自从发了上篇文章《阿里妈妈出的新工具,给批量修改项目代码减轻了痛苦》 之后,我们收到大家在用 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代码转换的专题文章,请持续关注阿里妈妈前端快爆。


    起源地下载网 » 「GoGoCode 实战」一口气学会 30 个 AST 代码替换小诀窍

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元