Skip to content

Commit a32c976

Browse files
committed
feat: Support for TS 4.1 mapped types + string literal types
Resolves #1397
1 parent bf67230 commit a32c976

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

59 files changed

+3491
-172
lines changed

examples/basic/src/flattened.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* A class that contains members with flattened properties.
33
*/
4-
class FlattenedClass {
4+
export class FlattenedClass {
55
/**
66
* A member that accepts an option object defined inline.
77
*
@@ -92,7 +92,7 @@ class FlattenedClass {
9292
* @param callback.param A parameter of the typed function callback.
9393
* @param callback.optionalParam An optional parameter of the typed function callback.
9494
*/
95-
function flattenedCallback(
95+
export function flattenedCallback(
9696
callback: (param: number, optionalParam?: string) => string
9797
) {}
9898

@@ -105,7 +105,7 @@ function flattenedCallback(
105105
* @param options.moreOptions A typed child object of the options object.
106106
* @param options.moreOptions.moreValues A value of the typed child object.
107107
*/
108-
function flattenedParameter(options: {
108+
export function flattenedParameter(options: {
109109
[name: string]: any;
110110
value?: string;
111111
anotherValue?: string;
@@ -121,7 +121,7 @@ function flattenedParameter(options: {
121121
* @param indexed.index The index property comment.
122122
* @param indexed.test A property of the index signature instance.
123123
*/
124-
function flattenedIndexSignature(indexed: {
124+
export function flattenedIndexSignature(indexed: {
125125
[index: number]: { name: string; value?: number };
126126
test: string;
127127
}) {}

examples/basic/src/generics.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* @param value A generic parameter.
66
* @returns A generic return value.
77
*/
8-
function testFunction<T>(value: T): T {
8+
export function testFunction<T>(value: T): T {
99
return value;
1010
}
1111

@@ -14,7 +14,7 @@ function testFunction<T>(value: T): T {
1414
*
1515
* @param T The generic type parameter.
1616
*/
17-
interface A<T> {
17+
export interface A<T> {
1818
/**
1919
* A generic member function.
2020
*
@@ -29,7 +29,7 @@ interface A<T> {
2929
* @param <T> The first generic type parameter.
3030
* @param <C> The second generic type parameter.
3131
*/
32-
interface B<T, C> {
32+
export interface B<T, C> {
3333
/**
3434
* A generic member function.
3535
*
@@ -51,31 +51,31 @@ interface B<T, C> {
5151
*
5252
* @typeparam T The leftover generic type parameter.
5353
*/
54-
interface AB<T> extends A<T>, B<T, boolean> {}
54+
export interface AB<T> extends A<T>, B<T, boolean> {}
5555

5656
/**
5757
* An interface extending a generic interface and setting its type parameter.
5858
*/
59-
interface ABString extends AB<string> {}
59+
export interface ABString extends AB<string> {}
6060

6161
/**
6262
* An interface extending a generic interface and setting its type parameter.
6363
*/
64-
interface ABNumber extends AB<number> {}
64+
export interface ABNumber extends AB<number> {}
6565

6666
/**
6767
* A function returning a generic array with type parameters.
6868
*
6969
* @return The return value with type arguments.
7070
*/
71-
function getGenericArray(): Array<string> {
71+
export function getGenericArray(): Array<string> {
7272
return [""];
7373
}
7474

7575
/**
7676
* Conditional type with infer
7777
*/
78-
type PopFront<T extends any[]> = ((...args: T) => any) extends (
78+
export type PopFront<T extends any[]> = ((...args: T) => any) extends (
7979
a: any,
8080
...r: infer R
8181
) => any
@@ -86,7 +86,7 @@ type PopFront<T extends any[]> = ((...args: T) => any) extends (
8686
* See GH#1150. Calling typeChecker.typeToString on this type will send TS into an infinite
8787
* loop, which is undesirable.
8888
*/
89-
type HorribleRecursiveTypeThatShouldNotBeUsedByAnyone<
89+
export type HorribleRecursiveTypeThatShouldNotBeUsedByAnyone<
9090
T extends any[],
9191
R = {}
9292
> = {
@@ -98,3 +98,9 @@ type HorribleRecursiveTypeThatShouldNotBeUsedByAnyone<
9898
}
9999
>;
100100
}[T["length"] extends 0 ? 0 : 1];
101+
102+
export type DoubleKey<T> = { [K in keyof T & string as `${K}${K}`]: T[K] }
103+
104+
export function doubleKey<T>(arg: T) {
105+
return {} as { [K in keyof T & string as `${K}${K}`]: T[K] }
106+
}

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
"progress": "^2.0.3",
3131
"semver": "^7.3.2",
3232
"shelljs": "^0.8.4",
33-
"typedoc-default-themes": "0.12.0-beta.6"
33+
"typedoc-default-themes": "0.12.0-beta.7"
3434
},
3535
"peerDependencies": {
3636
"typescript": "3.9.x || 4.0.x || 4.1.x"

src/lib/converter/types.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import {
2222
UnknownType,
2323
MappedType,
2424
} from "../models";
25+
import { TemplateLiteralType } from "../models/types/template-literal";
26+
import { zip } from "../utils/array";
2527
import { Context } from "./context";
2628
import { ConverterEvents } from "./converter-events";
2729
import { createSignature } from "./factories/signature";
@@ -68,6 +70,7 @@ export function loadConverters() {
6870
namedTupleMemberConverter,
6971
mappedConverter,
7072
literalTypeConverter,
73+
templateLiteralConverter,
7174
thisConverter,
7275
tupleConverter,
7376
typeOperatorConverter,
@@ -500,6 +503,7 @@ const mappedConverter: TypeConverter<
500503
templateType: ts.Type;
501504
typeParameter: ts.TypeParameter;
502505
constraintType: ts.Type;
506+
nameType?: ts.Type;
503507
}
504508
> = {
505509
kind: [ts.SyntaxKind.MappedType],
@@ -514,7 +518,8 @@ const mappedConverter: TypeConverter<
514518
? removeUndefined(templateType)
515519
: templateType,
516520
kindToModifier(node.readonlyToken?.kind),
517-
optionalModifier
521+
optionalModifier,
522+
node.nameType ? convertType(context, node.nameType, target) : void 0
518523
);
519524
},
520525
convertType(context, type, node, target) {
@@ -529,7 +534,8 @@ const mappedConverter: TypeConverter<
529534
? removeUndefined(templateType)
530535
: templateType,
531536
kindToModifier(node.readonlyToken?.kind),
532-
optionalModifier
537+
optionalModifier,
538+
type.nameType ? convertType(context, type.nameType, target) : void 0
533539
);
534540
},
535541
};
@@ -601,6 +607,33 @@ const literalTypeConverter: TypeConverter<
601607
},
602608
};
603609

610+
const templateLiteralConverter: TypeConverter<
611+
ts.TemplateLiteralTypeNode,
612+
ts.TemplateLiteralType
613+
> = {
614+
kind: [ts.SyntaxKind.TemplateLiteralType],
615+
convert(context, node, target) {
616+
return new TemplateLiteralType(
617+
node.head.text,
618+
node.templateSpans.map((span) => {
619+
return [
620+
convertType(context, span.type, target),
621+
span.literal.text,
622+
];
623+
})
624+
);
625+
},
626+
convertType(context, type, _node, target) {
627+
assert(type.texts.length === type.types.length + 1);
628+
const parts: [Type, string][] = [];
629+
for (const [a, b] of zip(type.types, type.texts.slice(1))) {
630+
parts.push([convertType(context, a, target), b]);
631+
}
632+
633+
return new TemplateLiteralType(type.texts[0], parts);
634+
},
635+
};
636+
604637
const thisConverter: TypeConverter<ts.ThisTypeNode> = {
605638
kind: [ts.SyntaxKind.ThisType],
606639
convert() {

src/lib/models/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export { PredicateType } from "./predicate";
1111
export { QueryType } from "./query";
1212
export { ReferenceType } from "./reference";
1313
export { ReflectionType } from "./reflection";
14+
export { TemplateLiteralType } from "./template-literal";
1415
export { NamedTupleMember, TupleType } from "./tuple";
1516
export { TypeOperatorType } from "./type-operator";
1617
export { TypeParameterType } from "./type-parameter";

src/lib/models/types/mapped.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Type } from "./abstract";
44
* Represents a mapped type.
55
*
66
* ```ts
7-
* { -readonly [K in keyof U]?: Foo }
7+
* { -readonly [K in keyof U & string as `a${K}`]?: Foo }
88
* ```
99
*/
1010
export class MappedType extends Type {
@@ -15,7 +15,8 @@ export class MappedType extends Type {
1515
public parameterType: Type,
1616
public templateType: Type,
1717
public readonlyModifier?: "+" | "-",
18-
public optionalModifier?: "+" | "-"
18+
public optionalModifier?: "+" | "-",
19+
public nameType?: Type
1920
) {
2021
super();
2122
}
@@ -26,11 +27,24 @@ export class MappedType extends Type {
2627
this.parameterType.clone(),
2728
this.templateType.clone(),
2829
this.readonlyModifier,
29-
this.optionalModifier
30+
this.optionalModifier,
31+
this.nameType?.clone()
3032
);
3133
}
3234

3335
equals(other: Type): boolean {
36+
if (!(other instanceof MappedType)) {
37+
return false;
38+
}
39+
40+
if (this.nameType && other.nameType) {
41+
if (!this.nameType.equals(other.nameType)) {
42+
return false;
43+
}
44+
} else if (this.nameType !== other.nameType) {
45+
return false;
46+
}
47+
3448
return (
3549
other instanceof MappedType &&
3650
other.parameter == this.parameter &&
@@ -54,6 +68,8 @@ export class MappedType extends Type {
5468
"": "",
5569
}[this.optionalModifier ?? ""];
5670

57-
return `{ ${read}[${this.parameter} in ${this.parameterType}]${opt}: ${this.templateType}}`;
71+
const name = this.nameType ? ` as ${this.nameType}` : "";
72+
73+
return `{ ${read}[${this.parameter} in ${this.parameterType}${name}]${opt}: ${this.templateType}}`;
5874
}
5975
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { Type } from "./abstract";
2+
3+
/**
4+
* TS 4.1 template literal types
5+
* ```ts
6+
* type Z = `${'a' | 'b'}${'a' | 'b'}`
7+
* ```
8+
*/
9+
export class TemplateLiteralType extends Type {
10+
readonly type = "template-literal";
11+
12+
head: string;
13+
tail: [Type, string][];
14+
15+
constructor(head: string, tail: [Type, string][]) {
16+
super();
17+
this.head = head;
18+
this.tail = tail;
19+
}
20+
21+
clone(): Type {
22+
return new TemplateLiteralType(
23+
this.head,
24+
this.tail.map(([type, text]) => [type.clone(), text])
25+
);
26+
}
27+
28+
equals(other: Type): boolean {
29+
return (
30+
other instanceof TemplateLiteralType &&
31+
this.head === other.head &&
32+
this.tail.length === other.tail.length &&
33+
this.tail.every(([type, text], i) => {
34+
return (
35+
type.equals(other.tail[i][0]) && text === other.tail[i][1]
36+
);
37+
})
38+
);
39+
}
40+
41+
toString() {
42+
return [
43+
"`",
44+
this.head,
45+
...this.tail.map(([type, text]) => {
46+
return "${" + type + "}" + text;
47+
}),
48+
"`",
49+
].join("");
50+
}
51+
}

src/lib/serialization/schema.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,8 @@ type _ModelToObject<T> =
8888
? TupleType
8989
: T extends M.UnknownType
9090
? UnknownType
91+
: T extends M.TemplateLiteralType
92+
? TemplateLiteralType
9193
: T extends M.Type
9294
? SomeType // Technically AbstractType, but the union is more useful
9395
: // Miscellaneous
@@ -285,6 +287,11 @@ export interface NamedTupleMemberType
285287
element: ModelToObject<M.NamedTupleMember["element"]>;
286288
}
287289

290+
export interface TemplateLiteralType
291+
extends Type,
292+
S<M.TemplateLiteralType, "type" | "head"> {
293+
tail: [Type, string][];
294+
}
288295
export interface MappedType
289296
extends Type,
290297
S<
@@ -295,6 +302,7 @@ export interface MappedType
295302
| "templateType"
296303
| "readonlyModifier"
297304
| "optionalModifier"
305+
| "nameType"
298306
> {}
299307

300308
export interface TypeOperatorType

src/lib/serialization/serializer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ const serializerComponents: (new (owner: Serializer) => SerializerComponent<
148148
S.ReflectionTypeSerializer,
149149
S.LiteralTypeSerializer,
150150
S.TupleTypeSerializer,
151+
S.TemplateLiteralTypeSerializer,
151152
S.NamedTupleMemberTypeSerializer,
152153
S.MappedTypeSerializer,
153154
S.TypeOperatorTypeSerializer,

0 commit comments

Comments
 (0)