AST简介:原理解析:JS 代码是如何被浏览器引擎编译、执行的?_js 编译解析?.-CSDN博客
AST在线解析:AST explorer 左边是代码右边是树状结构,上方选择js和@bable/parser
JS实现:安装包npm install @babel/core --save
Babel官网:Babel 是什么? · Babel 中文文档 | Babel中文网 (babeljs.cn)
1. 节点树字段解释
- type: 表示当前节点的类型,我们常用的类型判断方法,就是判断当前的节点是否为某个类型。
- start: 表示当前节点的起始位。
- end: 表示当前节点的末尾。
- loc : 表示当前节点所在的行列位置,里面也有start与end节点,这里的start与上面的start是不同
的,这里的start是表示节点所在起始的行列位。置,而end表示的是节点所在末尾的行列位置。 - errors:是File节点所特有的属性,可以不用理会。
- program:包含整个源代码,不包含注释节点。
- comments:源代码中所有的注释会在这里显示
2. Babel使用
- @babel/core :Babel 编译器本身,提供了 babel 的编译 API;
- @babel/parser :将 JavaScript 代码解析成 AST 语法树;
- @babel/traverse :遍历、修改 AST 语法树的各个节点;
- @babel/generator :将 AST 还原成 JavaScript 代码;
- @babel/types :判断、验证节点的类型、构建新 AST 节点等。
2.1 parser使用
const parse = require('@babel/parser')
jscode = `var a = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";`
let ast = parse.parse(jscode)
console.log(JSON.stringify(ast, null, '\t'));
2.2 traverse使用
- path.node :表示当前path下的node节点
- path.toString() :当前路径所对应的源代码
- path.parentPath :用于获取当前path下的父path,多用于判断节点类型
- path.container :用于获取当前path下的所有兄弟节点(包括自身)
- path.type :获取当前节点类型
- path.get(”) :获取path的子路径
- path.replaceWith(newNode):(单)节点替换函数
- path.replaceWithMultiple(ArrayNode);多节点替换函数
简单拼接还原
const parse = require('@babel/parser')
const {types} = require("@babel/core");
const traverse = require('@babel/traverse').default
const generator = require("@babel/generator").default;
jscode = `var b = 1 + 2;
var c = "coo" + "kie";
var a = 1+1;
var c = 3;`
let ast = parse.parse(jscode)
const vis = {
BinaryExpression(path) {
let {left, operator, right} = path.node;
// 如果两边是数字且是加号就进行相加
if (types.isNumericLiteral(left) && types.isNumericLiteral(right) && operator == '+') {
value = left.value + right.value
path.replaceWith(types.valueToNode(value))
}
if (types.isStringLiteral(left) && types.isStringLiteral(right) && operator == '+') {
value = left.value + right.value
path.replaceWith(types.valueToNode(value))
}
}
}
traverse(ast, vis)
const {code} = generator(ast)
console.log(code);
replaceWithMultiple的使用
const parse = require('@babel/parser')
const {types} = require("@babel/core");
const traverse = require('@babel/traverse').default
const generator = require("@babel/generator").default;
jscode = `var arr = '3,4,0,5,1,2'['split'](',')`
let ast = parse.parse(jscode)
const vis = {
CallExpression(path) {
let {callee, arguments} = path.node
let object = callee.object.value;
let property = callee.property.value;
let argument = arguments[0].value;
array = object[property](argument)
path.replaceWithMultiple(types.valueToNode(array))
}
}
traverse(ast, vis)
const {code} = generator(ast)
console.log(code);
自执行方法还原
const parse = require('@babel/parser')
const {types} = require("@babel/core");
const traverse = require('@babel/traverse').default
const generator = require("@babel/generator").default;
jscode = `!(function (){
console.log('123')
})`
let ast = parse.parse(jscode)
const vis = {
UnaryExpression(path) {
let {argument} = path.node;
if (!types.isFunctionExpression(argument)) {
return;
}
let {body, id, params} = argument;
if (id != null || params.length != 0) {
return;
}
path.replaceWithMultiple(body.body)
}
}
traverse(ast, vis)
const {code} = generator(ast)
console.log(code);
evaluate方法
const parse = require('@babel/parser')
const {types} = require("@babel/core");
const traverse = require('@babel/traverse').default
const generator = require("@babel/generator").default;
jscode = `function _xl(){
x = 1 + 2 + 3 +4 + 5 + 6 + 7
}`
let ast = parse.parse(jscode)
const vis = {
"BinaryExpression|Identifier"(path) {
const {confident, value} = path.evaluate();
confident && path.replaceInline(types.valueToNode(value))
}
}
traverse(ast, vis)
const {code} = generator(ast)
console.log(code);
编码类型还原
const parse = require('@babel/parser')
const {types} = require("@babel/core");
const traverse = require('@babel/traverse').default
const generator = require("@babel/generator").default;
jscode = `var a = 0x25,b = 0b10001001,c = 0o123456,
d = "\x68\x65\x6c\x6c\x6f\x2c\x41\x53\x54",
e = "\u0068\u0065\u006c\u006c\u006f\u002c\u0041\u0053\u0054";`
let ast = parse.parse(jscode)
const vis = {
NumericLiteral({node}) {
if (node.extra && /^0[obx]/i.test(node.extra.raw)) {
node.extra = undefined;
}
},
StringLiteral({node}) {
if (node.extra && /\\[ux]/gi.test(node.extra.raw)) {
node.extra = undefined;
}
},
}
traverse(ast, vis)
const {code} = generator(ast)
console.log(code);
三元表达式还原
const parse = require('@babel/parser')
const {types} = require("@babel/core");
const traverse = require('@babel/traverse').default
const generator = require("@babel/generator").default;
jscode = `var d =true?1:2;`
let ast = parse.parse(jscode)
const vis = {
"BinaryExpression|UnaryExpression|ConditionalExpression"(path) {
// 防止溢出
if (path.isUnaryExpression({operator: "-"}) ||
path.isUnaryExpression({operator: "void"})) {
return;
}
const {confident, value} = path.evaluate();
if (!confident)
return;
if (typeof value == 'number' && (!Number.isFinite(value))) {
return;
}
path.replaceWith(types.valueToNode(value));
},
}
traverse(ast, vis)
const {code} = generator(ast)
console.log(code);
2.3 generator使用
生成还原后的js代码
// 删除所有注释,其他选项可到官网查询
const {code} = generator(ast,opts = {"comments":false});
作业
const parse = require('@babel/parser')
const {types} = require("@babel/core");
const traverse = require('@babel/traverse').default
const generator = require("@babel/generator").default;
jscode = `
const a = !![]+!![]+!![];
const b = Math.floor(12.34 * 2.12)
const c = 10 >> 3 << 1
const d = String(21.3 + 14 * 1.32)
const e = parseInt("1.893" + "45.9088")
const f = parseFloat("23.2334" + "21.89112")
const g = 20 < 18 ? '1' : '2'
`
let ast = parse.parse(jscode)
const vis1 = {
"BinaryExpression|Identifier"(path) {
const {confident, value} = path.evaluate();
confident && path.replaceInline(types.valueToNode(value))
}
}
traverse(ast, vis1)
const vis2 = {
CallExpression(path) {
let {callee, arguments} = path.node
if (path.get('callee').isMemberExpression() && callee.object.name == 'Math' && callee.property.name == 'floor') {
path.replaceWith(types.valueToNode(Math.floor(arguments[0].value)))
} else if (path.get('callee').isIdentifier() && callee.name == 'String') {
path.replaceWith(types.valueToNode(String(arguments[0].value)))
} else if (path.get('callee').isIdentifier() && callee.name == 'parseInt') {
path.replaceWith(types.valueToNode(parseInt(arguments[0].value)))
} else if (path.get('callee').isIdentifier() && callee.name == 'parseFloat') {
path.replaceWith(types.valueToNode(parseFloat(arguments[0].value)))
} else {
}
},
"BinaryExpression|UnaryExpression|ConditionalExpression"(path) {
if (path.isUnaryExpression({operator: "-"}) ||
path.isUnaryExpression({operator: "void"})) {
return;
}
const {confident, value} = path.evaluate();
if (!confident)
return;
if (typeof value == 'number' && (!Number.isFinite(value))) {
return;
}
path.replaceWith(types.valueToNode(value));
},
}
traverse(ast, vis2)
const {code} = generator(ast, opt = {compact: false})
console.log(code);