How create TypeLiteral without string/writer when using ts-morph?

399 Views Asked by At

I want to create type files like this programatically:

export type Car = {
    color: string;
    // ... tons of properties
};

Thats seems pretty easy using ts-morph addTypeAlias method:

sourceFile.addTypeAlias({
    name: 'Car',
    type: theType,
    isExported: true,
});

Though unfortunately theType here must be string | WriterFunction. And I want it to be a TypeLiteral! Is there a way to this with ts-morph that I am missing? Ofcourse I could construct my type literal as a string by some looping and string concatenation etc, but I would really like to avoid building my files that way - then I might as well use template/stub files and fs.

Any other way to do this in a more programmatic/Node-class based approach?

2

There are 2 best solutions below

0
Sergey Vyacheslavovich Brunov On BEST ANSWER

Introduction

Let's consider the following Node.js version as the current version:

$ node --version
v18.13.0

Let's consider the following npm version as the current version:

$ npm --version
9.2.0

Let's consider the following versions of the dependencies as the current versions (an excerpt from the package.json file):

{
  <…>,
  "dependencies": {
    "ts-morph": "17.0.1",
    "typescript": "4.9.5"
  },
  "devDependencies": {
    "ts-node": "10.9.1"
  }
}

Overview

Though unfortunately theType here must be string | WriterFunction. And I want it to be a TypeLiteral! Is there a way to this with ts-morph that I am missing?

Currently, it seems there is no straightforward solution.

Please, see the following seemingly related GitHub issues:

Any other way to do this in a more programmatic/Node-class based approach?

Let's consider some possible solutions.

Possible solution: ts-morph: Use object type writer (Writers.objectType() static method)

It may be considered as more programmatic, but it is still a writer-based (not a node-based) approach.

References:

Draft example

src/index-ts-morph.ts file

import { Project, WriterFunction, Writers } from "ts-morph";

async function createFile(outputFilePath: string) {
    const project = new Project();
    const sourceFile = project.createSourceFile(
        outputFilePath,
        undefined,
        { overwrite: true }
    );

    const typeAliasDeclarations = [
        createExportedTypeAliasDeclaration("Book"),
        createExportedTypeAliasDeclaration("House"),
        createExportedTypeAliasDeclaration("Car"),
    ];
    sourceFile.addTypeAliases(typeAliasDeclarations);

    await sourceFile.save();
};

function createExportedTypeAliasDeclaration(name: string) {
    const writerFunction: WriterFunction = Writers.objectType({
        properties: [
            {
                name: "color",
                type: "string"
            }
        ]
    });
    return ({
        name: name,
        type: writerFunction,
        isExported: true,
    });
}

await createFile("output.ts");

Running script and checking its output

$ npx ts-node src/index-ts-morph.ts
$ cat output.ts 
export type Book = {
        color: string;
    };
export type House = {
        color: string;
    };
export type Car = {
        color: string;
    };

Please, note the strange indentation.
Probably, it is caused by a ts-morph defect?

Possible solution: typescript: Use typescript instead of ts-morph

References:

Draft example

src/index-typescript.ts file

import { writeFile } from "fs/promises";
import ts from "typescript";
const { factory } = ts;

async function createFile(outputFilePath: string) {
    const typeAliasDeclarations = [
        createExportedTypeAliasDeclaration("Book"),
        createExportedTypeAliasDeclaration("House"),
        createExportedTypeAliasDeclaration("Car"),
    ];

    const printer = ts.createPrinter({
        newLine: ts.NewLineKind.LineFeed
    });
    const nodes = factory.createNodeArray(typeAliasDeclarations);
    const sourceFile = ts.createSourceFile(
        "this-name-is-ignored.ts",
        "",
        ts.ScriptTarget.Latest,
        false,
        ts.ScriptKind.TS
    );
    const printedString = printer.printList(
        ts.ListFormat.MultiLine,
        nodes,
        sourceFile
    );

    await writeFile(outputFilePath, printedString);
};

function createExportedTypeAliasDeclaration(name: string) {
    const colorPropertySignature = factory.createPropertySignature(
        undefined,
        factory.createIdentifier("color"),
        undefined,
        factory.createTypeReferenceNode(
            factory.createIdentifier("string"),
            undefined
        )
    );

    return factory.createTypeAliasDeclaration(
        [
            factory.createToken(ts.SyntaxKind.ExportKeyword)
        ],
        factory.createIdentifier(name),
        undefined,
        factory.createTypeLiteralNode([
            colorPropertySignature
        ])
    );
}

await createFile("output.ts");

Running script and checking its output

$ npx ts-node src/index-typescript.ts
$ cat output.ts 
export type Book = {
    color: string;
};
export type House = {
    color: string;
};
export type Car = {
    color: string;
};
0
Yacine On

You can use the TypeLiteral type from the typescript library to define the properties of your type Car and then pass it as the type argument to addTypeAlias method in tsmorph.

 import { TypeLiteral } from 'typescript';

const carProperties = [
  { name: 'color', type: 'string' },
  // ... tons of properties
];

const typeLiteral = new TypeLiteral(
  carProperties.map(prop => {
    return {
      name: prop.name,
      type: prop.type,
    };
  })
);

sourceFile.addTypeAlias({
  name: 'Car',
  type: typeLiteral,
  isExported: true,
});