Our types

enum TokenType {
  VariableDeclaration = 'VariableDeclaration',
  AssignmentOperator = 'AssignmentOperator',
  Literal = 'Literal',
  String = 'String',
  LineBreak = 'LineBreak',
  Log = 'Log'
}

interface TokenNode<T extends TokenType> {
  type: T
}

interface TokenValueNode<T extends TokenType> extends TokenNode<T> {
  value: string
}

type Token =
  TokenNode<TokenType.AssignmentOperator> |
  TokenNode<TokenType.VariableDeclaration> |
  TokenNode<TokenType.LineBreak> |
  TokenNode<TokenType.ConsoleLog> |
  TokenValueNode<TokenType.Literal> |
  TokenValueNode<TokenType.String>

Phew, some actual code.

Take a minute to read over this, and we'll step through it to explain what it's doing, and why we're doing it.