ts-morph-structures

Type Structures

In ts-morph, types in structures appear as strings, writer functions, or arrays of strings and writer functions. You can convert writer functions to strings, and in fact you do, so this isn’t the burden. The burden is strings, which are not as easy to work with when you’re dealing with a type having arguments, such as Extract<Structures, { kind: StructureKind.Class }>. TypeScript is not a regular language, meaning regular expressions are not sufficient to deal with it. TypeScript types do not define a regular language either, so an abstract syntax tree of the types is really nice to have.

Where ts-morph stops in its structure objects, ts-morph-structures lends a further hand. This project bootstraps from ts-morph nodes to build a more complete (not complete - see “statements”) structure tree, including subtrees for types.

If you just want to parse a type string

declare function parseLiteralType(source: string): TypeStructures;

parseLiteralType() will take a stringified type, and return a TypeStructures object. (It works by creating a temporary type alias, then converting the type alias’s type node into a type structure.)

Basic type structures

Type structures are instances of classes, representing type nodes in the TypeScript AST. There are a few fundamental type structures (meaning they can’t get any simpler, and thus they have no children):

LiteralTypeStructureImpl and StringTypeStructureImpl have a read-only stringValue property. Their constructors take a single string as the string value.

Similarly, NumberTypeStructureImpl has a readonly numberValue property, and a constructor taking a numeric value.

Also, each of these has a static .get() method, which returns a singleton structure for the value you pass in. These structures are not cloneable because of their constant values, but I don’t see this being a big problem.

Similarly, WriterTypeStructureImpl takes a writer function as its constructor’s first argument, and exposes it via the readonly writerFunction property.

From these, I provide more complex type structures.

Complex type structures

These are type structures which contain other type structures.

Table of type structure classes

Class name Examples Key properties
ArrayTypeStructureImpl string[] objectType
ConditionalTypeStructureImpl foo extends true ? string : never checkType, extendsType, trueType, falseType
FunctionTypeStructureImpl ("new" or "get" or "set" or "") name<typeParameters>(parameters, ...restParameter) ("=>" or ":" ) returnType name, typeParameters, parameters, restParameter, returnType, writerStyle
ImportTypeStructurlImpl import("ts-morph").StatementStructures<> argument, qualifier, childTypes
IndexedAccessStructureImpl NumberStringType["repeatForward"] objectType, childTypes ([TypeStructures])
InferTypeStructureImpl Elements extends [infer Head, ...infer Tail] typeParameter
IntersectionTypeStructureImpl Foo & Bar childTypes
LiteralTypeStructureImpl string, number, identifiers, etc. stringValue
MappedTypeStructureImpl { readonly [key in keyof Foo]: boolean } parameter, type
MemberedObjectTypeStructureImpl See below getAccessors, indexSignatures, methods, properties, setAccessors
NumberTypeStructureImpl 1, 2, 3, 4.5, 6, Infinity, 0.25, etc. numberValue
ParameterTypeStructureImpl foo: boolean name, typeStructure
ParenthesesTypeStructureImpl (string) childTypes ([TypeStructure])
PrefixOperatorsTypeStructureImpl keyof typeof MyClass operators, objectType
QualifiedNameTypeStructureImpl SyntaxKind.SourceFile childTypes (string[])
StringTypeStructureImpl "Hello World" stringValue
TemplateLiteralTypeStructureImpl `one${"A"}two${"C"}three` head, spans
TupleTypeStructureImpl [string, number] childTypes
TypeArgumentedTypeStructureImpl Pick<Array, "slice"> objectType, childTypes
TypePredicateTypeStructureImpl asserts condition is true hasAssertsKeyword, parameterName, isType
UnionTypeStructureImpl “one” | “two” | “three” childTypes
WriterTypeStructureImpl Wrapper for (writer: CodeBlockWriter) => void writerFunction

Membered object types

These are very similar to interfaces.

class Foo {}
type FooAlias = {
  readonly value: string;
  doSomething(): number;
  get myValue(): string;
  set myValue(value: string);

  // call signature
  (...args: unknown[]): Foo;

  // construct signature
  new (...args): Foo;
}

type BarAlias = {
  // index signature
  [foo: string]: number;
}

Why is there a literal type structure? Couldn’t we just define a string instead?

Initially, raw strings for literals was a specific goal for this. Alas, the forEachAugmentedStructureChild() API is for iterating over objects. To keep the iteration API simpler, I converted back to requiring LiteralTypeStructureImpl objects in these cases.

It’s a trade-off. Simpler types for obvious uses, or consistent API for convenient iteration?

Besides, if I had kept strings as literals, I would forever be reserving them for that purpose, and no future expansion of this library could ever use them for any other purpose. I can’t justify that right now.

A few notes