Using typescript compiler API to get interface fields type information

2.2k Views Asked by At

Consider the following interface:

interface X { 
  x: string 
}

I'm trying to use the typescript compiler API to get property x's type. Here's what I have so far:

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

describe('Compiler test', () => {
  it('should be able to find type information about X.x', () => {
    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))
  })
})

Now the content of the propX node is the following:

{
  "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
  }
}

From which the name of the node is clearly extractable, however the type node doesn't seem to have any information that's useful.

How would I get the type information of the property? All I need is "string".

1

There are 1 best solutions below

0
On

So the way to do it was to build a Program (need a CompilerHost to do it) and use the TypeChecker as @MattMcCutchen suggested:

The CompilerHost (you don't need a class implementation, but I 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)
  }
}

Build 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))

Find the interface and property like in the question (only difference is the way of accessing the SourceFile):

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 get the type:

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

Where propX is the same variable as in my question.