typescripttypescript-compiler-api

How can I calculate which properties are in use in a TypeScript object type?


I'm looking for a way to calculate what parts of my object types are actually in use throughout my codebase. Let's say I have the following in a types.ts file:

export type Animal = {
  id: number;
  name: string;
  height: number;
  isMammal: boolean;
}

And in another file I make an API call that returns an Animal:

const getAnimal: (id: number) => Promise<Animal>

And in two separate files, I call getAnimal and use the resulting Animal.

const animal1 = await getAnimal(1);
console.log(`Hi! Animal with id ${animal1.id} is named ${animal1.name}.`)
const animal1 = await getAnimal(1);
console.log(`This animal is ${animal1.height} feet tall.`)

But nowhere in my project do I use the isMammal property. How can I write a script that scans all of the files in my project and tells me that isMammal is not in use on the Animal type? Or alternatively, that only id, name, and height are?


Solution

  • To do this with the compiler API, you will want to use and setup a ts.LanguageServer via ts.createLanguageService(...) then use the findReferences method, which returns back all the references used similar to what occurs in an editor.

    Here's a self contained example:

    // setup code because doing this with the compiler API alone is a lot to show
    import { createProjectSync, ts } from "https://deno.land/x/ts_morph@15.1.0/bootstrap/mod.ts";
    
    const project = createProjectSync();
    const sourceFile = project.createSourceFile("file.ts", `
    export type Animal = {
      id: number;
      name: string;
      height: number;
      isMammal: boolean;
    };
    
    const animal: Animal = {} as any;
    animal.id;
    animal.name;
    animal.height;
    `);
    const languageService = project.getLanguageService();
    
    // compiler API code starts here
    const animalType = sourceFile.statements[0] as ts.TypeAliasDeclaration;
    const animalObjectType = animalType.type as ts.TypeLiteralNode;
    
    for (const member of animalObjectType.members) {
      if (ts.isPropertySignature(member)) {
        const findResult = languageService.findReferences(
          sourceFile.fileName,
          member.name.getStart(sourceFile),
        );
        const references = (findResult ?? [])
          .reduce<ts.ReferencedSymbolEntry[]>((a, b) => a.concat(b.references), [])
          .filter(r => !r.isDefinition);
        if (references.length === 0) {
          console.log(member.name.getText(sourceFile));
        }
      }
    }
    

    Outputs:

    isMammal