Skip to content

Commit

Permalink
Add support for conditional type.
Browse files Browse the repository at this point in the history
  • Loading branch information
NaridaL committed Oct 5, 2018
1 parent 23dfaf5 commit 52a893b
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 20 deletions.
58 changes: 58 additions & 0 deletions src/lib/converter/types/conditional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import * as ts from 'typescript';

import { ConditionalType } from '../../models/types';
import { Component, ConverterTypeComponent, TypeConverter } from '../components';
import { Context } from '../context';

@Component({name: 'type:conditional'})
export class ConditionalConverter extends ConverterTypeComponent implements TypeConverter<ts.ConditionalType, ts.ConditionalTypeNode> {
/**
* Test whether this converter can handle the given TypeScript node.
*/
supportsNode(context: Context, node: ts.ConditionalTypeNode): boolean {
return node.kind === ts.SyntaxKind.ConditionalType;
}

/**
* Test whether this converter can handle the given TypeScript type.
*/
supportsType(context: Context, type: ts.ConditionalType): boolean {
return !!(type.flags & ts.TypeFlags.Conditional);
}

/**
* Convert the given conditional type node to its type reflection.
*
* This is a node based converter, see [[convertType]] for the type equivalent.
*
* @param context The context object describing the current state the converter is in.
* @param node The conditional or intersection type node that should be converted.
* @returns The type reflection representing the given conditional type node.
*/
convertNode(context: Context, node: ts.ConditionalTypeNode): ConditionalType {
return new ConditionalType(
this.owner.convertType(context, node.checkType),
this.owner.convertType(context, node.extendsType),
this.owner.convertType(context, node.trueType),
this.owner.convertType(context, node.falseType)
);
}

/**
* Convert the given conditional type to its type reflection.
*
* This is a type based converter, see [[convertNode]] for the node equivalent.
*
* @param context The context object describing the current state the converter is in.
* @param type The conditional type that should be converted.
* @returns The type reflection representing the given conditional type.
*/
convertType(context: Context, type: ts.ConditionalType): ConditionalType {
return new ConditionalType(
this.owner.convertType(context, null, type.checkType),
this.owner.convertType(context, null, type.extendsType),
this.owner.convertType(context, null, type.resolvedTrueType),
this.owner.convertType(context, null, type.resolvedFalseType)
);
}
}
1 change: 1 addition & 0 deletions src/lib/converter/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { AliasConverter } from './alias';
export { ArrayConverter } from './array';
export { BindingArrayConverter } from './binding-array';
export { BindingObjectConverter } from './binding-object';
export { ConditionalConverter } from './conditional';
export { EnumConverter } from './enum';
export { IntrinsicConverter } from './intrinsic';
export { StringLiteralConverter } from './string-literal';
Expand Down
2 changes: 1 addition & 1 deletion src/lib/converter/types/union-or-intersection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class UnionOrIntersectionConverter extends ConverterTypeComponent impleme
/**
* Convert the given union type to its type reflection.
*
* This is a type based converter, see [[convertUnionTypeNode]] for the node equivalent.
* This is a type based converter, see [[convertNode]] for the node equivalent.
*
* ```
* let someValue: string|number;
Expand Down
72 changes: 72 additions & 0 deletions src/lib/models/types/conditional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Type } from './abstract';

/**
* Represents a conditional type.
*
* ~~~
* let value: C extends E ? T : F;
* let value2: Check extends Extends ? True : False;
* ~~~
*/
export class ConditionalType extends Type {
/**
* The type name identifier.
*/
readonly type: string = 'conditional';

constructor(
public checkType: Type,
public extendsType: Type,
public trueType: Type,
public falseType: Type
) {
super();
}

/**
* Clone this type.
*
* @return A clone of this type.
*/
clone(): Type {
return new ConditionalType(this.checkType, this.extendsType, this.trueType, this.falseType);
}

/**
* 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: any): boolean {
if (!(type instanceof ConditionalType)) {
return false;
}
return this.checkType.equals(type.checkType) &&
this.extendsType.equals(type.extendsType) &&
this.trueType.equals(type.trueType) &&
this.falseType.equals(type.falseType);
}

/**
* Return a raw object representation of this type.
* @deprecated Use serializers instead
*/
toObject(): any {
const result: any = super.toObject();

result.checkType = this.checkType.toObject();
result.extendsType = this.extendsType.toObject();
result.trueType = this.trueType.toObject();
result.falseType = this.falseType.toObject();

return result;
}

/**
* Return a string representation of this type.
*/
toString() {
return this.checkType + ' extends ' + this.extendsType + ' ? ' + this.trueType + ' : ' + this.falseType;
}
}
1 change: 1 addition & 0 deletions src/lib/models/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { Type } from './abstract';
export { ArrayType } from './array';
export { ConditionalType } from './conditional';
export { IntrinsicType } from './intrinsic';
export { IntersectionType } from './intersection';
export { ReferenceType } from './reference';
Expand Down
24 changes: 24 additions & 0 deletions src/lib/serialization/serializers/types/conditional.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Component } from '../../../utils/component';
import { ConditionalType } from '../../../models';
import { TypeSerializerComponent } from '../../components';

@Component({name: 'serializer:conditional-type'})
export class ConditionalTypeSerializer extends TypeSerializerComponent<ConditionalType> {

initialize(): void {
super.initialize();
this.supports = (t: ConditionalType) => t instanceof ConditionalType;
}

toObject(conditional: ConditionalType, obj?: any): any {
obj = obj || {};

obj.checkType = this.owner.toObject(conditional.checkType);
obj.extendsType = this.owner.toObject(conditional.extendsType);
obj.trueType = this.owner.toObject(conditional.trueType);
obj.falseType = this.owner.toObject(conditional.falseType);

return obj;
}

}
1 change: 1 addition & 0 deletions src/lib/serialization/serializers/types/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './abstract';
export * from './array';
export * from './conditional';
export * from './intersection-union';
export * from './intrinsic';
export * from './reference';
Expand Down
8 changes: 6 additions & 2 deletions src/test/converter/conditional-type/conditional-type.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
function A<T> (x: T): T extends number ? number : string {
return 'number' === typeof x ? x : x.toString() as any;
class C {
foo: number;
}

function A<T> (x: T): T extends number ? number : C {
return 'number' === typeof x ? x : new C() as any;
}

type B<T> = T extends string ? 'string' : 'notstring';
114 changes: 97 additions & 17 deletions src/test/converter/conditional-type/specs.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,57 @@
"originalName": "%BASE%/conditional-type/conditional-type.ts",
"children": [
{
"id": 6,
"id": 2,
"name": "C",
"kind": 128,
"kindString": "Class",
"flags": {},
"children": [
{
"id": 3,
"name": "foo",
"kind": 1024,
"kindString": "Property",
"flags": {},
"sources": [
{
"fileName": "conditional-type.ts",
"line": 2,
"character": 7
}
],
"type": {
"type": "intrinsic",
"name": "number"
}
}
],
"groups": [
{
"title": "Properties",
"kind": 1024,
"children": [
3
]
}
],
"sources": [
{
"fileName": "conditional-type.ts",
"line": 1,
"character": 7
}
]
},
{
"id": 8,
"name": "B",
"kind": 4194304,
"kindString": "Type alias",
"flags": {},
"typeParameter": [
{
"id": 7,
"id": 9,
"name": "T",
"kind": 131072,
"kindString": "Type parameter",
Expand All @@ -32,41 +75,56 @@
"sources": [
{
"fileName": "conditional-type.ts",
"line": 5,
"line": 9,
"character": 6
}
],
"type": {
"type": "unknown",
"name": "B<T>"
"type": "conditional",
"checkType": {
"type": "typeParameter",
"name": "T"
},
"extendsType": {
"type": "intrinsic",
"name": "string"
},
"trueType": {
"type": "stringLiteral",
"value": "string"
},
"falseType": {
"type": "stringLiteral",
"value": "notstring"
}
}
},
{
"id": 2,
"id": 4,
"name": "A",
"kind": 64,
"kindString": "Function",
"flags": {},
"signatures": [
{
"id": 3,
"id": 5,
"name": "A",
"kind": 4096,
"kindString": "Call signature",
"flags": {},
"typeParameter": [
{
"id": 4,
"id": 6,
"name": "T",
"kind": 131072,
"kindString": "Type parameter",
"flags": {}
}
],
"parameters": [
{
"id": 5,
"id": 7,
"name": "x",
"kind": 32768,
"kindString": "Parameter",
"flags": {},
"type": {
"type": "typeParameter",
Expand All @@ -75,33 +133,55 @@
}
],
"type": {
"type": "unknown",
"name": "T extends number ? number : string"
"type": "conditional",
"checkType": {
"type": "typeParameter",
"name": "T"
},
"extendsType": {
"type": "intrinsic",
"name": "number"
},
"trueType": {
"type": "intrinsic",
"name": "number"
},
"falseType": {
"type": "reference",
"name": "C"
}
}
}
],
"sources": [
{
"fileName": "conditional-type.ts",
"line": 1,
"fileName": "%BASE%/conditional-type/conditional-type.ts",
"line": 5,
"character": 10
}
]
}
],
"groups": [
{
"title": "Classes",
"kind": 128,
"children": [
2
]
},
{
"title": "Type aliases",
"kind": 4194304,
"children": [
6
8
]
},
{
"title": "Functions",
"kind": 64,
"children": [
2
4
]
}
],
Expand Down

0 comments on commit 52a893b

Please sign in to comment.