一、AST技术简介
AST (Abstract Syntax Tree),译为抽象语法树,是编译原理中的一个概念,为源代码的抽象语法结构的树状表示,树上的每个节点都表示源代码中的一种结构,这种数据结构可以类别为一个大的 JSON 对象。通过 AST 技术,我们面对的就不再是各种符号混杂空格而成的文本字符串,而是一个严谨规范的 树形结构,我们可以通过对 AST 树节点的一系列操作,借助机器高效且精准地修改代码。 AST 的用途很广, IDE 的语法高亮、代码检查、格式化、压缩、转译等,都需要先将代码转化成 AST 再进行后续的操作, ES5 和 ES6 语法差异,为了向后兼容,在实际应用中需要进行语法的转换,也会用到 AST 。 AST 并不是为了逆向而生,但做逆向学会了AST ,在解混淆时可以如鱼得水.
在编译器和解释器中,AST常用于对源代码进行分析、优化和转换。它可以帮助程序理解代码的结构,进行静态分析、语法检查、类型检查、代码生成等操作。AST还可以用于代码编辑器、IDE和代码重构工具等开发工具中,提供语法高亮、代码导航、自动补全等功能。
AST由语法解析器生成,它将源代码解析为一系列语法节点,并构建节点之间的层次关系。每个节点表示代码中的一个语法元素,如表达式、语句、函数、类等。节点之间的关系通过父子关系和兄弟关系来表示代码的结构。
AST技术在各种编程语言中都有应用,包括C、C++、Java、Python等。它提供了一种抽象的、可靠的方式来处理和分析程序代码,为开发者和工具提供了更多的灵活性和功能性。
AST解析网站:https://astexplorer.net/
二、AST之V8 引擎简介
V8引擎是由Google开发的高性能JavaScript引擎,用于解析、编译和执行JavaScript代码。它最初是为Google Chrome浏览器而开发的,但现在已广泛应用于其他项目和应用中。
以下是V8引擎的一些主要特点和功能:
- 高性能:V8引擎被设计为快速执行JavaScript代码。它使用了许多优化技术,包括即时编译(Just-In-Time Compilation,JIT)、内联缓存、垃圾回收等,以提供高效的执行速度和优化的内存管理。
- 强大的执行环境:V8引擎提供了一个强大的JavaScript执行环境,支持多线程执行、异步编程和高效的内存管理。它具有事件驱动的架构,可以处理大规模的并发请求。``````
- 支持最新的JavaScript标准:V8引擎支持最新的ECMAScript标准(例如ES6、ES7等),并不断更新以支持新的语言特性和语法。这使得开发者可以使用最新的JavaScript语言功能来编写更现代化和高效的代码。
- 跨平台支持:除了Google Chrome浏览器,V8引擎还可以嵌入到其他应用程序和项目中,例如Node.js、Electron等。它支持多种操作系统,包括Windows、MacOS和Linux等。
- 调试和性能分析:V8引擎提供了丰富的调试和性能分析工具,开发者可以使用这些工具来调试和优化JavaScript代码。其中包括Chrome开发者工具和V8性能分析器等。
总体而言,V8引擎是一个高性能、可扩展且功能强大的JavaScript引擎。它在提供快速执行速度和优化内存管理方面表现出色,为开发者提供了强大的JavaScript执行环境和工具。
三、AST之V8 引擎执行JS代码过程
- 解析(Parsing):V8引擎首先会对JavaScript代码进行解析,将源代码转换为抽象语法树(AST)。解析阶段会对代码进行词法分析和语法分析,以确定代码的结构和语法是否正确。
- 优化(Optimization):V8引擎在执行代码之前,会进行一系列的优化操作,以提高代码的执行效率。其中包括基于静态分析的优化,例如内联缓存、内联函数、类型推断等。V8引擎会根据代码的执行情况和上下文信息,动态地进行优化,以生成高效的机器码。
- 编译(Compilation):在优化阶段之后,V8引擎会将优化后的代码编译成机器码。V8引擎使用即时编译(Ju
- 执行(Execution):一旦代码被编译成机器码,V8引擎就可以执行它了。执行阶段涉及将机器码加载到内存中,并按照指令序列执行代码。V8引擎使用一种称为“热点探测”(HotSpot Detection)的技术,通过监测代码的执行频率和热点,以确定哪些代码需要进行进一步的优化。
- 垃圾回收(Garbage Collection):V8引擎还负责管理JavaScript代码中的内存分配和回收。它使用垃圾回收器来检测和回收不再使用的内存,以避免内存泄漏和资源浪费。
这些阶段是在V8引擎内部自动进行的,开发者通常无需干预。V8引擎通过这些阶段的处理,实现了高性能和优化的JavaScript执行,其中,生成 AST 、生成机器码是比较重要的阶段。
四、AST在编译中的位置(编译器转换过程)
- 词法分析
- 这个阶段会将源代码拆成最小的、不可再分的词法单元,称为 token 。比如这行代码 var a =1 ;通常会被分解成 var 、a、=、1、; 这五个词法单元。另外刚才代码中的空格在 JavaScript 中是直接忽略的。
- 比如这句话 “2019年是祖国70周年”,我们可以把这句话拆分成最小单元,即:2019年、是、祖国、70、周年。这就是我们所说的分词,也是最小单元,因为如果我们把它再拆分出去的话,那就没有什么实际意义了。
- Javascript 代码中的语法单元主要包括以下这么几种:
- 关键字:例如 var、let、const
- 标识符:没有被引号括起来的连续字符,可能是一个变量,也可能是 ifr、else、true、false
- 运算符: +、-、*、/
- 数字:像十六进制,十进制,八进制以及科学表达式等语法
- 字符串:因为对计算机而言,字符串的内容会参与计算或显示
- 空格:连续的空格,换行,缩进等
- 注释:行注释或块注释都是一个不可拆分的最小语法单元
- 其他:大括号、小括号、分号、冒号等
- 语法分析
- 这个过程是将词法单元转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树, 这个树被称为抽象语法树
- 上面我们已经得到了我们分词的结果,需要将词汇进行一个立体的组合,确定词语之间的关系,确定词语最终的表达含义。
- 简而言之,语法分析是对分词的语法进行归类和结构化
// 举例
var a = 42;
var b = 5;
function addA(d) {
return a + d;
}
var c = addA(2) + b;
{
"type": "Program",
"start": 0,
"end": 88,
"body": [
{
"type": "VariableDeclaration",
"start": 6,
"end": 17,
"declarations": [
{
"type": "VariableDeclarator",
"start": 10,
"end": 16,
"id": {
"type": "Identifier",
"start": 10,
"end": 11,
"name": "a"
},
"init": {
"type": "Literal",
"start": 14,
"end": 16,
"value": 42,
"raw": "42"
}
}
],
"kind": "var"
},
{
"type": "VariableDeclaration",
"start": 18,
"end": 28,
"declarations": [
{
"type": "VariableDeclarator",
"start": 22,
"end": 27,
"id": {
"type": "Identifier",
"start": 22,
"end": 23,
"name": "b"
},
"init": {
"type": "Literal",
"start": 26,
"end": 27,
"value": 5,
"raw": "5"
}
}
],
"kind": "var"
},
{
"type": "FunctionDeclaration",
"start": 29,
"end": 67,
"id": {
"type": "Identifier",
"start": 38,
"end": 42,
"name": "addA"
},
"expression": false,
"generator": false,
"async": false,
"params": [
{
"type": "Identifier",
"start": 43,
"end": 44,
"name": "d"
}
],
"body": {
"type": "BlockStatement",
"start": 46,
"end": 67,
"body": [
{
"type": "ReturnStatement",
"start": 52,
"end": 65,
"argument": {
"type": "BinaryExpression",
"start": 59,
"end": 64,
"left": {
"type": "Identifier",
"start": 59,
"end": 60,
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 63,
"end": 64,
"name": "d"
}
}
}
]
}
},
{
"type": "VariableDeclaration",
"start": 68,
"end": 88,
"declarations": [
{
"type": "VariableDeclarator",
"start": 72,
"end": 87,
"id": {
"type": "Identifier",
"start": 72,
"end": 73,
"name": "c"
},
"init": {
"type": "BinaryExpression",
"start": 76,
"end": 87,
"left": {
"type": "CallExpression",
"start": 76,
"end": 83,
"callee": {
"type": "Identifier",
"start": 76,
"end": 80,
"name": "addA"
},
"arguments": [
{
"type": "Literal",
"start": 81,
"end": 82,
"value": 2,
"raw": "2"
}
],
"optional": false
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 86,
"end": 87,
"name": "b"
}
}
}
],
"kind": "var"
}
],
"sourceType": "module"
}
- 语义分析:在AST上进行语义分析,检查变量使用是否合法,进行类型检查,生成符号表,检查函数调用等。这是确保源代码语义正确性的重要步骤。
- 代码生成:在AST上应用各种优化技术,并将AST转换为目标代码,可以是机器代码、字节码或其他形式的代码。这是生成可执行代码的最终阶段。
- 链接:如果源代码包含多个文件,链接阶段将这些文件合并成一个可执行文件,解决符号引用等。
- 加载和执行:生成的目标代码或可执行文件被加载到内存中并执行
- 输入(Input):您需要将源代码作为输入,这是解析过程的第一步。
- 词法分析(Lexical Analysis):这一步将源代码拆分成一个个的单词或符号 tokens,这是语法分析的基础。
- 语法分析(syntax analysis):在这一步中,根据编程语言的语法规则,将单词或符号组合成语句,并构建一棵抽象语法树(AST)。
- 代码生成(code generation):在构建好AST之后,将AST转化为目标代码,可以是机器语言或者是另一种编程语言。
五、AST语法学习:AST输出树结构
- type: 表示当前节点的类型,我们常用的类型判断方法,就是判断当前的节点是否为某个类型。
- start: 表示当前节点的起始位。
- end: 表示当前节点的末尾。
- loc : 表示当前节点所在的行列位置,里面也有start与end节点,这里的start与上面的start是不同 的,这里的start是表示节点所在起始的行列位置,而end表示的是节点所在末尾的行列位置。
- errors:是File节点所特有的属性,可以不用理会。
- program:包含整个源代码,不包含注释节点。
- sourceType: 通常用于标识源代码的类型,以告诉解析器或编译器它正在处理的代码是模块代码还是脚本代码(Script, Module)
- body:包含了程序的主体代码,即程序的主要逻辑。
- 语句块:“body” 可能表示一组语句,通常是一个代码块,这些语句按顺序执行。
- 函数体:对于函数或方法定义,“body” 包含了函数的主体代码,即函数内部的语句和逻辑。
- 类定义:对于类定义,“body” 可能包含类的成员,如属性和方法。
- 模块内容:对于模块或文件,“body” 可能包含文件中的顶级语句和声明。
- declarations:通常用于表示变量、常量、函数、类等的声明
- id:是函数,变量,类的名称
- init: 通常代表声明的初始化值
- comments:源代码中所有的注释会在这里显示。
六、AST语法学习:常见节点类型
七、babel 库简介
Babel是一个广泛使用的JavaScript编译器工具,主要功能是将使用了最新JavaScript语言特性的代码转换为向后兼容的代码,以确保代码能够在不同的浏览器和环境中运行。它可以将ES6+、ESNext和其他JS语言扩展转换为ES5或更早版本的JavaScript。这使得开发者可以使用最新的JavaScript语言特性,而无需担心在不支持这些特性的环境中运行代码时出现兼容性问题。
Babel的工作方式是通过使用插件和预设来进行代码转换。插件是用于执行特定转换操作的小型工具,而预设则是一组插件的集合,用于执行一系列转换操作。开发者可以根据自己的需求选择和配置插件和预设,以定制Babel的转换过程,官网:https://www.babeljs.cn/docs/
八、babel 软件包,本课程主要讲与AST有关的工具软件包,其他软件包可通过官网了解
- 工具软件包
- @babel/core :Babel 编译器本身,提供了 babel 的编译 API;
- @babel/parser :将 JavaScript 代码解析成 AST 语法树;
- @babel/traverse :遍历、修改 AST 语法树的各个节点;
- @babel/generator :将 AST 还原成 JavaScript 代码;
- @babel/types :判断、验证节点的类型、构建新 AST 节点等。
- @babel/template:用于生成AST(抽象语法树)节点。
- @babel/code-frame:用于在控制台中生成带有错误源代码上下文的错误信息。
- @babel/runtime:用于在编译过程中将ES6+的语法转换为对应的ES5代码。
- 预设软件包
- @babel/preset-env:用于根据目标环境的配置自动选择需要的转换规则。它根据目标环境的浏览器版本或Node.js版本等信息,确定需要转换的语法和特性,并将代码转换为适合该环境的版本。这使得开发者可以使用最新的JavaScript语言特性,而无需担心兼容性问题。
- @babel/preset-react:用于在React应用中转换JSX语法和其他与React相关的特性。它包含了一系列转换规则,将JSX语法转换为普通的JavaScript代码,以便在不支持JSX的环境中运行。此外,它还处理一些与React开发相关的转换,如React的PropTypes类型检查等。
- @babel/preset-typescript:用于将TypeScript代码转换为JavaScript代码。它包含了一系列转换规则,用于处理TypeScript的类型注解、接口、泛型等特性,并将其转换为普通的JavaScript代码,以便在不支持TypeScript的环境中运行。
- @babel/preset-flow:用于将Flow类型注解转换为JavaScript代码。Flow是JavaScript的静态类型检查工具,它使用特定的注解语法来标记变量的类型。@babel/preset-flow可以将Flow类型注解从代码中移除,并将其转换为普通的JavaScript代码,以便在不支持Flow类型的环境中运行。
- 辅助软件包
- @babel/helper-compilation-targets:用于确定需要转换的JavaScript语法和特性,以适应目标环境的要求。它根据Babel配置中的目标环境信息,例如浏览器版本或Node.js版本,提供一个工具函数来确定需要进行转换的语法和特性。这有助于确保转换的代码在目标环境中正确运行。
- @babel/helper-module-imports:用于在转换过程中处理模块导入语句。它提供了一些工具函数,用于生成模块导入语句的AST节点,并处理模块的命名冲突和重复导入等问题。这有助于确保转换后的代码正确地导入和使用模块。
- @babel/helper-validator-identifier:用于验证标识符(Identifier)的有效性。它提供了一些工具函数,用于检查标识符是否符合JavaScript的命名规范,以及是否与保留关键字冲突。这有助于确保转换后的代码中的标识符是有效和合法的。
- @babel/helper-environment-visitor:用于创建适应不同环境的访问者(visitor)对象。它提供了一些工具函数,用于根据目标环境的配置创建访问者对象,以便在转换过程中根据目标环境的要求应用不同的转换规则。这有助于确保转换的代码在不同的环境中正确地应用转换规则。
- 其他软件包
- @babel/cli:用于在命令行中运行Babel命令。它提供了一些命令,如编译文件、编译目录、监听文件变化等,可以方便地使用Babel进行代码转换和编译。通过@babel/cli,开发者可以在命令行中配置和执行Babel的转换任务。
- @babel/polyfill:用于在目标环境中提供缺失的JavaScript功能和API。它通过修改全局对象和原型链,以及引入一些辅助函数和内置对象的实现,使得开发者可以在目标环境中使用一些新的JavaScript语法和功能。@babel/polyfill可以在应用程序的入口点导入,以确保目标环境具备所需的功能。
- @babel/plugin-transform-runtime:用于将代码中的公共辅助函数提取为独立模块,并通过引入运行时(runtime)来减少重复代码。它通过将Babel转换过程中需要的辅助函数替换为模块导入,以减小转换后的代码体积。这有助于避免在每个文件中重复生成辅助函数的代码,并减少转换后的代码大小。
- @babel/register:用于在Node.js环境中实时编译代码。它通过注册Node.js的模块加载器,使得在运行时动态地将ES6+代码转换为普通的JavaScript代码。这使得开发者可以在Node.js环境中使用最新的JavaScript语法和特性,而无需预先编译代码。
- @babel/standalone:可以直接在浏览器中使用,而无需依赖于构建工具或构建过程。它包含了Babel的核心转换引擎和一些预设和插件,可以将ES6+代码转换为浏览器可执行的JavaScript代码。@babel/standalone可以通过
九、babel 库之 @babel/parser :将 JavaScript 代码解析成 AST 语法树
//安装
npm install @babel/parser --save-dev
- parse.parse(jsCode,options):将源代码解析为完整的 AST(抽象语法树)
- parse.parseExpression(jsCode,options):将源代码解析为单个表达式的 AST
- parse.ParserOptions:解析配置,对应parse.parse、parse.parseExpression中的options
- allowImportExportEverywhere:默认情况下,import 和 export 声明只能出现在程序的顶层。将此选项设置为 true 可以允许它们出现在任何语句允许的位置。
- allowAwaitOutsideFunction:默认情况下,await 关键字只能在异步函数内部使用。将此选项设置为 true 可以接受在非异步函数中使用 await 的代码。
- allowReturnOutsideFunction:默认情况下,位于顶层的 return 语句会引发错误。将此选项设置为 true 可以接受这样的代码。
- allowNewTargetOutsideFunction:默认情况下,new.target 只能在函数或类内部使用。将此选项设置为 true 可以接受在函数或类外部使用 new.target 的代码。
- allowSuperOutsideMethod:默认情况下,super 关键字只能在类的方法内部使用。将此选项设置为 true 可以接受在方法外部使用 super 的代码。
- allowUndeclaredExports:默认情况下,导出的标识符必须引用一个已声明的变量。将此选项设置为 true 可以允许导出语句引用未声明的变量。
- annexB:默认情况下,Babel 解析器会按照附录 B 的语法解析 JavaScript 代码。将此选项设置为 false 可以禁用此行为。
- attachComment:默认情况下,Babel 将注释附加到相邻的 AST 节点上。将此选项设置为 false 可以禁止附加注释。这可以在代码中有大量注释时提高性能。
- errorRecovery:默认情况下,当解析器遇到无效的代码时会抛出错误。将此选项设置为 true 可以存储解析错误并尝试继续解析无效的输入文件。
- sourceType:指定代码的解析模式。可以是 “script”、“module” 或 “unambiguous”。默认为 “script”。使用 “unambiguous” 时,@babel/parser 会根据代码中是否存在 ES6 的 import 或 export 语句来猜测代码类型。
- sourceFilename:将输出的 AST 节点与源文件名关联起来。在从多个输入文件的 AST 生成代码和源映射时很有用。
- startLine:默认情况下,解析的代码从第一行开始,行号为 1。可以提供一个行号作为起始行,用于与其他源代码工具集成。
- startColumn:默认情况下,解析的代码从第一列开始,列号为 0。可以提供一个列号作为起始列,用于与其他源代码工具集成。
- plugins:一个包含要启用的插件的数组。
- strictMode:默认情况下,解析器在 sourceType 为 “module” 时处于严格模式,否则不启用严格模式。
- ranges:为每个节点添加一个 ranges 属性,表示节点在源代码中的范围(起始位置和结束位置)。
- tokens:将所有解析的标记添加到文件节点的 tokens 属性中。
- createParenthesizedExpressions:默认情况下,解析器通过将 extra.parenthesized 设置为 true 来添加括号的信息。将此选项设置为 true 时,解析器将创建 ParenthesizedExpression AST 节点来表示括号表达式。
- createImportExpressions:默认情况下,解析器将 import(foo) 解析为 CallExpression(Import, [Identifier(foo)])。将此选项设置为 true 可以将其解析为 ImportExpression 节点。
十、babel 库之 @babel/traverse :遍历、修改 AST 语法树的各个节点
//安装
npm install @babel/traverse --save-dev
- traverse(parent, opts = {}, scope, state, parentPath, visitSelf):用于遍历和操作 AST
- parent:AST 的根节点,也就是整个 AST。
- opts:配置对象,用于指定一些选项。
- scope:作用域对象,用于共享作用域。可以将一个作用域对象传递给 scope 参数,以便在遍历过程中共享作用域。
- state:状态对象,用于在遍历过程中传递状态信息。可以将一个状态对象传递给 state 参数,以便在访问者对象的处理函数中使用。
- parentPath:父路径对象,表示当前节点在 AST 中的父节点的路径。可以将一个父路径对象传递给 parentPath 参数,以便在访问者对象的处理函数中使用。
- visitSelf:布尔值,用于指定是否在遍历过程中访问根节点(parent)。默认情况下,根节点会被跳过,不会触发访问者对象的处理函数。将 visitSelf 设置为 true 可以访问根节点。
const parse = require('@babel/parser')
const traverse = require('@babel/traverse').default
// JS 转 ast语法树
jscode = `var a = "hello word";`
let ast = parse.parse(jscode);
traverse(ast, {
//进入节点时执行
enter(path) {},
//离开节点时执行
exit(path) {},
// Literal节点类型,path节点类型的地址
Literal(path) {
console.log(path.node.extra.rawValue)
console.log(path.toString())
}
})
- path属性语法
- path.node:表示当前节点的 AST 节点对象
- path.parent:表示当前节点的父节点的 path 对象。
- path.parentPath:表示当前节点的父节点的 path 对象,与 path.parent 属性相同。
- path.state:状态对象
- path.scope:表示当前节点的作用域对象。
- path.type:表示当前节点的类型。
- path.key:表示当前节点在父节点中的键值。
- path.container:表示包含当前节点的容器对象,例如数组或对象。
- path.listKey:如果当前节点是容器对象的元素,则表示当前节点在容器中的键值。
- path.inList:表示当前节点是否在容器对象中。
- path.toString() :当前路径所对应的源代码
- path.get(key):获取当前节点的子节点中特定键值的 path 对象。
- path.getSibling(key):获取当前节点的兄弟节点中特定键值的 path 对象
- path.getAllNextSiblings():获取当前节点路径之后的所有兄弟节点路径。
- path.getAllPrevSiblings():获取当前节点路径之前的所有兄弟节点路径。
- path.getBindingIdentifierPaths(duplicates, outerOnly):获取一个路径中所有的 BindingIdentifier 节点路径。
- path.getBindingIdentifiers(duplicates):获取一个路径中所有的 BindingIdentifier 节点。
- path.getCompletionRecords():获取一个路径中所有的可完成记录的路径。
- path.getNextSibling():获取当前节点路径的下一个兄弟节点路径。
- path.getOpposite():获取与当前节点路径相反的节点路径。
- path.getPrevSibling():获取当前节点路径的前一个兄弟节点路径。
- path.getOuterBindingIdentifierPaths(duplicates):获取一个路径中所有的 OuterBindingIdentifier 节点路径。
- path.getOuterBindingIdentifiers(duplicates):获取一个路径中所有的 OuterBindingIdentifier 节点。
- path.findParent(callback):在当前节点的父节点链中查找满足给定回调函数条件的最近的父节点的 path 对象。
- path.find(callback):在当前节点及其子节点中查找满足给定回调函数条件的第一个节点的 path 对象。
- path.getFunctionParent():获取当前节点所在的最近的函数父节点的 path 对象。
- path.getStatementParent():获取当前节点所在的最近的语句父节点的 path 对象。
- path.isAncestor(maybeDescendant):判断当前节点是否是给定节点的祖先节点。
- path.isDescendant(maybeAncestor):判断当前节点是否是给定节点的后代节点。
- path.replaceWith(node):用指定的节点 node 替换当前节点。
- path.replaceWithMultiple(nodes):用多个节点替换当前节点。
- path.replaceWithSourceString(source):使用指定的源代码字符串替换当前节点。
- path.remove():从父节点中移除当前节点。
- path.insertBefore(nodes):在当前节点之前插入一个或多个节点。
- path.insertAfter(nodes):在当前节点之后插入一个或多个节点。
- path.isNodeType(type):判断当前节点是否是指定类型 type 的节点。
- path.isXXX():判断当前节点是否属于某个特定类型(例如 path.isIdentifier() 判断当前节点是否为标识符节点)
- path.set(key, node): 用指定的键和节点替换路径(path)上的节点。这将更新路径上的节点,并且会影响到整个 AST。
- path.setData(key, val):在路径(path)上设置一个自定义的数据项。你可以使用这个方法将自定义的数据关联到路径上,以便后续使用。
- path.getData(key, def):获取路径(path)上指定键的自定义数据项。如果找不到指定的键,则返回默认值(def)。
- path.hasNode():检查路径(path)是否有关联的节点。如果路径上有节点,则返回 true,否则返回 false。
- path.getPathLocation():获取路径(path)的位置信息。返回一个包含 start 和 end 属性的对象,表示路径在源代码中的起始位置和结束位置。
- path.debug(message):在控制台输出调试信息。你可以使用这个方法在特定的路径上输出自定义的调试信息,以帮助你理解和调试代码。
- path.getScope(scope):获取路径(path)的作用域对象。可以通过指定作用域的类型(scope)来获取对应类型的作用域对象。如果没有指定作用域类型,则返回最近的作用域对象。
- path.traverse(visitor):用于在当前节点上继续遍历,可以传入一个访问者对象 visitor。
- path.skip():跳过对当前节点的子节点的遍历。
- path.skipKey(key):跳过对当前节点的特定键值的子节点的遍历。
- path.stop():停止遍历,不再继续遍历其他节点。
- path.scope属性语法
- path.scope.block: 获取当前作用域所在的块级作用域的 path 对象。
- path.scope.getBinding(name): 获取指定名称的绑定(变量)的 Binding 对象。可以通过 Binding 对象来访问绑定的属性和方法。
- binding.kind: 表示绑定的类型,可以是 “var”、“let”、“const”、“function”、“module”、“class” 或 “TDZ”(暂时性死区)。
- binding.path: 表示绑定所在的 path 对象。
- binding.identifier: 表示绑定的标识符(Identifier)节点。
- binding.constant: 表示绑定是否为常量。
- binding.references: 表示绑定在作用域中的所有引用的 path 对象的数组。
- binding.referencePaths: 表示绑定在作用域中的所有引用的 path 对象的数组(与 references 相同)。
- binding.constantViolations: 表示违反常量性的路径(path)的数组。如果绑定是常量,但在作用域中存在对该绑定进行重新赋值的路径,那么这些路径将被列为违反常量性的路径。
- binding.referencePaths: 表示绑定在作用域中的所有引用的路径(path)的数组。可以通过遍历这些引用路径来进一步分析和处理绑定的使用情况。
- binding.scope: 表示包含该绑定的作用域的路径(path)。
- binding.constantViolations.length: 表示违反常量性的路径(path)的数量。
- binding.referencePaths.length: 表示绑定在作用域中的引用的数量
- path.scope.hasBinding(name): 检查当前作用域是否存在指定名称的绑定。
- path.scope.hasOwnBinding(name): 检查当前作用域是否有自己的指定名称的绑定,而不是继承自父级作用域。
- path.scope.generateUid(name, options): 生成一个唯一的标识符(UID)。可以指定名称和选项来自定义生成的 UID。
- path.scope.generateUidIdentifier(name, options): 生成一个唯一的标识符(Identifier)节点。可以指定名称和选项来自定义生成的标识符。
- path.scope.getData(key): 获取存储在当前作用域的指定键值(key)下的数据。
- path.scope.setData(key, value): 在当前作用域中存储指定键值(key)的数据。
- path.scope.removeData(key): 从当前作用域中删除指定键值(key)的数据。
- path.scope.getProgramParent(): 获取当前作用域所在的最外层程序(Program)作用域的 path 对象。
- path.scope.getFunctionParent(): 获取当前作用域所在的最近的函数作用域的 path 对象。
- path.scope.getDeepestCommonAncestorFrom(paths): 获取给定多个 path 对象的最深公共祖先作用域的 path 对象。
- path.evaluate():针对作用域和引用,直接依据引用来计算出执行结果。
- path.scope.hasGlobal(name): 检查当前作用域以及所有父级作用域是否存在指定名称的全局变量。
- path.scope.hasReference(name): 检查当前作用域以及所有父级作用域是否存在指定名称的变量引用。
- path.scope.generateUidIdentifierBasedOnNode(node, defaultName?, options?): 基于给定节点生成一个唯一的标识符(Identifier)节点。可以选择性地提供默认名称和选项来自定义生成的标识符。
- path.scope.rename(oldName, newName, block?): 将当前作用域中指定名称的绑定(变量)重命名为新的名称。可以选择性地指定作用域的块级作用域。
- path.scope.dump(): 将当前作用域及其绑定的信息打印到控制台。
- traverse.cheap(node, enter):用于在不创建路径对象的情况下遍历 AST 节点 node,并执行 enter 函数。它主要用于在 AST 遍历过程中执行一些简单的操作,而不需要访问路径对象。
- traverse.node(node, opts, scope, state, path, skipKeys):用于创建一个路径对象,并在遍历 AST 节点 node 时执行相应的处理函数。它是 traverse 函数内部使用的一个辅助函数。一般情况下,你不需要直接调用,而是使用 traverse 函数来遍历 AST。
- traverse.clearNode(node, opts):用于清除 AST 节点 node 中的所有属性。它会将节点的属性设置为 null 或空数组,以清空节点的内容。在某些情况下可能会用到,例如在 AST 转换过程中需要移除节点的内容。
- traverse.removeProperties(tree, opts):用于移除 AST 树 tree 中节点的指定属性。它可以接收一个选项对象 opts,用于指定需要移除的属性列表。在某些情况下可能会用到,例如在 AST 转换过程中需要移除节点的特定属性。
- traverse.hasType(tree, type, denylistTypes):用于检查 AST 树 tree 是否包含指定类型 type 的节点。它可以接收一个 denylistTypes 参数,用于指定需要排除的节点类型列表。在某些情况下可能会用到,例如在 AST 转换过程中需要判断是否存在某种类型的节点。
- traverse.verify(visitor):用于验证访问者对象 visitor 是否符合规范。它会检查访问者对象中的处理函数是否合法,并抛出相应的错误信息。在开发自定义的 Babel 插件时可能会用到,用于验证插件的访问者对象是否正确。
- traverse.explode(visitor):用于将访问者对象 visitor 展开为一个扁平的对象,其中键是节点类型,值是处理函数。它可以方便地将访问者对象转换为一组处理函数,以便在遍历 AST 时执行相应的处理函数。在开发自定义的 Babel 插件时可能会用到,用于处理访问者对象的格式。
- traverse.visitors.explode(visitor):用于将访问者对象 visitor 展开为一个扁平的对象,其中键是节点类型,值是处理函数。它可以方便地将访问者对象转换为一组处理函数,以便在遍历 AST 时执行相应的处理函数。
- traverse.visitors.isExplodedVisitor(visitor):用于检查访问者对象 visitor 是否已经是展开的形式。它会判断访问者对象是否符合展开的格式,如果是则返回 true,否则返回 false。
- traverse.visitors.merge(visitors, states = [], wrapper):用于合并多个访问者对象 visitors。它可以接收一个可选的状态数组 states,用于指定访问者对象的状态。还可以传入一个包装函数 wrapper,用于对每个访问者对象进行包装或修改。合并后的访问者对象将包含所有输入访问者对象的处理函数。
- traverse.visitors.verify(visitor):用于验证访问者对象 visitor 是否符合规范。它会检查访问者对象中的处理函数是否合法,并抛出相应的错误信息。在开发自定义的 Babel 插件时可能会用到,用于验证插件的访问者对象是否正确。
- traverse.cache.clear():清除遍历过程中的取消状态。它会清除所有已设置的取消状态,使得遍历可以继续进行。
- traverse.cache.clearPath():清除路径相关的取消状态。它会清除与路径相关的取消状态,使得遍历可以继续进行。
- traverse.cache.clearScope():清除作用域相关的取消状态。它会清除与作用域相关的取消状态,使得遍历可以继续进行。
- traverse.cache.getCachedPaths(hub, parent):获取给定父节点 parent 的已缓存路径。它会返回一个数组,包含与给定父节点相关的已缓存路径。
- traverse.cache.getOrCreateCachedPaths(hub, parent):获取或创建给定父节点 parent 的已缓存路径。如果已存在与给定父节点相关的已缓存路径,则直接返回;否则,会创建新的已缓存路径并返回。
- traverse.cache.path:表示遍历过程中的路径。它包含了当前遍历节点的信息,如节点类型、父节点、路径等。
- traverse.cache.scope:表示遍历过程中的作用域。它包含了当前遍历节点的作用域信息,如变量、函数等。
十一、babel 库之 @babel/generator :将 AST 还原成 JavaScript 代码
//安装
npm install @babel/generator --save-dev
- generate(ast, opts, code):将 AST(抽象语法树)转换回代码字符串
- ast:要转换为代码的 AST 对象。
- opts:可选参数对象,用于配置代码生成的选项。
- code:可选参数,用于指定原始代码字符串。如果提供了原始代码字符串,则生成的代码将尽可能与原始代码保持一致
const parse = require('@babel/parser')
const generator = require('@babel/generator').default;
const fs = require('fs');
// JS 转 ast语法树
jscode = `var a = "hello word";console.log(a)`
let ast = parse.parse(jscode);
console.log(generator(ast).code)
fs.writeFile('decode.js', generator(ast).code,()=>{});
十二、babel 库之 @babel/types :判断、验证节点的类型、构建新 AST 节点等
大概有上千种方法,只列举部分方法,无论是判断、验证、创建节点存在的AST节点,都有对应的操作,如:声明函数、声明变量、判断变量类型等。
//安装
npm install @babel/types --save-dev
- types.program(body, directives, sourceType, interpreter):创建一个程序节点。
- body:包含程序主体的节点数组。
- directives:包含指令的节点数组。
- sourceType:表示代码源类型的字符串,可以是 “script” 或 “module”。
- interpreter:可选的表示脚本解释器的节点。
- types.identifier(name):创建一个标识符节点,如:var、const、if
- types.blockStatement(body, directives):创建一个块语句节点,等同于 { }
- body:包含块语句主体的节点数组。
- directives:包含指令的节点数组。
- types.expressionStatement(expression):创建一个表达式语句节点,如:变量赋值表达式、加减乘除表达式、函数调用表达式
- types.callExpression(callee, arguments):创建一个函数调用表达式节点,调用函数
- callee:表示被调用函数的节点,通常是一个标识符或成员表达式。
- arguments:包含参数的节点数组。
- types.memberExpression(object, property, computed, optional):创建一个成员表达式节点,用于访问对象的属性或方法,如:console.log
- object:表示对象的节点,可以是一个标识符、成员表达式等。
- property:表示属性的节点,可以是一个标识符、字符串字面量等。
- computed:布尔值,表示属性是否是通过计算得到的。
- optional:布尔值,表示成员表达式是否是可选的。
- types.functionDeclaration(id, params, body, generator, async):创建一个函数声明节点,函数声明
- id:表示函数名的节点,通常是一个标识符。
- params:包含参数的节点数组。
- body:表示函数体的节点,通常是一个块语句。
- generator:布尔值,表示函数是否是生成器函数。
- async:布尔值,表示函数是否是异步函数。
- types.variableDeclaration(kind, declarations) :创建变量声明节点,如:var、let、const
- kind:变量声明的类型,可以是 “var”、“let” 或 “const”。
- declarations:一个包含变量声明器节点的数组,每个声明器节点表示一个变量声明。
- types.variableDeclarator(id, init):创建一个变量声明节点,变量赋值操作
- id:变量声明的标识符,可以是一个 Identifier 或 Pattern。
- init:变量的初始值,可以是一个表达式。
- types.stringLiteral(value):创建一个字符串字面量节点,字符串
- types.numericLiteral(value):创建表示数字字面量的节点的,数字
- types.arrayExpression(elements):创建一个表示数组字面量的节点,数组。
- elements:一个包含数组元素的数组,每个元素可以是一个表达式。
- types.objectExpression(properties):创建一个表示对象字面量的节点,如:json。
- properties:一个包含对象属性的数组,每个属性可以是 ObjectProperty 或 ObjectMethod。
- types.objectProperty(key, value, computed, shorthand, decorators):创建一个对象属性节点,如:json中的键值对。
- key:属性的键,可以是一个 Identifier 或 StringLiteral。
- value:属性的值,可以是一个表达式。
- computed:一个布尔值,指示属性键是否是计算得出的。
- shorthand:一个布尔值,指示属性是否是简写形式。
- decorators:一个包含装饰器的数组,每个装饰器可以是 Decorator。
- types.isFor():判断节点是否是 for 循环语句。该函数接受一个节点对象作为参数,返回一个布尔值,表示节点是否是 for 循环语句。
- types.isArrayExpression(node, opts):判断节点是否是数组表达式。node 是要检查的节点对象,opts 是一个可选的配置对象。
- types.isArrowFunctionExpression(node, opts):判断节点是否是箭头函数表达式。node 是要检查的节点对象,opts 是一个可选的配置对象。
- types.isAssignmentExpression(node, opts):判断节点是否是赋值表达式。node 是要检查的节点对象,opts 是一个可选的配置对象。
- types.isBlock(node, opts):判断节点是否是代码块。node 是要检查的节点对象,opts 是一个可选的配置对象。
- types.isDeclareFunction(node, opts):判断节点是否是声明的函数。node 是要检查的节点对象,opts 是一个可选的配置对象。
- types.isDeclareVariable(node, opts):判断节点是否是声明的变量。node 是要检查的节点对象,opts 是一个可选的配置对象。
- types.isExpression():判断节点是否是表达式。该函数接受一个节点对象作为参数,返回一个布尔值,表示节点是否是表达式。
- type.isNumericLiteral(node, opts):检查给定节点是否为数字。
- node:要检查的节点。
- opts:可选参数,用于配置检查行为。
- type.isStringLiteral(node,opts):检查给定节点是否为字符串。
- node:要检查的节点。
- opts:可选参数,用于配置检查行为。
- type.isBooleanLiteral(node,opts):检查给定节点是否为不布尔。
- node:要检查的节点。
- opts:可选参数,用于配置检查行为。
- type.isObjectExpression(node,opts):检查给定节点是否为对象。
- node:要检查的节点。
- opts:可选参数,用于配置检查行为。
- type.isFunction(node,opts):检查给定节点是否为函数。
- node:要检查的节点。
- opts:可选参数,用于配置检查行为。
十三、babel 库之 @babel/template:用于生成AST节点
//安装
npm install @babel/template --save-dev
- template.expression.ast(code, options):用于创建一个表达式(Expression)类型的 AST 节点。它接受一个字符串代码片段作为输入,并返回一个对应的表达式节点。
- template.program.ast(body, directives): 用于创建一个程序(Program)类型的 AST 节点。它接受一个由语句(Statement)组成的数组作为主体,并可选地接受指令(Directive)数组。返回一个表示整个程序的 AST 节点。
- template.smart.ast(code, options):与 template.default 类似,但会根据输入代码的内容自动选择适当的节点类型。它接受一个字符串代码片段作为输入,并返回一个对应的智能节点(Node)。
- template.statement.ast(code, options):创建一个语句(Statement)类型的 AST 节点。它接受一个字符串代码片段作为输入,并返回一个对应的语句节点。
- template.statements.ast(codes): 创建多个语句(Statement)类型的 AST 节点。它接受多个字符串代码片段作为输入,并返回一个语句节点数组。
const template = require('@babel/template');
// 创建表达式节点const expressionNode = template.expression.ast('a + b');
console.log('**************template.expression**************','\n',expressionNode);
// 创建智能节点const smartNode = template.smart.ast('x ? y : z');
console.log('**************template.smart**************','\n',smartNode);
// 创建语句节点const statementNode = template.statement.ast('console.log("Hello, world!");');
console.log('**************template.statement**************','\n',statementNode);
// 创建一个表示多个语句的 AST 节点const statements = template.statements.ast(` const a = 1; let b = 2; console.log(a + b);`);
console.log('**************template.statements**************','\n',statements)
// 创建一个表示程序的 AST 节点const program = template.program.ast(` const a = 1; let b = 2; console.log(a + b);`);
console.log('**************template.program**************','\n',program)
十四、babel 库之 @babel/core :Babel 编译器本身,提供了 babel 的编译 API
//安装
npm install @babel/core --save-dev
- core.File(filename: string, options: object):创建一个表示源文件的 File 对象。filename 参数表示文件名,options 参数是可选的配置选项对象。返回创建的 File 对象。
- core.buildExternalHelpers(helpers: string[] | Set, outputType: string):构建 Babel 外部助手函数。helpers 参数是一个字符串数组或字符串集合,表示要构建的外部助手函数的名称。outputType 参数表示输出类型,可以是 ‘global’、‘module’ 或 ‘umd’。返回构建的外部助手函数的字符串代码。
- core.createConfigItem(value: any, options?: object):创建一个配置项 ConfigItem 对象。value 参数表示配置项的值,options 参数是可选的配置选项对象。返回创建的 ConfigItem 对象。
- core.createConfigItemAsync(value: any, options?: object):异步创建一个配置项 ConfigItem 对象。value 参数表示配置项的值,options 参数是可选的配置选项对象。返回一个 Promise,解析为创建的 ConfigItem 对象。
- core.createConfigItemSync(value: any, options?: object):同步创建一个配置项 ConfigItem 对象。value 参数表示配置项的值,options 参数是可选的配置选项对象。返回创建的 ConfigItem 对象。
- core.getEnv(): 获取当前环境的 Babel 配置。返回一个包含当前环境配置的对象。
- core.loadOptions(options: object):加载 Babel 配置选项。options 参数是一个配置选项对象。返回加载后的配置选项对象。
- core.loadOptionsAsync(options: object):异步加载 Babel 配置选项。options 参数是一个配置选项对象。返回一个 Promise,解析为加载后的配置选项对象。
- core.loadOptionsSync(options: object):同步加载 Babel 配置选项。options 参数是一个配置选项对象。返回加载后的配置选项对象。
- core.loadPartialConfig(options: object):加载部分 Babel 配置。options 参数是一个配置选项对象。返回加载的部分配置对象 PartialConfig,如果没有找到配置,则返回 null。
- core.loadPartialConfigAsync(options: object):异步加载部分 Babel 配置。options 参数是一个配置选项对象。返回一个 Promise,解析为加载的部分配置对象 PartialConfig,如果没有找到配置,则解析为 null。
- core.loadPartialConfigSync(options: object):同步加载部分 Babel 配置。options 参数是一个配置选项对象。返回加载的部分配置对象 PartialConfig,如果没有找到配置,则返回 null。
- core.parse(code: string, options?: object):解析代码字符串并生成 AST。code 参数是要解析的代码字符串,options 参数是可选的配置选项对象。返回解析结果对象 ParseResult。
- core.parseAsync(code: string, options?: object):异步解析代码字符串并生成 AST。code 参数是要解析的代码字符串,options 参数是可选的配置选项对象。返回一个 Promise,解析为解析结果对象 ParseResult。
- core.parseSync(code: string, options?: object):同步解析代码字符串并生成 AST。code 参数是要解析的代码字符串,options 参数是可选的配置选项对象。返回解析结果对象 ParseResult。
- core.resolvePlugin(name: string, dirname: string):解析指定插件的名称。name 参数是插件的名称,dirname 参数是插件所在的目录名。返回解析后的插件模块路径,如果未找到插件,则返回 null。
- core.resolvePreset(name: string, dirname: string):解析指定预设的名称。name 参数是预设的名称,dirname 参数是预设所在的目录名。返回解析后的预设模块路径,如果未找到预设,则返回 null。
- core.template(code: string, options?: object, plugins?: string[]):用于创建一个 Template 对象,用于生成代码模板。code 参数是代码模板字符串,options 参数是可选的配置选项对象,plugins 参数是可选的插件数组。返回创建的 Template 对象。
- core.transform(code: string, options?: object):对代码进行转译和转换。code 参数是要转译的代码字符串,options 参数是可选的配置选项对象。返回转译结果对象 TransformResult。
- core.transformAsync(code: string, options?: object):异步对代码进行转译和转换。code 参数是要转译的代码字符串,options 参数是可选的配置选项对象。返回一个 Promise,解析为转译结果对象 TransformResult。
- core.transformFile(filename: string, options?: object):异步转译和转换指定文件。filename 参数是要转译的文件路径,options 参数是可选的配置选项对象。返回一个 Promise,解析为转译结果对象 TransformResult。
- core.transformFileAsync(filename: string, options?: object):异步转译和转换指定文件。filename 参数是要转译的文件路径,options 参数是可选的配置选项对象。返回一个 Promise,解析为转译结果对象 TransformResult。
- core.transformFileSync(filename: string, options?: object):同步转译和转换指定文件。filename 参数是要转译的文件路径,options 参数是可选的配置选项对象。返回转译结果对象 TransformResult。
- core.transformFromAst(ast: Node, code: string, options?: object):从给定的 AST 和代码进行转译和转换。ast 参数是要转译的 AST 节点,code 参数是对应的代码字符串,options 参数是可选的配置选项对象。返回转译结果对象 TransformResult。
- core.transformFromAstAsync(ast: Node, code: string, options?: object):异步从给定的 AST 和代码进行转译和转换。ast 参数是要转译的 AST 节点,code 参数是对应的代码字符串,options 参数是可选的配置选项对象。返回一个 Promise,解析为转译结果对象 TransformResult。
- core.transformFromAstSync(ast: Node, code: string, options?: object):同步从给定的 AST 和代码进行转译和转换。ast 参数是要转译的 AST 节点,code 参数是对应的代码字符串,options 参数是可选的配置选项对象。返回转译结果对象 TransformResult。
- core.transformSync(code: string, options?: object):同步对代码进行转译和转换。code 参数是要转译的代码字符串,options 参数是可选的配置选项对象。返回转译结果对象 TransformResult。
- core.traverse(ast: Node, visitor: object, scope?: Scope, state?: any):遍历给定的 AST,并在每个节点上调用相应的访问者方法。ast 参数是要遍历的 AST 节点,visitor 参数是访问者对象,scope 参数是可选的作用域对象,state 参数是可选的状态对象。
- core.OptionManager(options: object):管理 Babel 的配置选项。options 参数是一个配置选项对象。创建 OptionManager 实例后,可以使用其方法来获取、合并和解析配置选项。
- core.Plugin(name: string, plugin: Function):创建 Babel 插件。name 参数是插件的名称,plugin 参数是插件的实现函数。
- core.tokTypes:包含 Babel 支持的所有词法标记类型(Token Types)的定义。
- core.types:包含 Babel 支持的所有节点类型(Node Types)和类型辅助函数的定义。
十五、案例
- 还原节点
var traverse = require('@babel/traverse').default;
var parse = require('@babel/parser');
var generator = require('@babel/generator').default;
var types = require('@babel/types')
function isNodeLiteral(node) {
if (Array.isArray(node)) {
return node.every(ele => isNodeLiteral(ele));
}
if (types.isLiteral(node)) {
if (node.value == null) {
return false;
}
return true;
}
if (types.isBinaryExpression(node)) {
return isNodeLiteral(node.left) && isNodeLiteral(node.right);
}
if (types.isUnaryExpression(node, {
"operator": "-"
}) || types.isUnaryExpression(node, {
"operator": "+"
})) {
return isNodeLiteral(node.argument);
}
if (types.isObjectExpression(node)) {
let {properties} = node;
if (properties.length == 0) {
return true;
}
return properties.every(property => isNodeLiteral(property));
}
if (types.isArrayExpression(node)) {
let {elements} = node;
if (elements.length == 0) {
return true;
}
return elements.every(element => isNodeLiteral(element));
}
return false;
}
var jsCode = `
var a, b, c, d;
d = "1" + 1;
a = 2 * 6;
c = "hello" + " word";
b = 7;
var e = 4 / 2;
var f = 0x25, g = 0b10001001, h = 0o123456;
var i = "\x68\x65\x6c\x6c\x6f\x2c\x41\x53\x54";
var j = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";
var k = e>0 ? 1 : 2;
var l = '3,4,0,5,1,2'['split'](',');
var m = JSON.parse('{"test":"hello word"}');
function test(a, b) {
return a + b;
}
var n = test(a, b);
var o = test(f, i);
var p = test(c, 10);
var q = test(20, 30);
!(function () {
console.log('自执行方法还原')
})
`
var ast = parse.parse(jsCode);
traverse(ast, {
VariableDeclarator(path) {//合并变量声明与定义:a、b、c、d
let {scope, node, parentPath} = path;
let {id, init} = node;
if (!init) {
let name = id.name;
let nextSiblings = parentPath.getAllNextSiblings();//获取兄弟节点
let total = parentPath.node.declarations.length;//当前行未赋值变量总数量
for (let nu = 0; nu < total; nu++) {
const nextSibling = nextSiblings[nu];
if (nextSibling.isExpressionStatement()) { //是否是表达式与语句
let expression = nextSibling.node.expression;
if (types.isAssignmentExpression(expression)) {//是否是赋值语句
let {left, operator, right} = expression;
if (types.isIdentifier(left, {name: name}) && operator == "=") {//是否可以合并。
path.set("init", right);
nextSibling.remove();
}
}
}
}
}
},
NumericLiteral(path) {//类型编码还原:f、g、h
const {extra, value} = path.node;
if (extra?.raw && /^(0x|0b|0o)+/.test(extra.raw)) {
path.replaceWith(types.valueToNode(value))
}
},
BinaryExpression(path) {//还原变量计算结果:a、b、c、d、e
const {left, operator, right} = path.node; //运算表达式
let calculationResult = undefined;
if (!path.getFunctionParent()) { //判断是否是函数节点
switch (operator) {
case '+':
calculationResult = left.value + right.value;
break;
case '-':
calculationResult = left.value - right.value;
break;
case '*':
calculationResult = left.value * right.value;
break;
case '/':
calculationResult = left.value / right.value;
break;
}
path.replaceWith(types.valueToNode(calculationResult))
}
},
ConditionalExpression(path) {//还原三元表达式:k
const {test, consequent, alternate} = path.node;
if (test && types.isBinaryExpression(test)) {
const {left, operator, right} = test;
const leftValue = path.scope.getBinding(left.name).path.node.init.value;//获取作用域中left.name的值
if (operator == '>') {
let calculationResult = leftValue > right.value ? consequent.value : alternate.value;
path.replaceWith(types.valueToNode(calculationResult))
}
}
},
CallExpression(path) {
const {callee, arguments} = path.node;
const {property, object} = callee;
if (types.isStringLiteral(object) && types.isStringLiteral(property)) { //split还原为数组
let data = object.value
let arg = arguments[0].value;
const fun = property.value;
let calculationResult = types.valueToNode(data[fun](arg));
path.replaceWith(calculationResult);
return
}
if (types.isIdentifier(object) && types.isIdentifier(property) && object.name == 'JSON') { //还原JSON
let arg = arguments[0].value;
if (types.isStringLiteral(arguments[0])) {//还原JSON.parse
let calculationResult = types.valueToNode(JSON.parse(arg));
path.replaceWith(calculationResult);
} else {//还原JSON.stringify
}
}
},
UnaryExpression(path) {//还原自执行方法还原
const {argument} = path.node;
if (types.isFunctionExpression(argument)) {
path.replaceWithMultiple(argument.body.body);
}
},
FunctionDeclaration(path) {//还原函数调用
const {body, id} = path.node;
let bodyLen = body.body.length;
if (types.isReturnStatement(body.body[bodyLen - 1])) { //函数最后是否有return
const binding = path.scope.getBinding(id.name);
// const sourcePath = binding.path;
if (binding?.constant) {// constant 属性表示标识符是否被认为是常量。如果标识符绑定在一个不可修改的值上(如字面量或函数声明),则该属性为 true
if (!binding.referenced && !parentPath.isProgram()) {
// binding.referenced 是一个属性,用于判断绑定对象是否在代码中被引用 引用true
// parentPath.isProgram 方法来检查节点是否为顶级程序节点 顶级程序节点表示整个程序的根节点 最外层
path.remove();
return;
}
let sourceCode = path.toString();
eval(sourceCode);//把函数加载到当前环境
// binding.referencePaths 获取与该函数绑定相关联的所有引用路径。
for (const referPath of binding.referencePaths) {
let {parentPath, node} = referPath;
let arguments = parentPath.node.arguments;
if (arguments.length != 0 && !isNodeLiteral(arguments)) {//判断参数中是不是都是字面量
arguments.forEach(arg => {
if (types.isIdentifier(arg)) {//判断是否是标识符
eval(parentPath.scope.getBinding(arg.name).path.toString()); //把变量加载到当前环境
}
});
}
let value = eval(parentPath.toString());
parentPath.replaceWith(types.valueToNode(value));
}
path.remove();
}
}
}
})
console.log('****************AST转js****************', '\n', generator(ast).code);
- 使用@babel/types创建函数,并写入js对象
const types = require("@babel/types");
const generator = require("@babel/generator").default;
const fs = require("fs");
const type = require("@babel/types");
// 创建函数节点
const functionName = types.identifier("myFunction"); // 方法名
const params = [types.identifier("param1"), types.identifier("param2")]; // 参数列表
const body = types.blockStatement([ //创建块
types.expressionStatement(//创建一个表达式语句节点
types.callExpression( //创建函数表达式
types.memberExpression(//用于创建一个成员表达式节点
types.identifier("console"), //创建表示符
types.identifier("log")
),
[params[0], params[1]]
)
)
]); // 方法体
const functionDeclaration = types.functionDeclaration(functionName, params, body); //创建函数
// 创建一个 const 变量声明
const variableDeclaration = types.variableDeclaration("const", [
types.variableDeclarator(
types.identifier("myVariable"), // 变量名
types.numericLiteral(10) // 变量值
)
]);
// 调用 myFunction
const program = types.program([//创建一个程序节点。程序节点表示整个 JavaScript 代码的顶层节点
functionDeclaration,
variableDeclaration,
types.expressionStatement(
types.callExpression(
functionName,
[types.identifier("myVariable"), types.stringLiteral("value2")] //创建参数
)
)
]);
console.log(generator(program).code)
fs.writeFile('1.js', generator(program).code, () => {
});