AST代码生成
AST代码生成
基础概念
一段代码被执行,首先会被分为一段一段的词法单元。然后会进行语法分析,最后在生成对应的真正可执行的指令。
const name = 'Germey'
语法分析为
const
name
=
Germey
语法分析生成为
{
"type": "Program",
"start": 0,
"end": 21,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 21
}
},
"range": [
0,
21
],
"comments": [],
"sourceType": "module",
"body": [
{
"type": "VariableDeclaration",
"start": 0,
"end": 21,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 21
}
},
"range": [
0,
21
],
"declarations": [
{
"type": "VariableDeclarator",
"start": 6,
"end": 21,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 21
}
},
"range": [
6,
21
],
"id": {
"type": "Identifier",
"start": 6,
"end": 10,
"loc": {
"start": {
"line": 1,
"column": 6
},
"end": {
"line": 1,
"column": 10
}
},
"range": [
6,
10
],
"name": "name"
},
"init": {
"type": "Literal",
"start": 13,
"end": 21,
"loc": {
"start": {
"line": 1,
"column": 13
},
"end": {
"line": 1,
"column": 21
}
},
"range": [
13,
21
],
"value": "Germey",
"raw": "'Germey'"
}
}
],
"kind": "const"
}
]
}
一个js代码被混淆后,改变的只是他的代码呈现。但是他的语法树是不会有太大的一个变换的。那么我们就可以通过语法树去反推出一个易读的代码。
混淆与其反混淆手法
这里我们使用的是@babe工具,来处理AST语法树
babe库
详细使用https://evilrecluse.top/Babel-traverse-api-doc/#
根据官网介绍,它是一个JavaScript 编译器,主要用于将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。
@babel/core:Babel 编译器本身,提供了 babel 的编译 API;@babel/parser:将 JavaScript 代码解析成 AST 语法树;@babel/traverse:遍历、修改 AST 语法树的各个节点;@babel/generator:将 AST 还原成 JavaScript 代码;@babel/types:判断、验证节点的类型、构建新 AST 节点等。
npm install @babel/core --save-dev
节点属性
通用属性
- type: 节点的类型(如
"Identifier"、"Literal"等)。 - start: 节点在源代码中的起始位置(索引)。
- end: 节点在源代码中的结束位置(索引)。
- loc: 包含节点位置信息的对象,包括
start和end,每个都有line和column属性。
特定属性
表示标识符(变量名、函数名等)。
{
type: "Identifier",
name: "x"
}
表示字面量(字符串、数字、布尔值等)
{
type: "Literal",
value: "Hello, World!",
raw: "\"Hello, World!\""
}
表示函数声明
{
type: "FunctionDeclaration",
id: {
type: "Identifier",
name: "myFunction"
},
params: [
{
type: "Identifier",
name: "a"
},
{
type: "Identifier",
name: "b"
}
],
body: {
type: "BlockStatement",
body: [
// 函数体中的语句
]
}
}
{
表示变量声明。
type: "VariableDeclaration",
declarations: [
{
type: "VariableDeclarator",
id: {
type: "Identifier",
name: "x"
},
init: {
type: "Literal",
value: 10,
raw: "10"
}
}
],
kind: "let" // 或 "const", "var"
}
二元表达式(如 `a + b`)
{
type: "BinaryExpression",
operator: "+",
left: {
type: "Identifier",
name: "a"
},
right: {
type: "Identifier",
name: "b"
}
}
表达式语句
{
type: "ExpressionStatement",
expression: {
type: "CallExpression",
callee: {
type: "Identifier",
name: "console.log"
},
arguments: [
{
type: "Literal",
value: "Hello, World!",
raw: "\"Hello, World!\""
}
]
}
}
更多的可以参考官方文档,或者问gpt(官方文档阅读性不太好)
generator与parser(代码与ATS的相互转化)
const code = `var a = !![];
var b = "abc" == "bcd"; var c = (1 << 3) | 2; var d = parseInt("5" + "0");`;
let ast = parse(code);
let ast = parse(jscode);
traverse(节点操作)
节点对象
path对象用于表示 AST 中的一个具体节点,并提供了许多操作方法和上下文信息,使得开发者能够方便地对节点进行访问、修改和操作。node对象是 AST 中一个具体节点的表示,包含了节点的类型、属性和值等信息,用于直接访问和修改节点的内容。scope对象用于描述变量和函数的作用域信息,提供了查询和操作作用域中变量和函数的方法。hub对象在 Babel 插件中提供了一些全局性的工具和信息,通常在一些上下文中使用,如获取文件信息或特定路径的作用域。state对象在 Babel 插件中用于传递和存储插件的全局状态或信息,允许在遍历过程中共享和修改这些信息。opts对象在 Babel 插件中用于接收插件的配置选项,允许插件根据配置选项的不同执行不同的操作或行为。
属性访问
一般是使用.的方式来访问对应的值
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import * as types from "@babel/types";
import _generate from "@babel/generator";
const generate = _generate.default;
var jscode = `
function f(){
var b = 123; a = ['a', 'b'];}`;
const visitor = {
BlockStatement(path)
{
console.log('当前路径 源码:\n', path.toString());
console.log('当前路径 节点:\n', path.node)
console.log('当前路径 父级节点:\n', path.parent);
console.log('当前路径 父级路径:\n', path.parentPath)
console.log('当前路径 类型:\n', path.type)
console.log('当前路径 contexts:\n', path.contexts);
console.log('当前路径 hub:\n', path.hub);
console.log('当前路径 state:\n', path.state);
console.log('当前路径 opts:\n', path.opts)
console.log('当前路径 skipKeys:\n', path.skipKeys)
console.log('当前路径 container:\n', path.container)
console.log('当前路径 key:\n', path.key)
console.log('当前路径 scope:\n', path.scope)
}
}
let ast = parse(jscode);
traverse(ast, visitor);
方法使用
一般是通过get方法去获取对象调用方法
console.log(path.get("test").evaluate());
详细参考https://evilrecluse.top/Babel-traverse-api-doc/#/?id=nodepath-%e5%b1%9e%e6%80%a7
实际案例
说了这么多,可能你还是会说,我丢还是不太会啊,我们还是来几个实际案例来看看。
表达式还原
var a = !![];
var b = "abc" == "bcd";
var c = (1 << 3) | 2;
var d = parseInt("5" + "0");
我们首先要获取到赋值语句的节点。我们问问gpt,或直接查看官方文档可以知道,他们为这4类
UnaryExpression|BinaryExpression|ConditionalExpression|CallExpression"
对应代码就为
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import _generate from "@babel/generator";
const generate = _generate.default;
import * as types from "@babel/types";
const code = `var a = !![];
var b = "abc" == "bcd"; var c = (1 << 3) | 2; var d = parseInt("5" + "0");`;
let ast = parse(code);
traverse(ast, {
"UnaryExpression|BinaryExpression|ConditionalExpression|CallExpression": (
path
) => {
console.log(path.node);
},
});
const { code: output } = generate(ast);
console.log(output);
然后我们要获取到对应的一个值,这里我们使用对应的一个方法
path.evaluate()
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import _generate from "@babel/generator";
const generate = _generate.default;
import * as types from "@babel/types";
const code = `var a = !![];
var b = "abc" == "bcd"; var c = (1 << 3) | 2; var d = parseInt("5" + "0");`;
let ast = parse(code);
traverse(ast, {
"UnaryExpression|BinaryExpression|ConditionalExpression|CallExpression": (
path
) => {
console.log(path.evaluate());
},
});
const { code: output } = generate(ast);
console.log(output);
我们有了值,就要对代码进行替换。我们这里使用types.valueToNode方法,他可以把值替换为ast节点。
import _generate from "@babel/generator";
const generate = _generate.default;
import * as types from "@babel/types";
const code = `var a = !![];
var b = "abc" == "bcd"; var c = (1 << 3) | 2; var d = parseInt("5" + "0");`;
let ast = parse(code);
traverse(ast, {
"UnaryExpression|BinaryExpression|ConditionalExpression|CallExpression": (
path
) => {
console.log(types.valueToNode(path.evaluate()));
},
});
const { code: output } = generate(ast);
console.log(output);
最后判断下值是否正确,并使用path.replaceWith方法进行替换即可。
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import _generate from "@babel/generator";
const generate = _generate.default;
import * as types from "@babel/types";
const code = `var a = !![];
var b = "abc" == "bcd"; var c = (1 << 3) | 2; var d = parseInt("5" + "0");`;
let ast = parse(code);
traverse(ast, {
"UnaryExpression|BinaryExpression|ConditionalExpression|CallExpression": (
path
) => {
const { confident, value } = path.evaluate();
if (value == Infinity || value == -Infinity) return;
confident && path.replaceWith(types.valueToNode(value));
},
});
const { code: output } = generate(ast);
console.log(output);
发现成功反混淆成功
var a = true;
var b = false;
var c = 10;
var d = 50;
这里有个注意的点是我们直接使用
const { confident, value } = path.evaluate();
console.log(confident, value);
我们发现它不只4个值,这里的原因是一行不只一个node,可能一个node是另一个node内(ast语法树,是树的结构啦)。那么我们只要最外面的node的值即可。
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import _generate from "@babel/generator";
const generate = _generate.default;
import * as types from "@babel/types";
const code = `var a = !![];
var b = "abc" == "bcd"; var c = (1 << 3) | 2; var d = parseInt("5" + "0");`;
let ast = parse(code);
traverse(ast, {
"UnaryExpression|BinaryExpression|ConditionalExpression|CallExpression": (
path
) => {
const { confident, value } = path.evaluate();
console.log(confident, value);
console.log(path.node.loc);
},
});
const { code: output } = generate(ast);
console.log(output);
当我们替换最外层,内部的就不会再计算了。这就是替换为什么只有4次的原因。
字符串还原
const strings = ["\x68\x65\x6c\x6c\x6f\x77\x6f","\x72\x6c\x64"]
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import _generate from "@babel/generator";
const generate = _generate.default;
const code =fs.readFileSync("1.js", "utf-8"); //注意要从文件读取,不然会被编译器自动替换了。
let ast = parse(code);
traverse(ast, {
StringLiteral({ node }) {
if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
node.extra.raw = node.extra.rawValue;
}
},
});
const { code: output } = generate(ast);
console.log(output);
我们直接去把他的node对象的rawValue替换为node.extra.raw值,就可以了
无用代码剔除
const _0x16c18d = function () {
if (!![[]]) {
console.log("hello world");
} else {
console.log("this");
console.log("is");
console.log("dead");
console.log("code");
}
};
const _0x1f7292 = function () {
if ("xmv2nOdfy2N".charAt(4) !== String.fromCharCode(110)) {
console.log("this");
console.log("is");
console.log("dead");
console.log("code");
} else {
console.log("nice to meet you");
}
};
_0x16c18d();
_0x1f7292();
即见使用了判断语句的进行干扰。
在 AST 中,IfStatement 节点有以下几个主要属性:
test: 条件表达式。consequent:if分支执行的代码。alternate:else分支执行的代码,如果没有else分支,则这个属性为null。
我们先获取到判断语句,直接搜索下!号位置
····
test: Node {
type: 'UnaryExpression',
start: 40,
end: 46,
loc: [SourceLocation],
operator: '!',
prefix: true,
argument: [Node]
},
····
我们使用
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import * as types from "@babel/types";
import _generate from "@babel/generator";
const generate = _generate.default;
import fs from "fs";
const code = `const _0x16c18d = function () { if (!![[]]) { console.log("hello world"); } else { console.log("this"); console.log("is"); console.log("dead"); console.log("code"); }}; const _0x1f7292 = function () {
if ("xmv2nOdfy2N".charAt(4) !== String.fromCharCode(110))
{ console.log("this"); console.log("is"); console.log("dead"); console.log("code"); }
else { console.log("nice to meet you"); }};_0x16c18d(); _0x1f7292();`;
let ast = parse(code);
traverse(ast, {
IfStatement(path) {
console.log(path.get("test").evaluate());
}
});
const { code: output } = generate(ast);
console.log(output);
去获取对应的值吗,然后去选择用consequent还是alternate替换原有的ast树节点。
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import * as types from "@babel/types";
import _generate from "@babel/generator";
const generate = _generate.default;
import fs from "fs";
const code = `const _0x16c18d = function () {
if (!![[]]) { console.log("hello world"); } else { console.log("this"); console.log("is"); console.log("dead"); console.log("code"); }}; const _0x1f7292 = function () {
if ("xmv2nOdfy2N".charAt(4) !== String.fromCharCode(110)) { console.log("this"); console.log("is"); console.log("dead"); console.log("code"); } else { console.log("nice to meet you"); }};
_0x16c18d(); _0x1f7292();`;
let ast = parse(code);
traverse(ast, {
IfStatement(path) {
let { consequent, alternate } = path.node;
let testPath = path.get("test");
// console.log("test path", testPath);
// console.log(typeof testPath);
const evaluateTest = testPath.evaluateTruthy();
console.log("evaluateTest", evaluateTest);
if (evaluateTest === true) {
if (types.isBlockStatement(consequent)) {
consequent = consequent.body;
}
path.replaceWithMultiple(consequent);
} else if (evaluateTest === false) {
if (alternate != null) {
if (types.isBlockStatement(alternate)) {
alternate = alternate.body;
}
path.replaceWithMultiple(alternate);
} else {
path.remove();
}
}
}
});
const { code: output } = generate(ast);
console.log(output);
反控制流平坦化
const code = `
const s = "3|1|2".split("|");
let x = 0;
while (true) {
switch (s[x++]) {
case "1":
const a = 1;
continue;
case "2":
const b = 3;
continue;
case "3":
const c = 0;
continue;
}
break;
}
`;
我们先查看对应的AST结构
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import * as types from "@babel/types";
import _generate from "@babel/generator";
const generate = _generate.default;
import fs from "fs";
const code = `const s = "3|1|2".split("|"); let x = 0; while (true) {
switch (s[x++]) { case "1": const a = 1;
continue; case "2": const b = 3; continue; case "3": const c = 0; continue; } break;}`;
let ast = parse(code);
traverse(ast, {
WhileStatement(path) {
console.log(path)
},
});
const { code: output } = generate(ast);
console.log(output);
发现东西有点多,搜索WhileStatement字段发现
node: Node {
type: 'WhileStatement',
start: 45,
end: 234,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
test: Node {
type: 'BooleanLiteral',
start: 52,
end: 56,
loc: [SourceLocation],
value: true
},
body: Node {
type: 'BlockStatement',
start: 58,
end: 234,
loc: [SourceLocation],
body: [Array],
directives: []
}
},
使用
traverse(ast, {
WhileStatement(path) {
console.log(path.node.body)
},
});
ast树
Node {
type: 'BlockStatement',
start: 58,
end: 234,
loc: SourceLocation {
start: Position { line: 3, column: 13, index: 58 },
end: Position { line: 5, column: 115, index: 234 },
filename: undefined,
identifierName: undefined
},
body: [
Node {
type: 'SwitchStatement',
start: 64,
end: 225,
loc: [SourceLocation],
discriminant: [Node],
cases: [Array]
},
Node {
type: 'BreakStatement',
start: 227,
end: 233,
loc: [SourceLocation],
label: null
}
],
directives: []
}
我们发现body有两个,我们要的是表达式,那么肯定是第一个(不知道就问gpt分别是什么意思)
WhileStatement(path) {
console.log(path.node.body.body[0])
},
ast树
Node {
type: 'SwitchStatement',
start: 64,
end: 225,
loc: SourceLocation {
start: Position { line: 4, column: 2, index: 64 },
end: Position { line: 5, column: 106, index: 225 },
filename: undefined,
identifierName: undefined
},
discriminant: Node {
type: 'MemberExpression',
start: 72,
end: 78,
loc: SourceLocation {
start: [Position],
end: [Position],
filename: undefined,
identifierName: undefined
},
object: Node {
type: 'Identifier',
start: 72,
end: 73,
loc: [SourceLocation],
name: 's'
},
computed: true,
property: Node {
type: 'UpdateExpression',
start: 74,
end: 77,
loc: [SourceLocation],
operator: '++',
prefix: false,
argument: [Node]
}
},
cases: [
Node {
type: 'SwitchCase',
start: 85,
end: 130,
loc: [SourceLocation],
consequent: [Array],
test: [Node]
},
Node {
type: 'SwitchCase',
start: 134,
end: 176,
loc: [SourceLocation],
consequent: [Array],
test: [Node]
},
Node {
type: 'SwitchCase',
start: 180,
end: 222,
loc: [SourceLocation],
consequent: [Array],
test: [Node]
}
]
}
那么这下我们就成功获取到了对应的表达式的
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import * as types from "@babel/types";
import _generate from "@babel/generator";
const generate = _generate.default;
import fs from "fs";
const code = `const s = "3|1|2".split("|"); let x = 0; while (true) {
switch (s[x++]) { case "1": const a = 1;
continue; case "2": const b = 3; continue; case "3": const c = 0; continue; } break;}`;
let ast = parse(code);
traverse(ast, {
WhileStatement(path) {
console.log(path.node.body.body[0].discriminant)
console.log(path.node.body.body[0].cases)
},
});
const { code: output } = generate(ast);
console.log(output);
那么我们接下来的目标就是求值。首先我们如何获取switch表示式的值,我们使用方法scope.getBinding(arrName)去获取变量绑定的节点
traverse(ast, {
WhileStatement(path) {
//console.log(path.node.body.body[0].discriminant)
//console.log(path.node.body.body[0].cases) console.log(path.scope.getBinding("s").path.node.init)
},
});
这时就要涉及到对函数的调用了
object = init.callee.object; //获取node对象
property = init.callee.property; //获取到node对应的方法名称
let argument = init.arguments[0].value; //获取到node对应的方法的参数
let arrayFlow = object.value[property.name](argument);//通过方法名称获取到方法进行调用。
代码如下
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import * as types from "@babel/types";
import _generate from "@babel/generator";
const generate = _generate.default;
import fs from "fs";
const code = `const s = "3|1|2".split("|"); let x = 0; while (true) {
switch (s[x++]) { case "1": const a = 1; continue; case "2": const b = 3; continue; case "3": const c = 0; continue; } break;}`;
let ast = parse(code);
traverse(ast, {
WhileStatement(path) {
const { node, scope } = path;
const { test, body } = node;
let switchNode = body.body[0];
let { discriminant, cases } = switchNode;
let { object, property } = discriminant;
let arrName = object.name;
let binding = scope.getBinding(arrName);
let { init } = binding.path.node;
object = init.callee.object;
property = init.callee.property;
let argument = init.arguments[0].value;
let arrayFlow = object.value[property.name](argument);
console.log(arrayFlow)
},
});
现在我们有了switch就要去匹配对应的一个case顺序了case的值是test.value属性
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import { parse } from "@babel/parser";
import * as types from "@babel/types";
import _generate from "@babel/generator";
const generate = _generate.default;
import fs from "fs";
const code = `const s = "3|1|2".split("|"); let x = 0; while (true) {
switch (s[x++]) { case "1": const a = 1; continue; case "2": const b = 3; continue; case "3": const c = 0; continue; } break;}`;
let ast = parse(code);
traverse(ast, {
WhileStatement(path) {
const { node, scope } = path;
const { test, body } = node;
let switchNode = body.body[0];
let { discriminant, cases } = switchNode;
//console.log(cases)
let { object, property } = discriminant;
let arrName = object.name;
let binding = scope.getBinding(arrName);
let { init } = binding.path.node;
object = init.callee.object;
property = init.callee.property;
let argument = init.arguments[0].value;
let arrayFlow = object.value[property.name](argument);
//console.log(arrayFlow);
let resultBody = [];
arrayFlow.forEach((index) => {
let switchCase = cases.filter((c) => c.test.value == index)[0];
console.log(switchCase);
});
path.replaceWithMultiple(resultBody);
},
});
这样就把执行顺序给排列清楚了。然后进行AST语法树替换即可,同时把 continue和break去除即可。
const code = `const s = "3|1|2".split("|");
let x = 0;
while (true) {
switch (s[x++]) { case "1": const a = 1; continue; case "2": const b = 3; continue; case "3": const c = 0; continue; } break;}`;
let ast = parse(code);
traverse(ast, {
WhileStatement(path) {
const { node, scope } = path;
const { test, body } = node;
let switchNode = body.body[0];
let { discriminant, cases } = switchNode;
let { object, property } = discriminant;
let arrName = object.name;
let binding = scope.getBinding(arrName);
let { init } = binding.path.node;
object = init.callee.object;
property = init.callee.property;
let argument = init.arguments[0].value;
let arrayFlow = object.value[property.name](argument);
let resultBody = [];
arrayFlow.forEach((index) => {
let switchCase = cases.filter((c) => c.test.value == index)[0];
let caseBody = switchCase.consequent;
if (types.isContinueStatement(caseBody[caseBody.length - 1])) {
caseBody.pop();
}
resultBody = resultBody.concat(caseBody);
});
path.replaceWithMultiple(resultBody);
},
});
const { code: output } = generate(ast);
console.log(output);
整合代码
import _traverse from "@babel/traverse";
const traverse = _traverse.default;
import * as types from "@babel/types";
import _generate from "@babel/generator";
import {parse} from "@babel/parser";
import fs from "fs";
const generate = _generate.default;
const code = fs.readFileSync("1.js", "utf-8");
let ast = parse(code);
function callToStr(path) {
}
traverse(ast, {
"UnaryExpression|BinaryExpression|ConditionalExpression|CallExpression": (
path
) => {
const { confident, value } = path.evaluate();
if (value == Infinity || value == -Infinity) return;
confident && path.replaceWith(types.valueToNode(value));
},
});
let { code: output } = generate(ast);
ast = parse(output);
traverse(ast, {
StringLiteral({ node }) {
if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
node.extra.raw = "\""+node.extra.rawValue+"\"";
}
},
});
output = generate(ast).code;
ast = parse(output);
traverse(ast, {
IfStatement(path) {
let { consequent, alternate } = path.node;
let testPath = path.get("test");
const evaluateTest = testPath.evaluateTruthy();
if (evaluateTest === true) {
if (types.isBlockStatement(consequent)) {
consequent = consequent.body;
}
path.replaceWithMultiple(consequent);
} else if (evaluateTest === false) {
if (alternate != null) {
if (types.isBlockStatement(alternate)) {
alternate = alternate.body;
}
path.replaceWithMultiple(alternate);
} else {
path.remove();
}
}
}
});
output = generate(ast).code;
ast = parse(output);
traverse(ast, {
WhileStatement(path) {
const { node, scope } = path;
const { test, body } = node;
if (!types.isLiteral(test, { value: true })) return;
if (body.body.length != 2) return;
let switchNode = body.body[0],
breakNode = body.body[1];
if (
!types.isSwitchStatement(switchNode) ||
!types.isBreakStatement(breakNode)
) {
return;
}
let { discriminant, cases } = switchNode;
if (!types.isMemberExpression(discriminant)) return;
let { object, property } = discriminant;
if (!types.isIdentifier(object) || !types.isUpdateExpression(property))
return;
let arrName = object.name;
let binding = scope.getBinding(arrName);
if (!binding || !binding.path || !binding.path.isVariableDeclarator())
return;
let arrayFlow = binding.path.get("init").evaluate().value;
let resultBody = [];
arrayFlow.forEach((index) => {
let switchCases = cases.filter(
(switchCase) => switchCase.test.value == index
);
let switchCase = switchCases.length > 0 ? switchCases[0] : undefined;
if (!switchCase) {
return;
}
let caseBody = switchCase.consequent;
if (types.isContinueStatement(caseBody[caseBody.length - 1])) {
caseBody.pop();
}
resultBody = resultBody.concat(caseBody);
});
path.replaceWithMultiple(resultBody);
},
});
output = generate(ast).code;
console.log(output);
参考内容
python3网络爬虫开发实践

浙公网安备 33010602011771号