I am trying to create a type tree from extracted types in a typescript file. I am using bun and ts-morph I can handle different types, like primitives, and some objects. But when trying to handle Records, I cannot get the types that compose the record. To give an example, this is the test I want the code to pass:
import { expect, test } from "bun:test";
import createTypeTree from "../src/create-type-tree";
test.only("handle complex Records", () => {
const sourceCode = `
type Id = string;
type Value = {a: number, b: string}
type MyRecord = Record<string, number>;
`;
const result = createTypeTree("temp.ts", sourceCode);
expect(result).toStrictEqual({
MyRecord: { ["string"]: { a: "number", b: "string" } },
Id: "string",
Value: { a: "number", b: "string" },
});
});
This is a simplified and reproducible version of my code
import { Project, Type } from "ts-morph";
type Result = Record<string, unknown>;
export default function createTypeTree(
filePath: string,
testCode?: string,
configFile?: string
): Result {
const project = new Project({
tsConfigFilePath: configFile,
skipAddingFilesFromTsConfig: true,
});
const sourceFile = !testCode
? project.addSourceFileAtPath(filePath)
: project.createSourceFile(filePath, testCode);
const result = [
...sourceFile?.getInterfaces(),
...sourceFile?.getTypeAliases(),
].reduce((acc, current) => {
const name = current.getName();
acc[name] = handleTypes(current.getType());
return acc;
}, {} as Result);
return result;
}
function handleTypes(t?: Type) {
return isPrimitive(t) ? handlePrimitive(t) : handleNotPrimitive(t);
}
function isPrimitive(t?: Type): boolean {
const isString = t?.isString() || t?.isStringLiteral();
const isBoolean = t?.isBoolean() || t?.isBooleanLiteral();
const isNumber = t?.isNumber() || t?.isNumberLiteral();
const isNullish = t?.isNullable();
return Boolean(isNumber || isString || isBoolean || isNullish);
}
function handlePrimitive(t?: Type) {
switch (true) {
case t?.isNumberLiteral(): {
return Number(t?.getText());
}
case t?.isBooleanLiteral(): {
return t?.getText() === "true";
}
default: {
return t?.getText();
}
}
}
function handleNotPrimitive(t?: Type) {
// Here the important part. Record is object but returns {}.
// I want to handle Records here.
// I did not include tuples or arrays for simplicity
if (t?.isObject()) {
return handleObject(t);
}
return "Type not handled";
}
function handleObject(t?: Type) {
const obj: Record<string, unknown> = {};
t?.getProperties().forEach((prop) => {
const name = prop?.isOptional() ? prop?.getName() + "?" : prop?.getName();
const innerDeclaration = prop.getDeclarations();
innerDeclaration.forEach((p) => {
const innerType = p.getType();
obj[name] = handleTypes(innerType);
});
});
return obj;
}
I am trying to create a type tree from extracted types in a typescript file. I am using bun and ts-morph I can handle different types, like primitives, and some objects. But when trying to handle Records, I cannot get the types that compose the record. To give an example, this is the test I want the code to pass:
This is a simplified and reproducible version of my code
How can I extract the types inside a record?