@yugu/gogocode
Version:
The simplest tool to parse/transform/generate code on ast
500 lines (491 loc) • 21.6 kB
JavaScript
const getSelector = require('./get-selector');
const { find, visit } = require('./find/general');
const parse = require('./parse');
const generate = require('./generate');
const nodeLinkMap = require('./node-link-map');
const buildMap = require('./build-map');
const core = {
// 通过选择器获取,返回ast片段
getAstsBySelector(ast, selector, { strictSequence, deep, parseOptions, expando = 'g123o456g789o' } = {}) {
//strictSequence作用:
// 有的时候数组不要求顺序,如{a:$_$}匹配{b:1, a:2}
// 有的时候需要,如function($_$, $_$)匹配function(a, b) {}
if (!Array.isArray(selector)) {
selector = [selector];
}
let nodePathList = [];
let matchWildCardList = [];
const selectorAst = [];
selector.forEach(item => {
let sels = getSelector(item, this.parseOptions || parseOptions, expando);
!Array.isArray(sels) && (sels = [sels])
sels.forEach(sel => {
if (!sel.nodeType) {
throw new Error('语句类型缺失,请在 https://github.com/thx/gogocode/issues 上提供您的代码样例')
}
selectorAst.push(sel);
})
})
const posStrList = [];
selectorAst.forEach(item => {
const res = find.call(ast, item.nodeType, item.structure, strictSequence, deep, expando);
res.nodePathList.forEach((p, i) => {
const posStr = `${p.node.start},${p.node.end}`;
if (posStrList.indexOf(posStr) == -1 || item.nodeType.match('Comment')) { // 去重
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, expando) {
const childCache = path.__childCache || {};
for (const childKey in childCache) {
if (['type', 'directives'].indexOf(childKey) > -1) {
continue;
}
const child = childCache[childKey];
const { nodePathList } = core.getAstsBySelector(child, childSelector, { deep: 'nn', expando });
if (nodePathList.length > 0) {
return true;
}
}
},
buildAstByAstStr(str, astPatialMap = {}, { isProgram = false, parseOptions } = {}) {
try {
let ast;
try {
ast = parse(str, parseOptions);
const program = core.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 if (program.program.comments && program.program.comments[0]) {
return program.program.comments[0];
} else if (program.program.directives
&& program.program.directives[0]
&& program.program.directives[0].value
&& program.program.directives[0].value.value) {
return {
type: 'StringLiteral',
value: program.program.directives[0].value.value
}
} else {
return program.program
}
}
} else {
return null;
}
} catch(e) {
if (str.match(/^{(\s|.)+\}$/)) {
if (str.match('...') && str.match('=')) {
// 解构入参
ast = buildMap.DestructuringParam(str)
} else {
// 对象字面量
ast = buildMap.ObjectExpression(str)
}
return ast;
} else if (e.message.match('Missing semicolon')) {
// 可能是对象属性
ast = buildMap.ObjectProperty(str)
return ast
} else if (e.message.match('Leading decorators must be attached to a class declaration')) {
// 是decorator
ast = buildMap.Decorators(str)
return ast
} else {
throw new Error(`buildAstByAstStr failed:${str}`)
}
}
} catch(error) {
throw new Error(`buildAstByAstStr failed:${str}`)
}
},
replaceStrByAst(ast, astPatialMap = {}) {
for (let key in astPatialMap) {
const valueAst = astPatialMap[key];
const { nodePathList } = core.getAstsBySelector(ast, [
{ type: 'Identifier', name: `$_$${key}$_$` },
{ type: 'StringLiteral', value: `$_$${key}$_$` }
]);
if (nodePathList.length > 0) {
nodePathList[0].replace(valueAst)
}
}
return ast;
},
replaceAstByAst(oldAst, newAst) {
if (Array.isArray(newAst)) {
const { arrPath = {}, index } = getArrPath(oldAst);
if (Array.isArray(arrPath.value) && index > -1) {
arrPath.value.splice(index, 1, ...newAst);
return;
}
}
if (newAst.type == 'BlockStatement' && Array.isArray(oldAst.parentPath.value)) {
const parentNode = oldAst.parentPath.value;
const oldIndex = parentNode.indexOf(oldAst.node);
parentNode.splice(oldIndex, 1);
newAst.body.forEach((replacer, index) => {
oldAst.parentPath.value.splice(oldIndex + index, 0, replacer)
})
} else if (!oldAst.parent && oldAst.node.type == 'File' ) {
oldAst.node.program.body = [ newAst ]
} else {
oldAst.replace(newAst)
}
},
replaceSelBySel(ast, selector, replacer, strictSequence, parseOptions, expando = 'g123o456g789o') {
// 用于结构不一致的,整体替换
const { nodePathList, matchWildCardList } = core.getAstsBySelector(ast, selector, { strictSequence, deep: 'nn', parseOptions: this.parseOptions || parseOptions, expando });
const originReplacer = replacer
nodePathList.forEach((path, i) => {
const extra = matchWildCardList[i];
replacer = originReplacer
if (typeof replacer == 'function') {
replacer = replacer(extra, path);
if (replacer === null) {
return;
}
}
if (Object.keys(extra).length > 0 && typeof replacer == 'string') {
let newReplacer = replacer;
for(let key in extra) {
if (key.match(/\$\$\$/)) {
let key$$$ = key.replace(/\$\$\$/, '');
key$$$ == '$' && (key$$$ = '');
let join = '\n'
let wildCardCode = extra[key].map(item => {
let codeStr = generate(item);
try {
// 嵌套replace
const childAst = core.buildAstByAstStr(codeStr, {}, { parseOptions });
core.replaceSelBySel(childAst, selector, replacer, strictSequence, parseOptions, expando);
codeStr = generate(childAst)
} catch(e) { //
}
nodeLinkMap[item.type] && (join = nodeLinkMap[item.type])
return codeStr
//
}).join(join)
// 不能都用,连接,需要找到$_$
if (!wildCardCode) {
// 通配符匹配为空时,去掉通配符后面可能出现的逗号
newReplacer = newReplacer.replace(new RegExp(`\\$\\$\\$${key$$$}\\s*,`), '')
}
newReplacer = newReplacer.replace('$$$' + key$$$, wildCardCode.replace(/\$/g,'$$$$'));
// 如果wildCardCode存在`$'`,会被命中替换,因此对$进行处理(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)
} else {
let realKey = key == '0' ? '' : key;
const matchLength = (newReplacer.match(new RegExp(`\\$_\\$${realKey}`, 'g')) || []).length;
if (matchLength == extra[key].length) {
extra[key].forEach(ext => {
newReplacer = newReplacer
.replace(`'$_$${realKey}'`, `'${ext.value}'`)
.replace(`"$_$${realKey}"`, `"${ext.value}"`)
.replace('$_$' + realKey, ext.raw || ext.value);
})
} else {
// 删除代码块外部{},find里前置处理了,不需要在这里做了
let wildCardCode = extra[key].map(item =>
typeof item.value !== 'object' ? (item.raw || item.value) : ``
).join(', ')
let wildCardCodeNoQuot = extra[key].map(item =>
typeof item.value !== 'object' ? (item.value) : ``
).join(', ')
newReplacer = newReplacer
.replace(new RegExp(`'\\$_\\$${realKey}'`, 'g'), `'` + wildCardCodeNoQuot.replace(/\$/g,'$$$$') + `'`)
.replace(new RegExp(`"\\$_\\$${realKey}"`, 'g'), `"` + wildCardCodeNoQuot.replace(/\$/g,'$$$$') + `"`)
.replace(new RegExp(`\\$_\\$${realKey}`, 'g'), wildCardCode.replace(/\$/g,'$$$$'));
// 通过选择器替换ast,返回完整ast
// 如果wildCardCode存在`$'`,会被命中替换,因此对$进行处理(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace)
}
}
}
if (!replacer) {
core.removePathSafe(path)
} else {
let replacerAst = null;
try {
replacerAst = core.buildAstByAstStr(newReplacer, {}, { parseOptions });
} catch(e) {
//
}
if (buildMap[path.node.type]) {
try {
if (buildMap[path.node.type](newReplacer)) {
replacerAst = buildMap[path.node.type](newReplacer)
}
} catch(e) {
//
}
}
if (!replacerAst) {
throw new Error(`replace failed: ${newReplacer} cannot be parsed!`)
}
if (replacerAst.expression && replacerAst.expression.type != 'AssignmentExpression' && path.parentPath.name != 'body') {
replacerAst = replacerAst.expression
}
path && core.replaceAstByAst(path, replacerAst);
}
} else {
if (!replacer) {
core.removePathSafe(path);
} else if (typeof replacer == 'string') {
let replacerAst = replacer.type ? replacer : core.buildAstByAstStr(replacer, { }, { parseOptions });
if (!replacer.type && buildMap[path.node.type]) {
try {
if (buildMap[path.node.type](replacer)) {
replacerAst = buildMap[path.node.type](replacer)
}
} catch(e) {
//
}
}
if (replacerAst.expression && replacerAst.expression.type != 'AssignmentExpression' && path.parentPath.name != 'body') {
replacerAst = replacerAst.expression
}
path && core.replaceAstByAst(path, replacerAst);
} else {
if (replacer[0] && replacer[0].nodePath) {
path.replace(replacer[0].nodePath.node)
} else {
core.replaceAstByAst(path, replacer);
}
}
}
});
},
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, { strictSequence, parseOptions, expando } = {}) {
if (!ast || typeof ast !== 'object') {
throw new Error('remove failed! first argument mast be object')
}
if (!selector || (typeof selector !== 'object' && typeof selector !== 'string' && !Array.isArray(selector))) {
throw new Error('remove failed! first argument mast be object、string or string array')
}
// const selectorAst = getSelector(selector, this.parseOptions);
// console.log(selectorAst)
const { nodePathList } = core.getAstsBySelector(ast, selector, { strictSequence, parseOptions, expando })
// 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) {
core.removePathSafe(path.parent);
} else {
core.removePathSafe(path)
}
});
},
remove(path) {
try {
core.removePathSafe(path)
} catch(e) {
throw `remove failed: ${e}`
}
},
removePathSafe(path) {
// 对于expression 删除之后,父节点 expressionStatement 还在,输出会多个分号。所以应该删除 expressionStatement
if (path.name == 'expression') {
path.parent.replace();
} else {
path.replace();
}
},
appendJsxAttr(ast, obj) {
if (!ast || typeof ast !== 'object') {
throw new Error('appendJsxAttr failed! first argument mast be object')
}
if (!obj || typeof obj !== 'object') {
throw new Error('appendJsxAttr failed! second argument mast be object')
}
const attrs = [];
for (let o in obj) {
attrs.push(`${o}=${obj[o]}`.replace(/'\$'/g, "$"));
}
try {
const jsxPartial = core.buildAstByAstStr(`<div ${attrs.join(' ')}></div>`);
const newAttrs = jsxPartial.expression.openingElement.attributes;
if (ast.value) {
ast.value.openingElement.attributes = ast.value.openingElement.attributes.concat(newAttrs);
} else {
ast.expression.openingElement.attributes = ast.expression.openingElement.attributes.concat(newAttrs);
}
} catch(e) {
throw new Error('appendJsxAttr failed!' + e)
}
},
visit() {
visit.call(this, ...Array.from(arguments));
},
traverse(node, cb, parentNode) {
if(!node || typeof node !== 'object'){
throw new Error('traverse failed! first argument mast be object')
}
if(!cb || typeof cb !== 'function'){
throw new Error('traverse failed! second argument mast be function')
}
if (node.type && typeof node.type == 'string') {
// 是一个ast节点,且不是token
if (['File', 'Program'].indexOf(node.type) == -1) {
cb(node, { parentNode });
}
for (let attr in node) {
const child = node[attr];
if (child) {
if (Array.isArray(child)) {
let i = 0;
while(child[i]) {
const c = child[i];
core.traverse(c, cb, child);
i = child.indexOf(c);
i++
}
// child.forEach(c => core.traverse(c, cb, node));
} else if (child.type) {
core.traverse(child, cb, node);
}
}
}
}
},
initComment(ast) {
core.traverse(ast, ((node, {parentNode}) => {
if (Array.isArray(parentNode)) {
const index = parentNode.indexOf(node);
if (index == parentNode.length - 1) {
if (node.trailingComments) {
node.trailingComments.forEach(comment => {
parentNode.push(comment);
})
}
}
if (node.leadingComments) {
node.leadingComments.reverse().forEach(comment => {
parentNode.splice(index, 0, comment);
})
}
}
}))
},
removeComments(ast) {
core.traverse(ast, ((node, {parentNode}) => {
if (Array.isArray(parentNode)) {
if (!parentNode.every(item => typeof item.type == 'string' && item.type.match('Comment'))) {
let i = 0;
while (parentNode[i]) {
const node = parentNode[i];
if (node && typeof node.type == 'string' && node.type.match('Comment')) {
parentNode.splice(i, 1);
i--
}
i++
}
}
}
}))
}
}
function getArrPath(path) {
let arrPath = path
if (!arrPath) return;
let lastNode = path.node;
let i = 0;
while(!Array.isArray(arrPath.value) && i < 3) {
lastNode = arrPath.node;
arrPath = arrPath.parentPath;
i++
}
if (Array.isArray(arrPath.value)) {
return { arrPath: arrPath, index : arrPath.value.indexOf(lastNode) }
} else {
return { arrPath: {}, index: -1 };
}
}
module.exports = core;