What is the technique for obtaining a complete AST representation of a union type in TypeScript?

Although I am familiar with ts-ast-viewer, I am unsure of how they extract a list of elements from the union.

I have experimented with different existing solutions, such as this one, but it appears that most of them are outdated. Some ts.[methods] have been deprecated.

Below is the initial code I used to debug the 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 have seen an example on the TS wiki and created a 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"]));

While this setup worked for

"type Foo = 'one'|'two'|'three'"
, it failed for
"type Foo = keyof Array<any>"
.

I am aware that my current version is incorrect.

I also attempted to use another example, but it appears that forEachDescendant does not exist for node.

Any suggestions on how to create a function to retrieve an array of elements from a union?

Here's the function structure I am aiming for:

import * as ts from "typescript";

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


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

This is crucial for my debugging process.

Answer №1

In order to understand how different parts of the code relate to each other, relying on the Abstract Syntax Tree (AST) is not sufficient. The AST only provides information on the appearance of the code in the file. To achieve a deeper understanding, the type checker must be utilized. Here's a concise example to demonstrate this:

// Find this module at @ts-morph/bootstrap on npm
import { ts, createProjectSync } from "https://deno.land/x/<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="4f3b3c1022203d3f270f7e7c617f617c">[email protected]</a>/bootstrap/mod.ts";

// Initial setup - you can opt for the vanilla ts compiler, though it requires more effort
const project = createProjectSync();
const sourceCode = "type Foo = keyof Array<number>";
const sourceFile = project.createSourceFile("file.ts", sourceCode);
const typeChecker = project.createProgram().getTypeChecker();

// Example usage 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));
  }
}

Resulting Output:

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

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Exploring the functionalities of TypeScript's mapKey and pick features

I am looking to convert the JavaScript code shown below into TypeScript, but I don't want to use loadish.js. let claimNames = _.filter<string>(_.keys(decodedToken), o => o.startsWith(ns) ); let claims = <any>( _.mapKeys(_ ...

Ways to resolve TypeScript type issues that are functioning correctly with one type but encountering errors when used with functions

When the route function encounters the middlewares parameter type, it always throws an error. However, no error occurs if the type is used directly, as seen in lines 72 and 75. Errors will occur on lines 107 and 98. abstract class BaseMiddleware< In ...

Exploring the world of mocking tests using Jest and inputs

Is there a way to create a jest test specifically for this function? const input = require('prompt-sync')(); export function choices(): void { const choice = input("Choose a letter"); if (choice === "a") { con ...

WebSocket connection outbound from Docker container fails to establish

Running a TypeScript program on Docker that needs to open a Websocket connection to an external server can be a bit tricky. Here is the scenario: ----------------------- ------------------------------ | My Local Docker | ...

To dismiss a popup on a map, simply click on any area outside the map

Whenever I interact with a map similar to Google Maps by clicking on various points, a dynamically generated popup appears. However, I am facing an issue where I want to close this popup when clicking outside the map area. Currently, the code I have writte ...

Issue with Angular ngFor within a particular dialog window?

(respPIN and internalNotes are both of type InternalNotes[]) When the code in encounter.component.ts is set like this: this.ps.GetInternalNotes(resp.PersonID.toString()).subscribe(respPIN => { this.internalNotes = respPIN; }); An ERROR occurs: Err ...

Creating keys from extensive JSON data without having to manually match types using Typescript

Is there a way to efficiently parse and access the values in large JSON files using Typescript, without the need to manually define interfaces for all expected key/value pairs? In the past, working with small JSON files required only extracting a few spec ...

The 'authorization' property is not available on the 'Request' object

Here is a code snippet to consider: setContext(async (req, { headers }) => { const token = await getToken(config.resources.gatewayApi.scopes) const completeHeader = { headers: { ...headers, authorization: token ...

A Promise signature allows for the compilation of function bodies that return undefined

The compiler error that I expected to see when using this function does not actually occur. The function body is capable of returning undefined, yet the type signature does not mention this possibility. async function chat(_: at.ChatLine): Promise<Arr ...

Customize the appearance of the Material UI expansion panel when it is in its expanded

Is there a way to customize the height of an expanded expansion panel summary? Specifically, I am looking to remove the min-height property and set the summary panel's height to 40px instead of the default 64px. I have attempted to make this change in ...

What is the best way to incorporate vertical scrolling into a React material table?

I'm having trouble getting vertical scroll to work with my material table in React Typescript. Horizontal scroll is functioning properly for large data, but I'm stuck on implementing the vertical scroll. Here's my code: {isLoading ? ...

Is it possible to exclude a certain prop from a styled component that has emotions?

Within my code, there exists a component called BoxWithAs, which is defined as follows: const BoxWithAs = styled.div( { WebkitFontSmoothing: 'antialiased', MozOsxFontSmoothing: 'grayscale' // And more … } ); Everythin ...

The code executes smoothly on my local machine, but encounters an error on Heroku: [TypeError: An invalid character is present in the header content ["Authorization"]] {error code: 'ERR_INVALID_CHAR'}

I am currently working on a chatbot project that utilizes the openAI API to generate responses based on specific prompts related to a particular topic. Everything works perfectly when I test the code on my local machine. However, upon deploying it to Herok ...

Issue with NgFor nested component not refreshing after @Input modification

When populating a component called ContactUpdatableItem within a NgFor, the code looks like this: <section class="plContactCreation-listItem" *ngFor="let contact of contacts$ | async; index as idx" > <contact-updatable-item [c ...

What are the steps to lift non-React statics using TypeScript and styled-components?

In my code, I have defined three static properties (Header, Body, and Footer) for a Dialog component. However, when I wrap the Dialog component in styled-components, TypeScript throws an error. The error message states: Property 'Header' does no ...

Dynamic form groups in Angular: Avoiding the issue of form inputs not binding to form groups

Purpose My goal is to develop a dynamic form in Angular that adjusts its fields based on an array received from the backend. For example, if the array contains ["one", "two", "three", "four"], the form should only ...

`The form input status color remains static and does not update`

I encountered a situation in one of my projects where I need to visually indicate if a field is correct or incorrect based on the value of another field. To better illustrate this issue, I have created an example here. The main challenge: I am struggling ...

Dynamically attach rows to a table in Angular by triggering a TypeScript method with a button click

I need help creating a button that will add rows to a table dynamically when pressed. However, I am encountering an error when trying to call the function in TypeScript (save_row()). How can I successfully call the function in TypeScript and dynamically a ...

What are effective strategies for troubleshooting Dependency Injection problems in nest.js?

Can someone explain the process of importing a third-party library into NestJS using Dependency Injection? Let's say we have a class called AuthService: export class AuthService { constructor( @Inject(constants.JWT) private jsonWebToken: any, ...

Can the ElasticSearch standard Node client be considered secure for integration with cloud functions?

When working with my Typescript cloud functions on GCP, I have been making direct HTTP requests to an ElasticSearch node. However, as my project expands, I am considering switching to the official '@elastic/elasticsearch' package for added conven ...