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.
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.)
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
, representing literal values (boolean, string, null, object, this, void, etc.) and identifiers (Foo, NumberStringType, etc.), which I print as-is.NumberTypeStructureImpl
, representing numbers, which I print as-is.StringTypeStructureImpl
, which represents strings in double quotes.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.
WriterTypeStructureImpl
, for wrapping writer functions, if we get them. (These are rare.)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.
These are type structures which contain other type structures.
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 |
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;
}
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.
FunctionTypeStructureImpl
: typeParameters, parameters, restParameterInferTypeStructureImpl
: typeParameterMappedTypeStructureImpl
: parameterMemberedObjectTypeStructureImpl
: getAccessors, setAccessorsParameterTypeStructureImpl
outside of FunctionTypeStructureImpl
.