Skip to content
This repository has been archived by the owner on Feb 20, 2020. It is now read-only.

Commit

Permalink
feat: support conditional type and infer type (#94)
Browse files Browse the repository at this point in the history
* feat: support conditional type

* feat: support infer type

* test: add tests

* refactor: fix linting
  • Loading branch information
ikatyang authored Apr 9, 2018
1 parent 0ff40bd commit 31c9e19
Show file tree
Hide file tree
Showing 13 changed files with 203 additions and 11 deletions.
24 changes: 13 additions & 11 deletions src/__tests__/__snapshots__/parse.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -6,60 +6,60 @@ Object {
Object {
"defalut": undefined,
"extends": undefined,
"kind": 13,
"kind": 14,
"name": "T",
},
Object {
"defalut": undefined,
"extends": undefined,
"kind": 13,
"kind": 14,
"name": "U",
},
],
"kind": 11,
"kind": 12,
"parameters": Array [
Object {
"kind": 32,
"kind": 34,
"name": "x",
"optional": undefined,
"rest": undefined,
"type": Object {
"generics": undefined,
"kind": 12,
"kind": 13,
"name": "T",
"parents": undefined,
},
},
Object {
"kind": 32,
"kind": 34,
"name": "y",
"optional": undefined,
"rest": undefined,
"type": Object {
"generics": undefined,
"kind": 12,
"kind": 13,
"name": "U",
"parents": undefined,
},
},
],
"return": Object {
"kind": 41,
"kind": 43,
"types": Array [
Object {
"generics": undefined,
"kind": 12,
"kind": 13,
"name": "T",
"parents": undefined,
},
Object {
"generics": undefined,
"kind": 12,
"kind": 13,
"name": "U",
"parents": undefined,
},
Object {
"kind": 29,
"kind": 31,
"type": TokenObject {
"end": -1,
"flags": 8,
Expand Down Expand Up @@ -187,5 +187,7 @@ type adjust = <T, U>(fn: (v: T) => U, index: number, array: T[]) => (T | U)[];
declare function x<T>(v: T): T;
import xyz = require(\\"xyz\\");
type X = A[B[C]];
type TypeName<T> = T extends string ? \\"string\\" : T extends number ? \\"number\\" : T extends boolean ? \\"boolean\\" : T extends undefined ? \\"undefined\\" : T extends Function ? \\"function\\" : \\"object\\";
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
"
`;
10 changes: 10 additions & 0 deletions src/__tests__/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,16 @@ declare function x<T>(v: T): T;
import xyz = require('xyz');
type X = A[B[C]];
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : T;
`;

it('should return correctly', () => {
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export enum ElementKind {
ArrayType,
ClassDeclaration,
ClassMember,
ConditionalType,
ConstructorType,
EnumDeclaration,
ExportDefault,
Expand All @@ -22,6 +23,7 @@ export enum ElementKind {
ImportNamed,
ImportNamespace,
IndexSignature,
InferType,
InterfaceDeclaration,
IntersectionType,
JSDocComment, // tslint:disable-line:naming-convention
Expand Down
4 changes: 4 additions & 0 deletions src/parse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { ITopLevelElement } from './others/top-level-element';
import { parse_array_type } from './parsers/array-type';
import { parse_call_signature } from './parsers/call-signature';
import { parse_class_declaration } from './parsers/class-declaration';
import { parse_conditional_type } from './parsers/conditional-type';
import { parse_construct_signature } from './parsers/construct-signature';
import { parse_constructor } from './parsers/constructor';
import { parse_constructor_type } from './parsers/constructor-type';
Expand All @@ -36,6 +37,7 @@ import { parse_import_equals_declaration } from './parsers/import-equals-declara
import { parse_import_specifier } from './parsers/import-specifier';
import { parse_index_signature } from './parsers/index-signature';
import { parse_indexed_access_type } from './parsers/indexed-access-type';
import { parse_infer_type } from './parsers/infer-type';
import { parse_interface_declaration } from './parsers/interface-declaration';
import { parse_intersection_type } from './parsers/intersection-type';
import { parse_mapped_type } from './parsers/mapped-type';
Expand Down Expand Up @@ -69,6 +71,7 @@ export const parse_native = (node: ts.Node): IElement<any> => {
case ts.SyntaxKind.BooleanKeyword: return boolean_type;
case ts.SyntaxKind.CallSignature: return parse_call_signature(node as ts.CallSignatureDeclaration);
case ts.SyntaxKind.ClassDeclaration: return parse_class_declaration(node as ts.ClassDeclaration);
case ts.SyntaxKind.ConditionalType: return parse_conditional_type(node as ts.ConditionalTypeNode);
case ts.SyntaxKind.ConstructSignature: return parse_construct_signature(node as ts.ConstructSignatureDeclaration);
case ts.SyntaxKind.Constructor: return parse_constructor(node as ts.ConstructorDeclaration);
case ts.SyntaxKind.ConstructorType: return parse_constructor_type(node as ts.ConstructorTypeNode);
Expand All @@ -86,6 +89,7 @@ export const parse_native = (node: ts.Node): IElement<any> => {
case ts.SyntaxKind.ImportSpecifier: return parse_import_specifier(node as ts.ImportSpecifier);
case ts.SyntaxKind.IndexSignature: return parse_index_signature(node as ts.IndexSignatureDeclaration);
case ts.SyntaxKind.IndexedAccessType: return parse_indexed_access_type(node as ts.IndexedAccessTypeNode);
case ts.SyntaxKind.InferType: return parse_infer_type(node as ts.InferTypeNode);
case ts.SyntaxKind.InterfaceDeclaration: return parse_interface_declaration(node as ts.InterfaceDeclaration);
case ts.SyntaxKind.IntersectionType: return parse_intersection_type(node as ts.IntersectionTypeNode);
case ts.SyntaxKind.LiteralType: return parse_native((node as ts.LiteralTypeNode).literal);
Expand Down
16 changes: 16 additions & 0 deletions src/parsers/conditional-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import * as ts from 'typescript';
import { parse_native } from '../parse';
import {
create_conditional_type,
IConditionalType,
} from '../types/conditional-type';

export const parse_conditional_type = (
node: ts.ConditionalTypeNode,
): IConditionalType =>
create_conditional_type({
check: parse_native(node.checkType),
extends: parse_native(node.extendsType),
true: parse_native(node.trueType),
false: parse_native(node.falseType),
});
9 changes: 9 additions & 0 deletions src/parsers/infer-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import * as ts from 'typescript';
import { IGenericDeclaration } from '../declarations/generic-declaration';
import { parse_native } from '../parse';
import { create_infer_type, IInferType } from '../types/infer-type';

export const parse_infer_type = (node: ts.InferTypeNode): IInferType =>
create_infer_type({
generic: parse_native(node.typeParameter) as IGenericDeclaration,
});
4 changes: 4 additions & 0 deletions src/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@ import { transform_top_level_element } from './others/top-level-element';
import { transform_triple_slash_reference } from './others/triple-slash-reference';
import { transform_type_predicate } from './others/type-predicate';
import { transform_array_type } from './types/array-type';
import { transform_conditional_type } from './types/conditional-type';
import { transform_constructor_type } from './types/constructor-type';
import { transform_function_type } from './types/function-type';
import { transform_general_type } from './types/general-type';
import { transform_infer_type } from './types/infer-type';
import { transform_intersection_type } from './types/intersection-type';
import { transform_keyof_type } from './types/keyof-type';
import { transform_literal_type } from './types/literal-type';
Expand All @@ -64,6 +66,7 @@ const select_transformer = (element: IElement<any>) => {
case ElementKind.ArrayType: return transform_array_type;
case ElementKind.ClassDeclaration: return transform_class_declaration;
case ElementKind.ClassMember: return transform_class_member;
case ElementKind.ConditionalType: return transform_conditional_type;
case ElementKind.ConstructorType: return transform_constructor_type;
case ElementKind.EnumDeclaration: return transform_enum_declaration;
case ElementKind.ExportDefault: return transform_export_default;
Expand All @@ -81,6 +84,7 @@ const select_transformer = (element: IElement<any>) => {
case ElementKind.ImportNamed: return transform_import_named;
case ElementKind.ImportNamespace: return transform_import_namespace;
case ElementKind.IndexSignature: return transform_index_signature;
case ElementKind.InferType: return transform_infer_type;
case ElementKind.InterfaceDeclaration: return transform_interface_declaration;
case ElementKind.IntersectionType: return transform_intersection_type;
case ElementKind.JSDocComment: return transform_jsdoc_comment;
Expand Down
3 changes: 3 additions & 0 deletions src/types/__tests__/__snapshots__/conditional-type.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should return correctly 1`] = `"T extends string ? boolean : number"`;
3 changes: 3 additions & 0 deletions src/types/__tests__/__snapshots__/infer-type.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should return correctly 1`] = `"infer T"`;
32 changes: 32 additions & 0 deletions src/types/__tests__/conditional-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { boolean_type, number_type, string_type } from '../../constants';
import { emit } from '../../emit';
import {
create_conditional_type,
is_conditional_type,
} from '../conditional-type';
import { create_general_type } from '../general-type';

it('should return correctly', () => {
expect(
emit(
create_conditional_type({
check: create_general_type({ name: 'T' }),
extends: string_type,
true: boolean_type,
false: number_type,
}),
),
).toMatchSnapshot();
});

describe('is_conditional_type', () => {
it('should return correctly', () => {
const element = create_conditional_type({
check: create_general_type({ name: 'T' }),
extends: string_type,
true: boolean_type,
false: number_type,
});
expect(is_conditional_type(element)).toBe(true);
});
});
23 changes: 23 additions & 0 deletions src/types/__tests__/infer-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { boolean_type, number_type, string_type } from '../../constants';
import { create_generic_declaration } from '../../declarations/generic-declaration';
import { emit } from '../../emit';
import { create_infer_type, is_infer_type } from '../infer-type';

it('should return correctly', () => {
expect(
emit(
create_infer_type({
generic: create_generic_declaration({ name: 'T' }),
}),
),
).toMatchSnapshot();
});

describe('is_infer_type', () => {
it('should return correctly', () => {
const element = create_infer_type({
generic: create_generic_declaration({ name: 'T' }),
});
expect(is_infer_type(element)).toBe(true);
});
});
45 changes: 45 additions & 0 deletions src/types/conditional-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as ts from 'typescript';
import { IType } from '../collections';
import { ElementKind } from '../constants';
import {
create_element,
is_element,
IElement,
IElementOptions,
} from '../element';
import { transform } from '../transform';

export interface IConditionalTypeOptions extends IElementOptions {
check: IType;
extends: IType;
true: IType;
false: IType;
}

export interface IConditionalType
extends IElement<ElementKind.ConditionalType>,
IConditionalTypeOptions {}

export const create_conditional_type = (
options: IConditionalTypeOptions,
): IConditionalType => ({
...create_element(ElementKind.ConditionalType),
...options,
});

export const is_conditional_type = (value: any): value is IConditionalType =>
is_element(value) && value.kind === ElementKind.ConditionalType;

/**
* @hidden
*/
export const transform_conditional_type = (
element: IConditionalType,
path: IElement<any>[],
) =>
ts.createConditionalTypeNode(
/* checkType */ transform(element.check, path) as ts.TypeNode,
/* extendsType */ transform(element.extends, path) as ts.TypeNode,
/* trueType */ transform(element.true, path) as ts.TypeNode,
/* falseType */ transform(element.false, path) as ts.TypeNode,
);
39 changes: 39 additions & 0 deletions src/types/infer-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import * as ts from 'typescript';
import { ElementKind } from '../constants';
import {
transform_generic_declaration,
IGenericDeclaration,
} from '../declarations/generic-declaration';
import {
create_element,
is_element,
IElement,
IElementOptions,
} from '../element';

export interface IInferTypeOptions extends IElementOptions {
generic: IGenericDeclaration;
}

export interface IInferType
extends IElement<ElementKind.InferType>,
IInferTypeOptions {}

export const create_infer_type = (options: IInferTypeOptions): IInferType => ({
...create_element(ElementKind.InferType),
...options,
});

export const is_infer_type = (value: any): value is IInferType =>
is_element(value) && value.kind === ElementKind.InferType;

/**
* @hidden
*/
export const transform_infer_type = (
element: IInferType,
path: IElement<any>[],
) =>
ts.createInferTypeNode(
/* typeParameter */ transform_generic_declaration(element.generic, path),
);

0 comments on commit 31c9e19

Please sign in to comment.