Skip to content

Commit

Permalink
fix: Add support for rest types
Browse files Browse the repository at this point in the history
Resolves #1457
  • Loading branch information
Gerrit0 committed Jan 9, 2021
1 parent 0d892ce commit 83d2a2c
Show file tree
Hide file tree
Showing 9 changed files with 311 additions and 55 deletions.
48 changes: 39 additions & 9 deletions src/lib/converter/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
MappedType,
SignatureReflection,
} from "../models";
import { RestType } from "../models/types/rest";
import { TemplateLiteralType } from "../models/types/template-literal";
import { zip } from "../utils/array";
import { Context } from "./context";
Expand Down Expand Up @@ -69,6 +70,7 @@ export function loadConverters() {
queryConverter,
typeLiteralConverter,
referenceConverter,
restConverter,
namedTupleMemberConverter,
mappedConverter,
ts3LiteralBooleanConverter,
Expand Down Expand Up @@ -628,6 +630,15 @@ const referenceConverter: TypeConverter<
},
};

const restConverter: TypeConverter<ts.RestTypeNode> = {
kind: [ts.SyntaxKind.RestType],
convert(context, node) {
return new RestType(convertType(context, node.type));
},
// This is handled in the tuple converter
convertType: requestBugReport,
};

const namedTupleMemberConverter: TypeConverter<ts.NamedTupleMember> = {
kind: [ts.SyntaxKind.NamedTupleMember],
convert(context, node) {
Expand Down Expand Up @@ -825,7 +836,7 @@ const thisConverter: TypeConverter<ts.ThisTypeNode> = {
},
};

const tupleConverter: TypeConverter<ts.TupleTypeNode, ts.TupleType> = {
const tupleConverter: TypeConverter<ts.TupleTypeNode, ts.TupleTypeReference> = {
kind: [ts.SyntaxKind.TupleType],
convert(context, node) {
// TS 3.9 support
Expand All @@ -837,25 +848,44 @@ const tupleConverter: TypeConverter<ts.TupleTypeNode, ts.TupleType> = {
// TS 3.9 support
const elementTypes = node.elements ?? (node as any).elementTypes;
// We need to do this because of type argument constraints, see GH1449
// In TS 4.0 we could use type.target.fixedLength
const types = type.typeArguments?.slice(0, elementTypes.length);
let elements = types?.map((type) => convertType(context, type));

// 3.9 doesn't have named tuple members, so it's fine to skip this there.
if (
ts.isNamedTupleMember &&
elementTypes.every(ts.isNamedTupleMember)
) {
const namedMembers = node.elements as readonly ts.NamedTupleMember[];
if (type.target.labeledElementDeclarations) {
const namedDeclarations = type.target.labeledElementDeclarations;
elements = elements?.map(
(el, i) =>
new NamedTupleMember(
namedMembers[i].name.text,
!!namedMembers[i].questionToken,
namedDeclarations[i].name.getText(),
!!namedDeclarations[i].questionToken,
removeUndefined(el)
)
);
}

// TS 3.9 support - always defined in 4.0
if (type.target.elementFlags) {
elements = elements?.map((el, i) => {
if (type.target.elementFlags[i] & ts.ElementFlags.Variable) {
// In the node case, we don't need to add the wrapping Array type... but we do here.
if (el instanceof NamedTupleMember) {
return new RestType(
new NamedTupleMember(
el.name,
el.isOptional,
new ArrayType(el.element)
)
);
}

return new RestType(new ArrayType(el));
}
// TODO: Optional elements
return el;
});
}

return new TupleType(elements ?? []);
},
};
Expand Down
1 change: 1 addition & 0 deletions src/lib/models/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export { ReferenceType } from "./reference";
export { ReflectionType } from "./reflection";
export { TemplateLiteralType } from "./template-literal";
export { NamedTupleMember, TupleType } from "./tuple";
export { RestType } from "./rest";
export { TypeOperatorType } from "./type-operator";
export { TypeParameterType } from "./type-parameter";
export { UnionType } from "./union";
Expand Down
59 changes: 59 additions & 0 deletions src/lib/models/types/rest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { Type } from "./abstract";

/**
* Represents a rest type
* ```ts
* type Z = [1, ...2[]]
* // ^^^^^^
* ```
*/
export class RestType extends Type {
/**
* The type of the rest array elements.
*/
elementType: Type;

/**
* The type name identifier.
*/
readonly type = "rest";

/**
* Create a new RestType instance.
*
* @param elementType The type of the array's elements.
*/
constructor(elementType: Type) {
super();
this.elementType = elementType;
}

/**
* Clone this type.
*
* @return A clone of this type.
*/
clone(): Type {
return new RestType(this.elementType.clone());
}

/**
* Test whether this type equals the given type.
*
* @param type The type that should be checked for equality.
* @returns TRUE if the given type equals this type, FALSE otherwise.
*/
equals(type: Type): boolean {
if (!(type instanceof RestType)) {
return false;
}
return type.elementType.equals(this.elementType);
}

/**
* Return a string representation of this type.
*/
toString() {
return `...${this.elementType.toString()}`;
}
}
5 changes: 5 additions & 0 deletions src/lib/serialization/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ type _ModelToObject<T> =
? ReferenceType
: T extends M.ReflectionType
? ReflectionType
: T extends M.RestType
? RestType
: T extends M.LiteralType
? LiteralType
: T extends M.TupleType
Expand Down Expand Up @@ -224,6 +226,7 @@ export type SomeType =
| PredicateType
| ReferenceType
| ReflectionType
| RestType
| TupleType
| TypeOperatorType
| TypeParameterType
Expand Down Expand Up @@ -273,6 +276,8 @@ export interface ReflectionType extends Type, S<M.ReflectionType, "type"> {
declaration?: ModelToObject<M.ReflectionType["declaration"]>;
}

export interface RestType extends Type, S<M.RestType, "type" | "elementType"> {}

export interface LiteralType extends Type, S<M.LiteralType, "type" | "value"> {}

export interface TupleType extends Type, S<M.TupleType, "type"> {
Expand Down
1 change: 1 addition & 0 deletions src/lib/serialization/serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ const serializerComponents: (new (
S.ReferenceTypeSerializer,
S.ReferenceTypeSerializer,
S.ReflectionTypeSerializer,
S.RestTypeSerializer,
S.LiteralTypeSerializer,
S.TupleTypeSerializer,
S.TemplateLiteralTypeSerializer,
Expand Down
1 change: 1 addition & 0 deletions src/lib/serialization/serializers/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export * from "./predicate";
export * from "./query";
export * from "./reference";
export * from "./reflection";
export * from "./rest";
export * from "./template-literal";
export * from "./tuple";
export * from "./type-operator";
Expand Down
22 changes: 22 additions & 0 deletions src/lib/serialization/serializers/types/rest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { RestType } from "../../../models";

import { TypeSerializerComponent } from "../../components";
import { RestType as JSONRestType } from "../../schema";

export class RestTypeSerializer extends TypeSerializerComponent<RestType> {
supports(t: unknown) {
return t instanceof RestType;
}

/**
* Will be run after [[TypeSerializer]] so `type` will already be set.
* @param type
* @param obj
*/
toObject(type: RestType, obj: Pick<JSONRestType, "type">): JSONRestType {
return {
...obj,
elementType: this.owner.toObject(type.elementType),
};
}
}
Loading

0 comments on commit 83d2a2c

Please sign in to comment.