refactor: build system

This commit is contained in:
2023-03-22 11:45:30 +01:00
parent 4df19e8c20
commit 8cb9285462
427 changed files with 3978 additions and 9810 deletions

View File

@@ -0,0 +1,117 @@
/*
* Copyright (C) 2021 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 * as ts from 'typescript';
import {cleanupEmpty} from './util.js';
import {LightweightComment} from './types/lightweight-comment.js';
/** @internal */
export function extractComment(node: ts.Node): LightweightComment | undefined {
const jsDocuments =
// @ts-expect-error jsDoc exists in reality
node.jsDoc as
| Array<{
comment?: string;
tags?: Array<{comment?: string; tagName?: {escapedText?: string}}>;
}>
| undefined;
const jsDocument = jsDocuments?.[jsDocuments.length - 1];
const comment = jsDocument?.comment?.split('\n\n');
return jsDocument === undefined
? undefined
: cleanupEmpty({
shortSummary: comment?.[0],
description: comment?.[comment.length - 1],
tags: jsDocument?.tags?.map(tag =>
cleanupEmpty({
name: tag.tagName?.escapedText ?? 'UNRESOLVED_NAME',
parameters: tag.comment?.split(' '),
}),
),
});
}
/** @internal */
export function isProperty(
node: ts.ClassElement | ts.TypeElement,
): node is ts.PropertyDeclaration | ts.PropertySignature {
return ts.isPropertyDeclaration(node) || ts.isPropertySignature(node);
}
/** @internal */
export function filterNodeTo<T extends ts.Node, S extends T>(
node: ts.NodeArray<T>,
check: (node: T) => node is S,
): S[] {
return node.filter(check);
}
/** @internal */
export function filterChildrenTo<T extends ts.Node>(node: ts.Node, check: (node: ts.Node) => node is T): T[] {
const out: T[] = [];
node.forEachChild(child => {
if (check(child)) {
out.push(child);
}
});
return out;
}
/** @internal */
export function getModifiers(text: string, kind: string): string[] {
return [
...text
.split(kind)[0]
.split(/\s+/)
.filter(it => it !== ''),
kind,
];
}
/** @internal */
export function resolvePropertyName(name?: ts.PropertyName): string | undefined {
return name === undefined
? undefined
: ts.isComputedPropertyName(name)
? 'UNSUPPORTED_IDENTIFIER_TYPE'
: name.getText();
}
/** @internal */
export function resolveTypeName(type?: ts.TypeNode): string | undefined {
// @ts-expect-error typeName exists in reality
return type?.typeName?.escapedText ?? type?.typeName?.right?.escapedText;
}
/** @internal */
export function isArrayLikeType(typeNode?: ts.TypeNode): typeNode is ts.ArrayTypeNode | ts.TypeReferenceNode {
return typeNode !== undefined && (ts.isArrayTypeNode(typeNode) || isArrayReference(typeNode));
}
/** @internal */
export function isArrayReference(typeNode: ts.TypeNode): boolean {
return ts.isTypeReferenceNode(typeNode) && (typeNode.typeName as ts.Identifier).escapedText === 'Array';
}
/** @internal */
export function isClassLikeNode(node: ts.Node): node is ts.ClassDeclaration | ts.InterfaceDeclaration {
return ts.isClassDeclaration(node) || ts.isInterfaceDeclaration(node);
}
/** @internal */
export function isEnumLikeNode(node: ts.Node): node is ts.EnumDeclaration | ts.TypeAliasDeclaration {
return ts.isEnumDeclaration(node) || ts.isTypeAliasDeclaration(node);
}

View File

@@ -0,0 +1,83 @@
/* eslint-disable jsdoc/require-jsdoc */
/*
* Copyright (C) 2021 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 ts from 'typescript';
import {LightweightAliasDefinition} from './types/lightweight-alias-definition.js';
import {LightweightClassDefinition} from './types/lightweight-class-definition.js';
import {LightweightDefinition} from './types/lightweight-definition.js';
import {LightweightDefinitionKind} from './types/lightweight-definition-kind.js';
import {LightweightProject} from './types/lightweight-project.js';
import {LightweightType} from './types/lightweight-type.js';
import {keyBy} from '@openstapps/collection-utils';
/**
* Creates a printable name of a type
*/
export function expandTypeValue(type: LightweightType): string | undefined {
if (type.isArray) {
return `${type.value}[]`;
}
if (isStringLiteralType(type)) {
return `'${type.value}'`;
}
if (isUnionOrIntersectionType(type)) {
return type.specificationTypes?.map(expandTypeValue).join(isUnionType(type) ? ' | ' : ' & ');
}
if (type.genericsTypes?.length === 0) {
return `${type.value}<${type.genericsTypes?.map(expandTypeValue).join(', ')}>`;
}
return type.value?.toString();
}
export function definitionsOf(project: LightweightProject): Record<string, LightweightDefinition> {
return keyBy(Object.values(project).flatMap(Object.values), it => it.name);
}
export function isPrimitiveType(type: {flags: ts.TypeFlags}): boolean {
return (type.flags & ts.TypeFlags.NonPrimitive) === 0;
}
export function isLiteralType(type: {flags: ts.TypeFlags}): boolean {
return (type.flags & ts.TypeFlags.Literal) !== 0;
}
export function isEnumLiteralType(type: {flags: ts.TypeFlags}): boolean {
return (type.flags & ts.TypeFlags.EnumLiteral) !== 0;
}
export function isStringLiteralType(type: {flags: ts.TypeFlags}): boolean {
return (type.flags & ts.TypeFlags.StringLiteral) !== 0;
}
export function isUnionOrIntersectionType(type: {flags: ts.TypeFlags}): boolean {
return (type.flags & ts.TypeFlags.UnionOrIntersection) !== 0;
}
export function isUnionType(type: {flags: ts.TypeFlags}): boolean {
return (type.flags & ts.TypeFlags.Union) !== 0;
}
export function isLightweightClass(node?: LightweightDefinition): node is LightweightClassDefinition {
return node?.kind === LightweightDefinitionKind.CLASS_LIKE;
}
export function isLightweightEnum(node?: LightweightDefinition): node is LightweightAliasDefinition {
return node?.kind === LightweightDefinitionKind.ALIAS_LIKE;
}
export function isTypeVariable(type: {flags: ts.TypeFlags}): boolean {
return (type.flags & ts.TypeFlags.TypeVariable) !== 0;
}

View File

@@ -0,0 +1,255 @@
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
/*
* Copyright (C) 2021 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 ts from 'typescript';
import {cleanupEmpty, mapNotNil, rejectNil, expandPathToFilesSync} from './util.js';
import {
extractComment,
filterChildrenTo,
filterNodeTo,
getModifiers,
isArrayLikeType,
isClassLikeNode,
isEnumLikeNode,
isProperty,
resolvePropertyName,
resolveTypeName,
} from './ast-internal-util.js';
import {isEnumLiteralType, isTypeVariable} from './ast-util.js';
import {LightweightAliasDefinition} from './types/lightweight-alias-definition.js';
import {LightweightClassDefinition} from './types/lightweight-class-definition.js';
import {LightweightDefinition} from './types/lightweight-definition.js';
import {LightweightDefinitionKind} from './types/lightweight-definition-kind.js';
import {LightweightProject} from './types/lightweight-project.js';
import {LightweightType} from './types/lightweight-type.js';
import path from 'path';
import {LightweightProperty} from './types/lightweight-property.js';
import {mapValues, groupBy, keyBy} from '@openstapps/collection-utils';
/**
* Convert a TypeScript project to a lightweight Type-AST representation of the project
*
* @param sourcePath either a directory or a set of input files
* @param includeComments if comments should be included (default true)
*/
export function lightweightProjectFromPath(
sourcePath: string | string[],
includeComments = true,
): LightweightProject {
return new LightweightDefinitionBuilder(sourcePath, includeComments).convert();
}
/**
* Convert a TypeScript project to a set of lightweight definition ASTs
*
* @param sourcePath either a directory or a set of input files
* @param includeComments if comments should be included (default true)
*/
export function lightweightDefinitionsFromPath(
sourcePath: string | string[],
includeComments = true,
): LightweightDefinition[] {
return rejectNil(new LightweightDefinitionBuilder(sourcePath, includeComments).convertToList());
}
/**
* Reads the reflection model and converts it into a flatter, easier to handle model
*/
class LightweightDefinitionBuilder {
readonly program: ts.Program;
readonly sourceFiles: readonly ts.SourceFile[];
readonly typeChecker: ts.TypeChecker;
constructor(sourcePath: string | string[], readonly includeComments: boolean) {
const rootNames = Array.isArray(sourcePath)
? sourcePath
: expandPathToFilesSync(path.resolve(sourcePath), file => file.endsWith('ts'));
this.program = ts.createProgram({
rootNames: rootNames,
options: {
alwaysStrict: true,
charset: 'utf8',
declaration: true,
esModuleInterop: true,
experimentalDecorators: true,
inlineSourceMap: true,
module: ts.ModuleKind.CommonJS,
strict: true,
target: ts.ScriptTarget.ES2015,
},
});
this.typeChecker = this.program.getTypeChecker();
this.sourceFiles = mapNotNil(this.program.getRootFileNames(), it => this.program.getSourceFile(it));
}
private convertAliasLike(
enumLike: ts.EnumDeclaration | ts.TypeAliasDeclaration,
): LightweightAliasDefinition {
return cleanupEmpty({
comment: this.includeComments ? extractComment(enumLike) : undefined,
name: enumLike.name.getText() ?? 'ERROR',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: getModifiers(enumLike.getText(), ts.isEnumDeclaration(enumLike) ? 'enum' : 'type'),
type: ts.isEnumDeclaration(enumLike)
? enumLike.members.length > 0
? {
flags: 1_048_576,
specificationTypes: enumLike.members.map(it => this.lightweightTypeAtNode(it)),
}
: undefined
: this.lightweightTypeFromType(this.typeChecker.getTypeFromTypeNode(enumLike.type), enumLike.type),
});
}
private convertClassLike(
classLike: ts.ClassDeclaration | ts.InterfaceDeclaration,
): LightweightClassDefinition {
const heritages = mapValues(
groupBy([...classLike.heritageClauses!], it => it.token.toString()),
heritages => heritages.flatMap(it => it.types),
);
return cleanupEmpty({
comment: this.includeComments ? extractComment(classLike) : undefined,
name: classLike.name?.escapedText ?? 'ERROR',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: getModifiers(classLike.getText(), ts.isClassDeclaration(classLike) ? 'class' : 'interface'),
extendedDefinitions: heritages[ts.SyntaxKind.ExtendsKeyword]?.map(it => this.lightweightTypeAtNode(it)),
implementedDefinitions: heritages[ts.SyntaxKind.ImplementsKeyword]?.map(it =>
this.lightweightTypeAtNode(it),
),
indexSignatures: keyBy(
filterNodeTo(
classLike.members as ts.NodeArray<ts.ClassElement | ts.TypeElement>,
ts.isIndexSignatureDeclaration,
).map(indexSignature =>
cleanupEmpty({
name:
this.typeChecker.getSignatureFromDeclaration(indexSignature)?.parameters?.[0]?.escapedName ??
'UNRESOLVED_INDEX_SIGNATURE',
type: this.lightweightTypeFromType(
this.typeChecker.getTypeFromTypeNode(indexSignature.type),
indexSignature.type,
),
indexSignatureType: this.lightweightTypeFromType(
this.typeChecker.getTypeFromTypeNode(indexSignature.parameters[0].type!),
indexSignature.parameters[0].type!,
),
}),
),
it => it.name,
),
typeParameters: classLike.typeParameters?.map(it => it.name.getText()),
properties: this.collectProperties(classLike.members),
});
}
collectProperties(
members: ts.NodeArray<ts.ClassElement | ts.TypeElement>,
): Record<string, LightweightProperty> {
return keyBy(
filterNodeTo(members as ts.NodeArray<ts.ClassElement | ts.TypeElement>, isProperty).map(property =>
cleanupEmpty({
comment: this.includeComments ? extractComment(property) : undefined,
name: resolvePropertyName(property.name) ?? property.getText(),
type: this.lightweightTypeAtNode(property),
properties: this.collectProperties((property.type as ts.TypeLiteralNode)?.members),
optional: ts.isPropertyDeclaration(property)
? property.questionToken === undefined
? undefined
: true
: undefined,
}),
),
it => it.name,
);
}
private lightweightTypeAtNode(node: ts.Node): LightweightType {
const type = this.typeChecker.getTypeAtLocation(node);
return this.lightweightTypeFromType(type, this.typeChecker.typeToTypeNode(type, node, undefined));
}
private lightweightTypeFromType(type: ts.Type, typeNode?: ts.TypeNode): LightweightType {
if (typeNode?.kind === ts.SyntaxKind.ConditionalType) {
return {value: 'UNSUPPORTED_CONDITIONAL_TYPE', flags: ts.TypeFlags.Unknown};
}
if (isArrayLikeType(typeNode)) {
const elementType = ts.isArrayTypeNode(typeNode) ? typeNode.elementType : typeNode.typeArguments?.[0]!;
const out = this.lightweightTypeFromType(
this.typeChecker.getTypeFromTypeNode(elementType),
elementType,
);
out.isArray = true;
return out;
}
const isReference =
typeNode !== undefined && ts.isTypeReferenceNode(typeNode) && !isEnumLiteralType(type);
const isTypeLiteral = typeNode !== undefined && ts.isTypeLiteralNode(typeNode);
// @ts-expect-error intrinsic name & value exist
const intrinsicName = (type.intrinsicName ?? type.value) as string | undefined;
return cleanupEmpty({
value: intrinsicName,
referenceName: isTypeLiteral
? undefined
: resolveTypeName(typeNode) ?? (type.symbol?.escapedName as string | undefined),
flags: type.flags,
genericsTypes: isTypeVariable(type)
? undefined
: this.typeChecker
.getApparentType(type)
// @ts-expect-error resolvedTypeArguments exits
?.resolvedTypeArguments?.filter(it => !it.isThisType)
?.map((it: ts.Type) => this.lightweightTypeFromType(it)),
specificationTypes:
type.isUnionOrIntersection() && !isReference
? type.types.map(it =>
this.lightweightTypeFromType(it, this.typeChecker.typeToTypeNode(it, undefined, undefined)),
)
: undefined,
});
}
/**
* Start the conversion process
*/
convert(): LightweightProject {
return mapValues(
keyBy([...this.sourceFiles], it => it.fileName),
file =>
keyBy(
[
...filterChildrenTo(file, isClassLikeNode).map(it => this.convertClassLike(it)),
...filterChildrenTo(file, isEnumLikeNode).map(it => this.convertAliasLike(it)),
],
it => it.name,
),
);
}
/**
* Same as conversion, but generates a simple list of all definitions.
*/
convertToList(): LightweightDefinition[] {
return Object.values(this.convert()).flatMap(it => it.values);
}
}

View File

@@ -0,0 +1,25 @@
/*
* Copyright (C) 2021 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/>.
*/
export * from './easy-ast.js'
export * from './ast-util.js'
export * from './types/lightweight-alias-definition.js'
export * from './types/lightweight-class-definition.js'
export * from './types/lightweight-comment.js'
export * from './types/lightweight-definition.js'
export * from './types/lightweight-definition-kind.js'
export * from './types/lightweight-project.js'
export * from './types/lightweight-property.js'
export * from './types/lightweight-type.js'

View File

@@ -0,0 +1,32 @@
/*
* Copyright (C) 2021 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 {LightweightDefinitionBase} from './lightweight-definition.js';
import {LightweightDefinitionKind} from './lightweight-definition-kind.js';
import {LightweightType} from './lightweight-type.js';
/**
* Represents an enum definition
*/
export interface LightweightAliasDefinition extends LightweightDefinitionBase {
/**
* Kind
*/
kind: LightweightDefinitionKind.ALIAS_LIKE;
/**
* Enumeration or union values
*/
type?: LightweightType;
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2021 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 {LightweightDefinitionBase} from './lightweight-definition.js';
import {LightweightDefinitionKind} from './lightweight-definition-kind.js';
import {LightweightIndexSignature, LightweightProperty} from './lightweight-property.js';
import {LightweightType} from './lightweight-type.js';
/**
* Represents a class definition
*/
export interface LightweightClassDefinition extends LightweightDefinitionBase {
/**
* String values of the extended definitions
*/
extendedDefinitions?: LightweightType[];
/**
* String values of the implemented definitions
*/
implementedDefinitions?: LightweightType[];
/**
* Index signatures
*/
indexSignatures?: Record<string, LightweightIndexSignature>;
/**
* Kind
*/
kind: LightweightDefinitionKind.CLASS_LIKE;
/**
* Properties of the definition
*/
properties?: Record<string, LightweightProperty>;
/**
* Generic type parameters of this class
*/
typeParameters?: string[];
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2021 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/>.
*/
/**
* Represents a Comment
*/
export interface LightweightComment {
/**
* Description of the comment
*/
description?: string;
/**
* Short summary of the comment
*/
shortSummary?: string;
/**
* Tags of the comment
*/
tags?: LightweightCommentTag[];
}
/**
* Lightweight comment tag
*/
export interface LightweightCommentTag {
/**
* The name of the tag
*/
name: string;
/**
* The parameters of the tag
*/
parameters?: string[];
}

View File

@@ -0,0 +1,19 @@
/*
* Copyright (C) 2021 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/>.
*/
export enum LightweightDefinitionKind {
CLASS_LIKE = 'class-like',
ALIAS_LIKE = 'alias-like',
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright (C) 2021 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 {LightweightDefinitionKind} from './lightweight-definition-kind.js';
import {LightweightComment} from './lightweight-comment.js';
import {LightweightClassDefinition} from './lightweight-class-definition.js';
import {LightweightAliasDefinition} from './lightweight-alias-definition.js';
export type LightweightDefinition = LightweightClassDefinition | LightweightAliasDefinition;
/**
* Represents any definition without specifics
*/
export interface LightweightDefinitionBase {
/**
* The comment of the definition
*/
comment?: LightweightComment;
/**
* Kind of the definition
*/
kind: LightweightDefinitionKind;
/**
* The definition type
* e.g. [`abstract`, `class`] or [`enum`] or [`export`, `type`]
*/
modifiers?: string[];
/**
* Name of the definition
*/
name: string;
}

View File

@@ -0,0 +1,101 @@
/*
* Copyright (C) 2021 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 {mapNotNil} from '../util.js';
import {definitionsOf, isLightweightClass} from '../ast-util.js';
import {lightweightProjectFromPath} from '../easy-ast.js';
import {LightweightClassDefinition} from './lightweight-class-definition.js';
import {LightweightDefinition} from './lightweight-definition.js';
/**
* Build an index for a lightweight project
*/
function buildIndex(project: LightweightProject): Record<string, string> {
return Object.fromEntries(
Object.values(project).flatMap((definitions, file) =>
Object.keys(definitions).map(definition => [definition, file.toString()]),
),
);
}
/**
* A lightweight definition class for more advanced use cases
*/
export class LightweightProjectWithIndex {
/**
* All definitions
*/
readonly definitions: Record<string, LightweightDefinition>;
/**
* Project
*/
readonly files: LightweightProject;
/**
* Index of all definitions to their respective files
*/
readonly index: {
[definitionName: string]: string;
};
constructor(project: LightweightProject | string) {
this.files = typeof project === 'string' ? lightweightProjectFromPath(project) : project;
this.index = buildIndex(this.files);
this.definitions = definitionsOf(this.files);
}
/**
* Apply inherited classes; default deeply
*/
applyInheritance(classLike: LightweightClassDefinition, deep?: boolean): LightweightDefinition {
return Object.assign(
mapNotNil(
[...(classLike.implementedDefinitions ?? []), ...(classLike.extendedDefinitions ?? [])],
extension => {
const object = this.definitions[extension.referenceName ?? ''];
return (deep ?? true) && isLightweightClass(object)
? this.applyInheritance(object)
: JSON.parse(JSON.stringify(object));
},
),
JSON.parse(JSON.stringify(classLike)),
);
}
/**
* Instantiate a definition
*
* Requires the program to be run with `--require ts-node/register`
*/
async instantiateDefinitionByName<T>(name: string, findCompiledModule = true): Promise<T | undefined> {
const fsPath = this.index[name];
if (fsPath === undefined) {
return undefined;
}
const module = await import(findCompiledModule ? `${fsPath.replace(/\.d\.ts$/, '')}.js` : fsPath);
return new module[name]() as T;
}
}
export interface LightweightFile {
[definitionName: string]: LightweightDefinition;
}
export interface LightweightProject {
[sourcePath: string]: LightweightFile;
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2021 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 {LightweightComment} from './lightweight-comment.js';
import {LightweightType} from './lightweight-type.js';
/**
* Represents a property definition
*/
export interface LightweightProperty {
/**
* The comment of the property
*/
comment?: LightweightComment;
/**
* Name of the property
*/
name: string;
/**
* Is the property marked as optional
*/
optional?: true;
/**
* A record of properties if the property happens to be a type literal
*/
properties?: Record<string, LightweightProperty>;
/**
* Type of the property
*/
type: LightweightType;
}
export interface LightweightIndexSignature extends LightweightProperty {
/**
* Type of the index signature, if it is an index signature
*/
indexSignatureType: LightweightType;
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2021 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 {TypeFlags} from 'typescript';
/**
* Describes an easy to use type definition.
*/
export interface LightweightType {
/**
* Type Flags
*/
flags: TypeFlags;
/**
* Contains all types inside of <> brackets
*/
genericsTypes?: LightweightType[];
/**
* If it is an array(-like) type
*/
isArray?: true;
/**
* If it is a type parameter
*/
isTypeParameter?: true;
/**
* The name of the type that is referenced. Enum members have reference names that lead no where.
*/
referenceName?: string;
/**
* Type specifications, if the type is combined by either an array, union or a typeOperator
*/
specificationTypes?: LightweightType[];
/**
* Value of the type
*
* Literal types have their value here, non-literals their type name (for example 'string')
*/
value?: string | number | boolean;
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (C) 2021 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 path from "path";
import {readdirSync, statSync} from "fs";
/**
* Filters only defined elements
*/
export function rejectNil<T>(array: Array<T | undefined | null>): T[] {
// eslint-disable-next-line unicorn/no-null
return array.filter(it => it == null) as T[];
}
/**
* Map elements that are not null
*/
export function mapNotNil<T, S>(array: readonly T[], transform: (element: T) => S | undefined | null): S[] {
return rejectNil(array.map(transform));
}
/**
* Deletes all properties with the value 'undefined', [] or {}
*/
// eslint-disable-next-line @typescript-eslint/ban-types
export function cleanupEmpty<T extends object>(object: T): T {
const out = {} as T;
for (const key in object) {
const value = object[key];
// eslint-disable-next-line unicorn/no-null
if (value != null && (typeof value !== 'object' || Object.values(value).length > 0)) {
out[key] = value;
}
}
return out;
}
/**
* Expand a path to a list of all files deeply contained in it
*/
export function expandPathToFilesSync(sourcePath: string, accept: (fileName: string) => boolean): string[] {
const fullPath = path.resolve(sourcePath);
const directory = statSync(fullPath);
return directory.isDirectory()
? readdirSync(fullPath).flatMap(fragment =>
expandPathToFilesSync(path.resolve(sourcePath, fragment), accept),
)
: [fullPath].filter(accept);
}