mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-01-10 11:42:59 +00:00
refactor: build system
This commit is contained in:
117
packages/easy-ast/src/ast-internal-util.ts
Normal file
117
packages/easy-ast/src/ast-internal-util.ts
Normal 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);
|
||||
}
|
||||
83
packages/easy-ast/src/ast-util.ts
Normal file
83
packages/easy-ast/src/ast-util.ts
Normal 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;
|
||||
}
|
||||
255
packages/easy-ast/src/easy-ast.ts
Normal file
255
packages/easy-ast/src/easy-ast.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
25
packages/easy-ast/src/index.ts
Normal file
25
packages/easy-ast/src/index.ts
Normal 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'
|
||||
32
packages/easy-ast/src/types/lightweight-alias-definition.ts
Normal file
32
packages/easy-ast/src/types/lightweight-alias-definition.ts
Normal 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;
|
||||
}
|
||||
54
packages/easy-ast/src/types/lightweight-class-definition.ts
Normal file
54
packages/easy-ast/src/types/lightweight-class-definition.ts
Normal 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[];
|
||||
}
|
||||
48
packages/easy-ast/src/types/lightweight-comment.ts
Normal file
48
packages/easy-ast/src/types/lightweight-comment.ts
Normal 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[];
|
||||
}
|
||||
19
packages/easy-ast/src/types/lightweight-definition-kind.ts
Normal file
19
packages/easy-ast/src/types/lightweight-definition-kind.ts
Normal 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',
|
||||
}
|
||||
46
packages/easy-ast/src/types/lightweight-definition.ts
Normal file
46
packages/easy-ast/src/types/lightweight-definition.ts
Normal 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;
|
||||
}
|
||||
101
packages/easy-ast/src/types/lightweight-project.ts
Normal file
101
packages/easy-ast/src/types/lightweight-project.ts
Normal 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;
|
||||
}
|
||||
53
packages/easy-ast/src/types/lightweight-property.ts
Normal file
53
packages/easy-ast/src/types/lightweight-property.ts
Normal 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;
|
||||
}
|
||||
58
packages/easy-ast/src/types/lightweight-type.ts
Normal file
58
packages/easy-ast/src/types/lightweight-type.ts
Normal 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;
|
||||
}
|
||||
61
packages/easy-ast/src/util.ts
Normal file
61
packages/easy-ast/src/util.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user