From 31c9e190b4bbffced3146d2d7209f165706ee19c Mon Sep 17 00:00:00 2001 From: Ika Date: Mon, 9 Apr 2018 22:01:50 +0800 Subject: [PATCH] feat: support conditional type and infer type (#94) * feat: support conditional type * feat: support infer type * test: add tests * refactor: fix linting --- src/__tests__/__snapshots__/parse.ts.snap | 24 +++++----- src/__tests__/parse.ts | 10 +++++ src/constants.ts | 2 + src/parse.ts | 4 ++ src/parsers/conditional-type.ts | 16 +++++++ src/parsers/infer-type.ts | 9 ++++ src/transform.ts | 4 ++ .../__snapshots__/conditional-type.ts.snap | 3 ++ .../__snapshots__/infer-type.ts.snap | 3 ++ src/types/__tests__/conditional-type.ts | 32 +++++++++++++ src/types/__tests__/infer-type.ts | 23 ++++++++++ src/types/conditional-type.ts | 45 +++++++++++++++++++ src/types/infer-type.ts | 39 ++++++++++++++++ 13 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 src/parsers/conditional-type.ts create mode 100644 src/parsers/infer-type.ts create mode 100644 src/types/__tests__/__snapshots__/conditional-type.ts.snap create mode 100644 src/types/__tests__/__snapshots__/infer-type.ts.snap create mode 100644 src/types/__tests__/conditional-type.ts create mode 100644 src/types/__tests__/infer-type.ts create mode 100644 src/types/conditional-type.ts create mode 100644 src/types/infer-type.ts diff --git a/src/__tests__/__snapshots__/parse.ts.snap b/src/__tests__/__snapshots__/parse.ts.snap index cb60b0c..5660982 100644 --- a/src/__tests__/__snapshots__/parse.ts.snap +++ b/src/__tests__/__snapshots__/parse.ts.snap @@ -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, @@ -187,5 +187,7 @@ type adjust = (fn: (v: T) => U, index: number, array: T[]) => (T | U)[]; declare function x(v: T): T; import xyz = require(\\"xyz\\"); type X = A[B[C]]; +type TypeName = T extends string ? \\"string\\" : T extends number ? \\"number\\" : T extends boolean ? \\"boolean\\" : T extends undefined ? \\"undefined\\" : T extends Function ? \\"function\\" : \\"object\\"; +type ReturnType = T extends (...args: any[]) => infer R ? R : T; " `; diff --git a/src/__tests__/parse.ts b/src/__tests__/parse.ts index d5f9bfb..691bdf0 100644 --- a/src/__tests__/parse.ts +++ b/src/__tests__/parse.ts @@ -131,6 +131,16 @@ declare function x(v: T): T; import xyz = require('xyz'); type X = A[B[C]]; + +type TypeName = + T extends string ? "string" : + T extends number ? "number" : + T extends boolean ? "boolean" : + T extends undefined ? "undefined" : + T extends Function ? "function" : + "object"; + +type ReturnType = T extends (...args: any[]) => infer R ? R : T; `; it('should return correctly', () => { diff --git a/src/constants.ts b/src/constants.ts index 393a48f..477f7b0 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -5,6 +5,7 @@ export enum ElementKind { ArrayType, ClassDeclaration, ClassMember, + ConditionalType, ConstructorType, EnumDeclaration, ExportDefault, @@ -22,6 +23,7 @@ export enum ElementKind { ImportNamed, ImportNamespace, IndexSignature, + InferType, InterfaceDeclaration, IntersectionType, JSDocComment, // tslint:disable-line:naming-convention diff --git a/src/parse.ts b/src/parse.ts index 9dc4acb..7b6f641 100644 --- a/src/parse.ts +++ b/src/parse.ts @@ -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'; @@ -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'; @@ -69,6 +71,7 @@ export const parse_native = (node: ts.Node): IElement => { 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); @@ -86,6 +89,7 @@ export const parse_native = (node: ts.Node): IElement => { 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); diff --git a/src/parsers/conditional-type.ts b/src/parsers/conditional-type.ts new file mode 100644 index 0000000..b0eb2e2 --- /dev/null +++ b/src/parsers/conditional-type.ts @@ -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), + }); diff --git a/src/parsers/infer-type.ts b/src/parsers/infer-type.ts new file mode 100644 index 0000000..b1bd0ae --- /dev/null +++ b/src/parsers/infer-type.ts @@ -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, + }); diff --git a/src/transform.ts b/src/transform.ts index 467407b..aff368f 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -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'; @@ -64,6 +66,7 @@ const select_transformer = (element: IElement) => { 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; @@ -81,6 +84,7 @@ const select_transformer = (element: IElement) => { 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; diff --git a/src/types/__tests__/__snapshots__/conditional-type.ts.snap b/src/types/__tests__/__snapshots__/conditional-type.ts.snap new file mode 100644 index 0000000..efc1d54 --- /dev/null +++ b/src/types/__tests__/__snapshots__/conditional-type.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should return correctly 1`] = `"T extends string ? boolean : number"`; diff --git a/src/types/__tests__/__snapshots__/infer-type.ts.snap b/src/types/__tests__/__snapshots__/infer-type.ts.snap new file mode 100644 index 0000000..c99054c --- /dev/null +++ b/src/types/__tests__/__snapshots__/infer-type.ts.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should return correctly 1`] = `"infer T"`; diff --git a/src/types/__tests__/conditional-type.ts b/src/types/__tests__/conditional-type.ts new file mode 100644 index 0000000..50c4c68 --- /dev/null +++ b/src/types/__tests__/conditional-type.ts @@ -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); + }); +}); diff --git a/src/types/__tests__/infer-type.ts b/src/types/__tests__/infer-type.ts new file mode 100644 index 0000000..a01f711 --- /dev/null +++ b/src/types/__tests__/infer-type.ts @@ -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); + }); +}); diff --git a/src/types/conditional-type.ts b/src/types/conditional-type.ts new file mode 100644 index 0000000..3f26698 --- /dev/null +++ b/src/types/conditional-type.ts @@ -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, + 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[], +) => + 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, + ); diff --git a/src/types/infer-type.ts b/src/types/infer-type.ts new file mode 100644 index 0000000..d35f214 --- /dev/null +++ b/src/types/infer-type.ts @@ -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, + 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[], +) => + ts.createInferTypeNode( + /* typeParameter */ transform_generic_declaration(element.generic, path), + );