Resolving generic types based on implementation

Viewed 108

I am currently trying to build a tool where i need to resolve generic types for my final output.

Note that i am using the ts-morph library.

As an example, given the following source and the ClassDeclaration of classA as an "entry-point" for crawling types:

type X<T> = { fieldInType: T };
interface Y<T> {
  fieldInInterface: T;
}

class classB<T> {
  fieldInB?: T;
  myFunction(a: T): void { //do something here }
}

class classA extends ClassB<string> {
  interfaceField?: Y<string>;
  typeField?: X<string>;
}

I would like to have an output like this:

types of classA:
   - interface Y { fieldInInterface: string } (from interfaceField)
   - type X = { fieldInType: string }  (from typeField)
   - string (from fieldInB in parentClass)
   - myFunction(a: string): void (from myFunction in parentClass) 

I am unsure how to approach this correctly. Currently, i am recursively crawling all types, starting with the base class (i use get class.getBaseClass() to also recursively crawl the base classes, i hope this is the correct/easiest way).

For the generic types i do the following:

  1. Wait until a generic type is encountered during crawling (e.g. on a class, or a type-alias inside the class) by checking if it has type-arguments.
  2. If so, get the corresponding type-parameters by jumping to the type definition (e.g. to the base class using getBaseClass() or the interface/alias-type using getSymbol() or getAliasSymbol())
  3. Iterate over all type-parameters and get the corresponding type-argument.
  4. Check if the type-argument itself has an entry in a map (explained in the next point). If so, replace the type argument with the resolved type. (This is needed for recursively defined generics)
  5. Save a mapping for each pair, i.e. the symbol of the i-th type-parameter to the type in the type argument

Sorry that i do not post a complete code sample for this, but i do not have a complete implementation of this yet.

My questions:

  1. Is this a proper way to do this? Or is there some magic typescript-compiler/ts-morph function that i overlook that achieves exactly this?
  2. I saw that there is a typeChecker.getTypeOfSymbolAtLocation(..), but i am unsure how to exactly use it, and if it helps in my use-case.
1 Answers

Yes, I think you'll find getTypeAtLocation and getTypeOfSymbolAtLocation useful.

Look at your example at ts-ast-viewer.com, then run this in the dev console:

{
    const classANode = sourceFile.statements[3];
    const classAType = checker.getTypeAtLocation(classANode);
    for (const prop of classAType.getProperties()) {
        const type = checker.getTypeOfSymbolAtLocation(prop, classANode);
        console.log(prop.escapedName, checker.typeToString(type));
    }
}
Related