interface TokenNode<T extends TokenType> {
  type: T

A basic interface that will be the "lowest level" node type that we support.

If you're not super familiar with Typescript or generics syntax, here's a quick recap:


Lets assume for the minute that the JS/TS standard library didn't come with a type for an array, and we wanted to create one that supported all of the basic Javascript primitives: string, number, boolean etc.

To accomplish this, we may write:

interface StringArray {
  elements: string[]

interface NumberArray {
  elements: number[]

const strings: StringArray = { elements: [ 'hello', 'world' ] }
const numbers: NumberArray = { elements: [ 1, 2, 3 ] }

This would work, but would mean that we're duplicating code for every single type that we may want to support.

To make this easier, Typescript allows us to pass types as arguments to interfaces & functions. These arguments can then be used as values for types inside that definition.

Take the following for example:

interface Array<T> {
  elements: T[]

Here we are taking a single generic parameter T, and we are using it as the value for our elements key.

This means that we can now use this interface like so:

const strings: Array<string> = { elements: [ 'hello', 'world' ] }
const numbers: Array<number> = { elements: [ 1, 2, 3 ] }

No more duplicating types.

T extends TokenType

So now that we know that T is a generic, what is this extends TokenType thing?

This is called a type constraint.

At a high level, this is equivalent to is one of <x>, where X is the type after extends.

So in this case, we're saying that T needs to be one of the possible values of TokenType


type VariableDeclarationNode = TokenNode<TokenType.VariableDeclaration>

Will not work:

type MyAwesomeNode = TokenNode<'awesome'>
// Type '"string"' does not satisfy the constraint 'TokenType'


So our interface here is just an object with a variable type, who's type is what we pass in when constructing our TokenNode.