DEV Community

Cover image for Typescript Code Generation
Jason
Jason

Posted on

Typescript Code Generation

Do you ever wonder how to generate TypeScript code automatically?

Using the TypeScript Compiler API, we can leverage it's factory functions for writing scripts that can generate TypeScript code, based on any rules we want!

To get started, create a new project with npm init then install the "typescript" npm package with npm install typescript. Create a file index.mjs with touch index.mjs which will hold our code generation script.

Next, we want to figure out what kind of code we want to generate. For this example, we will generate the following function:

function add(a: number, b: number): number {
  return a + b;
}
Enter fullscreen mode Exit fullscreen mode

This is a very simple example, but the concepts apply to any type of code you want to generate.

Inside index.mjs, import the "typescript" package and start exploring what is available in it via intellisense. For this example, we see that we will need to define a few "identifiers", specifically a, b and add. Those are all "names" and are represented by identifiers inside Abstract Syntax Trees.

We can start by defining each identifier at the top of our file using the factory method called createIdentifier:

const aId = ts.factory.createIdentifier("a");
const bId = ts.factory.createIdentifier("b");
const addId = ts.factory.createIdentifier("add");
Enter fullscreen mode Exit fullscreen mode

We'll also need the number keyword a few times so we can add that right below the others:

const numberKeyword = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
Enter fullscreen mode Exit fullscreen mode

Then we can start creating the function declaration and use intellisense to figure out what needs to be provided:

const addFunc = ts.factory.createFunctionDeclaration(
  /* modifiers */,
  /* asteriskToken */,
  /* name */,
  /* typeParameters */,
  /* parameters */,
  /* type */,
  /* body */
)
Enter fullscreen mode Exit fullscreen mode

We can see that it takes a few slots for different types of AST nodes. We'll need to use a few of them, and the others we can leave blank with an undefined value.

The first two, modifiers and asteriskToken we can leave blank since we don't need them here. The next one name we can use our addId we defined earlier. For typeParameters we can leave blank. For parameters, we'll need to define two, one for each parameter, using the identifiers we already created. For type we can use the numberKeyword we already defined, this represents the return type of the function. Finally, for body we will create a new block node that includes a return statement with a binary expression that adds the two parameters together.

There are a lot of technical terms here - for in-depth details on everything mentioned here, take a look at my new book "The Typescript Compiler API" that explains everything from A-Z for code generation, AST's and more!

Then to print it out, we can use a printer by doing:

function print(nodes) {
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
  const resultFile = ts.createSourceFile(
    "temp.ts",
    "",
    ts.ScriptTarget.Latest,
    false,
    ts.ScriptKind.TSX
  );

  console.log(printer.printList(ts.ListFormat.MultiLine, nodes, resultFile));
}

print([addFunc]);
Enter fullscreen mode Exit fullscreen mode

The final script should look like this:

import ts from "typescript";

const aId = ts.factory.createIdentifier("a");
const bId = ts.factory.createIdentifier("b");
const addId = ts.factory.createIdentifier("add");
const numberKeyword = ts.factory.createKeywordTypeNode(
  ts.SyntaxKind.NumberKeyword
);

const addFunc = ts.factory.createFunctionDeclaration(
  undefined,
  undefined,
  addId,
  undefined,
  [
    ts.factory.createParameterDeclaration(
      undefined,
      undefined,
      aId,
      undefined,
      numberKeyword,
      undefined
    ),
    ts.factory.createParameterDeclaration(
      undefined,
      undefined,
      bId,
      undefined,
      numberKeyword,
      undefined
    ),
  ],
  numberKeyword,
  ts.factory.createBlock(
    [
      ts.factory.createReturnStatement(
        ts.factory.createBinaryExpression(
          aId,
          ts.factory.createToken(ts.SyntaxKind.PlusToken),
          bId
        )
      ),
    ],
    true
  )
);

function print(nodes) {
  const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
  const resultFile = ts.createSourceFile(
    "temp.ts",
    "",
    ts.ScriptTarget.Latest,
    false,
    ts.ScriptKind.TSX
  );

  console.log(printer.printList(ts.ListFormat.MultiLine, nodes, resultFile));
}

print([addFunc]);
Enter fullscreen mode Exit fullscreen mode

When we run node index.mjs, we should see the following printed to the console:

function add(a: number, b: number): number {
    return a + b;
}
Enter fullscreen mode Exit fullscreen mode

Congratulations, you just generated your first function! If you enjoyed this exercise and want to learn more about code generation, abstract syntax trees, linters, customer diagnostics in Typescript and more, then feel free to check out my new book that covers all of this in-depth. Cheers!

Top comments (0)