当前位置: 首页 > article >正文

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;
    },
  };
};

http://www.kler.cn/a/506651.html

相关文章:

  • openssl s_server源码剥离
  • 微信小程序订阅消息提醒-云函数
  • SpringBoot入门实现简单增删改查
  • [UE4图文系列] 5.字符串转中文乱码问题说明
  • 关于在 Kotlin DSL 中,ndk 的配置方式
  • sql_实用查询语句模版
  • Vue 和 uniApp 中 CSS 样式差别
  • Dart语言的多线程编程
  • VUE中css样式scope和deep
  • 创客匠人老蒋:创始人IP如何为传统产业注入新活力?
  • kubernetes V1.32强制删除namespace
  • 什么是CDN,为什么他可以做缓存?
  • easyui datagrid表头和网格错位问题
  • 【0393】Postgres内核 checkpointer process ③ 构建 WAL records 工作缓存区
  • 1月15学习
  • 掌握C语言内存布局:数据存储的智慧之道
  • 1️⃣Java中的集合体系学习汇总(List/Map/Set 详解)
  • Ubuntu 系统支持高刷显示:探索与实践
  • 深入Android架构(从线程到AIDL)_33 JNI开发与NDK 01
  • vscode 极简Linux下 cmake c++开发环境
  • ASP.NET Core - 配置系统之配置添加
  • FIDO2密码钥匙与无密码认证:打造安全便捷的数字世界
  • 建造者模式(或者称为生成器(构建器)模式)
  • Web3如何赋能元宇宙中的数字身份与隐私保护
  • 28、【OS】【Nuttx】最小系统初始化分析(4):定时器(二)
  • word合并