/* * Copyright (C) 2019 StApps * This program is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation, version 3. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see . */ import {Logger} from '@openstapps/logger'; import { ArrayType, ConditionalType, DeclarationReflection, IntrinsicType, ProjectReflection, QueryType, ReferenceType, ReflectionKind, ReflectionType, StringLiteralType, Type, TypeOperatorType, TypeParameterType, UnionType, } from 'typedoc/dist/lib/models'; import {getFullTypeName} from '../common'; import {LightweightClassDefinition} from './model/lightweight-class-definition'; import {LightweightDefinition} from './model/lightweight-definition'; import {LightweightEnumDefinition} from './model/lightweight-enum-definition'; import {LightweightProperty} from './model/lightweight-property'; import {LightweightType} from './model/lightweight-type'; /** * Reads the reflection model from typedoc and converts it into a flatter, easier to handle model * * @param srcPath Path to source file directory */ export function readDefinitions(projectReflection: ProjectReflection): LightweightDefinition[] { const definitions: LightweightDefinition[] = []; // define known types and categorize them const enumLike: string[] = ['Type alias', 'Enumeration']; const classLike: string[] = ['Class', 'Interface']; const unused: string[] = ['Function', 'Object literal', 'Variable']; // children need to be not undefined, if they are return empty if (typeof projectReflection.children === 'undefined') { return []; } for (const module of projectReflection.children) { if (Array.isArray(module.children) && module.children.length > 0) { // iterate over class and enum declarations for (const type of module.children) { // only if kindString is set if (typeof type.kindString !== 'undefined') { // check if declaration is enum if (classLike.includes(type.kindString)) { definitions.push(readAsClassDefinition(type)); } else if (enumLike.includes(type.kindString)) { definitions.push(readAsEnumDefinition(type)); } else if (unused.includes(type.kindString)) { Logger.info(`Unconverted ${type.kindString} : ${type.name}`); } else { Logger.log( `Uncaught declaration type (${type.kindString}) : ${type.name}`, ); } } } } } return definitions; } /** * Transforms the declaration into a `LightweightClassDefinition` * * @param declaration declaration */ export function readAsEnumDefinition( declaration: DeclarationReflection, ): LightweightEnumDefinition { // init enum definition const enumDefinition: LightweightEnumDefinition = new LightweightEnumDefinition( declaration.name, ); // get enum values according to type if (declaration.kindString === 'Enumeration' && typeof declaration.children !== 'undefined') { // standard enumeration for (const child of declaration.children) { if (child.kindString === 'Enumeration member') { let value = child.name; if (typeof child.defaultValue !== 'undefined') { value = `${value} = ${child.defaultValue}`; } enumDefinition.values.push(value); } else { Logger.log( "Every enumeration member should be an 'EnumerationMemberType'", ); } } } else if ( declaration.kindString === 'Type alias' && typeof declaration.type !== 'undefined' ) { // enum like declaration try { const a = readTypeInformation(declaration.type); enumDefinition.values = enumDefinition.values.concat( getTypeInformation(a), ); } catch (e) { Logger.warn( `Could not read the light type for ${declaration.name}. ${e}`, ); } } return enumDefinition; } /** * Used for enumrations to get the type value */ function getTypeInformation(type: LightweightType): string[] { const values: string[] = []; if (!type.hasTypeInformation) { for (const specificType of type.specificationTypes) { for (const value of getTypeInformation(specificType)) { values.push(value); } } } else { values.push(type.name); } return values; } /** * Transforms the declaration into a `LightweightClassDefinition` * * @param declaration declaration */ export function readAsClassDefinition( declaration: DeclarationReflection, ): LightweightClassDefinition { let type = typeof declaration.kindString !== 'undefined' ? declaration.kindString.toLowerCase() : ''; type = (declaration.flags.isAbstract ? 'abstract ' : '') + type; const classDefinition: LightweightClassDefinition = new LightweightClassDefinition( declaration.name, type, ); // get generic types if (typeof declaration.typeParameters !== 'undefined') { const typeParameters: string[] = []; declaration.typeParameters.forEach((typeParameter) => typeParameters.push(typeParameter.name), ); classDefinition.typeParameters = typeParameters; } // extracts extended types of the declaration if (typeof declaration.extendedTypes !== 'undefined') { for (const extType of declaration.extendedTypes) { classDefinition.extendedDefinitions.push((extType as ReferenceType).name); } } // extracts implemented types of the declaration // HINT: typedoc automatically adds inherited interfaces to the declaration directly if (typeof declaration.implementedTypes !== 'undefined') { for (const implType of declaration.implementedTypes) { classDefinition.implementedDefinitions.push( (implType as ReferenceType).name, ); } } if (typeof declaration.children !== 'undefined') { for (const child of declaration.getChildrenByKind( ReflectionKind.Property, )) { try { if (typeof child.type === 'undefined') { throw new Error(); } const myType: LightweightType = readTypeInformation(child.type); const property = new LightweightProperty(child.name, myType); const flags = child.flags; if (flags.isOptional !== undefined) { property.optional = flags.isOptional as boolean; property.inherited = !( child.inheritedFrom === undefined || child.inheritedFrom === null ); } classDefinition.properties.push(property); } catch (e) { Logger.warn(e); } } } return classDefinition; } /** * The structure of reflection type has a huge overhead * This method and all submethods will convert these types in easier to process Types * * @param declarationType Type to be converted */ function readTypeInformation(declarationType: Type): LightweightType { if (declarationType instanceof ReflectionType) { return readAsReflectionType(declarationType); } if (declarationType instanceof TypeOperatorType) { return readAsTypeOperatorType(declarationType); } if (declarationType instanceof TypeParameterType) { return readAsTypeParameterType(declarationType); } if (declarationType instanceof IntrinsicType) { return readAsIntrinsicType(declarationType); } if (declarationType instanceof StringLiteralType) { return readAsStringLiteralType(declarationType); } if (declarationType instanceof ReferenceType) { return readAsReferenceType(declarationType); } if (declarationType instanceof ArrayType) { return readAsArrayType(declarationType); } if (declarationType instanceof UnionType) { return readAsUnionType(declarationType); } if (declarationType instanceof QueryType) { return readAsQueryType(declarationType); } if (declarationType instanceof ConditionalType) { return readAsConditionalType(declarationType); } throw new Error(`Could not read type ${declarationType.type}`); } /** * Conversion method for ConditionalTypes * * @param _type Type to be converted */ function readAsConditionalType(_type: ConditionalType): LightweightType { const returnType: LightweightType = new LightweightType(); returnType.specificationTypes = []; returnType.name = getFullTypeName(returnType); returnType.isUnion = true; return returnType; } /** * Conversion method for QueryTypes * * @param type Type to be converted */ function readAsQueryType(type: QueryType): LightweightType { const out = readAsReferenceType(type.queryType); out.isReference = true; return out; } /** * Conversion method for IntrinsicType's * * e.g. remainingAttendeeCapacity?: number; * * @param type Type to be converted */ function readAsIntrinsicType(type: IntrinsicType): LightweightType { const easyType: LightweightType = new LightweightType(); easyType.name = type.name; easyType.isPrimitive = true; easyType.hasTypeInformation = true; return easyType; } /** * Conversion method for StringLiteralType's * * e.g. inputType: 'multipleChoice'; * * @param type Type to be converted */ function readAsStringLiteralType(type: StringLiteralType): LightweightType { const returnType: LightweightType = new LightweightType(); returnType.name = type.value; returnType.isLiteral = true; returnType.hasTypeInformation = true; return returnType; } /** * Conversion method for ReferenceType's * * Everything that is a user or API designed definition and not a primitive type or core-language feature. * * e.g. publishers?: Array; * * Array, SCPersonWithoutReferences and SCOrganizationWithoutReferences will be recognized as reference types! * * @param type Type to be converted */ function readAsReferenceType(type: ReferenceType): LightweightType { const returnType: LightweightType = new LightweightType(); returnType.name = type.name; if (type.typeArguments !== undefined && type.typeArguments.length > 0) { const typeArguments: LightweightType[] = []; for (const value of type.typeArguments) { typeArguments.push(readTypeInformation(value)); } returnType.isTyped = true; returnType.genericsTypes = typeArguments; } if (type.reflection !== undefined && type.reflection !== null) { const tempTypeReflection = type.reflection as DeclarationReflection; // interfaces and classes in a type are a sink, since their declaration are defined elsewhere if ( typeof tempTypeReflection.kindString !== 'undefined' && ['Interface', 'Class', 'Enumeration', 'Type alias'].includes( tempTypeReflection.kindString)) { returnType.isReference = true; } } returnType.hasTypeInformation = true; return returnType; } /** * Conversion method for ArrayType's * * The actual type of the array is stored in the first element of specificationTypes. * * e.g. articleBody?: string[]; * * @param type Type to be converted */ function readAsArrayType(type: ArrayType): LightweightType { const returnType: LightweightType = new LightweightType(); const typeOfArray: LightweightType = readTypeInformation(type.elementType); returnType.name = getFullTypeName(typeOfArray); returnType.specificationTypes = [typeOfArray]; returnType.isArray = true; return returnType; } /** * Conversion method for UnionType's * * The Union-LightType store the single types of the union inside a * separate LightType inside specificationTypes. * * e.g. maintainer?: SCPerson | SCOrganization; * * @param type Type to be converted */ function readAsUnionType(type: UnionType): LightweightType { const returnType: LightweightType = new LightweightType(); const typesOfUnion: LightweightType[] = []; for (const value of type.types) { typesOfUnion.push(readTypeInformation(value)); } returnType.specificationTypes = typesOfUnion; returnType.name = getFullTypeName(returnType); returnType.isUnion = true; return returnType; } /** * Conversion method for ReflectionType's * * The explicit type is not contained in reflection! * It might be possible to get the structure of type by reading tempType.decoration.children, * but this structure is currently not supported in the data-model. * * e.g. categorySpecificValues?: { [s: string]: U }; * * @param type Type to be converted */ function readAsReflectionType(type: ReflectionType): LightweightType { const returnType: LightweightType = new LightweightType(); if (typeof type.declaration.sources !== 'undefined') { const src = type.declaration.sources[0]; Logger.warn( `${src.line} : ${src.fileName}: Reflection Type not recognized. Refactoring to explicit class is advised.`, ); } returnType.name = 'object'; returnType.isReflection = true; return returnType; } /** * Conversion method for TypeOperatorType's * * This type is similar to reflection, that the actual type can only be evaluated at runtime. * * e.g. universityRole: keyof SCSportCoursePriceGroup; * * @param type Type to be converted */ function readAsTypeOperatorType(type: TypeOperatorType): LightweightType { const returnType: LightweightType = new LightweightType(); const typeOf: LightweightType = readTypeInformation(type.target); returnType.name = `keyof ${getFullTypeName(typeOf)}`; returnType.specificationTypes = [typeOf]; // can't be traced deeper! so might as well be a primitive returnType.isPrimitive = true; returnType.hasTypeInformation = true; return returnType; } /** * Conversion method for TypeParameterType's * * Should only be called in generic classes/interfaces, when a property is * referencing the generic-type. * * e.g. prices?: T; * * Does not match on Arrays of the generic type. Those will be matched with ArrayType. * * @param type Needs to be a TypeParameterType */ function readAsTypeParameterType(type: TypeParameterType): LightweightType { const returnType: LightweightType = new LightweightType(); returnType.name = type.name; returnType.isTypeParameter = true; returnType.hasTypeInformation = true; return returnType; }