Skip to content

Commit

Permalink
fix: Add support for optional types
Browse files Browse the repository at this point in the history
Resolves #1312
  • Loading branch information
Gerrit0 committed Jan 9, 2021
1 parent 83d2a2c commit 28c234e
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 43 deletions.
22 changes: 21 additions & 1 deletion src/lib/converter/types.ts
Expand Up @@ -23,6 +23,7 @@ import {
MappedType,
SignatureReflection,
} from "../models";
import { OptionalType } from "../models/types/optional";
import { RestType } from "../models/types/rest";
import { TemplateLiteralType } from "../models/types/template-literal";
import { zip } from "../utils/array";
Expand Down Expand Up @@ -65,6 +66,7 @@ export function loadConverters() {
intersectionConverter,
jsDocVariadicTypeConverter,
keywordConverter,
optionalConverter,
parensConverter,
predicateConverter,
queryConverter,
Expand Down Expand Up @@ -453,6 +455,17 @@ const keywordConverter: TypeConverter<ts.KeywordTypeNode> = {
},
};

const optionalConverter: TypeConverter<ts.OptionalTypeNode> = {
kind: [ts.SyntaxKind.OptionalType],
convert(context, node) {
return new OptionalType(
removeUndefined(convertType(context, node.type))
);
},
// Handled by the tuple converter
convertType: requestBugReport,
};

const parensConverter: TypeConverter<ts.ParenthesizedTypeNode> = {
kind: [ts.SyntaxKind.ParenthesizedType],
convert(context, node) {
Expand Down Expand Up @@ -881,7 +894,14 @@ const tupleConverter: TypeConverter<ts.TupleTypeNode, ts.TupleTypeReference> = {

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

if (
type.target.elementFlags[i] & ts.ElementFlags.Optional &&
!(el instanceof NamedTupleMember)
) {
return new OptionalType(removeUndefined(el));
}

return el;
});
}
Expand Down
3 changes: 2 additions & 1 deletion src/lib/models/types/index.ts
Expand Up @@ -7,13 +7,14 @@ export { IntersectionType } from "./intersection";
export { IntrinsicType } from "./intrinsic";
export { LiteralType } from "./literal";
export { MappedType } from "./mapped";
export { OptionalType } from "./optional";
export { PredicateType } from "./predicate";
export { QueryType } from "./query";
export { ReferenceType } from "./reference";
export { ReflectionType } from "./reflection";
export { RestType } from "./rest";
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
67 changes: 67 additions & 0 deletions src/lib/models/types/optional.ts
@@ -0,0 +1,67 @@
import { Type } from "./abstract";
import { UnionType } from "./union";
import { IntersectionType } from "./intersection";

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

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

/**
* Create a new OptionalType instance.
*
* @param elementType The type of the element
*/
constructor(elementType: Type) {
super();
this.elementType = elementType;
}

/**
* Clone this type.
*
* @return A clone of this type.
*/
clone(): Type {
return new OptionalType(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 OptionalType)) {
return false;
}
return type.elementType.equals(this.elementType);
}

/**
* Return a string representation of this type.
*/
toString() {
if (
this.elementType instanceof UnionType ||
this.elementType instanceof IntersectionType
) {
return `(${this.elementType.toString()})?`;
}
return `${this.elementType.toString()}?`;
}
}
12 changes: 11 additions & 1 deletion src/lib/serialization/schema.ts
Expand Up @@ -76,8 +76,12 @@ type _ModelToObject<T> =
? IntersectionType
: T extends M.IntrinsicType
? IntrinsicType
: T extends M.OptionalType
? OptionalType
: T extends M.PredicateType
? PredicateType
: T extends M.QueryType
? QueryType
: T extends M.ReferenceType
? ReferenceType
: T extends M.ReflectionType
Expand Down Expand Up @@ -223,7 +227,9 @@ export type SomeType =
| IntersectionType
| IntrinsicType
| LiteralType
| OptionalType
| PredicateType
| QueryType
| ReferenceType
| ReflectionType
| RestType
Expand Down Expand Up @@ -260,12 +266,16 @@ export interface IntrinsicType
extends Type,
S<M.IntrinsicType, "type" | "name"> {}

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

export interface PredicateType
extends Type,
S<M.PredicateType, "type" | "name" | "asserts" | "targetType"> {}

export interface QueryType extends Type, S<M.QueryType, "type" | "queryType"> {}

export interface ReferenceType
extends Type,
S<M.ReferenceType, "type" | "name" | "typeArguments"> {
Expand Down
3 changes: 2 additions & 1 deletion src/lib/serialization/serializer.ts
Expand Up @@ -141,8 +141,9 @@ const serializerComponents: (new (
S.InferredTypeSerializer,
S.IntersectionTypeSerializer,
S.IntrinsicTypeSerializer,
S.QueryTypeSerializer,
S.OptionalTypeSerializer,
S.PredicateTypeSerializer,
S.QueryTypeSerializer,
S.ReferenceTypeSerializer,
S.ReferenceTypeSerializer,
S.ReflectionTypeSerializer,
Expand Down
1 change: 1 addition & 0 deletions src/lib/serialization/serializers/types/index.ts
Expand Up @@ -7,6 +7,7 @@ export * from "./intersection";
export * from "./intrinsic";
export * from "./literal";
export * from "./mapped";
export * from "./optional";
export * from "./predicate";
export * from "./query";
export * from "./reference";
Expand Down
25 changes: 25 additions & 0 deletions src/lib/serialization/serializers/types/optional.ts
@@ -0,0 +1,25 @@
import { OptionalType } from "../../../models";

import { TypeSerializerComponent } from "../../components";
import { OptionalType as JSONOptionalType } from "../../schema";

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

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

0 comments on commit 28c234e

Please sign in to comment.