当我们遇到代码中有大量如下代码,有时候格式化不一样的时候,是非常头痛的,有没有办法处理?
js
let a,b,c,v,d;
var name,age,email, pageSize;
我们希望变成
js
let a;
let b;
let c;
let v;
let d;
var name;
var age;
var email;
var pageSize;
答: 有的
今天我们通过AST
的方式操作一下, 顺便学习一下 babel
操作 AST
思路
- 拿到字符串(可以是当前ts/js中写的测试代码, 也可以是读取到的文件)
- 把字符串js代码或者 ts代码, 转成 AST
- 使用
traverse
遍历 AST, 根据 AST 特性,找到VariableDeclaration
这个钩子,在这里做拦截处理 - 将多个变量声明变成同节点的单个变量声明(具体看下面代码实现), 需要手动构建相同的类型声明,比如之前使用let 或者const定义的, 拆分成多条声明语句,仍旧要保持一样的类型声明。
注意: 我们改变的是写法,并不是对原来代码做破坏性改动
- 使用
@babel/generator
将AST
还原成代码
环境安装
makefile
node: 20.10.0
shell
pnpm init
安装babel相关的核心包
shell
pnpm add @babel/generator @babel/parser @babel/traverse @babel/types -S
- @babel/parser 把字符串解析成 AST
- @babel/traverse 操作/新增/删除/修改 AST节点
- @babel/types 手动构建AST节点
- @babel/generator 把处理完成后的AST节点树,变成代码, 最终是一个字符串
安装ts, 不想使用ts, 可以不装
csharp
pnpm add typescript tslib ts-node -D
- tslib 与 ts-node 搭档, ts-node依赖的
- ts-node 直接运行ts文件
安装类型声明的包
shell
pnpm add @types/node @types/babel-types @types/babel__generator @types/babel__traverse -D
注意:
- 不要把
package.json
中的type
设置为module
, 直接不设置这个字段 - 修改ts.config.json里面的
module: "NodeNext"
, 其他配置可以不动, 保证 ts-node 运行ts文件的时候不会有问题
package.json
json
"dependencies": {
"@babel/generator": "^7.23.6",
"@babel/parser": "^7.23.9",
"@babel/traverse": "^7.23.9",
"@babel/types": "^7.23.9"
},
"devDependencies": {
"@types/babel-types": "^7.0.15",
"@types/babel__core": "^7.20.5",
"@types/babel__generator": "^7.6.8",
"@types/babel__traverse": "^7.20.5",
"@types/node": "^20.11.19",
"ts-node": "^10.9.2",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
}
代码实现
测试代码: demo/05.js
js
let a, b = 1
var c,d
const kk = 2
function test() {
var b,g
return () => {
b = 10
console.log(b);
let n, m
return {
test() {
var L1, L2
}
}
}
}
demo.ts
ts
import generate from '@babel/generator'
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import * as types from '@babel/types'
import fs from 'node:fs'
import path from 'node:path'
const target = path.resolve(__dirname, '../demo/05.js')
const content = fs.readFileSync(target, {
encoding: 'utf-8',
})
let ast = parse(content, {
sourceType: 'module',
})
traverse(ast, {
/**
* 变量声明
* @param path
*/
VariableDeclaration(path) {
let declarations = path.node.declarations
// 只针对 path.node里面 声明有多个的情况处理,已经是单个的,不管
if (declarations.length > 1) {
const list = declarations.slice(1)
const first = declarations.shift()
// ts里面就需要这样做一下类型判断, 判断我们拿出来的节点是一个 类型声明节点
if (types.isVariableDeclarator(first)) {
// 把原来的多个声明改为一个
path.node.declarations = [first]
}
// 注意:
// 这里要考虑2种情况,一是全局环境,也就是global 最外层的声明
// 另一种是函数作用域里面的变量声明要改写
if ((path.parent.type === 'Program' || path.parent.type === 'BlockStatement') && path.parent.body) {
// 逐个遍历,把节点插入到父节点中
list.forEach((item) => {
// 先拿到之前的声明,之前是 let, 改为多行声明仍旧用let,之前为 const,仍旧用const
let kind = path.node.kind
const currentNode = types.variableDeclaration(kind, [
types.variableDeclarator(
// 原来的名字
item.id,
// 原来的值
item.init
),
])
currentNode.declarations = [item]
// 在指定位置插入,不能直接 body.push
// body.splice(pos + 1, 0, currentNode)
path.insertAfter(currentNode)
})
}
}
}
})
console.log('----处理后的结果-----');
console.log(generate(ast).code)
运行:
bash
ts-node demo.ts
结果:
js
let a;
let b = 1;
var c;
var d;
const kk = 2;
function test() {
var b;
var g;
return () => {
b = 10;
console.log(b);
let n;
let m;
return {
test() {
var L1;
var L2;
}
};
};
}
效果杠杠的。
总结
从上面案例中,你可以学习到
- 学习了使用ts-node 运行typescript代码
- 操作代码的AST, 完整思路
- AST还原成代码
- @babel/types 手动构建 AST节点