Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: Add a method for converting a selector object to a string #144

Open
StyleShit opened this issue Jan 25, 2024 · 1 comment

Comments

@StyleShit
Copy link

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
Copy link

auvred commented Jan 25, 2024

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants