From 525d1cc4d732e70a8c3f3ca0aa822ccfc34f0ffe Mon Sep 17 00:00:00 2001 From: makana Date: Mon, 1 May 2017 14:18:01 +0200 Subject: [PATCH] Support for multidimensional arrays Added an array type model to support multidimensional arrays. Removed the isArray flag in favor of the type model. Added supportsType/convertType to array converter to detect the type:reference arrays. --- package.json | 2 +- src/lib/converter/plugins/TypePlugin.ts | 4 +- src/lib/converter/types/array.ts | 42 ++++- src/lib/models/types/abstract.ts | 8 - src/lib/models/types/array.ts | 75 ++++++++ src/lib/models/types/index.ts | 1 + src/lib/models/types/intersection.ts | 7 +- src/lib/models/types/intrinsic.ts | 7 +- src/lib/models/types/reference.ts | 5 +- src/lib/models/types/reflection.ts | 4 +- src/lib/models/types/string-literal.ts | 5 +- src/lib/models/types/tuple.ts | 7 +- src/lib/models/types/type-parameter.ts | 9 +- src/lib/models/types/union.ts | 7 +- src/lib/models/types/unknown.ts | 5 +- src/test/converter/array/array.ts | 16 ++ src/test/converter/array/specs.json | 175 ++++++++++++++++++ src/test/converter/class/specs.json | 8 +- src/test/converter/destructuring/specs.json | 26 +-- src/test/converter/function/specs.json | 8 +- src/test/converter/generic-class/specs.json | 16 +- .../converter/generic-function/specs.json | 16 +- .../interface-implementation/specs.json | 22 ++- src/test/converter/literal-object/specs.json | 13 +- src/test/converter/literal-type/specs.json | 8 +- .../union-or-intersection/specs.json | 29 ++- .../renderer/specs/modules/_modules_.html | 2 +- .../specs/modules/_modules_.mymodule.html | 2 +- .../specs/modules/_typescript_1_5_.html | 4 +- 29 files changed, 394 insertions(+), 139 deletions(-) create mode 100644 src/lib/models/types/array.ts create mode 100644 src/test/converter/array/array.ts create mode 100644 src/test/converter/array/specs.json diff --git a/package.json b/package.json index 25830f251..00da6d2eb 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "minimatch": "^3.0.0", "progress": "^2.0.0", "shelljs": "^0.7.0", - "typedoc-default-themes": "^0.4.2", + "typedoc-default-themes": "^0.4.4", "typescript": "2.3.2" }, "devDependencies": { diff --git a/src/lib/converter/plugins/TypePlugin.ts b/src/lib/converter/plugins/TypePlugin.ts index 68e4059dd..05a068144 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, IntersectionType} from '../../models/types/index'; +import {Type, ReferenceType, TupleType, UnionType, IntersectionType, ArrayType} from '../../models/types/index'; import {Component, ConverterComponent} from '../components'; import {Converter} from '../converter'; import {Context} from '../context'; @@ -113,6 +113,8 @@ export class TypePlugin extends ConverterComponent { for (let index = 0, count = unionOrIntersectionType.types.length; index < count; index++) { resolveType(reflection, unionOrIntersectionType.types[index]); } + } else if (type instanceof ArrayType) { + resolveType(reflection, type.elementType); } } } diff --git a/src/lib/converter/types/array.ts b/src/lib/converter/types/array.ts index 2b87e4023..ec953f7eb 100644 --- a/src/lib/converter/types/array.ts +++ b/src/lib/converter/types/array.ts @@ -1,11 +1,11 @@ import * as ts from 'typescript'; -import {Type, IntrinsicType} from '../../models/index'; -import {Component, ConverterTypeComponent, TypeNodeConverter} from '../components'; +import {Type, ArrayType} from '../../models/index'; +import {Component, ConverterTypeComponent, TypeConverter} from '../components'; import {Context} from '../context'; @Component({name: 'type:array'}) -export class ArrayConverter extends ConverterTypeComponent implements TypeNodeConverter { +export class ArrayConverter extends ConverterTypeComponent implements TypeConverter { /** * Test whether this converter can handle the given TypeScript node. */ @@ -13,6 +13,14 @@ export class ArrayConverter extends ConverterTypeComponent implements TypeNodeCo return node.kind === ts.SyntaxKind.ArrayType; } + /** + * Test whether this converter can handle the given TypeScript type. + */ + supportsType(context: Context, type: ts.TypeReference): boolean { + // Is there a better way to detect the {"type":"reference","name":"Array","typeArguments":{...}} types that are in fact arrays? + return !!(type.flags & ts.TypeFlags.Object) && !!type.symbol && type.symbol.name === 'Array' && !type.symbol.parent && !!type.typeArguments && type.typeArguments.length === 1; + } + /** * Convert the given array type node to its type reflection. * @@ -27,14 +35,28 @@ export class ArrayConverter extends ConverterTypeComponent implements TypeNodeCo * @returns The type reflection representing the given array type node. */ convertNode(context: Context, node: ts.ArrayTypeNode): Type { - let result = this.owner.convertType(context, node.elementType); + const result = this.owner.convertType(context, node.elementType); - if (result) { - result.isArray = true; - } else { - result = new IntrinsicType('Array'); - } + return new ArrayType(result); + } + + /** + * Convert the given type reference to its type reflection. + * + * This is a type based converter, see [[convertTypeReference]] for the node equivalent. + * + * ``` + * class SomeClass { } + * let someValue: SomeClass; + * ``` + * + * @param context The context object describing the current state the converter is in. + * @param type The type reference that should be converted. + * @returns The type reflection representing the given type reference. + */ + convertType(context: Context, type: ts.TypeReference): Type { + const result = this.owner.convertType(context, null, type.typeArguments[0]); - return result; + return new ArrayType(result); } } diff --git a/src/lib/models/types/abstract.ts b/src/lib/models/types/abstract.ts index ec4546acc..8e0297fed 100644 --- a/src/lib/models/types/abstract.ts +++ b/src/lib/models/types/abstract.ts @@ -4,10 +4,6 @@ * Instances of this class are also used to represent the type `void`. */ export abstract class Type { - /** - * Is this an array type? - */ - isArray = false; /** * The type name identifier. @@ -38,10 +34,6 @@ export abstract class Type { let result: any = {}; result.type = this.type; - if (this.isArray) { - result.isArray = this.isArray; - } - return result; } diff --git a/src/lib/models/types/array.ts b/src/lib/models/types/array.ts new file mode 100644 index 000000000..ce3879fee --- /dev/null +++ b/src/lib/models/types/array.ts @@ -0,0 +1,75 @@ +import {Type, UnionType, IntersectionType} from './index'; + +/** + * Represents an array type. + * + * ~~~ + * let value: string[]; + * ~~~ + */ +export class ArrayType extends Type { + + /** + * The type of the array elements. + */ + elementType: Type; + + /** + * The type name identifier. + */ + readonly type: string = 'array'; + + /** + * Create a new TupleType instance. + * + * @param elementType The type of the array's elements. + */ + constructor(elementType: Type) { + super(); + this.elementType = elementType; + } + + /** + * Clone this type. + * + * @return A clone of this type. + */ + clone(): Type { + return new ArrayType(this.elementType); + } + + /** + * 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 ArrayType)) { + return false; + } + return type.elementType.equals(this.elementType); + } + + /** + * Return a raw object representation of this type. + */ + toObject(): any { + const result: any = super.toObject(); + result.elementType = this.elementType.toObject(); + + return result; + } + + /** + * Return a string representation of this type. + */ + toString() { + const elementTypeStr = this.elementType.toString(); + if (this.elementType instanceof UnionType || this.elementType instanceof IntersectionType) { + return '(' + elementTypeStr + ')[]'; + } else { + return elementTypeStr + '[]'; + } + } +} diff --git a/src/lib/models/types/index.ts b/src/lib/models/types/index.ts index b57575b0c..0f64bd8ed 100644 --- a/src/lib/models/types/index.ts +++ b/src/lib/models/types/index.ts @@ -1,4 +1,5 @@ export {Type} from './abstract'; +export {ArrayType} from './array'; export {IntrinsicType} from './intrinsic'; export {IntersectionType} from './intersection'; export {ReferenceType} from './reference'; diff --git a/src/lib/models/types/intersection.ts b/src/lib/models/types/intersection.ts index 7fbc11eac..86bb1e6df 100644 --- a/src/lib/models/types/intersection.ts +++ b/src/lib/models/types/intersection.ts @@ -34,9 +34,7 @@ export class IntersectionType extends Type { * @return A clone of this type. */ clone(): Type { - const clone = new IntersectionType(this.types); - clone.isArray = this.isArray; - return clone; + return new IntersectionType(this.types); } /** @@ -49,9 +47,6 @@ export class IntersectionType extends Type { if (!(type instanceof IntersectionType)) { return false; } - if (type.isArray !== this.isArray) { - return false; - } return Type.isTypeListSimiliar(type.types, this.types); } diff --git a/src/lib/models/types/intrinsic.ts b/src/lib/models/types/intrinsic.ts index ca52018d4..f3e3bb49f 100644 --- a/src/lib/models/types/intrinsic.ts +++ b/src/lib/models/types/intrinsic.ts @@ -34,9 +34,7 @@ export class IntrinsicType extends Type { * @return A clone of this type. */ clone(): Type { - const clone = new IntrinsicType(this.name); - clone.isArray = this.isArray; - return clone; + return new IntrinsicType(this.name); } /** @@ -47,7 +45,6 @@ export class IntrinsicType extends Type { */ equals(type: IntrinsicType): boolean { return type instanceof IntrinsicType && - type.isArray === this.isArray && type.name === this.name; } @@ -64,6 +61,6 @@ export class IntrinsicType extends Type { * Return a string representation of this type. */ toString() { - return this.name + (this.isArray ? '[]' : ''); + return this.name; } } diff --git a/src/lib/models/types/reference.ts b/src/lib/models/types/reference.ts index 21f7dbb6c..f5e456b99 100644 --- a/src/lib/models/types/reference.ts +++ b/src/lib/models/types/reference.ts @@ -73,7 +73,6 @@ export class ReferenceType extends Type { */ clone(): Type { const clone = new ReferenceType(this.name, this.symbolID, this.reflection); - clone.isArray = this.isArray; clone.typeArguments = this.typeArguments; return clone; } @@ -86,7 +85,6 @@ export class ReferenceType extends Type { */ equals(type: ReferenceType): boolean { return type instanceof ReferenceType && - type.isArray === this.isArray && (type.symbolID === this.symbolID || type.reflection === this.reflection); } @@ -114,7 +112,6 @@ export class ReferenceType extends Type { */ toString() { const name = this.reflection ? this.reflection.name : this.name; - const arraySuffix = this.isArray ? '[]' : ''; let typeArgs = ''; if (this.typeArguments) { typeArgs += '<'; @@ -122,6 +119,6 @@ export class ReferenceType extends Type { typeArgs += '>'; } - return name + typeArgs + arraySuffix; + return name + typeArgs; } } diff --git a/src/lib/models/types/reflection.ts b/src/lib/models/types/reflection.ts index b56543bb9..4d1695e25 100644 --- a/src/lib/models/types/reflection.ts +++ b/src/lib/models/types/reflection.ts @@ -35,9 +35,7 @@ export class ReflectionType extends Type { * @return A clone of this type. */ clone(): Type { - const clone = new ReflectionType(this.declaration); - clone.isArray = this.isArray; - return clone; + return new ReflectionType(this.declaration); } /** diff --git a/src/lib/models/types/string-literal.ts b/src/lib/models/types/string-literal.ts index 477b8c12b..cac94139f 100644 --- a/src/lib/models/types/string-literal.ts +++ b/src/lib/models/types/string-literal.ts @@ -34,9 +34,7 @@ export class StringLiteralType extends Type { * @return A clone of this type. */ clone(): Type { - const clone = new StringLiteralType(this.value); - clone.isArray = this.isArray; - return clone; + return new StringLiteralType(this.value); } /** @@ -47,7 +45,6 @@ export class StringLiteralType extends Type { */ equals(type: StringLiteralType): boolean { return type instanceof StringLiteralType && - type.isArray === this.isArray && type.value === this.value; } diff --git a/src/lib/models/types/tuple.ts b/src/lib/models/types/tuple.ts index d674b852e..c20b47131 100644 --- a/src/lib/models/types/tuple.ts +++ b/src/lib/models/types/tuple.ts @@ -34,9 +34,7 @@ export class TupleType extends Type { * @return A clone of this type. */ clone(): Type { - const clone = new TupleType(this.elements); - clone.isArray = this.isArray; - return clone; + return new TupleType(this.elements); } /** @@ -49,9 +47,6 @@ export class TupleType extends Type { if (!(type instanceof TupleType)) { return false; } - if (type.isArray !== this.isArray) { - return false; - } return Type.isTypeListEqual(type.elements, this.elements); } diff --git a/src/lib/models/types/type-parameter.ts b/src/lib/models/types/type-parameter.ts index 870cec6ff..6568f93cc 100644 --- a/src/lib/models/types/type-parameter.ts +++ b/src/lib/models/types/type-parameter.ts @@ -27,7 +27,6 @@ export class TypeParameterType extends Type { */ clone(): Type { const clone = new TypeParameterType(); - clone.isArray = this.isArray; clone.name = this.name; clone.constraint = this.constraint; return clone; @@ -44,17 +43,13 @@ export class TypeParameterType extends Type { return false; } - let constraintEquals: boolean; if (this.constraint && type.constraint) { - constraintEquals = type.constraint.equals(this.constraint); + return type.constraint.equals(this.constraint); } else if (!this.constraint && !type.constraint) { - constraintEquals = true; + return true; } else { return false; } - - return constraintEquals && - type.isArray === this.isArray; } /** diff --git a/src/lib/models/types/union.ts b/src/lib/models/types/union.ts index fe36a1b41..851151f8a 100644 --- a/src/lib/models/types/union.ts +++ b/src/lib/models/types/union.ts @@ -34,9 +34,7 @@ export class UnionType extends Type { * @return A clone of this type. */ clone(): Type { - const clone = new UnionType(this.types); - clone.isArray = this.isArray; - return clone; + return new UnionType(this.types); } /** @@ -49,9 +47,6 @@ export class UnionType extends Type { if (!(type instanceof UnionType)) { return false; } - if (type.isArray !== this.isArray) { - return false; - } return Type.isTypeListSimiliar(type.types, this.types); } diff --git a/src/lib/models/types/unknown.ts b/src/lib/models/types/unknown.ts index f87225935..0bf051667 100644 --- a/src/lib/models/types/unknown.ts +++ b/src/lib/models/types/unknown.ts @@ -30,9 +30,7 @@ export class UnknownType extends Type { * @return A clone of this type. */ clone(): Type { - const clone = new UnknownType(this.name); - clone.isArray = this.isArray; - return clone; + return new UnknownType(this.name); } /** @@ -43,7 +41,6 @@ export class UnknownType extends Type { */ equals(type: UnknownType): boolean { return type instanceof UnknownType && - type.isArray === this.isArray && type.name === this.name; } diff --git a/src/test/converter/array/array.ts b/src/test/converter/array/array.ts new file mode 100644 index 000000000..7482ac984 --- /dev/null +++ b/src/test/converter/array/array.ts @@ -0,0 +1,16 @@ +/** + * A custom array interface. + */ +export interface Array +{ +} + +/** + * A const of a complex type. + */ +export const complex: ((Array[] | number[])[] | string)[][] = []; + +/** + * An exported const of the custom array type. + */ +export const custom: Array = {}; \ No newline at end of file diff --git a/src/test/converter/array/specs.json b/src/test/converter/array/specs.json new file mode 100644 index 000000000..bdfd7695d --- /dev/null +++ b/src/test/converter/array/specs.json @@ -0,0 +1,175 @@ +{ + "id": 0, + "name": "typedoc", + "kind": 0, + "flags": {}, + "children": [ + { + "id": 1, + "name": "\"array\"", + "kind": 1, + "kindString": "External module", + "flags": { + "isExported": true + }, + "originalName": "%BASE%/array/array.ts", + "children": [ + { + "id": 2, + "name": "Array", + "kind": 256, + "kindString": "Interface", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "A custom array interface." + }, + "typeParameter": [ + { + "id": 3, + "name": "T", + "kind": 131072, + "kindString": "Type parameter", + "flags": {} + } + ], + "sources": [ + { + "fileName": "array.ts", + "line": 4, + "character": 22 + } + ] + }, + { + "id": 4, + "name": "complex", + "kind": 32, + "kindString": "Variable", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "A const of a complex type." + }, + "sources": [ + { + "fileName": "array.ts", + "line": 11, + "character": 20 + } + ], + "type": { + "type": "array", + "elementType": { + "type": "array", + "elementType": { + "type": "union", + "types": [ + { + "type": "intrinsic", + "name": "string" + }, + { + "type": "array", + "elementType": { + "type": "union", + "types": [ + { + "type": "array", + "elementType": { + "type": "reference", + "name": "Array", + "id": 2, + "typeArguments": [ + { + "type": "intrinsic", + "name": "string" + } + ] + } + }, + { + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "number" + } + } + ] + } + } + ] + } + } + }, + "defaultValue": " []" + }, + { + "id": 5, + "name": "custom", + "kind": 32, + "kindString": "Variable", + "flags": { + "isExported": true + }, + "comment": { + "shortText": "An exported const of the custom array type." + }, + "sources": [ + { + "fileName": "array.ts", + "line": 16, + "character": 19 + } + ], + "type": { + "type": "reference", + "name": "Array", + "id": 2, + "typeArguments": [ + { + "type": "intrinsic", + "name": "number" + } + ] + } + } + ], + "groups": [ + { + "title": "Interfaces", + "kind": 256, + "children": [ + 2 + ] + }, + { + "title": "Variables", + "kind": 32, + "children": [ + 4, + 5 + ] + } + ], + "sources": [ + { + "fileName": "array.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/class/specs.json b/src/test/converter/class/specs.json index b775d2ce4..34aa59bf0 100644 --- a/src/test/converter/class/specs.json +++ b/src/test/converter/class/specs.json @@ -89,9 +89,11 @@ } ], "type": { - "type": "intrinsic", - "isArray": true, - "name": "number" + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "number" + } } }, { diff --git a/src/test/converter/destructuring/specs.json b/src/test/converter/destructuring/specs.json index 43bcc92c3..ec6b2ecae 100644 --- a/src/test/converter/destructuring/specs.json +++ b/src/test/converter/destructuring/specs.json @@ -110,14 +110,11 @@ } ], "type": { - "type": "reference", - "name": "Array", - "typeArguments": [ - { - "type": "intrinsic", - "name": "number" - } - ] + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "number" + } } }, { @@ -134,14 +131,11 @@ } ], "type": { - "type": "reference", - "name": "Array", - "typeArguments": [ - { - "type": "intrinsic", - "name": "number" - } - ] + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "number" + } } }, { diff --git a/src/test/converter/function/specs.json b/src/test/converter/function/specs.json index 040919636..d6843bdb9 100644 --- a/src/test/converter/function/specs.json +++ b/src/test/converter/function/specs.json @@ -483,9 +483,11 @@ "text": "The rest parameter." }, "type": { - "type": "intrinsic", - "isArray": true, - "name": "string" + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } } } ], diff --git a/src/test/converter/generic-class/specs.json b/src/test/converter/generic-class/specs.json index d9f96ce3d..d8eb54571 100644 --- a/src/test/converter/generic-class/specs.json +++ b/src/test/converter/generic-class/specs.json @@ -128,9 +128,11 @@ } ], "type": { - "type": "typeParameter", - "isArray": true, - "name": "T" + "type": "array", + "elementType": { + "type": "typeParameter", + "name": "T" + } } }, { @@ -321,9 +323,11 @@ } ], "type": { - "type": "intrinsic", - "isArray": true, - "name": "string" + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "string" + } }, "inheritedFrom": { "type": "reference", diff --git a/src/test/converter/generic-function/specs.json b/src/test/converter/generic-function/specs.json index e2f1131ae..88e7e6f97 100644 --- a/src/test/converter/generic-function/specs.json +++ b/src/test/converter/generic-function/specs.json @@ -65,16 +65,20 @@ "text": "A generic array parameter." }, "type": { - "type": "typeParameter", - "isArray": true, - "name": "T" + "type": "array", + "elementType": { + "type": "typeParameter", + "name": "T" + } } } ], "type": { - "type": "typeParameter", - "isArray": true, - "name": "T" + "type": "array", + "elementType": { + "type": "typeParameter", + "name": "T" + } } } ], diff --git a/src/test/converter/interface-implementation/specs.json b/src/test/converter/interface-implementation/specs.json index 3b8c352a6..c1e7b4049 100644 --- a/src/test/converter/interface-implementation/specs.json +++ b/src/test/converter/interface-implementation/specs.json @@ -65,16 +65,18 @@ } ], "type": { - "type": "reference", - "isArray": true, - "name": "ISubscription", - "id": 7, - "typeArguments": [ - { - "type": "typeParameter", - "name": "T" - } - ] + "type": "array", + "elementType": { + "type": "reference", + "name": "ISubscription", + "id": 7, + "typeArguments": [ + { + "type": "typeParameter", + "name": "T" + } + ] + } } }, { diff --git a/src/test/converter/literal-object/specs.json b/src/test/converter/literal-object/specs.json index 093b94217..cb8804d41 100644 --- a/src/test/converter/literal-object/specs.json +++ b/src/test/converter/literal-object/specs.json @@ -129,14 +129,11 @@ } ], "type": { - "type": "reference", - "name": "Array", - "typeArguments": [ - { - "type": "intrinsic", - "name": "number" - } - ] + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "number" + } }, "defaultValue": " [100, 200, 300]" }, diff --git a/src/test/converter/literal-type/specs.json b/src/test/converter/literal-type/specs.json index 76b12b361..cb7119229 100644 --- a/src/test/converter/literal-type/specs.json +++ b/src/test/converter/literal-type/specs.json @@ -112,9 +112,11 @@ } ], "type": { - "type": "intrinsic", - "isArray": true, - "name": "number" + "type": "array", + "elementType": { + "type": "intrinsic", + "name": "number" + } } }, { diff --git a/src/test/converter/union-or-intersection/specs.json b/src/test/converter/union-or-intersection/specs.json index 94370f048..2e407f988 100644 --- a/src/test/converter/union-or-intersection/specs.json +++ b/src/test/converter/union-or-intersection/specs.json @@ -151,18 +151,17 @@ } ], "type": { - "type": "union", - "isArray": true, - "types": [ - { - "type": "intrinsic", - "name": "string" - }, - { - "type": "reference", - "name": "Array", - "typeArguments": [ - { + "type": "array", + "elementType": { + "type": "union", + "types": [ + { + "type": "intrinsic", + "name": "string" + }, + { + "type": "array", + "elementType": { "type": "intersection", "types": [ { @@ -177,9 +176,9 @@ } ] } - ] - } - ] + } + ] + } } }, { diff --git a/src/test/renderer/specs/modules/_modules_.html b/src/test/renderer/specs/modules/_modules_.html index fe28e641e..c70e74833 100644 --- a/src/test/renderer/specs/modules/_modules_.html +++ b/src/test/renderer/specs/modules/_modules_.html @@ -297,7 +297,7 @@

valueX

valueA

-
valueA: Array<number> = [100, 200, 300]
+
valueA: number[] = [100, 200, 300]