@yugu/gogocode
Version:
The simplest tool to parse/transform/generate code on ast
229 lines (219 loc) • 8.63 kB
JavaScript
const { isObject, hasOwn } = require('../../util')
// 通过简单ast结构查找ast节点
const recast = require('recast-yx');
const visit = recast.types.visit;
const filterProps = require('../filter-prop.js');
const generate = require('../generate')
const handleSpecific = require('./specific');
const strictSequenceAttrList = ['arguments', 'params'];
let Expando = 'g123o456g789o';
function checkIsMatch(full, partial, extraData, strictSequence) {
return Object.keys(partial).every((prop) => {
const { specific, result } = handleSpecific({ full, partial, prop, extraData, Expando, find$$$ });
if (specific) {
return result;
}
if (!full || !partial) {
// full没有
return false;
} else if (isObject(partial[prop])) {
let res = false;
let has$$$ = false;
if (Array.isArray(partial[prop])) {
// 处理$$$这种情况
has$$$ = find$$$(partial[prop], full[prop], extraData, strictSequence);
}
if (Array.isArray(partial[prop]) && !strictSequence && strictSequenceAttrList.indexOf(prop) == -1) {
if (hasOwn(full, prop)) {
res = partial[prop].every((p) => {
let a = false;
if (!full[prop].length && partial[prop].length == 1 && has$$$) {
return true
}
full[prop] &&
full[prop].forEach((f) => {
if (f && f.type == 'ObjectProperty') {
// 兼容 { a: 1 } 匹配 { 'a': 1 } 这种情况
f.key.name && (f.key.value = f.key.name);
f.key.value && (f.key.name = f.key.value);
}
if (
checkIsMatch(
f,
p,
extraData,
strictSequence
)
) {
a = true;
}
});
return a;
});
} else {
res = false;
}
} else {
try {
// 例如 使用{ $_$: $_$ }匹配{ a() {} }
let fullProp = full[prop];
if (!fullProp && !Array.isArray(full)) {
if (partial[prop] && typeof partial[prop].name == 'string' &&
(partial[prop].name.match(Expando) || partial[prop].name.match(new RegExp(Expando.slice(0, -1) + '\\$3')))
) {
if (full.type == 'VariableDeclarator' && prop == 'init' && partial[prop].name.match(Expando)) {
// var $_$ = $_$匹配 var a这种;
const expandoKey = partial[prop].name.replace(Expando, '') || '0';
extraData[expandoKey] = extraData[expandoKey] || [];
extraData[expandoKey].push({ node: null, value: null })
return true;
} else {
fullProp = full;
}
}
}
res =
// hasOwn(full, prop)
// &&
checkIsMatch(
fullProp,
partial[prop],
extraData,
strictSequence
);
} catch (e) {
console.log(e);
}
}
return res;
} else {
if (partial[prop].match && partial[prop].match(new RegExp(Expando.slice(0, -1) + '\\$3'))) {
return true;
}
if (partial[prop].match && partial[prop].match(Expando)) {
if (!full) return;
let extra = {
node: full
};
const expandoKey = partial[prop].replace(Expando, '') || '0';
extraData[expandoKey] = extraData[expandoKey] || [];
switch (full.type) {
case 'Identifier':
extra.value = full.name;
break;
case 'ThisExpression':
extra.value = 'this';
break;
case 'StringLiteral':
extra.raw = `'${full.value}'`;
extra.value = full.value
break;
case 'NumericLiteral':
case 'BooleanLiteral':
extra.value = full.value;
break;
case 'NullLiteral':
extra.value = null;
break;
case 'CommentLine':
case 'CommentBlock':
extra.value = full.value;
break;
default:
try {
extra.value = generate(full);
} catch(e) {
if (full[prop]) {
extra.value = full[prop];
} else {
extra.value = {};
filterProps(full, extra.value);
}
}
}
extraData[expandoKey].push(extra);
return true;
} else if (partial[prop]) {
// const reg = /^(?:\$\[).*(?=\]\$)/;
}
if (full && full.type == 'ObjectProperty') {
// 兼容 { a: 1 } 匹配 { 'a': 1 } 这种情况
full.key.name && (full.key.value = full.key.name);
full.key.value && (full.key.name = full.key.value);
}
return full ? full[prop] == partial[prop] : false;
}
});
}
function find$$$(partial, full, extraData, strictSequence) {
// 先考虑strctSequence = false的情况
let key$$$;
let index$$$ = -1;
partial.forEach((p, i) => {
for (const key in p) {
const value = p[key] ? (p[key].name || p[key].value || p[key]) : null;
if (value && value.match && value.match(new RegExp(Expando.slice(0, -1) + '\\$3'))) {
key$$$ = value.replace(new RegExp(Expando.slice(0, -1) + '\\$3'), '') || '$'
index$$$ = i;
break;
}
}
})
if (!key$$$) {
return false;
}
const extraNodeList = full ? full.slice(0) : [];
partial.forEach((p, i) => {
if (i == index$$$) {
return;
}
let fi = 0;
while(extraNodeList[fi]) {
if (checkIsMatch(extraNodeList[fi], p, {}, strictSequence)) {
extraNodeList.splice(fi, 1);
} else {
fi++;
}
}
})
extraData[`$$$${key$$$}`] = (extraData[`$$$${key$$$}`] || []).concat(extraNodeList);
return true;
}
function find(nodeType, structure, strictSequence, deep = 'nn', expando = 'g123o456g789o') {
const nodePathList = [];
const matchWildCardList = [];
let isMatch = false;
Expando = expando
visit(this, {
[`visit${nodeType}`](path) {
const extraData = {};
isMatch = checkIsMatch(
path.value,
structure,
extraData,
strictSequence
);
if (isMatch) {
nodePathList.push(path);
matchWildCardList.push(extraData);
}
switch (deep) {
case '1':
this.abort();
break;
case 'n':
return false;
case 'nn':
this.traverse(path);
break;
default:
return false;
}
},
visitComment() {
return false;
},
});
return { nodePathList, matchWildCardList };
}
module.exports = { find, visit };