How to use getPreviousSibling to get the node instead of getting syntax dot in ts-morph?

112 Views Asked by At

I am currently trying to resolve the "definition" of "Identifier".

Note that I am using the ts-morph library.

As an example, given the following source:

  const fn = () => {}

  const fn2 = fn.bind(this);

I want to get the "definition" of fn Identifier (in the second line).

ts-morph is able to use "getDefinitionNodes" to get the actual fn function, but it does only to nodes with the type of Identifier and on the correct node.

So here I found the bind Identifier (from there I want to start).

Now I need to find the fn (it also can be this.fn sometimes).

I try to use getPreviousSibling, but it returns . (dot) and not fn.

Is there a better way to get the previous node instead to do getPreviousSibling().getPreviousSibling()?

import { Project, SyntaxKind } from "ts-morph";

console.clear();

const project = new Project();

const file = project.createSourceFile(
  "foo.ts",
  `
  const fn = () => {}

  const fn2 = fn.bind(this);
`
);

const identifiers = file.getDescendantsOfKind(SyntaxKind.Identifier);

const bind = identifiers.find((i) => i.getText() === "bind");

console.log({ bind });

const fn = bind?.getPreviousSibling();

console.log({ fn: fn?.getText() }); //<-- returns . but I was expected to fn.

codesandbox.io

2

There are 2 best solutions below

0
coderaiser On

You can try to use Putout code transformer I'm working on. It has support of template values like __a and __b, and when you need to change the name, you just put something like hello in place of __a:

export const report = () => '';

export const match = () => ({
    '__a.bind(__b)': ({__a, __b}) => {
        console.log(__a, __b);
        return true;
    }
});

export const replace = () => ({
    '__a.bind(__b)':'hello.bind(__b)',
});

It will output identifiers you need:

enter image description here

You can try it in Putout Editor.

And produce changes:

const fn = () => {}
const fn2 = hello.bind(this);

It much simpler then using imperative approach of searching for each previous sibling.

0
souporserious On

There are a few different ways to handle this in ts-morph. Like you said, we can keep getting the previous sibling which navigates by token. This can be wrapped into a utility function:

function getPreviousSiblingUntilKindOrThrow(
  node: Node | undefined,
  kind: SyntaxKind
): Node | null {
  if (node === undefined) {
    throw new Error("node was undefined");
  }

  const previousSibling = node.getPreviousSibling();

  return previousSibling
    ? previousSibling.getKind() === kind
      ? previousSibling
      : getPreviousSiblingUntilKindOrThrow(previousSibling, kind)
    : null;
}

...

const fn = getPreviousSiblingUntilKindOrThrow(
  bind,
  SyntaxKind.Identifier
);

Alternatively, you can use the ancestor helpers and move up the AST and then back down:

const fn = bind
  ?.getFirstAncestorByKindOrThrow(SyntaxKind.PropertyAccessExpression)
  .getExpression();

original code AST representation in ts ast viewer with the PropertyAccessExpression node highlighted

Codesandbox demo