ciallorize
Version:
Ciallorize your bundled js into ascii art.
121 lines (107 loc) • 4.05 kB
JavaScript
const parser = require('@babel/parser');
const IGNORED_PROPERTIES = new Set([
'loc', 'start', 'end', 'comments', 'extra', 'tokens',
'leadingComments', 'trailingComments', 'innerComments'
]);
/**
* 比较两段代码的 AST 是否相同,失败时抛出差异
* @param {string} code1 原始代码
* @param {string} code2 重组后的代码
* @throws {Error} 包含具体差异信息
*/
function compareASTWithDiff(code1, code2) {
const ast1 = normalizeAST(parseAST(code1));
const ast2 = normalizeAST(parseAST(code2));
const path = []; // 记录当前比较路径(如 ["Program", "body", "0"])
try {
deepCompareAST(ast1, ast2, path);
return true;
} catch (error) {
// console.error(`AST 比较失败:\n${error.message}`);
return false;
}
}
function parseAST(code) {
return parser.parse(code, {
sourceType: 'script',
plugins: [], // 根据需要使用 JSX/Flow 等插件
});
}
function normalizeAST(ast) {
return JSON.parse(
JSON.stringify(ast, (key, value) => {
if (IGNORED_PROPERTIES.has(key)) return undefined; // 删除无关属性
return value;
})
);
}
/**
* 递归比较 AST 节点,失败时抛出差异
* @param {ASTNode} node1
* @param {ASTNode} node2
* @param {string[]} path
*/
function deepCompareAST(node1, node2, path) {
if (node1 === node2) return;
if (!node1 || !node2) {
throw new Error(`节点缺失: ${path.join('.')}\n 节点1: ${JSON.stringify(node1)}\n 节点2: ${JSON.stringify(node2)}`);
}
// 检查节点类型
if (node1.type !== node2.type) {
throw new Error(`类型不同: ${path.join('.')}\n 节点1类型: ${node1.type}\n 节点2类型: ${node2.type}`);
}
// 特殊节点处理
switch (node1.type) {
case 'Identifier':
if (node1.name !== node2.name) {
throw new Error(`标识符名称不同: ${path.join('.')}\n 节点1: ${node1.name}\n 节点2: ${node2.name}`);
}
return;
case 'Literal':
if (node1.value !== node2.value) {
throw new Error(`字面量值不同: ${path.join('.')}\n 节点1: ${node1.value}\n 节点2: ${node2.value}`);
}
return;
case 'RegExpLiteral':
if (node1.pattern !== node2.pattern || node1.flags !== node2.flags) {
throw new Error(`正则表达式不同: ${path.join('.')}\n 节点1: /${node1.pattern}/${node1.flags}\n 节点2: /${node2.pattern}/${node2.flags}`);
}
return;
}
// 比较子节点(忽略位置和额外属性)
const keys1 = Object.keys(node1).filter(k => !['loc', 'start', 'end', 'extra', 'tokens', 'comments'].includes(k));
const keys2 = Object.keys(node2);
// 检查属性数量
if (keys1.length !== keys2.length) {
const missingKeys = keys1.filter(k => !keys2.includes(k));
const extraKeys = keys2.filter(k => !keys1.includes(k));
throw new Error(`属性数量不同: ${path.join('.')}\n 缺失属性: ${missingKeys.join(', ')}\n 多余属性: ${extraKeys.join(', ')}`);
}
// 递归比较每个属性
for (const key of keys1) {
const val1 = node1[key];
const val2 = node2[key];
path.push(key);
// 处理数组(如 body、params)
if (Array.isArray(val1)) {
if (!Array.isArray(val2) || val1.length !== val2.length) {
throw new Error(`数组长度不同: ${path.join('.')}\n 节点1长度: ${val1.length}\n 节点2长度: ${val2.length}`);
}
for (let i = 0; i < val1.length; i++) {
path.push(`${i}`);
deepCompareAST(val1[i], val2[i], path);
path.pop();
}
}
// 递归对象
else if (typeof val1 === 'object' && val1 !== null) {
deepCompareAST(val1, val2, path);
}
// 基本类型直接比较
else if (val1 !== val2) {
throw new Error(`属性值不同: ${path.join('.')}\n 节点1.${key}: ${val1}\n 节点2.${key}: ${val2}`);
}
path.pop();
}
}
module.exports = compareASTWithDiff;