Feature Request: Add a method for converting a selector object to a string
StyleShit opened this issue · 1 comments
StyleShit commented
Currently, the parsing is a one-way ticket, you can take a string and convert it to a selector object.
I propose that we'll have a way to go the other way around - taking a selector and converting it to a string.
Roughly something like this:
const filter = 'MethodDefinition[value.typeParameters]';
const selector = esquery.parse(filter);
const unparsed = esquery.unparse(selector);
filter === unparsed; // true
(not sure about the name though)
I'm willing to send a PR for this if this is something that you think can be done properly by someone who's unfamiliar with the codebase (yet 😄)
auvred commented
It's actually not that hard
const esquery = require("esquery");
function stringify(selector) {
switch (selector.type) {
case "wildcard":
return "*";
case "identifier":
return selector.value;
case "field":
return "." + selector.name.split(".");
case "matches":
return ":matches(" + selector.selectors.map(stringify).join(", ") + ")";
case "compound":
return selector.selectors.map(stringify).join("");
case "not":
return ":not(" + selector.selectors.map(stringify).join(", ") + ")";
case "has":
return ":has(" + selector.selectors.map(stringify).join(", ") + ")";
case "child":
return stringify(selector.left) + " > " + stringify(selector.right);
case "descendant":
return stringify(selector.left) + " " + stringify(selector.right);
case "attribute": {
const parts = ["[", selector.name];
if (selector.operator) {
parts.push(selector.operator);
if (selector.value.type === "regexp") {
parts.push(selector.value.value.toString());
} else if (selector.value.type === "literal") {
parts.push(`${selector.value.value}`);
} else if (selector.value.type === "type") {
parts.push("type(" + selector.value.value + ")");
}
}
return [...parts, "]"].join("");
}
case "sibling":
return stringify(selector.left) + " ~ " + stringify(selector.right);
case "adjacent":
return stringify(selector.left) + " + " + stringify(selector.right);
case "nth-child":
return ":nth-child(" + selector.index.value + ")";
case "nth-last-child":
return ":nth-last-child(" + selector.index.value + ")";
case "class":
return ":" + selector.name;
}
}
console.log(stringify(esquery.parse("aaa.bbb[ccc=111]:statement > eee:matches(* ~ ttt + ddd :not(ee, yy[a])), *:first-child")));
// :matches(aaa.bbb[ccc=111]:statement > eee:matches(* ~ ttt + ddd :not(ee, yy[a])), *:nth-child(1))
But it will replace top level matches
with :matches
and first-child
/last-child
with :nth-child(...)
. Also it will remove all extra spaces. (limitations of AST-based codegen)