AST抽象语法树
一个在线的AST生成网站,支持多种格式
//把下面的代码测试,基本能把AST的结构搞懂了
import _utilsTools from '@/utils/tools'
import {getDate} from '@/utils/tools'
import * as test from "@/utils/tools"
const abc = require("@/utils/tools").default
export const zzh = 2
export default function () {
console.log(a);
return abc
}
let a = "abc"
let fff = a ? "123" : false
const {efg} = require(`abc${a}efg${b}`)
var b = 123
var c = a.b.c.f
const d = null;
const f = {a:1,b:a};
const h = (a,b,c) =>{
let test = a;
let test1 = b;
let total = (test + test1)*c / 4 + 10
}
function lth(a,b,c){
console.log(a,b,c);
}
lth(1,"dfasd",{a:a,b:b,c:123,d:"你好呀"});
一、结构解析
1.最外层结构
最外层的结构没啥好分析的
{
type:"Program", //固定类型
start:0, //整个代码的开始位置
end:399, //结束位置
body:[
//body数组里面的每一项都是一个语句
// ...
],
sourceType:"script"
}
2.body结构
body里面就是每一句语句
body: [
{
type: "ImportDeclaration",
start: 40,
end: 77,
specifiers: [
{
type: "ImportNamespaceSpecifier",
start: 47,
end: 56,
local: {
type: "Identifier",
start: 52,
end: 56,
name: "test",
},
},
],
source: {
type: "Literal",
start: 62,
end: 77,
value: "@/utils/tools",
raw: '"@/utils/tools"',
},
}
],
3.各种type
- ImportDeclaration:import导入语句
import _utilsTools from '@/utils/tools'
- ImportDefaultSpecifier:默认导入
- ImportSpecifier:具名导入
- ImportNamespaceSpecifier:别名导入
- ExportNamedDeclaration:具名导出
export const zzh = 2
- ExportDefaultDeclaration:默认导出
export default function () {
console.log(a);
}
- Literal:基础数据类型
- Identifier:变量标识符
- ObjectExpression:对象
- ArrowFunctionExpression:箭头函数,每个函数有新的作用域,新的body
- BinaryExpression:运算符
- FunctionDeclaration:函数表达式
- ExpressionStatement:函数调用语句
- TemplateLiteral:模板字符串语句
- TemplateElement:模板字符串语句中的字符串
const {efg} = require(`abc${a}`)
- MemberExpression:链式调用
var c = a.b.c.f
- ReturnStatement: return语句
- ConditionalExpression:三目表达式
二、@babel/parser
babel提供的解析器,可以把js字符串解析成AST抽象语法树,具体看官网
import parser from '@babel/parser';
const code = `
function add(a, b) {
return a + b;
}
`;
const ast = parser.parse(code, {
// 可选值:'module'解析为 ECMAScript 模块, 'script'解析为脚本文件, 'unambiguous'根据文件内容自动判断
sourceType: 'module',
plugins: ['jsx', 'typescript'] // 根据需要添加插件
});
console.log(ast);
三、@babel/traverse
babel提供的遍历器,可以帮助我们过滤我们想要的type,具体看官网
// 遍历 AST
traverse.default(ast, {
enter(path) { //在进入某个节点时执行
if (path.isFunctionDeclaration()) {
console.log('Function Declaration:', path.node.id.name);
}
},
exit(path){ //在离开某个节点时执行
//....
},
CallExpression(path){ //只有type为CallExpression走这里,可以写其他类型
//...
}
});
提供的方法
- path.remove():删除当前节点
- path.node.name = ‘sum’ 修改节点
- path.node.id = newIdentifier 插入节点
- path.replaceWith 替换当前节点
- path.insertBefore 在当前节点前插入节点
- path.insertAfter 在当前节点后插入新节点
- path.stop 停止遍历
三、@babel/generator
babel提供的将AST抽象语法树转成js字符串,具体看官网
import parser from '@babel/parser';
import generate from '@babel/generator';
const code = `
function add(a, b) {
return a + b;
}
`;
// 解析代码为 AST
const ast = parser.parse(code, {
sourceType: 'module'
});
// 生成源代码
const output = generate.default(ast, {}, code);
console.log(output.code);
generate
方法接受三个参数:
- AST:要生成源代码的 AST
- options:生成选项
- code:原始源代码(可选,用于生成更准确的代码)
options常用选项
- comments:是否保留注释。
- compact:是否生成紧凑的代码。
- minified:是否生成最小化的代码
- sourceMaps:是否生成源映射。
- auxiliaryCommentBefore:在生成的代码前添加注释
- auxiliaryCommentAfter:在生成的代码后添加注释。
四、@babel/types
babel提供的工具函数,可以生成节点、判断节点类型等等,具体看官网
常用方法
创建节点
- t.identifier(name):创建一个标识符节点。
- t.functionDeclaration(id, params, body):创建一个函数声明节点。
- t.blockStatement(body):创建一个块语句节点。
- t.returnStatement(argument):创建一个返回语句节点。
- t.binaryExpression(operator, left, right):创建一个二元表达式节点。
- t.importDeclaration(specifiers, source)
检查节点类型
- t.isIdentifier(node):检查节点是否为标识符节点。
- isStringLiteral(node) :检查节点是否为字符串节点
- t.isFunctionDeclaration(node):检查节点是否为函数声明节点。
- t.isBlockStatement(node):检查节点是否为块语句节点。
- t.isReturnStatement(node):检查节点是否为返回语句节点。
- t.isBinaryExpression(node):检查节点是否为二元表达式节点。
四、实战
将require导入语句转换成esmodule的import导入语句,用的是vite插件
import { Plugin } from "vite";
import { parse } from "@babel/parser";
import traverse from "@babel/traverse";
import generate from "@babel/generator";
import * as t from "@babel/types";
export default (options: any): Plugin => {
const requireRE = /require\((['"])(\S+)\1\)/g;
return {
name: "plugin-request",
enforce: "post",
transform(code, id, options) {
if (!/\.vue$/.test(id)) {
return;
}
if (!requireRE.test(code)) {
return;
}
let ast = parse(code, { sourceType: "module" });
traverse.default(ast, {
CallExpression(path) {
// 示例 const { formatDate } = require('@/utils/tools')
if (
t.isIdentifier(path.node.callee) &&
path.node.callee.name === "require"
) {
const parentPath = path.parentPath; //获取父节点
//只有一个参数并且是字符串类型才有效
if (
path.node.arguments.length === 1 &&
t.isStringLiteral(path.node.arguments[0])
) {
// 获取到require的参数
let requireArg = path.node.arguments[0].value;
// 创建一个import节点
let importNode;
// 判断是否是具名导入
if (t.isObjectPattern(parentPath.node.id)) {
//具名是
let nameArr = [];
parentPath.node.id.properties.forEach((item) => {
nameArr.push(
t.importSpecifier(
t.identifier(item.key.name), // 导入的名称
t.identifier(item.value.name) // 作为的名称
)
);
});
importNode = t.importDeclaration(
//创建导入节点
nameArr,
t.stringLiteral(requireArg)
);
} else {
// 默认导入
importNode = t.importDeclaration(
[
t.importDefaultSpecifier(
t.identifier(parentPath.node.id.name)
),
],
t.stringLiteral(requireArg)
);
}
/**
* 替换当前节点
* 原语句 const { formatDate } = require('@/utils/tools');
* path是 require('@/utils/tools')
* path.parentPath是 { formatDate } = require('@/utils/tools')
* path.parentPath.parentPath是 const { formatDate } = require('@/utils/tools');
* 所以需要替换path.parentPath.parentPath
*/
parentPath.parentPath.replaceWith(importNode);
}
}
},
});
// 根据新的AST语法树生成为代码
const newCode = generate.default(ast).code;
return newCode;
},
};
};