Home > front end >  How to get full AST representation of union type in typescript?
How to get full AST representation of union type in typescript?

Time:02-01

I know about ts-ast-viewer but I don't know how they extract list of elements from the union.

I have tried several existing solutions, including this and it seems that most of them are obsolete. Some ts.[methods] are deprecated now.

This is initial code to start debug compiler API:

import * as ts from "typescript";

const code = "type Foo = 'one'|'two'|'three'";
const sourceFile = ts.createSourceFile(
  "foo.ts",
  code,
  ts.ScriptTarget.Latest,
  true
);

function print(node: ts.Node, name: string) {
  console.log({ node });
}

print(sourceFile, "Foo");

I'm aware about example from TS wiki. This is my slightly modified version:

//@ts-ignore
import * as ts from "typescript";

const code = "type Foo = 'one'|'two'|'three'";
const sourceFile = ts.createSourceFile(
  "foo.ts",
  code,
  ts.ScriptTarget.Latest,
  true
);

function extract(identifiers: string[]): void {
  //@ts-ignore
  const unfoundNodes = [];
  //@ts-ignore
  const foundNodes = [];
  //@ts-ignore

  ts.forEachChild(sourceFile, (node) => {
    let name = "";
    //@ts-ignore
    if (ts.isFunctionDeclaration(node)) {
      //@ts-ignore
      name = node.name.text;
      //@ts-ignore
      node.body = undefined;
      //@ts-ignore
    } else if (ts.isVariableStatement(node)) {
      //@ts-ignore
      name = node.declarationList.declarations[0].name.getText(sourceFile);
      //@ts-ignore
    } else if (ts.isInterfaceDeclaration(node)) {
      name = node.name.text;
    }
    //@ts-ignore
    const container = identifiers.includes(name) ? foundNodes : unfoundNodes;
    //@ts-ignore
    container.push([name, node]);
  });
  //@ts-ignore
  return (unfoundNodes[0][1] as any).type.types.map(
    (elem: any) => elem.literal.text
  );
}

// Run the extract function with the script's arguments
console.log(extract(["Foo"]));

And it works. However, if I change source code from "type Foo = 'one'|'two'|'three'" to "type Foo = keyof Array<any>" - it does not.

I know that my version is wrong.

Also, I have tried this example, but it seems that forEachDescendant does not exists on node.

How to write a function to obtain an array of elements of the union?

Expected function:

import * as ts from "typescript";

const sourceCode = "type Foo = keyof Array<number>";


union(sourceCode, "Foo") // ["forEach", "reduce", "map" ...]

I need this for debug purposes.

CodePudding user response:

You can't use the AST for this because the AST only contains information about what the text looks like in the file and not how parts of the code relate to other parts. You'll need to use the type checker for that. Here's a self contained example:

// available as @ts-morph/bootstrap on npm
import { ts, createProjectSync } from "https://deno.land/x/[email protected]/bootstrap/mod.ts";

// setup code... you can use the vanilla ts compiler for this, but it's a lot of work
const project = createProjectSync();
const sourceCode = "type Foo = keyof Array<number>";
const sourceFile = project.createSourceFile("file.ts", sourceCode);
const typeChecker = project.createProgram().getTypeChecker();

// example use with the compiler API
const fooTypeAlias = sourceFile.statements[0] as ts.TypeAliasDeclaration;

const fooType = typeChecker.getTypeAtLocation(fooTypeAlias.name);
if (fooType.isUnion()) {
  for (const type of fooType.types) {
    console.log(typeChecker.typeToString(type, fooTypeAlias.name));
  }
}

Outputs:

number
"length"
"toString"
"toLocaleString"
"pop"
"push"
"concat"
"join"
"reverse"
"shift"
"slice"
"sort"
"splice"
"unshift"
"indexOf"
"lastIndexOf"
"every"
"some"
"forEach"
"map"
"filter"
"reduce"
"reduceRight"
  •  Tags:  
  • Related