mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-21 17:12:43 +00:00
style: apply strict style rules for this feature
This commit is contained in:
438
src/uml/read-definitions.ts
Normal file
438
src/uml/read-definitions.ts
Normal file
@@ -0,0 +1,438 @@
|
||||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Logger} from '@openstapps/logger';
|
||||
import {
|
||||
ArrayType,
|
||||
DeclarationReflection,
|
||||
IntrinsicType,
|
||||
ProjectReflection,
|
||||
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' && declaration.children) {
|
||||
// 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 = declaration.kindString ? 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);
|
||||
}
|
||||
|
||||
throw new Error(`Could not read type ${declarationType.type}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<SCPersonWithoutReferences | SCOrganizationWithoutReferences>;
|
||||
*
|
||||
* 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 (
|
||||
tempTypeReflection.kindString &&
|
||||
['Interface', 'Class', 'Enumeration', 'Type alias'].indexOf(
|
||||
tempTypeReflection.kindString,
|
||||
) > -1
|
||||
) {
|
||||
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 (type.declaration.sources) {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user