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,8 @@
# What is Easy AST?
Easy [AST](https://en.wikipedia.org/wiki/Abstract_syntax_tree) is a "wrapper" around the TypeScript Compiler API. The TS Compiler API is built for many
much more complex things than just accessing type declarations, and thus comes with a ton of
bloat that makes it really difficult to work with.
This is why we built the Easy AST, which converts the TS representation to a really easy and
lightweight structure, that removes a lot of the stuff you wouldn't really need.

View File

@@ -0,0 +1,61 @@
{
"name": "@openstapps/easy-ast",
"description": "Tool to easily handle TypeScript AST",
"version": "2.1.0",
"type": "module",
"license": "GPL-3.0-only",
"repository": "git@gitlab.com:openstapps/core-tools.git",
"author": "Thea Schöbl <dev@theaninova.de>",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"scripts": {
"build": "tsup --dts",
"format": "prettier .",
"format:fix": "prettier --write .",
"lint": "eslint --ext .ts src/",
"lint:fix": "eslint --fix --ext .ts src/",
"test": "nyc mocha 'test/**/*.spec.ts'"
},
"dependencies": {
"@openstapps/collection-utils": "workspace:*",
"@openstapps/logger": "workspace:*",
"typescript": "4.8.4"
},
"devDependencies": {
"@openstapps/eslint-config": "workspace:*",
"@openstapps/nyc-config": "workspace:*",
"@openstapps/prettier-config": "workspace:*",
"@openstapps/tsconfig": "workspace:*",
"@testdeck/mocha": "0.3.3",
"@types/chai": "4.3.4",
"@types/fs-extra": "9.0.13",
"@types/mocha": "10.0.1",
"@types/node": "18.15.3",
"mocha": "10.2.0",
"nock": "13.3.0",
"tsup": "6.7.0"
},
"tsup": {
"entry": [
"src/app.ts",
"src/index.ts"
],
"sourcemap": true,
"clean": true,
"format": "esm",
"outDir": "lib"
},
"prettier": "@openstapps/prettier-config",
"eslintConfig": {
"extends": [
"@openstapps"
]
},
"eslintIgnore": [
"resources",
"openapi"
],
"nyc": {
"extends": "@openstapps/nyc-config"
}
}

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);
}

View File

@@ -0,0 +1,68 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
type TestTypeAlias = number | string;
enum TestEnum {
Foo,
Bar,
}
export const testConfig: EasyAstSpecType = {
testName: `should resolve alias-likes`,
expected: {
TestTypeAlias: {
name: 'TestTypeAlias',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: ['type'],
type: {
flags: 1_048_576,
specificationTypes: [
{
value: 'string',
flags: 4,
},
{
value: 'number',
flags: 8,
},
],
},
},
TestEnum: {
name: 'TestEnum',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: ['enum'],
type: {
flags: 1_048_576,
specificationTypes: [
{
referenceName: 'Foo',
value: 0,
flags: 1280,
},
{
referenceName: 'Bar',
value: 1,
flags: 1280,
},
],
},
},
},
};

View File

@@ -0,0 +1,75 @@
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-empty-interface */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
interface Random {}
type TestArrayGeneric = Array<string>;
type TestArrayLiteral = number[];
type TestArrayReferenceGeneric = Array<Random>;
type TestArrayReferenceLiteral = Random[];
export const testConfig: EasyAstSpecType = {
testName: `should resolve array-likes`,
expected: {
TestArrayGeneric: {
name: 'TestArrayGeneric',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: ['type'],
type: {
value: 'string',
flags: 4,
isArray: true,
},
},
TestArrayLiteral: {
name: 'TestArrayLiteral',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: ['type'],
type: {
value: 'number',
flags: 8,
isArray: true,
},
},
TestArrayReferenceGeneric: {
name: 'TestArrayReferenceGeneric',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: ['type'],
type: {
referenceName: 'Random',
flags: 524_288,
isArray: true,
},
},
TestArrayReferenceLiteral: {
name: 'TestArrayReferenceLiteral',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: ['type'],
type: {
referenceName: 'Random',
flags: 524_288,
isArray: true,
},
},
Random: {
name: 'Random',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
},
},
};

View File

@@ -0,0 +1,59 @@
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-inferrable-types */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
interface TestInterface {
foo: number;
}
class TestClass {
bar: string = 'test';
}
export const testConfig: EasyAstSpecType = {
testName: `should resolve class-likes`,
expected: {
TestInterface: {
name: 'TestInterface',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
properties: {
foo: {
name: 'foo',
type: {
value: 'number',
flags: 8,
},
},
},
},
TestClass: {
name: 'TestClass',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['class'],
properties: {
bar: {
name: 'bar',
type: {
value: 'string',
flags: 4,
},
},
},
},
},
};

View File

@@ -0,0 +1,161 @@
/* eslint-disable @typescript-eslint/no-unused-vars,jsdoc/check-tag-names */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
/**
* Class comment
*
* Class description
*
* More description
*
* @classTag classParameter1 classParameter2
*/
interface TestInterface {
/**
* Property comment
*
* Property description
*
* More description
*
* @propertyTag propertyParameter1 propertyParameter2
*/
foo: string;
}
/**
* Class comment
*
* Class description
*
* More description
*
* @classTag classParameter1 classParameter2
*/
class TestClass {
/**
* Property comment
*
* Property description
*
* More description
*
* @propertyTag propertyParameter1 propertyParameter2
*/
foo = 1;
}
/**
* Enum comment
*
* Enum description
*
* More description
*
* @enumTag enumParameter1
*/
enum TestAlias {}
export const testConfig: EasyAstSpecType = {
testName: `should resolve comments`,
expected: {
TestInterface: {
comment: {
shortSummary: 'Class comment',
description: 'Class description\n\nMore description',
tags: [
{
name: 'classTag',
parameters: ['classParameter1', 'classParameter2'],
},
],
},
name: 'TestInterface',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
properties: {
foo: {
comment: {
shortSummary: 'Property comment',
description: 'Property description\n\nMore description',
tags: [
{
name: 'propertyTag',
parameters: ['propertyParameter1', 'propertyParameter2'],
},
],
},
name: 'foo',
type: {
value: 'string',
flags: 4,
},
},
},
},
TestClass: {
comment: {
shortSummary: 'Class comment',
description: 'Class description\n\nMore description',
tags: [
{
name: 'classTag',
parameters: ['classParameter1', 'classParameter2'],
},
],
},
name: 'TestClass',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['class'],
properties: {
foo: {
comment: {
shortSummary: 'Property comment',
description: 'Property description\n\nMore description',
tags: [
{
name: 'propertyTag',
parameters: ['propertyParameter1', 'propertyParameter2'],
},
],
},
name: 'foo',
type: {
value: 'number',
flags: 8,
},
},
},
},
TestAlias: {
comment: {
shortSummary: 'Enum comment',
description: 'Enum description\n\nMore description',
tags: [
{
name: 'enumTag',
parameters: ['enumParameter1'],
},
],
},
name: 'TestAlias',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: ['enum'],
},
},
};

View File

@@ -0,0 +1,66 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
interface Test1<T = number> {
foo: T;
}
interface Test2 {
bar: Test1;
}
export const testConfig: EasyAstSpecType = {
testName: `should resolve default generics`,
expected: {
Test1: {
name: 'Test1',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
typeParameters: ['T'],
properties: {
foo: {
name: 'foo',
type: {
referenceName: 'T',
flags: 262_144,
},
},
},
},
Test2: {
name: 'Test2',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
properties: {
bar: {
name: 'bar',
type: {
referenceName: 'Test1',
flags: 524_288,
genericsTypes: [
{
value: 'number',
flags: 8,
},
],
},
},
},
},
},
};

View File

@@ -0,0 +1,20 @@
/*
* 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 {LightweightFile} from '../../src/easy-ast/types/lightweight-project.js';
export interface EasyAstSpecType {
testName: string;
expected: LightweightFile;
}

View File

@@ -0,0 +1,73 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
enum TestAuto {
Foo,
Bar,
}
enum TestSpecified {
YES = 'yes',
NO = 'no',
}
export const testConfig: EasyAstSpecType = {
testName: `should resolve auto and specified enums`,
expected: {
TestAuto: {
name: 'TestAuto',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: ['enum'],
type: {
flags: 1_048_576,
specificationTypes: [
{
referenceName: 'Foo',
value: 0,
flags: 1280,
},
{
referenceName: 'Bar',
value: 1,
flags: 1280,
},
],
},
},
TestSpecified: {
name: 'TestSpecified',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: ['enum'],
type: {
flags: 1_048_576,
specificationTypes: [
{
referenceName: 'YES',
value: 'yes',
flags: 1152,
},
{
referenceName: 'NO',
value: 'no',
flags: 1152,
},
],
},
},
},
};

View File

@@ -0,0 +1,80 @@
/* eslint-disable @typescript-eslint/no-empty-interface,@typescript-eslint/no-unused-vars */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
interface $Random {}
interface Generics {
baz: Foo<number, $Random>;
}
interface Foo<T, S> {
foo: T;
bar: S;
}
export const testConfig: EasyAstSpecType = {
testName: `should resolve generics`,
expected: {
Generics: {
name: 'Generics',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
properties: {
baz: {
name: 'baz',
type: {
referenceName: 'Foo',
flags: 524_288,
genericsTypes: [
{
value: 'number',
flags: 8,
},
{
referenceName: '$Random',
flags: 524_288,
},
],
},
},
},
},
Foo: {
name: 'Foo',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
typeParameters: ['T', 'S'],
properties: {
foo: {
name: 'foo',
type: {
referenceName: 'T',
flags: 262_144,
},
},
bar: {
name: 'bar',
type: {
referenceName: 'S',
flags: 262_144,
},
},
},
},
},
};

View File

@@ -0,0 +1,69 @@
/* eslint-disable @typescript-eslint/no-empty-interface,@typescript-eslint/no-unused-vars */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
interface $Random {}
interface IndexSignatureObject {
[key: string]: $Random;
}
interface IndexSignaturePrimitive {
[key: string]: number;
}
export const testConfig: EasyAstSpecType = {
testName: `should resolve index signatures`,
expected: {
IndexSignatureObject: {
name: 'IndexSignatureObject',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
indexSignatures: {
key: {
name: 'key',
indexSignatureType: {
value: 'string',
flags: 4,
},
type: {
referenceName: '$Random',
flags: 524_288,
},
},
},
},
IndexSignaturePrimitive: {
name: 'IndexSignaturePrimitive',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
indexSignatures: {
key: {
name: 'key',
indexSignatureType: {
value: 'string',
flags: 4,
},
type: {
value: 'number',
flags: 8,
},
},
},
},
},
};

View File

@@ -0,0 +1,35 @@
/*
* 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 {expandPathToFilesSync, toUnixPath} from '../src/util.js';
import {EasyAstSpecType} from './easy-ast-spec-type.js';
import {lightweightProjectFromPath} from '../src/easy-ast.js';
import {expect} from 'chai';
describe('Easy AST', async () => {
const project = lightweightProjectFromPath('./test/easy-ast', true);
for (const file of expandPathToFilesSync('./test/easy-ast', file => file.endsWith('ast-test.ts'))) {
try {
const test = (await import(file))['testConfig'] as EasyAstSpecType;
it(test.testName, () => {
expect(omitBy(project[toUnixPath(file)], (_value, key) => key.startsWith('$'))).to.be.deep.equal(
test.expected,
);
});
} catch (error) {
console.error(error);
}
}
});

View File

@@ -0,0 +1,82 @@
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-inferrable-types */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
interface $BaseInterface<T> {
foo: T;
}
interface $BaseInterface2 {
bar: string;
}
class $BaseClass {}
class InheritingClass extends $BaseClass implements $BaseInterface<number>, $BaseInterface2 {
bar: string = '';
foo: number = 1;
}
export const testConfig: EasyAstSpecType = {
testName: `inheritance`,
expected: {
InheritingClass: {
name: 'InheritingClass',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['class'],
implementedDefinitions: [
{
referenceName: '$BaseInterface',
genericsTypes: [
{
value: 'number',
flags: 8,
},
],
flags: 524_288,
},
{
referenceName: '$BaseInterface2',
flags: 524_288,
},
],
extendedDefinitions: [
{
referenceName: '$BaseClass',
flags: 524_288,
},
],
properties: {
foo: {
name: 'foo',
type: {
value: 'number',
flags: 8,
},
},
bar: {
name: 'bar',
type: {
value: 'string',
flags: 4,
},
},
},
},
},
};

View File

@@ -0,0 +1,61 @@
/* eslint-disable @typescript-eslint/no-empty-interface,@typescript-eslint/no-unused-vars */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
interface NestedObject {
nested: {
deeplyNested: {
aNumber: number;
};
};
}
export const testConfig: EasyAstSpecType = {
testName: `should handle nested/type literals`,
expected: {
NestedObject: {
name: 'NestedObject',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
properties: {
nested: {
name: 'nested',
type: {
flags: 524_288,
},
properties: {
deeplyNested: {
name: 'deeplyNested',
type: {
flags: 524_288,
},
properties: {
aNumber: {
name: 'aNumber',
type: {
flags: 8,
value: 'number',
},
},
},
},
},
},
},
},
},
};

View File

@@ -0,0 +1,99 @@
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-explicit-any */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
interface Test {
number_type: number;
string_type: string;
boolean_type: boolean;
any_type: any;
unknown_type: unknown;
null_type: null;
undefined_type: undefined;
}
export const testConfig: EasyAstSpecType = {
testName: `should interpret primitive types correctly`,
expected: {
Test: {
name: 'Test',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
properties: {
number_type: {
name: 'number_type',
type: {
value: 'number',
flags: 8,
},
},
string_type: {
name: 'string_type',
type: {
value: 'string',
flags: 4,
},
},
boolean_type: {
name: 'boolean_type',
type: {
value: 'boolean',
flags: 1_048_592,
specificationTypes: [
{
value: 'false',
flags: 512,
},
{
value: 'true',
flags: 512,
},
],
},
},
any_type: {
name: 'any_type',
type: {
value: 'any',
flags: 1,
},
},
unknown_type: {
name: 'unknown_type',
type: {
value: 'unknown',
flags: 2,
},
},
null_type: {
name: 'null_type',
type: {
value: 'null',
flags: 65_536,
},
},
undefined_type: {
name: 'undefined_type',
type: {
value: 'undefined',
flags: 32_768,
},
},
},
},
},
};

View File

@@ -0,0 +1,61 @@
/* eslint-disable @typescript-eslint/no-unused-vars,@typescript-eslint/no-explicit-any */
/*
* 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 {EasyAstSpecType} from './easy-ast-spec-type.js';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind.js';
interface Foo<T extends Bar<string>> {
bar: T;
}
interface Bar<T> {
foo: T;
}
export const testConfig: EasyAstSpecType = {
testName: `should ignore type constraints`,
expected: {
Foo: {
name: 'Foo',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
typeParameters: ['T'],
properties: {
bar: {
name: 'bar',
type: {
referenceName: 'T',
flags: 262_144,
},
},
},
},
Bar: {
name: 'Bar',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: ['interface'],
typeParameters: ['T'],
properties: {
foo: {
name: 'foo',
type: {
referenceName: 'T',
flags: 262_144,
},
},
},
},
},
};

View File

@@ -0,0 +1,3 @@
{
"extends": "@openstapps/tsconfig"
}