UNPKG

@mistlog/typetype

Version:

A programming language designed for typescript type generation

308 lines (244 loc) 7.12 kB
# TypeType &middot; [![Build Status](https://github.com/mistlog/typedraft/workflows/build/badge.svg)](https://github.com/mistlog/typedraft/workflows/build/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/mistlog/typetype/badge.svg)](https://coveralls.io/github/mistlog/typetype) TypeType is designed to generate complex typescript type with ease. * playground: https://mistlog.github.io/typetype-playground/ ## Usage ```bash > npm i -D @mistlog/typetype ``` ### CLI > example: [typetype-examples/package.json](https://github.com/mistlog/typetype-examples/blob/main/package.json) ```bash typetype build <dir>: build all *.type files in <dir> typetype build -w <dir>: watch all *.type files in <dir> typetype clean <dir>: remove all generated *.ts files in <dir> typetype debug <file>: build <file> in debug mode(backtrace will be available) ``` ### API > example: [typetype-examples/index.ts](https://github.com/mistlog/typetype-examples/blob/main/index.ts) ```ts import { transform } from "@mistlog/typetype"; const input = ` type function TypeName = (T) => ^{ if(T extends string) { return "string" } else { return "number" } } `; const output = transform(input).code; console.log(output); // output: type TypeName<T> = T extends string ? "string" : "number"; ``` Debug mode: ```ts const output = transform(input, { debug: true }).code; ``` when `debug` is true, backtrace will be available: ```log Expected end of input but ";" found. x 1:11-1:11 MultiLineComment | type a = 1; | ^ o 1:11-1:11 _ | type a = 1; | ^ x 1:11-1:11 TypeFunctionDeclaration | type a = 1; ... |/ / | | |/ o 1:1-1:11 TypeFile type a = 1; ``` ## Examples - all examples: https://github.com/mistlog/typetype-examples In the [url-parser](https://github.com/mistlog/typetype-examples/blob/main/examples/url-parser/url-parser.type) example, `function parseURL` will be translated to generic type `parseURL<text>` in typescript: ```ts // input type function parseURL = (text) => ^{ if (parseProtocol<text> extends [infer protocol, infer rest]) { return { protocol, rest } } else { return never } } ``` ```ts // output type parseURL<text> = parseProtocol<text> extends [infer protocol, infer rest] ? { protocol: protocol; rest: rest; } : never; ``` Conditional type is presented in this way: ```ts ^{ if ... else ...} ``` It can be nested so that the logic is clear: ```ts type function _isNumberString = (text) => ^{ if(text extends "") { return true } else if(text extends `${infer digit}${infer rest}`) { return ^{ if(digit extends Digit) { return _isNumberString<rest> } else { return false } } } else { return false } } ``` ## Syntax * visit playground: https://mistlog.github.io/typetype-playground/ * or [examples/syntax](https://github.com/mistlog/typetype-examples/blob/main/examples/syntax/syntax.type) ### Basic type ```ts type a = never type b = number type c = string ``` ```ts type value = 1 type bool = true type tuple = [1, 2, 3] type array = string[][] type str = "abc" type template = `value is: ${value}` type obj = { a: 1, b: "abc", c: [1, 2] } type valueDeep = obj["c"][1] type keys = keyof { readonly a?: 1, b: 2 } ``` ### Union and Intersection We use `union [...]` or `| [...]` to denote union type. ```ts type u1 = union [0, 1, 2] type u2 = | [0, 1, 2] ``` Because [an intersection type combines multiple types into one](https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html?ref=hackernoon.com#intersection-types), we use `combine [...]` or `& [...]` for intersection type: ```ts type i1 = combine [{ a: 1 }, { b: 2 }] type i2 = & [{ a: 1 }, { b: 2 }] ``` ### Function type ```ts type f1 = type () => void type f2 = type (a:number, b:string) => number type f3 = type () => type (a:number, b:string) => void ``` ### Conditional type ```ts /* type conditional = 1 extends string ? "string" : "number" */ type conditional = ^{ if(1 extends string) { return "string" } else { return "number" } } ``` nested: ```ts /* type conditional2 = 1 extends string ? "string" : 1 extends 1 ? "is 1" : "not 1"; */ type conditional2 = ^{ if(1 extends string) { return "string" } else { return ^{ if(1 extends 1) { return "is 1" } else { return "not 1" } } } } ``` ### Mapped type ```ts /* type mapped1 = { [K in Keys]: boolean } */ type mapped1 = ^{ for(K in Keys) { return { key: K, value: boolean } } } ``` ```ts /* type mapped2 = { [K in Keys as `get${K}`]: () => string } */ type mapped2 = ^{ for(K in Keys) { return { key: `get${K}`, value: type () => string } } } ``` ### Generic ```ts /* export type Foo<T> = T extends { a: infer U; b: infer U; } ? U : never */ type function Foo = (T) => ^{ if(T extends {a: infer U, b: infer U}) { return U } else { return never } } ``` With constraint: * [examples/type-challenges/4-easy-pick](https://github.com/mistlog/typetype-examples/blob/main/examples/type-challenges/4-easy-pick.type) ```ts /* export type MyPick<T, Keys extends keyof T> = { [K in Keys]: T[K] } */ export type function MyPick = (T, Keys extends keyof T) => ^{ for(K in Keys) { return { key: K, value: T[K] } } } ``` ### Object spread Object spread syntax can be used, and it will be translated to `object$assign<{}, [...]>`: * [examples/url-parser-2/url-parser-2.type](https://github.com/mistlog/typetype-examples/blob/main/examples/url-parser-2/url-parser-2.type) ```ts export type function parseURL = (text) => ^{ if (parseProtocol<text> extends [infer protocol, infer rest]) { return { protocol, ...parseAuthority<rest> } } else { return never } } ``` as long as `object$assign` is available globally, this works fine: ```ts export type parseURL<text> = parseProtocol<text> extends [infer protocol, infer rest] ? object$assign<{}, [{ protocol: protocol; }, parseAuthority<rest>]> : never; ``` you can polyfill it using type lib such as [ts-toolbelt](https://github.com/millsp/ts-toolbelt), for example: [polyfill/global.d.ts](https://github.com/mistlog/typetype-examples/blob/main/polyfill/global.d.ts). ## How it works? It's `AST -> AST` transformation. We use [react-peg](https://github.com/mistlog/react-peg) to write parser, as you can see in [./src/parser/expression](./src/parser/expression/expression.tsx), generator is even simpler than parser, in [./src/generator/generator](./src/generator/generator.ts), `typetype AST` is used to generate corresponding `babel AST`. ## License This project is [MIT licensed](https://github.com/mistlog/typetype/blob/master/LICENSE).