gogoast
Version:
The simplest tool to parse/transform/generate code by ast
323 lines (316 loc) • 12.1 kB
JavaScript
const getSelector = require('./get-selector');
const { find, visit } = require('./find');
const parse = require('./parse');
const generate = require('./generate');
const filterProps = [
'computed',
'range',
'loc',
'type',
'raw',
'start',
'end',
'leadingComments',
'shorthand',
'extra',
'static',
'type',
'original'
]
const api = {
// 通过选择器获取,返回ast片段
getAstsBySelector(ast, selector, { strictSequence = true, deep, parseOptions } = {}) {
//strictSequence作用:
// 有的时候数组不要求顺序,如{a:$_$}匹配{b:1, a:2}
// 有的时候需要,如function($_$, $_$)匹配function(a, b) {}
// 入参不需要顺序,根据类型转换,也可以传入对象
for(let i = 2; i <=3 ; i++) {
const arg = arguments[i];
if (typeof arg == 'boolean') strictSequence = arg;
if (typeof ['nn', 'n', '1'].indexOf(arg) > -1) deep = arg;
}
if (!Array.isArray(selector)) {
selector = [selector];
}
let nodePathList = [];
let matchWildCardList = [];
const selectorAst = selector.map(item => getSelector(item, this.parseOptions || parseOptions))
selectorAst.forEach(item => {
const res = find.call(ast, item.nodeType, item.structure, strictSequence, deep);
const posStrList = [];
res.nodePathList.forEach((p, i) => {
const posStr = `${p.node.start},${p.node.end}`;
if (posStrList.indexOf(posStr) == -1) { // 去重
nodePathList.push(p);
matchWildCardList.push(res.matchWildCardList[i]);
posStrList.push(posStr);
}
});
});
return {
nodePathList,
matchWildCardList,
pathList: nodePathList,
extraDataList: matchWildCardList
};
},
getParentListByAst(path) {
const list = [];
while(path && path.parentPath) {
list.push(path.parentPath);
path = path.parentPath;
}
return list
},
getPrevAst(path) {
let parent = path.parentPath;
while(parent.value && !Array.isArray(parent.value)) {
path = parent;
parent = parent.parentPath;
}
parent = parent.value;
if (parent) {
const index = parent.indexOf(path.node);
if (index > 0) {
return parent[index - 1];
} else return null;
}
return null;
},
getNextAst(path) {
let parent = path.parentPath;
while(parent.value && !Array.isArray(parent.value)) {
path = parent;
parent = parent.parentPath;
}
parent = parent.value;
if (parent) {
const index = parent.indexOf(path.node);
if (parent[index + 1]) {
return parent[index + 1];
} else return null;
}
return null;
},
hasChildrenSelector(path, childSelector) {
const childCache = path.__childCache || {};
for (childKey in childCache) {
if (['type', 'directives'].indexOf(childKey) > -1) {
continue;
}
const child = childCache[childKey];
const { nodePathList } = api.getAstsBySelector(child, childSelector, 'nn');
if (nodePathList.length > 0) {
return true;
}
}
},
buildAstByAstStr(str, astPatialMap = {}, { isProgram = false, parseOptions }) {
try {
let ast;
try {
const ast = parse(str, parseOptions);
const program = api.replaceStrByAst(ast, astPatialMap);
if (program) {
if (isProgram) {
return program;
} else {
if (program.program.body.length > 1) {
return program.program.body
} else if (program.program.body.length == 1) {
return program.program.body[0];
} else {
return program.program.comments[0];
}
}
} else {
return null;
}
} catch(e) {
if (str.match(/^{(\s|.)+\}$/)) {
// 对象字面量
ast = parse(`var o = ${str}`);
ast = ast.program.body[0].declarations[0].init;
return ast;
}
}
} catch(e) {
console.log('buildAstByAstStr Error:' + e)
}
},
replaceStrByAst(ast, astPatialMap = {}) {
for (let key in astPatialMap) {
const valueAst = astPatialMap[key];
const { nodePathList } = api.getAstsBySelector(ast, [
{ type: 'Identifier', name: `$_$${key}$_$` },
{ type: 'StringLiteral', value: `$_$${key}$_$` }
]);
if (nodePathList.length > 0) {
nodePathList[0].replace(valueAst)
}
}
return ast;
},
replaceAstByAst(oldAst, newAst) {
oldAst.replace(newAst)
},
replaceSelBySel(ast, selector, replacer, strictSequence = true, parseOptions) {
// 用于结构不一致的,整体替换
const { nodePathList, matchWildCardList } = api.getAstsBySelector(ast, selector, strictSequence, 'nn', this.parseOptions || parseOptions);
nodePathList.forEach((path, i) => {
const extra = matchWildCardList[i];
if (extra.length > 0) {
let newReplacer = replacer;
extra.forEach(v => {
newReplacer = newReplacer.replace('$_$', generate(v.structure));
// 通过选择器替换ast,返回完整ast
});
if (!replacer) {
path.replace(null);
} else {
let replacerAst = api.buildAstByAstStr(newReplacer);
if (replacerAst.expression && replacerAst.expression.type != 'AssignmentExpression') {
replacerAst = replacerAst.expression
}
path && path.replace(replacerAst);
}
} else {
if (!replacer) {
path.replace(null);
} else {
let replacerAst = replacer.type ? replacer : api.buildAstByAstStr(replacer);
if (replacerAst.expression && replacerAst.expression.type != 'AssignmentExpression') {
replacerAst = replacerAst.expression
}
path && path.replace(replacerAst);
}
}
});
},
modifySelBySel(ast, selector, replacer) {
// 用于结构完全一致的
// 通过选择器替换ast,返回完整ast
const selectorAst = getSelector(selector, this.parseOptions);
const replacerAst = getSelector(replacer, this.parseOptions);
const { nodePathList, matchWildCardList } = find.call(ast, selectorAst.nodeType, selectorAst.structure, true, 'nn');
// console.log(nodePathList);
nodePathList.forEach(path => {
replaceAttr(path.value, replacerAst.structure);
});
function replaceAttr(path, replacerStructure) {
if (isObject(path) && isObject(replacerStructure)) {
for(const key in replacerStructure) {
if (!hasOwn(path, key)) return;
const value = replacerStructure[key];
if (isObject(value)) {
replaceAttr(path[key], value);
} else if (value != '$_$') {
path[key] = value;
}
}
}
}
},
insertAstListBefore(path, nodeList) {
if (!Array.isArray(nodeList)) {
nodeList = [nodeList]
}
for (let i = 0; i< 3; i++) {
const pNode = path.parentPath;
if (pNode && pNode.value && Array.isArray(pNode.value)) {
const index = pNode.value.indexOf(path.value);
nodeList.reverse().forEach(item => {
pNode.value.splice(index, 0, item);
});
i = 3
} else {
path = pNode;
}
}
},
insertAstListAfter(path, nodeList) {
if (!Array.isArray(nodeList)) {
nodeList = [nodeList]
}
for (let i = 0; i< 3; i++) {
const pNode = path.parentPath;
if (pNode && pNode.value && Array.isArray(pNode.value)) {
const index = pNode.value.indexOf(path.value) + 1;
nodeList.reverse().forEach(item => {
pNode.value.splice(index, 0, item);
});
i = 3
} else {
path = pNode;
}
}
},
removeAst(ast, selector) {
if (!ast || typeof ast !== 'object') {
throw new Exception('第一个参数必须为object类型')
}
if (!selector || (typeof selector !== 'object' && typeof selector !== 'string')) {
throw new Exception('第二个参数必须为object或string类型')
}
const selectorAst = getSelector(selector, this.parseOptions);
// bug
// console.log(selectorAst)
const { nodePathList } = find.call(ast, selectorAst.nodeType, selectorAst.structure, true, 'nn');
// console.log(nodePathList)
nodePathList.forEach(path => {
// 多条语句逗号分割的话,只删除一个;一条语句的话,删除父节点
if ((!path.parentPath.value.length) || path.parentPath.value.length == 1) {
path.parent.replace(null);
} else {
path.replace(null)
}
});
},
appendJsxAttr(ast, obj) {
if (!ast || typeof ast !== 'object') {
throw new Exception('第一个参数必须为object类型')
}
if (!obj || typeof obj !== 'object') {
throw new Exception('第二个参数必须为object类型')
}
const attrs = [];
for (let o in obj) {
attrs.push(`${o}=${obj[o]}`.replace(/'\$'/g, "$"));
}
const jsxPartial = buildAstByAstStr(`<div ${attrs.join(' ')}></div>`);
const newAttrs = jsxPartial.expression.openingElement.attributes;
ast.value.openingElement.attributes = ast.value.openingElement.attributes.concat(newAttrs);
},
visit() {
visit.call(this, ...Array.from(arguments));
},
traverse(node, cb) {
if(!node || typeof node !== 'object'){
throw new Exception('第一个参数必须为object类型')
}
if(!cb || typeof cb !== 'function'){
throw new Exception('第二个参数必须为function类型')
}
if (node.type && typeof node.type == 'string') {
// 是一个ast节点,且不是token
if (['File', 'Program'].indexOf(node.type) == -1) {
cb(node);
}
for (let attr in node) {
const child = node[attr];
if (child) {
if (Array.isArray(child)) {
child.forEach(c => api.traverse(c, cb));
} else if (child.type) {
api.traverse(child, cb);
}
}
}
}
}
}
function isObject(value) {
return typeof value === 'object' && value;
}
const hasOwn = Object.prototype.hasOwnProperty.call.bind(Object.prototype.hasOwnProperty);
module.exports = api;