Typescript Code Generation
Tags: typescript, javascript, code, generation
Published: 2023-05-24 17:52:58

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:

```ts
function add(a: number, b: number): number {
  return a + b;
}
```

> 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`:

```ts
const aId = ts.factory.createIdentifier("a");
const bId = ts.factory.createIdentifier("b");
const addId = ts.factory.createIdentifier("add");
```

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

```ts
const numberKeyword = ts.factory.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword)
```

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

```ts
const addFunc = ts.factory.createFunctionDeclaration(
  /* modifiers */,
  /* asteriskToken */,
  /* name */,
  /* typeParameters */,
  /* parameters */,
  /* type */,
  /* body */
)
```

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](https://typescriptcompilerapi.com)" 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:

```ts
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]);
```

The final script should look like this:

```js
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]);
```

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;
}
```

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](https://typescriptcompilerapi.com) that covers all of this in-depth. Cheers!