Leveraging the TypeScript compiler API to retrieve data on interface field types

Let's dive into the TypeScript compiler API to extract type information from a given interface:

interface X { 
  x: string 
}

In this example, we are specifically interested in getting the type of property x. Here's a snippet of code showcasing how we can achieve this using the TypeScript compiler API:

import {
  PropertySignature,
  createSourceFile,
  ScriptTarget,
  ScriptKind,
  SyntaxKind,
  InterfaceDeclaration,
  Identifier,
} from 'typescript'

describe('Compiler exploration', () => {
  it('should retrieve the type of X.x property', () => {
    const sourceText = 'interface X { x: string }'
    const ast = createSourceFile('source.ts', sourceText, ScriptTarget.ES5, false, ScriptKind.TS)
    const interfaceX = ast
      .getChildAt(0)
      .getChildren()
      .find((child) => child.kind === SyntaxKind.InterfaceDeclaration) as InterfaceDeclaration
    const propX = interfaceX.members.find((member) => (member.name as Identifier).escapedText === 'x')
    console.log(JSON.stringify(propX, null, 2))
  })
})

The retrieved node for propX reveals the structure and properties related to the desired property:

{
  "pos": 13,
  "end": 23,
  "flags": 0,
  "kind": 151,
  "name": {
    "pos": 13,
    "end": 15,
    "flags": 0,
    "escapedText": "x"
  },
  "type": {
    "pos": 16,
    "end": 23,
    "flags": 0,
    "kind": 137
  }
}

While the name of the node is readily accessible, extracting the actual type information might prove to be more challenging due to insufficient data within the type node.

How can we obtain the type information for the property? Our goal is to acquire the type "string" associated with property x.

Answer №1

To achieve this, the solution involved constructing a Program (requiring a CompilerHost) and utilizing the TypeChecker as suggested by @MattMcCutchen:

The CompilerHost (although not obligatory in class form, I personally found it more convenient):

export const SAMPLE_FILE_NAME = 'sample.ts'

export class TestCompilerHost implements CompilerHost {
  constructor(private readonly code: string) {}
  fileExists = () => true
  getCanonicalFileName = () => SAMPLE_FILE_NAME
  getCurrentDirectory = () => ''
  getDefaultLibFileName = () => 'lib.d.ts'
  getDirectories = () => []
  getNewLine = () => '\n'
  readFile = () => null
  useCaseSensitiveFileNames = () => true
  writeFile = () => {}
  getSourceFile(filename: string): SourceFile {
    return createSourceFile(filename, this.code, ScriptTarget.ES5, true)
  }
}

Create a Program:

const config: CompilerOptions = {
  noResolve: true,
  target: ScriptTarget.ES5,
}
const sourceText = `interface X { x: string }`
const program = createProgram([SAMPLE_FILE_NAME], config, new TestCompilerHost(sourceText))

Determine the interface and property similar to the question (with a small alteration in how the SourceFile is accessed):

const ast = program.getSourceFile(SAMPLE_FILE_NAME)
const interfaceX = ast
  .getChildAt(0)
  .getChildren()
  .find((child) => child.kind === SyntaxKind.InterfaceDeclaration) as InterfaceDeclaration
const propX = interfaceX.members.find((member) => (member.name as Identifier).escapedText === 'x')

Lastly, retrieve the type:

const typeChecker = program.getTypeChecker()
const type = typeChecker.getTypeAtLocation(propX.type)
const stringType = typeChecker.typeToString(type)

In this context, propX refers to the same variable as mentioned in my initial query.

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

Can all objects within an interface be iterated through in order to retrieve both the key and its corresponding value?

I have created an interface that is capable of accepting a variety of search criteria and then passing it to a service that will incorporate those values into the service URL. I am wondering if there is a way to iterate through all the objects in the inter ...

Is it possible for transclusion to display content from external sources using *ngIf and <ng-content>?

In my Angular4 Project, I have come across this snippet of code: <div class="divider"></div> <ng-content select=".nav-toggle"></ng-content> Now, I am trying to figure out a way to display the divider only when there is content pr ...

Getting the current page name within the app.component.ts file in Ionic 3 - a simple guide

Is it possible to retrieve the current active page name within the app.component.ts file in Ionic without having to add code to any other pages? ...

What methods can TypeScript use to accommodate this kind of Generic Type?

It appears that there is already an existing GitHub issue related to this topic. You can find it here: ts#1213. This type of usage resembles a high-order function, and I am unsure if TypeScript supports it. Although the interface remains the same, there ...

How to troubleshoot the issue of "Property not found in type 'Vue'"

I am currently utilizing Typescript along with Vuejs to create an application. I have multiple independent components (.vue) files that I am bringing in to a Typescript (.ts) file. Within the Typescript file, I am importing Vue from the npm Vue library and ...

Creating an image using the @aws-sdk/client-bedrock-runtime package is a simple process

Having crafted a BedrockRuntimeClient using typescript, I'm stumped on how to call upon the model and execute the command. const client = new BedrockRuntimeClient({ region: "us-east-1", apiVersion: '2023-09-30', ...

Implementing a NextJS client component within a webpage

I am currently working with NextJS version 14 and I am in the process of creating a landing page. In one of the sections, I need to utilize the useState hook. I have specified my component as "use-client" but I am still encountering an error stating that " ...

Angular 6 - Ensuring all child components are instances of the same component

My issue has been simplified: <div *ngIf="layout1" class="layout1"> <div class="sidebar-layout1"> some items </div> <child-component [something]="sth"></child-component> </div> <div *ngIf="!layout1" class= ...

The definition of "regeneratorRuntime" is missing in the rete.js library

After encountering a problem, I managed to find a potential solution. My current challenge involves trying to implement Rete.js in Next.js while using Typescript. The specific error message that's appearing is: regeneratorRuntime is not defined Be ...

What is the best way to "connect" an interface in TypeScript?

I'm working on creating a child interface that mirrors the structure of a base interface. Any tips on how to achieve this? interface BaseInterface { api: { [key: string]: string }; ui: { [key: string]: string }; } interface ChildInterface { / ...

Using @Input to pass data from a parent component to a

Looking to modularize the form code into a separate component for reusability? Consider using @Input and referencing it in the HTML to pass values to the post method. Here's how you can achieve this: Previously, everything worked smoothly when all th ...

What is the best way to retrieve a Map object from Firebase in a TypeScript environment?

Currently, I am working on a cloud function in TypeScript, where I am attempting to retrieve a Map object (also known as nested objects or maps) from Firebase in order to iterate through it. Here is the structure of my Firebase data: https://i.sstatic.ne ...

Stop Mat-chip from automatically inserting a row upon selection

I am working on preventing the automatic addition of a row by the mat-chip module after a single chip has been selected. Even though the max chip count is set to 1, the input remains enabled and adds a new row beneath it as if the user can still type more ...

The Angular Service code cannot be accessed

Currently, I am utilizing Local Storage in an Angular 5 Service by referencing https://github.com/cyrilletuzi/angular-async-local-storage. My goal is to retrieve data from storage initially. In case the value is not present, I intend to fetch data from fir ...

Issue with RxDB: Collection not found upon reload

Exploring the integration of RxDB in my Angular project. I wanted to start with a simple example: export const LANG = { version: 0, title: "Language Key", type: "object", properties: { key: { type: "string", primary: true } }, requ ...

Steps to automatically make jest mocked functions throw an error:

When using jest-mock-extended to create a mock like this: export interface SomeClient { someFunction(): number; someOtherFunction(): number; } const mockClient = mock<SomeClient>(); mockClient.someFunction.mockImplementation(() => 1); The d ...

Angular: The type '"periodic-background-sync"' cannot be assigned to type 'PermissionName'

I am trying to enable background sync, but I keep encountering an error when I try to enter the code. Why can't it be found? Do I need to update something? This is my code: if ('periodicSync' in worker) { const status = await navigato ...

Equivalent Types that Do Not Compile

After reading the article on Building Complex Types in TypeScript Part 2, I encountered issues with the Equal Type code provided: Implementing Type Equality type Equal<A, B> = (<T>() => T extends A ? true : false) extends (<T> ...

The type 'MouseEvent<HTMLButtonElement, MouseEvent>' cannot be matched with the type 'boolean'

Just starting out with TS and running into a problem that TS is pointing out to me. Error: Type '(x: boolean) => void' is not compatible with type '(e: MouseEvent<HTMLButtonElement, MouseEvent>) => void'. Parameters ' ...

Learn how to easily set a radio button using Angular 4 and JavaScript

It seems like a simple task, but I am looking for a solution without using jQuery. I have the Id of a specific radio button control that I need to set. I tried the following code: let radiobutton = document.getElementById("Standard"); radiobutton.checke ...