diff --git a/src/lib/converter/plugins/TypePlugin.ts b/src/lib/converter/plugins/TypePlugin.ts index a0026b881..68e4059dd 100644 --- a/src/lib/converter/plugins/TypePlugin.ts +++ b/src/lib/converter/plugins/TypePlugin.ts @@ -1,5 +1,5 @@ import {Reflection, ReflectionKind, Decorator, DeclarationReflection, DeclarationHierarchy} from '../../models/reflections/index'; -import {Type, ReferenceType, TupleType, UnionType} from '../../models/types/index'; +import {Type, ReferenceType, TupleType, UnionType, IntersectionType} from '../../models/types/index'; import {Component, ConverterComponent} from '../components'; import {Converter} from '../converter'; import {Context} from '../context'; @@ -108,10 +108,10 @@ export class TypePlugin extends ConverterComponent { for (let index = 0, count = tupleType.elements.length; index < count; index++) { resolveType(reflection, tupleType.elements[index]); } - } else if (type instanceof UnionType) { - const unionType: UnionType = type; - for (let index = 0, count = unionType.types.length; index < count; index++) { - resolveType(reflection, unionType.types[index]); + } else if (type instanceof UnionType || type instanceof IntersectionType) { + const unionOrIntersectionType: UnionType | IntersectionType = type; + for (let index = 0, count = unionOrIntersectionType.types.length; index < count; index++) { + resolveType(reflection, unionOrIntersectionType.types[index]); } } } diff --git a/src/lib/converter/types/index.ts b/src/lib/converter/types/index.ts index b9ba3a520..d10f7ea06 100644 --- a/src/lib/converter/types/index.ts +++ b/src/lib/converter/types/index.ts @@ -9,5 +9,5 @@ export {ReferenceConverter} from './reference'; export {ThisConverter} from './this'; export {TupleConverter} from './tuple'; export {TypeParameterConverter} from './type-parameter'; -export {UnionConverter} from './union'; +export {UnionOrIntersectionConverter} from './union-or-intersection'; export {UnknownConverter} from './unknown'; diff --git a/src/lib/converter/types/union.ts b/src/lib/converter/types/union-or-intersection.ts similarity index 56% rename from src/lib/converter/types/union.ts rename to src/lib/converter/types/union-or-intersection.ts index bdef3b666..66b8412a4 100644 --- a/src/lib/converter/types/union.ts +++ b/src/lib/converter/types/union-or-intersection.ts @@ -1,39 +1,39 @@ import * as ts from 'typescript'; -import {Type, UnionType} from '../../models/types/index'; +import {Type, UnionType, IntersectionType} from '../../models/types/index'; import {Component, ConverterTypeComponent, TypeConverter} from '../components'; import {Context} from '../context'; -@Component({name: 'type:union'}) -export class UnionConverter extends ConverterTypeComponent implements TypeConverter { +@Component({name: 'type:union-or-intersection'}) +export class UnionOrIntersectionConverter extends ConverterTypeComponent implements TypeConverter { /** * Test whether this converter can handle the given TypeScript node. */ - supportsNode(context: Context, node: ts.UnionTypeNode): boolean { - return node.kind === ts.SyntaxKind.UnionType; + supportsNode(context: Context, node: ts.UnionOrIntersectionTypeNode): boolean { + return node.kind === ts.SyntaxKind.UnionType || node.kind === ts.SyntaxKind.IntersectionType; } /** * Test whether this converter can handle the given TypeScript type. */ - supportsType(context: Context, type: ts.UnionType): boolean { - return !!(type.flags & ts.TypeFlags.Union); + supportsType(context: Context, type: ts.UnionOrIntersectionType): boolean { + return !!(type.flags & ts.TypeFlags.UnionOrIntersection); } /** * Convert the given union type node to its type reflection. * - * This is a node based converter, see [[convertUnionType]] for the type equivalent. + * This is a node based converter, see [[convertType]] for the type equivalent. * * ``` * let someValue: string|number; * ``` * * @param context The context object describing the current state the converter is in. - * @param node The union type node that should be converted. + * @param node The union or intersection type node that should be converted. * @returns The type reflection representing the given union type node. */ - convertNode(context: Context, node: ts.UnionTypeNode): UnionType { + convertNode(context: Context, node: ts.UnionOrIntersectionTypeNode): UnionType | IntersectionType { let types: Type[] = []; if (node.types) { types = node.types.map((n) => this.owner.convertType(context, n)); @@ -41,7 +41,7 @@ export class UnionConverter extends ConverterTypeComponent implements TypeConver types = []; } - return new UnionType(types); + return node.kind === ts.SyntaxKind.IntersectionType ? new IntersectionType(types) : new UnionType(types); } /** @@ -57,7 +57,7 @@ export class UnionConverter extends ConverterTypeComponent implements TypeConver * @param type The union type that should be converted. * @returns The type reflection representing the given union type. */ - convertType(context: Context, type: ts.UnionType): UnionType { + convertType(context: Context, type: ts.UnionOrIntersectionType): UnionType | IntersectionType { let types: Type[]; if (type && type.types) { types = type.types.map((t) => this.owner.convertType(context, null, t)); @@ -65,6 +65,6 @@ export class UnionConverter extends ConverterTypeComponent implements TypeConver types = []; } - return new UnionType(types); + return !!(type.flags & ts.TypeFlags.Intersection) ? new IntersectionType(types) : new UnionType(types); } } diff --git a/src/lib/models/types/abstract.ts b/src/lib/models/types/abstract.ts index c613a58c8..ec4546acc 100644 --- a/src/lib/models/types/abstract.ts +++ b/src/lib/models/types/abstract.ts @@ -9,6 +9,11 @@ export abstract class Type { */ isArray = false; + /** + * The type name identifier. + */ + readonly type: string = 'void'; + /** * Clone this type. * @@ -31,7 +36,7 @@ export abstract class Type { */ toObject(): any { let result: any = {}; - result.type = 'void'; + result.type = this.type; if (this.isArray) { result.isArray = this.isArray; diff --git a/src/lib/models/types/index.ts b/src/lib/models/types/index.ts index 33e7f5156..b57575b0c 100644 --- a/src/lib/models/types/index.ts +++ b/src/lib/models/types/index.ts @@ -1,5 +1,6 @@ export {Type} from './abstract'; export {IntrinsicType} from './intrinsic'; +export {IntersectionType} from './intersection'; export {ReferenceType} from './reference'; export {ReflectionType} from './reflection'; export {StringLiteralType} from './string-literal'; diff --git a/src/lib/models/types/intersection.ts b/src/lib/models/types/intersection.ts new file mode 100644 index 000000000..7fbc11eac --- /dev/null +++ b/src/lib/models/types/intersection.ts @@ -0,0 +1,82 @@ +import {Type} from './abstract'; + +/** + * Represents an intersection type. + * + * ~~~ + * let value: A & B; + * ~~~ + */ +export class IntersectionType extends Type { + /** + * The types this union consists of. + */ + types: Type[]; + + /** + * The type name identifier. + */ + readonly type: string = 'intersection'; + + /** + * Create a new TupleType instance. + * + * @param types The types this union consists of. + */ + constructor(types: Type[]) { + super(); + this.types = types; + } + + /** + * Clone this type. + * + * @return A clone of this type. + */ + clone(): Type { + const clone = new IntersectionType(this.types); + clone.isArray = this.isArray; + return 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: IntersectionType): boolean { + if (!(type instanceof IntersectionType)) { + return false; + } + if (type.isArray !== this.isArray) { + return false; + } + return Type.isTypeListSimiliar(type.types, this.types); + } + + /** + * Return a raw object representation of this type. + */ + toObject(): any { + const result: any = super.toObject(); + + if (this.types && this.types.length) { + result.types = this.types.map((e) => e.toObject()); + } + + return result; + } + + /** + * Return a string representation of this type. + */ + toString() { + const names: string[] = []; + this.types.forEach((element) => { + names.push(element.toString()); + }); + + return names.join(' & '); + } +} diff --git a/src/lib/models/types/intrinsic.ts b/src/lib/models/types/intrinsic.ts index 57aea07e1..7fb180057 100644 --- a/src/lib/models/types/intrinsic.ts +++ b/src/lib/models/types/intrinsic.ts @@ -13,6 +13,11 @@ export class IntrinsicType extends Type { */ name: string; + /** + * The type name identifier. + */ + readonly type: string = 'instrinct'; // TODO: Is there any change to correct this typo? + /** * Create a new instance of IntrinsicType. * @@ -51,7 +56,6 @@ export class IntrinsicType extends Type { */ toObject(): any { const result: any = super.toObject(); - result.type = 'instrinct'; result.name = this.name; return result; } diff --git a/src/lib/models/types/reference.ts b/src/lib/models/types/reference.ts index f7eea5f50..21f7dbb6c 100644 --- a/src/lib/models/types/reference.ts +++ b/src/lib/models/types/reference.ts @@ -9,6 +9,11 @@ import {Type} from './abstract'; * ~~~ */ export class ReferenceType extends Type { + /** + * The type name identifier. + */ + readonly type: string = 'reference'; + /** * The name of the referenced type. * @@ -90,7 +95,6 @@ export class ReferenceType extends Type { */ toObject(): any { const result: any = super.toObject(); - result.type = 'reference'; result.name = this.name; if (this.reflection) { diff --git a/src/lib/models/types/reflection.ts b/src/lib/models/types/reflection.ts index 27f1f48a2..b56543bb9 100644 --- a/src/lib/models/types/reflection.ts +++ b/src/lib/models/types/reflection.ts @@ -14,6 +14,11 @@ export class ReflectionType extends Type { */ declaration: DeclarationReflection; + /** + * The type name identifier. + */ + readonly type: string = 'reflection'; + /** * Create a new instance of ReflectionType. * @@ -50,7 +55,6 @@ export class ReflectionType extends Type { */ toObject(): any { const result: any = super.toObject(); - result.type = 'reflection'; if (this.declaration) { result.declaration = this.declaration.toObject(); diff --git a/src/lib/models/types/string-literal.ts b/src/lib/models/types/string-literal.ts index 9b361a280..477b8c12b 100644 --- a/src/lib/models/types/string-literal.ts +++ b/src/lib/models/types/string-literal.ts @@ -13,6 +13,11 @@ export class StringLiteralType extends Type { */ value: string; + /** + * The type name identifier. + */ + readonly type: string = 'stringLiteral'; + /** * Create a new instance of StringLiteralType. * @@ -51,7 +56,6 @@ export class StringLiteralType extends Type { */ toObject(): any { const result: any = super.toObject(); - result.type = 'stringLiteral'; result.value = this.value; return result; } diff --git a/src/lib/models/types/tuple.ts b/src/lib/models/types/tuple.ts index 2d3677751..d674b852e 100644 --- a/src/lib/models/types/tuple.ts +++ b/src/lib/models/types/tuple.ts @@ -13,6 +13,11 @@ export class TupleType extends Type { */ elements: Type[]; + /** + * The type name identifier. + */ + readonly type: string = 'tuple'; + /** * Create a new TupleType instance. * @@ -55,7 +60,6 @@ export class TupleType extends Type { */ toObject(): any { const result: any = super.toObject(); - result.type = 'tuple'; if (this.elements && this.elements.length) { result.elements = this.elements.map((e) => e.toObject()); diff --git a/src/lib/models/types/type-parameter.ts b/src/lib/models/types/type-parameter.ts index c26d61ac1..870cec6ff 100644 --- a/src/lib/models/types/type-parameter.ts +++ b/src/lib/models/types/type-parameter.ts @@ -15,6 +15,11 @@ export class TypeParameterType extends Type { constraint: Type; + /** + * The type name identifier. + */ + readonly type: string = 'typeParameter'; + /** * Clone this type. * @@ -57,7 +62,6 @@ export class TypeParameterType extends Type { */ toObject(): any { const result: any = super.toObject(); - result.type = 'typeParameter'; result.name = this.name; if (this.constraint) { diff --git a/src/lib/models/types/union.ts b/src/lib/models/types/union.ts index 41ebaba61..fe36a1b41 100644 --- a/src/lib/models/types/union.ts +++ b/src/lib/models/types/union.ts @@ -13,6 +13,11 @@ export class UnionType extends Type { */ types: Type[]; + /** + * The type name identifier. + */ + readonly type: string = 'union'; + /** * Create a new TupleType instance. * @@ -55,7 +60,6 @@ export class UnionType extends Type { */ toObject(): any { const result: any = super.toObject(); - result.type = 'union'; if (this.types && this.types.length) { result.types = this.types.map((e) => e.toObject()); diff --git a/src/lib/models/types/unknown.ts b/src/lib/models/types/unknown.ts index 3cb7ad304..f87225935 100644 --- a/src/lib/models/types/unknown.ts +++ b/src/lib/models/types/unknown.ts @@ -9,6 +9,11 @@ export class UnknownType extends Type { */ name: string; + /** + * The type name identifier. + */ + readonly type: string = 'unknown'; + /** * Create a new instance of UnknownType. * @@ -47,7 +52,6 @@ export class UnknownType extends Type { */ toObject(): any { const result: any = super.toObject(); - result.type = 'unknown'; result.name = this.name; return result; } diff --git a/src/test/converter/union-or-intersection/specs.json b/src/test/converter/union-or-intersection/specs.json new file mode 100644 index 000000000..52008aea3 --- /dev/null +++ b/src/test/converter/union-or-intersection/specs.json @@ -0,0 +1,303 @@ +{ + "id": 0, + "name": "typedoc", + "kind": 0, + "flags": {}, + "children": [ + { + "id": 1, + "name": "\"union-or-intersection\"", + "kind": 1, + "kindString": "External module", + "flags": { + "isExported": true + }, + "originalName": "%BASE%/union-or-intersection/union-or-intersection.ts", + "children": [ + { + "id": 2, + "name": "FirstType", + "kind": 256, + "kindString": "Interface", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "First type for union or intersection type tests." + }, + "children": [ + { + "id": 3, + "name": "firstProperty", + "kind": 1024, + "kindString": "Property", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "Property of first type." + }, + "sources": [ + { + "fileName": "union-or-intersection.ts", + "line": 9, + "character": 17 + } + ], + "type": { + "type": "instrinct", + "name": "string" + } + } + ], + "groups": [ + { + "title": "Properties", + "kind": 1024, + "children": [ + 3 + ] + } + ], + "sources": [ + { + "fileName": "union-or-intersection.ts", + "line": 4, + "character": 26 + } + ] + }, + { + "id": 4, + "name": "SecondType", + "kind": 256, + "kindString": "Interface", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "Second type for union or intersection type tests." + }, + "children": [ + { + "id": 5, + "name": "secondProperty", + "kind": 1024, + "kindString": "Property", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "Property of second type." + }, + "sources": [ + { + "fileName": "union-or-intersection.ts", + "line": 20, + "character": 18 + } + ], + "type": { + "type": "instrinct", + "name": "number" + } + } + ], + "groups": [ + { + "title": "Properties", + "kind": 1024, + "children": [ + 5 + ] + } + ], + "sources": [ + { + "fileName": "union-or-intersection.ts", + "line": 15, + "character": 27 + } + ] + }, + { + "id": 6, + "name": "ThirdType", + "kind": 256, + "kindString": "Interface", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "Third type for union or intersection type tests." + }, + "children": [ + { + "id": 9, + "name": "thirdComplexProperty", + "kind": 1024, + "kindString": "Property", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "Complex Property of third type." + }, + "sources": [ + { + "fileName": "union-or-intersection.ts", + "line": 41, + "character": 24 + } + ], + "type": { + "type": "union", + "isArray": true, + "types": [ + { + "type": "instrinct", + "name": "string" + }, + { + "type": "reference", + "name": "Array", + "typeArguments": [ + { + "type": "intersection", + "types": [ + { + "type": "reference", + "name": "FirstType", + "id": 2 + }, + { + "type": "reference", + "name": "SecondType", + "id": 4 + } + ] + } + ] + } + ] + } + }, + { + "id": 8, + "name": "thirdIntersectionProperty", + "kind": 1024, + "kindString": "Property", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "Intersection Property of third type." + }, + "sources": [ + { + "fileName": "union-or-intersection.ts", + "line": 36, + "character": 29 + } + ], + "type": { + "type": "intersection", + "types": [ + { + "type": "reference", + "name": "FirstType", + "id": 2 + }, + { + "type": "reference", + "name": "ThirdType", + "id": 6 + } + ] + } + }, + { + "id": 7, + "name": "thirdUnionProperty", + "kind": 1024, + "kindString": "Property", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "Union Property of third type." + }, + "sources": [ + { + "fileName": "union-or-intersection.ts", + "line": 31, + "character": 22 + } + ], + "type": { + "type": "union", + "types": [ + { + "type": "reference", + "name": "FirstType", + "id": 2 + }, + { + "type": "reference", + "name": "SecondType", + "id": 4 + } + ] + } + } + ], + "groups": [ + { + "title": "Properties", + "kind": 1024, + "children": [ + 9, + 8, + 7 + ] + } + ], + "sources": [ + { + "fileName": "union-or-intersection.ts", + "line": 26, + "character": 26 + } + ] + } + ], + "groups": [ + { + "title": "Interfaces", + "kind": 256, + "children": [ + 2, + 4, + 6 + ] + } + ], + "sources": [ + { + "fileName": "union-or-intersection.ts", + "line": 1, + "character": 0 + } + ] + } + ], + "groups": [ + { + "title": "External modules", + "kind": 1, + "children": [ + 1 + ] + } + ] +} \ No newline at end of file diff --git a/src/test/converter/union-or-intersection/union-or-intersection.ts b/src/test/converter/union-or-intersection/union-or-intersection.ts new file mode 100644 index 000000000..6e521577f --- /dev/null +++ b/src/test/converter/union-or-intersection/union-or-intersection.ts @@ -0,0 +1,42 @@ +/** + * First type for union or intersection type tests. + */ +export interface FirstType +{ + /** + * Property of first type. + */ + firstProperty: string; +} + +/** + * Second type for union or intersection type tests. + */ +export interface SecondType +{ + /** + * Property of second type. + */ + secondProperty: number; +} + +/** + * Third type for union or intersection type tests. + */ +export interface ThirdType +{ + /** + * Union Property of third type. + */ + thirdUnionProperty: FirstType | SecondType; + + /** + * Intersection Property of third type. + */ + thirdIntersectionProperty: FirstType & ThirdType; + + /** + * Complex Property of third type. + */ + thirdComplexProperty: ((FirstType & SecondType)[] | string)[]; +} diff --git a/tsconfig.json b/tsconfig.json index 9c1efad10..a4dc524e3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,10 +2,10 @@ "compilerOptions": { "module": "commonjs", "lib": [ - "DOM", - "ES5", - "ES2015.Collection", - "ES2015.Iterable" + "dom", + "es5", + "es2015.collection", + "es2015.iterable" ], "target": "ES5", "noImplicitAny": false,