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

46 阅读2分钟

const root = j(fileInfo.source)

const callExpressions = root.find(j.CallExpression, { callee: { type: 'MemberExpression', object: { type: 'Identifier', name: 'console' }, }, } );

callExpressions.remove();

return root.toSource(); }; 复制代码

这需要大家对 AST 结构比较熟悉,在编写的时候需要对着解析好的节点结构才能缓缓写出,过一段时间再一看,也不会比弯弯绕绕的正则更好理解——大家平时太少接触 AST 了。

试着结合一下

有一次正当我为一个项目 API 大重构发愁,准备人肉爆肝的时候,我旁边的小姐姐实在看不下去了——她的项目比我更早地做了重构,人家不仅没爆肝,还顺手做了个工具 GoGoCode,这个工具借鉴了 jQuery 的两大思想:

选择器链式调用

用这工具去掉 console.log(xxx) ,其实就是一句话的事:

const $ = require('gogocode')

/** 刻意凌乱的代码 **/ const input = console .log(\a, b,c`); `

// 关键代码 const output = $(input).replace('console.log()', '').generate()

console.log(output) 复制代码

它的创新之处就在于,把你输入的 console.log() 解析成一段 AST 节点去源代码里做节点树的匹配,这样自然就没有代码格式的问题了。你输入的代码就相当于 jQuery 里面的选择器,只不过这一次选择的是代码节点。

更多例子

清理 console.log 这个操作还是太简单了,我再举一个栗子!

我们经常使用这样的枚举列表:

const list = [ { text: "A策略", value: 1, tips: "Atip", }, { text: "B策略", value: 2, tips: "Btip", }, { text: "C策略", value: 3, tips: "Ctip", }, ]; 复制代码

突然有一天,为了统一代码里的各种枚举,我们需要把 text 属性更名为 name,把 value 属性更名为 id,这个用正则很难精确匹配容易误伤,操作AST树还有些麻烦,用 GoGoCode 只需要这么替换一下就行了:

const $ = require('gogocode')

const input = `

const list = [ { text: "A策略", value: 1, tips: "Atip", }, { text: "B策略", value: 2, tips: "Btip", }, { text: "C策略", value: 3, tips: "Ctip", }, ];

// ts的类型标记,这种正则替换会被错误替换的,在 gogocode 里就不会 const text: string = '' // 这一段因为没有 value 就不会被选择器匹配到,也不会被错误替换 const cfg = { text: '' }

`

const output = (input2).replace( '{ text: 1,value:1, value: 2, $$$ }', '{ name: 1,id:1, id: $2, $$$ }' ).generate(); 复制代码

其中 $_$1$_$2 相当于正则中的通配符,但是在这里只会匹配代码里有效的 AST 节点,$$$ 则可以匹配剩下的节点,有点像 es6 里的 ... ,这段代码匹配出了 textvalue 这对应的值填给了 nameid,剩下的原封不动放回去。

而下半部分我刻意加了一些「干扰代码」,以往我通过字符串替换 text:name:的土办法遇到这样的就会误伤了,但 GoGoCode 不会。

再看前一段社区里的一个例子

正巧,前一段我在掘金看到了文章 像玩 jQuery 一样玩 AST,里面介绍了一个用 jscodeshift 进行 React jsx 代码转换的例子:

打算对这样一份代码做修改:

  • 从 @alifd/next 导入改成 antd
  • 转译前 改成 转译后
  • Button 中 type 参数转换:normal -> default,medium -> middle
  • Button 中有 text 参数的改成 type="link"
  • Button 中warning 参数的改成 danger

import * as React from 'react'; import styles from './index.module.scss'; import { Button } from "@alifd/next";

const Btn = () => { return (

转译前

Normal Prirmary Secondary

Normal Primary Secondary

Normal

); };

export default Btn; 复制代码

大概是这样:

这种需求其实挺常见的,原文提供了一个 基于 jscodeshift 的实现,深入到了 AST 进行操作,但如果用 GoGoCode 就会直观很多:

// 省略依赖和 input const output = $(input) .replace(import { $$$ } from "@alifd/next", import { $$$ } from "antd") .replace(<h2>转译前</h2>, <h2>转译后</h2>) .replace( <Button type="normal" $$$></Button>, <Button type="default" $$$></Button> ) .replace( <Button size="medium" $$$></Button>, <Button size="middle" $$$></Button> ) .replace(<Button text $$$></Button>, <Button type="link" $$$></Button>) .replace(<Button warning $$$></Button>, <Button danger $$$></Button>) .generate(); 复制代码

相信你不要讲解也能知道这段代码是要做什么了~

开源了,希望能得到大家的反馈

我觉得这个项目挺有趣的,可以说是专门给代码做了一个 replace 程序,小姐姐说我看到得太浅显,其实这个项目不仅仅是 replace 这一招,这个项目支撑了我们几个几万行前端工程的架构升级计划,就算需求更复杂也是有办法搞定的,大家可以关注我们的账号或专栏,后面作者会发表更专业全面的介绍文章。