sparkofreason
sparkofreason

Reputation: 191

TypeScript Compiler API: get type structure

Is there an API method to get the "bottomed out" type information from the TypeScript compiler? Example:

interface User {
  id: number
  name: string
}

type NameOnly = Pick<User, 'name'>

type NameOnlyAliased = NameOnly

In VSCode, if I hover over NameOnlyAliased, it shows:

type NameOnlyAliased = {
    name: string;
}

My question is if there is a function in the compiler API (or other simple way without applying the semantics of aliases, Pick, etc.) to get the info on the right-hand side of the = above, preferably as data (not just a string), something like:

{
  NameAliasedOnly: {
    properties: {
      name: {
         type: 'string'
      }
    }
  }
}

The use-case is to generate code to create fast-check arbitraries from type definitions (if that already exists, then fantastic). I've played around a bit with using ts-json-schema-generator for this purpose, but there are some type definitions it doesn't handle.

Upvotes: 7

Views: 2820

Answers (2)

Scott Rippey
Scott Rippey

Reputation: 15810

I created a CLI tool, ts-simplify, based off of @sparkofreason's answer. It's a compiler, which outputs the simplified primitive types. For example:

npx ts-simplify source-code.ts output-types.ts

This would output a file with the desired output:

export type NameOnlyAliased = {
    name: string;
}

Upvotes: 1

sparkofreason
sparkofreason

Reputation: 191

I've found a solution. It doesn't use the TypeScript compiler API, directly, but rather the excellent ts-morph library, a wrapper for the compiler API which simplifies many tasks. Here's some example code, where the test.ts file contains the example code from my question above.

import { Project, TypeFormatFlags } from 'ts-morph'

const project = new Project({
  tsConfigFilePath: 'tsconfig.json',
  skipAddingFilesFromTsConfig: true,
})
const file = 'test.ts'
project.addSourceFileAtPath(file)

const sourceFile = project.getSourceFile(file)

const typeAlias = sourceFile?.getTypeAlias('NameOnlyAliased')
if (typeAlias) {
  console.log(
    typeAlias
      .getType()
      .getProperties()
      .map(p => [
        p.getName(),
        p
          .getTypeAtLocation(typeAlias)
          .getText(
            undefined,
            TypeFormatFlags.UseAliasDefinedOutsideCurrentScope
          ),
      ])
  )
}

Executing this script gives output [ [ 'name', 'string' ] ], as desired. For more complex types you can navigate into the type hierarchy.

Upvotes: 8

Related Questions