7

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".

2
  • 1
    I would definitely recommend using the type checker API for this rather than traversing the AST. It looks like you would create a Program, call getTypeChecker, get the symbol of the interface (I'm not sure how to do that part), then use getDeclaredTypeOfSymbol and getPropertyOfType. I'm not writing an answer because I don't have a complete answer. Commented Sep 27, 2018 at 0:12
  • Thanks Matt, that's a good start! Commented Sep 27, 2018 at 9:05

1 Answer 1

6

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.