feat: modernize core-tools

This commit is contained in:
Wieland Schöbl
2021-08-25 09:47:36 +00:00
parent 106dd26f89
commit fe59204b42
106 changed files with 4131 additions and 6216 deletions

2
.eslintignore Normal file
View File

@@ -0,0 +1,2 @@
resources
openapi

102
.eslintrc.json Normal file
View File

@@ -0,0 +1,102 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*"
],
"overrides": [
{
"files": [
"*.ts"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module",
"project": [
"tsconfig.json"
],
"createDefaultProgram": true
},
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended",
"plugin:prettier/recommended",
"plugin:jsdoc/recommended",
"plugin:unicorn/recommended"
],
"plugins": [
"eslint-plugin-unicorn",
"eslint-plugin-jsdoc",
"prettier"
],
"settings": {
"jsdoc": {
"mode": "typescript"
}
},
"rules": {
"unicorn/filename-case": "error",
"unicorn/no-array-callback-reference": "off",
"unicorn/no-useless-undefined": "off",
"unicorn/prefer-node-protocol": "off",
"unicorn/no-process-exit": "off",
"unicorn/prevent-abbreviations": [
"error",
{
"replacements": {
"ref": false,
"i": false
}
}
],
"unicorn/no-nested-ternary": "off",
"unicorn/better-regex": "off",
"jsdoc/no-types": "error",
"jsdoc/require-param": "off",
"jsdoc/require-param-description": "error",
"jsdoc/check-param-names": "error",
"jsdoc/require-returns": "off",
"jsdoc/require-param-type": "off",
"jsdoc/require-returns-type": "off",
"jsdoc/check-tag-names": [
"error",
{
"definedTags": ["internal"]
}
],
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": [
"error",
{
"args": "after-used",
"argsIgnorePattern": "^_"
}
],
"@typescript-eslint/lines-between-class-members": [
"error",
"always"
],
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-non-null-assertion": "off",
"prettier/prettier": [
"error",
{
"tabWidth": 2,
"printWidth": 110,
"useTabs": false,
"semi": true,
"singleQuote": true,
"quoteProps": "consistent",
"trailingComma": "all",
"bracketSpacing": false,
"arrowParens": "avoid",
"endOfLine": "lf"
}
]
}
}
]
}

View File

@@ -57,17 +57,6 @@ openapi:
paths:
- openapi/openapi.json
mapping:
dependencies:
- build
stage: test
services:
- name: registry.gitlab.com/openstapps/database:master
alias: elasticsearch
script:
- npm install @openstapps/core
- node lib/cli.js put-es-templates ./node_modules/@openstapps/core/src http://elasticsearch:9200/ "pattern,see,minlength,tjs-format"
package:
dependencies:
- build

View File

@@ -45,6 +45,15 @@ Inside of a script in `package.json` or if the npm package is installed globally
openstapps-core-tools schema src/core lib/schema
```
## 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.
## How to use the validator?
### Using the validator programatically
@@ -143,51 +152,6 @@ To pack all the different files into two distribution files - one for definition
openstapps-core-tools pack
```
## How to use the Elasticsearch Mapping generator
The mapping generator is intended to be used by the backend directly, but it can also be used to generate these files
manually.
### Generating the mapping files by hand
To generate the mapping files by hand, you need a local copy of the core-tools and the core, both need to be built first.
After that you can run
```
node lib/cli.js mapping path/to/core path/to/destination ignoredTag1,ignoredTag2,ignoredTag3
```
If you don't pass in any ignored tags, you will likely be prompted with errors, despite the core being absolutely correct.
This is because there are some tags that are not relevant to Elasticsearch, but the program has no direct way to tell
which ones simply lack an implementation and which ones can be ignored. Currently the ignored tags include
`minlength`, `pattern` and `see`.
### Generating the mapping directly from another TypeScript program
This is the more easy way, and it gives you direct access to the generated mapping as a (mostly typesafe) object. To
use it, call `generateTemplate`, However you will first need to generate a ProjectReflection of the core you are working
with. If you use the core as a dependency, you can for example use
```typescript
const map = generateTemplate(getProjectReflection(resolve('node_modules', '@openstapps', 'core', 'src')),
ignoredTags, false);
```
to generate the mappings. Note that the result object contains both a list of errors in `map.errors` and the actual mapping
in `map.template`. You can also specify whether you want the generator to show any errors while generating the mappings
in the console in the last parameter, `true` (default) being show errors and `false` to suppress them. That said it is very
easy to replace all `type: "MISSING_PREMAP"`, `type: "PARSE_ERROR"`, `type: "TYPE_CONFLICT"` with `dynamic: true` (you
can take these exactly like written here and run a replace over the file).
### Fixing a generated mapping by hand
If you get errors when generating the mappings, the mappings might not work, however they will still be generated to the
programs best efforts. Most small issues can be fixed after the mapping was generated as a temporary solution and without
changing anything in the mapper's code. This however requires some understanding of how mappings work.
The output of the program can easily reach 25.000 lines, but you can find errors quickly by searching for `MISSING_PREMAP`,
`PARSE_ERROR` and `TYPE_CONFLICT`. When you reach them you can then manually replace them with your code.
As a last resort you can also replace all errors with dynamic types, this should get the mapping working, but it is NOT
RECOMMENDED as a fix other than using it locally.
## How to use the UML generator
The UML Generator generates PlantUML from the project reflection of the source files. By default it will include externals, which will take considerably longer to execute, you can disable this behaviour via an option. It can help you to visually explore the data model or document a specific part.

1503
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,7 @@
"Wieland Schöbl"
],
"scripts": {
"build": "npm run tslint && npm run compile",
"build": "npm run lint && npm run compile",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md && git commit -m 'docs: update changelog'",
"check-configuration": "openstapps-configuration",
"compile": "rimraf lib && tsc && prepend lib/cli.js '#!/usr/bin/env node\n'",
@@ -41,48 +41,56 @@
"preversion": "npm run prepublishOnly",
"push": "git push && git push origin \"v$npm_package_version\"",
"test": "mocha --require ts-node/register test/*.spec.ts",
"tslint": "tslint -p tsconfig.json -c tslint.json 'src/**/*.ts'"
"lint:fix": "eslint --fix -c .eslintrc.json --ignore-path .eslintignore --ext .ts src/",
"lint": "eslint -c .eslintrc.json --ignore-path .eslintignore --ext .ts src/"
},
"dependencies": {
"@krlwlfrt/async-pool": "0.6.0",
"@openstapps/logger": "0.7.0",
"@types/fs-extra": "9.0.12",
"@types/glob": "7.1.4",
"@types/json-schema": "7.0.8",
"@types/mustache": "4.1.2",
"@types/node": "14.17.5",
"ajv": "6.12.6",
"better-ajv-errors": "0.7.0",
"chai": "4.3.4",
"commander": "7.2.0",
"commander": "8.1.0",
"deepmerge": "4.2.2",
"del": "6.0.0",
"flatted": "3.2.1",
"eslint": "7.30.0",
"flatted": "3.2.2",
"fs-extra": "10.0.0",
"glob": "7.1.7",
"got": "11.8.2",
"humanize-string": "2.1.0",
"json-schema": "0.3.0",
"lodash": "4.17.21",
"mustache": "4.2.0",
"openapi-types": "9.1.0",
"plantuml-encoder": "1.4.0",
"toposort": "2.0.2",
"ts-json-schema-generator": "0.70.2",
"ts-node": "10.1.0",
"typedoc": "0.18.0",
"typescript": "3.8.3"
"ts-json-schema-generator": "0.95.0",
"ts-node": "10.2.0",
"typescript": "4.3.5"
},
"devDependencies": {
"@openstapps/configuration": "0.27.0",
"@testdeck/mocha": "0.1.2",
"@types/chai": "4.2.21",
"@types/fs-extra": "9.0.12",
"@types/glob": "7.1.4",
"@types/json-schema": "7.0.9",
"@types/lodash": "4.14.172",
"@types/mocha": "8.2.3",
"@types/mustache": "4.1.2",
"@types/node": "14.17.9",
"@types/rimraf": "3.0.1",
"@typescript-eslint/eslint-plugin": "4.3.0",
"@typescript-eslint/parser": "4.3.0",
"conventional-changelog-cli": "2.1.1",
"mocha": "9.0.2",
"eslint-config-prettier": "8.3.0",
"eslint-plugin-jsdoc": "35.4.1",
"eslint-plugin-prettier": "3.4.0",
"eslint-plugin-unicorn": "34.0.1",
"mocha": "9.0.3",
"nock": "13.1.1",
"prepend-file-cli": "1.0.6",
"rimraf": "3.0.2",
"tslint": "6.1.3"
"prettier": "2.3.2",
"rimraf": "3.0.2"
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018-2019 StApps
* Copyright (C) 2018-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.
@@ -16,27 +16,16 @@ import {Logger} from '@openstapps/logger';
import {Command} from 'commander';
import {existsSync, readFileSync, writeFileSync} from 'fs';
import {copy} from 'fs-extra';
import got from 'got';
import {join, relative, resolve} from 'path';
import {exit} from 'process';
import {
capitalize,
getProjectReflection,
mkdirPromisified,
readFilePromisified,
toArray,
} from './common';
import {generateTemplate} from './mapping';
import path from 'path';
import {mkdirPromisified, readFilePromisified} from './common';
import {lightweightDefinitionsFromPath, lightweightProjectFromPath} from './easy-ast/easy-ast';
import {pack} from './pack';
import {openapi3Template} from './resources/openapi-303-template';
import {
gatherRouteInformation,
generateOpenAPIForRoute,
} from './routes';
import {Converter, getValidatableTypesFromReflection} from './schema';
import {gatherRouteInformation, generateOpenAPIForRoute} from './routes';
import {Converter, getValidatableTypesInPath} from './schema';
import {createDiagram, createDiagramFromString} from './uml/create-diagram';
import {readDefinitions} from './uml/read-definitions';
import {UMLConfig} from './uml/uml-config';
import {capitalize} from './util/string';
import {validateFiles, writeReport} from './validate';
// handle unhandled promise rejections
@@ -50,35 +39,41 @@ process.on('unhandledRejection', async (reason: unknown) => {
const commander = new Command('openstapps-core-tools');
commander
.version(JSON.parse(
readFileSync(resolve(__dirname, '..', 'package.json'))
.toString(),
).version);
// eslint-disable-next-line unicorn/prefer-module
commander.version(JSON.parse(readFileSync(path.resolve(__dirname, '..', 'package.json')).toString()).version);
commander.command('prototype <srcBundle> <out>').action(async (sourcePath, out) => {
const files = lightweightProjectFromPath(sourcePath);
writeFileSync(path.resolve(out), JSON.stringify(files, undefined, 2));
});
commander
.command('openapi <srcPath> <outDirPath>')
.action(async (relativeSrcPath, relativeOutDirPath) => {
.action(async (relativeSourceBundlePath, relativeOutDirectoryPath) => {
// get absolute paths
const srcPath = resolve(relativeSrcPath);
const outDirPath = resolve(relativeOutDirPath);
const outDirSchemasPath = join(outDirPath, 'schemas');
// get project reflection
const projectReflection = getProjectReflection(srcPath);
const sourcePath = path.resolve(relativeSourceBundlePath);
const outDirectoryPath = path.resolve(relativeOutDirectoryPath);
const outDirectorySchemasPath = path.join(outDirectoryPath, 'schemas');
// get information about routes
const routes = await gatherRouteInformation(projectReflection);
const routes = await gatherRouteInformation(sourcePath);
routes.sort((a, b) => a.route.urlFragment.localeCompare(b.route.urlFragment));
// change url path parameters to openapi notation
routes.forEach((routeWithMetaInformation) => {
routeWithMetaInformation.route.urlFragment = routeWithMetaInformation.route.urlFragment.replace(/:\w+/g, (match) => `{${match.replace(':','')}}`);
});
for (const routeWithMetaInformation of routes) {
routeWithMetaInformation.route.urlFragment = routeWithMetaInformation.route.urlFragment.replace(
/:\w+/g,
(match: string) => `{${match.replace(':', '')}}`,
);
}
// keep openapi tags for routes that actually share url fragments
let tagsToKeep = routes.map((routeWithMetaInformation) => capitalize(routeWithMetaInformation.route.urlFragment.split('/')[1]));
tagsToKeep = tagsToKeep.filter((element, i, array) => array.indexOf(element) === i && array.lastIndexOf(element) !== i);
let tagsToKeep = routes.map(routeWithMetaInformation =>
capitalize(routeWithMetaInformation.route.urlFragment.split('/')[1]),
);
tagsToKeep = tagsToKeep.filter(
(element, i, array) => array.indexOf(element) === i && array.lastIndexOf(element) !== i,
);
// initialize json output
const output = openapi3Template;
@@ -87,194 +82,99 @@ commander
const schemasToCopy: string[] = [];
// generate documentation for all routes
routes.forEach((routeWithMetaInformation) => {
for (const routeWithMetaInformation of routes) {
routeWithMetaInformation.tags = [capitalize(routeWithMetaInformation.route.urlFragment.split('/')[1])];
output.paths[routeWithMetaInformation.route.urlFragment] = generateOpenAPIForRoute(
routeWithMetaInformation,
relative(relativeOutDirPath,outDirSchemasPath),
path.relative(relativeOutDirectoryPath, outDirectorySchemasPath),
schemasToCopy,
tagsToKeep,
);
});
}
// copy schema json schema files
try {
if (!existsSync(outDirSchemasPath)){
await mkdirPromisified(outDirSchemasPath, {
if (!existsSync(outDirectorySchemasPath)) {
await mkdirPromisified(outDirectorySchemasPath, {
recursive: true,
});
}
for (const fileName of schemasToCopy) {
await copy(join(srcPath, 'schema', `${fileName}.json`), join(outDirSchemasPath, `${fileName}.json`));
await copy(
path.join(sourcePath, 'schema', `${fileName}.json`),
path.join(outDirectorySchemasPath, `${fileName}.json`),
);
}
} catch (error) {
await Logger.error(error);
// tslint:disable-next-line: no-magic-numbers
process.exit(-2);
}
// write openapi object to file (prettified)
// tslint:disable-next-line: no-magic-numbers
writeFileSync(join(outDirPath, 'openapi.json'), JSON.stringify(output, null, 2));
writeFileSync(path.join(outDirectoryPath, 'openapi.json'), JSON.stringify(output, undefined, 2));
Logger.ok(`OpenAPI representation resources written to ${outDirPath} .`);
Logger.ok(`OpenAPI representation resources written to ${outDirectoryPath} .`);
});
commander
.command('mapping <relativeSrcPath>')
.option('-m, --mappingPath <relativeMappingPath>', 'Mapping Path')
.option('-i, --ignoredTags <ignoredTags>', 'Ignored Tags (comma-separated)')
.option('-a, --aggPath <relativeAggregationPath>', 'Aggregations Path')
.option('-e, --errorPath <relativeErrorPath>', 'Error Path')
.action(async (relativeSrcPath, options) => {
// get absolute paths
const srcPath = resolve(relativeSrcPath);
commander.command('schema <srcPath> <schemaPath>').action(async (relativeSourcePath, relativeSchemaPath) => {
// get absolute paths
const absoluteSourcePath = path.resolve(relativeSourcePath);
const schemaPath = path.resolve(relativeSchemaPath);
let ignoredTagsList: string[] = [];
if (typeof options.ignoredTags === 'string') {
ignoredTagsList = options.ignoredTags.split(',');
}
// initialize new core converter
const coreConverter = new Converter(absoluteSourcePath);
// get project reflection
const projectReflection = getProjectReflection(srcPath);
// get validatable types
const validatableTypes = getValidatableTypesInPath(absoluteSourcePath);
const result = generateTemplate(projectReflection, ignoredTagsList, true);
if (result.errors.length !== 0) {
await Logger.error('Mapping generated with errors!');
} else {
Logger.ok('Mapping generated without errors!');
}
Logger.info(`Found ${validatableTypes.length} type(s) to generate schemas for.`);
// write documentation to file
if (typeof options.aggPath !== 'undefined') {
const aggPath = resolve(options.aggPath);
// tslint:disable-next-line:no-magic-numbers
writeFileSync(aggPath, JSON.stringify(result.aggregations, null, 2));
Logger.ok(`Elasticsearch aggregations written to ${aggPath}.`);
}
if (typeof options.mappingPath !== 'undefined') {
const mappingPath = resolve(options.mappingPath);
// tslint:disable-next-line:no-magic-numbers
writeFileSync(mappingPath, JSON.stringify(result.mappings, null, 2));
Logger.ok(`Elasticsearch mappings written to ${mappingPath}.`);
}
if (typeof options.errorPath !== 'undefined') {
const errPath = resolve(options.errorPath);
// tslint:disable-next-line:no-magic-numbers
writeFileSync(errPath, JSON.stringify(result.errors, null, 2));
Logger.ok(`Mapping errors written to ${errPath}.`);
}
await mkdirPromisified(schemaPath, {
recursive: true,
});
commander
.command('put-es-templates <srcPath> <esAddress> [ignoredTags]')
.action(async (relativeSrcPath, esAddress, ignoredTags) => {
// get absolute paths
const srcPath = resolve(relativeSrcPath);
Logger.info(`Trying to find a package.json for ${absoluteSourcePath}.`);
let ignoredTagsList: string[] = [];
if (typeof ignoredTags === 'string') {
ignoredTagsList = ignoredTags.split(',');
}
let packagePath = absoluteSourcePath;
// TODO: this check should be less ugly! --- What is this doing anyway?
while (!existsSync(path.join(packagePath, 'package.json')) && packagePath.length > 5) {
packagePath = path.resolve(packagePath, '..');
}
// get project reflection
const projectReflection = getProjectReflection(srcPath);
const corePackageJsonPath = path.join(packagePath, 'package.json');
const result = generateTemplate(projectReflection, ignoredTagsList, true);
if (result.errors.length !== 0) {
await Logger.error(`Mapping generated with errors:\n${JSON.stringify(result.errors)}`);
exit(-1);
} else {
Logger.ok('Mapping generated without errors!');
}
Logger.info(`Using ${corePackageJsonPath} to determine version for schemas.`);
for (const template in result.mappings) {
if (!result.mappings.hasOwnProperty(template)) {
continue;
}
const buffer = await readFilePromisified(corePackageJsonPath);
const corePackageJson = JSON.parse(buffer.toString());
const coreVersion = corePackageJson.version;
const response = await got.put(`${esAddress}_template/${template}`, {
json: result.mappings[template],
});
Logger.log(`Using ${coreVersion} as version for schemas.`);
const HTTP_STATUS_OK = 200;
if (response.statusCode !== HTTP_STATUS_OK) {
await Logger.error(`Template for "${template}" failed in Elasticsearch:\n${JSON.stringify(response.body)}`);
exit(-1);
}
}
// generate and write JSONSchema files for validatable types
for (const type of validatableTypes) {
const schema = coreConverter.getSchema(type, coreVersion);
Logger.ok(`Templates accepted by Elasticsearch.`);
});
const stringifiedSchema = JSON.stringify(schema, undefined, 2);
commander
.command('schema <srcPath> <schemaPath>')
.action(async (relativeSrcPath, relativeSchemaPath) => {
// get absolute paths
const srcPath = resolve(relativeSrcPath);
const schemaPath = resolve(relativeSchemaPath);
const file = path.join(schemaPath, `${type}.json`);
// initialize new core converter
const coreConverter = new Converter(srcPath);
// write schema to file
writeFileSync(file, stringifiedSchema);
// get project reflection
const projectReflection = getProjectReflection(srcPath);
Logger.info(`Generated schema for ${type} and saved to ${file}.`);
}
// get validatable types
const validatableTypes = getValidatableTypesFromReflection(
projectReflection,
);
Logger.info(`Found ${validatableTypes.length} type(s) to generate schemas for.`);
await mkdirPromisified(schemaPath, {
recursive: true,
});
Logger.info(`Trying to find a package.json for ${srcPath}.`);
let path = srcPath;
// TODO: this check should be less ugly! --- What is this doing anyway?
// tslint:disable-next-line:no-magic-numbers
while (!existsSync(join(path, 'package.json')) && path.length > 5) {
path = resolve(path, '..');
}
const corePackageJsonPath = join(path, 'package.json');
Logger.info(`Using ${corePackageJsonPath} to determine version for schemas.`);
const buffer = await readFilePromisified(corePackageJsonPath);
const corePackageJson = JSON.parse(buffer.toString());
const coreVersion = corePackageJson.version;
Logger.log(`Using ${coreVersion} as version for schemas.`);
// generate and write JSONSchema files for validatable types
validatableTypes.forEach((type) => {
const schema = coreConverter.getSchema(type, coreVersion);
// tslint:disable-next-line:no-magic-numbers
const stringifiedSchema = JSON.stringify(schema, null, 2);
const file = join(schemaPath, `${type}.json`);
// write schema to file
writeFileSync(file, stringifiedSchema);
Logger.info(`Generated schema for ${type} and saved to ${file}.`);
});
Logger.ok(`Generated schemas for ${validatableTypes.length} type(s).`);
});
Logger.ok(`Generated schemas for ${validatableTypes.length} type(s).`);
});
commander
.command('validate <schemaPath> <testPath> [reportPath]')
.action(async (relativeSchemaPath, relativeTestPath, relativeReportPath) => {
// get absolute paths
const schemaPath = resolve(relativeSchemaPath);
const testPath = resolve(relativeTestPath);
const schemaPath = path.resolve(relativeSchemaPath);
const testPath = path.resolve(relativeTestPath);
const errorsPerFile = await validateFiles(schemaPath, testPath);
@@ -284,11 +184,11 @@ commander
continue;
}
unexpected = unexpected || errorsPerFile[file].some((error) => !error.expected);
unexpected = unexpected || errorsPerFile[file].some(error => !error.expected);
}
if (typeof relativeReportPath !== 'undefined') {
const reportPath = resolve(relativeReportPath);
const reportPath = path.resolve(relativeReportPath);
await writeReport(reportPath, errorsPerFile);
}
@@ -300,70 +200,34 @@ commander
}
});
commander
.command('pack')
.action(async () => {
await pack();
});
commander.command('pack').action(async () => {
await pack();
});
commander
.command('plantuml <srcPath> <plantumlserver>')
.option(
'--definitions <definitions>',
'Shows these specific definitions (class, interface or enum)',
toArray,
.option('--definitions <definitions>', 'Shows these specific definitions (class, interface or enum)', it =>
it.split(','),
)
.option('--showAssociations', 'Shows associations of definitions')
.option(
'--showInheritance',
'Shows extensions and implementations of definitions',
)
.option('--showInheritance', 'Shows extensions and implementations of definitions')
.option('--showEnumValues', 'Show enum values')
.option('--showProperties', 'Show attributes')
.option(
'--showInheritedProperties',
'Shows inherited attributes, needs --showProperties',
)
.option(
'--showOptionalProperties',
'Shows optional attributes and relations, needs --showProperties',
)
.option(
'--excludeExternals',
'Exclude external definitions',
)
.option(
'--outputFileName <fileName>',
'Defines the filename of the output',
)
.action(async (relativeSrcPath, plantumlserver, options) => {
.option('--showInheritedProperties', 'Shows inherited attributes, needs --showProperties')
.option('--showOptionalProperties', 'Shows optional attributes and relations, needs --showProperties')
.option('--excludeExternals', 'Exclude external definitions')
.option('--outputFileName <fileName>', 'Defines the filename of the output')
.action(async (relativeSourcePath, plantumlServer, options) => {
const plantUmlConfig: UMLConfig = {
definitions:
typeof options.definitions !== 'undefined' ? options.definitions : [],
showAssociations:
typeof options.showAssociations !== 'undefined'
? options.showAssociations
: false,
showEnumValues:
typeof options.showEnumValues !== 'undefined'
? options.showEnumValues
: false,
showInheritance:
typeof options.showInheritance !== 'undefined'
? options.showInheritance
: false,
definitions: typeof options.definitions !== 'undefined' ? options.definitions : [],
showAssociations: typeof options.showAssociations !== 'undefined' ? options.showAssociations : false,
showEnumValues: typeof options.showEnumValues !== 'undefined' ? options.showEnumValues : false,
showInheritance: typeof options.showInheritance !== 'undefined' ? options.showInheritance : false,
showInheritedProperties:
typeof options.showInheritedProperties !== 'undefined'
? options.showInheritedProperties
: false,
typeof options.showInheritedProperties !== 'undefined' ? options.showInheritedProperties : false,
showOptionalProperties:
typeof options.showOptionalProperties !== 'undefined'
? options.showOptionalProperties
: false,
showProperties:
typeof options.showProperties !== 'undefined'
? options.showProperties
: false,
typeof options.showOptionalProperties !== 'undefined' ? options.showOptionalProperties : false,
showProperties: typeof options.showProperties !== 'undefined' ? options.showProperties : false,
};
if (typeof options.outputFileName !== 'undefined') {
plantUmlConfig.outputFileName = options.outputFileName;
@@ -371,21 +235,14 @@ commander
Logger.log(`PlantUML options: ${JSON.stringify(plantUmlConfig)}`);
const srcPath = resolve(relativeSrcPath);
const projectReflection = getProjectReflection(srcPath, !options.excludeExternals ? false : true);
const definitions = readDefinitions(projectReflection);
await createDiagram(definitions, plantUmlConfig, plantumlserver);
await createDiagram(lightweightDefinitionsFromPath(relativeSourcePath), plantUmlConfig, plantumlServer);
});
commander
.command('plantuml-file <inputFile> <plantumlserver> [outputFile]')
.action(async (file: string, plantumlserver: string, outputFile: string) => {
const fileContent = readFileSync(resolve(file))
.toString();
await createDiagramFromString(fileContent, plantumlserver, outputFile);
.action(async (file: string, plantumlServer: string, outputFile: string) => {
const fileContent = readFileSync(path.resolve(file)).toString();
await createDiagramFromString(fileContent, plantumlServer, outputFile);
});
commander.parse(process.argv);

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018-2019 StApps
* Copyright (C) 2018-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.
@@ -13,16 +13,11 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Logger} from '@openstapps/logger';
import {existsSync, mkdir, PathLike, readFile, unlink, writeFile} from 'fs';
import {existsSync, mkdir, readFile, unlink, writeFile} from 'fs';
import {Glob} from 'glob';
import {JSONSchema7 as JSONSchema} from 'json-schema';
import {platform} from 'os';
import {join, sep} from 'path';
import {Definition} from 'ts-json-schema-generator';
import {Application, ProjectReflection} from 'typedoc';
import {ModuleKind, ScriptTarget} from 'typescript';
import {promisify} from 'util';
import {LightweightType} from './uml/model/lightweight-type';
import path from 'path';
export const globPromisified = promisify(Glob);
export const mkdirPromisified = promisify(mkdir);
@@ -30,267 +25,6 @@ export const readFilePromisified = promisify(readFile);
export const writeFilePromisified = promisify(writeFile);
export const unlinkPromisified = promisify(unlink);
/**
* A route instance with its relevant meta information
*/
export interface RouteWithMetaInformation {
/**
* Description of the route
*/
description: {
/**
* Short text of the description - title
*/
shortText?: string;
/**
* Text of the description
*/
text?: string;
};
/**
* Name of the route
*/
name: string;
/**
* Instance of the route
*/
route: {
/**
* Possible errors on a route
*/
errors: SCErrorResponse[];
/**
* Method of the route
*/
method: string;
/**
* Obligatory parameters of the route
*/
obligatoryParameters: {
[k: string]: string;
};
/**
* Description of the request body
*/
requestBodyDescription: string;
/**
* Name of the request body
*/
requestBodyName: string;
/**
* Description of the response body
*/
responseBodyDescription: string;
/**
* Name of the response body
*/
responseBodyName: string;
/**
* Status code on success
*/
statusCodeSuccess: number;
/**
* URL fragment
*/
urlFragment: string;
};
/**
* Possible tags/keywords the route can be associated with
*/
tags?: [string];
}
/**
* A node with its relevant meta information
*/
export interface NodeWithMetaInformation {
/**
* Module the node belongs to
*/
module: string;
/**
* Type of the node
*/
type: string;
}
/**
* A generic error that can be returned by the backend if somethings fails during the processing of a request
*/
export interface SCErrorResponse extends Error {
/**
* Additional data that describes the error
*/
additionalData?: unknown;
/**
* HTTP status code to return this error with
*/
statusCode: number;
}
/**
* A map of nodes indexed by their name
*/
export interface NodesWithMetaInformation {
/**
* Index signature
*/
[k: string]: NodeWithMetaInformation;
}
/**
* A schema with definitions
*/
interface SchemaWithDefinitions extends JSONSchema {
/**
* Definitions of the schema
*/
definitions: { [name: string]: Definition; };
}
/**
* The validation result
*/
export interface ValidationResult {
/**
* A list of errors that occurred
*/
errors: ValidationError[];
/**
* whether the validation was successful
*/
valid: boolean;
}
/**
* An error that occurred while validating
*
* This is a duplicate of the ValidationError in core/protocol/errors/validation because of incompatibilities
* between TypeDoc and TypeScript
*/
export interface ValidationError {
/**
* JSON schema path
*/
dataPath: string;
/**
* The instance
*/
instance: unknown;
/**
* The message
*
* Provided by https://www.npmjs.com/package/better-ajv-errors
*/
message: string;
/**
* Name of the error
*/
name: string;
/**
* Path within the Schema
*/
schemaPath: string;
/**
* Suggestion to fix the occurring error
*
* Provided by https://www.npmjs.com/package/better-ajv-errors
*/
suggestion?: string;
}
/**
* An expected error
*/
export interface ExpectedValidationError extends ValidationError {
/**
* Whether or not the error is expected
*/
expected: boolean;
}
/**
* A map of files and their expected validation errors
*/
export interface ExpectedValidationErrors {
[fileName: string]: ExpectedValidationError[];
}
/**
* Get a project reflection from a path
*
* @param srcPath Path to get reflection from
* @param excludeExternals Exclude external dependencies
*/
export function getProjectReflection(srcPath: PathLike, excludeExternals = true): ProjectReflection {
Logger.info(`Generating project reflection for ${srcPath.toString()}.`);
const tsconfigPath = getTsconfigPath(srcPath.toString());
// initialize new Typedoc application
const app = new Application();
app.options.setValues({
excludeExternals: excludeExternals,
ignoreCompilerErrors: false, // TODO: true
includeDeclarations: true,
module: ModuleKind.CommonJS,
target: ScriptTarget.Latest,
tsconfig: join(tsconfigPath, 'tsconfig.json'),
});
let inputFilePath = srcPath;
if (inputFilePath === tsconfigPath) {
inputFilePath = join(tsconfigPath, 'src');
}
// get input files
const inputFiles = app.expandInputFiles([inputFilePath.toString()]);
// get project reflection from input files
const result = app.convert(inputFiles);
if (typeof result === 'undefined') {
throw new Error('Project reflection could not be generated.');
}
return result;
}
/**
* Guard method for checking if a schema has definitions
*
* @param schema Schema to check
*/
export function isSchemaWithDefinitions(
schema: JSONSchema,
): schema is SchemaWithDefinitions {
return typeof schema.definitions !== 'undefined';
}
// tslint:disable: completed-docs
/**
* Guard method for determining if an object (a thing) has a type property with a type of string
*
* @param thing An object (thing)
*/
export function isThingWithType(thing: unknown): thing is { type: string; } {
return typeof thing === 'object' &&
thing !== null &&
'type' in thing &&
typeof (thing as { type: unknown; }).type === 'string';
}
// tslint:enable: completed-docs
/**
* Get path that contains a tsconfig.json
*
@@ -300,12 +34,10 @@ export function getTsconfigPath(startPath: string): string {
let tsconfigPath = startPath;
// see https://stackoverflow.com/questions/9652043/identifying-the-file-system-root-with-node-js
const root = (platform() === 'win32') ? process
.cwd()
.split(sep)[0] : '/';
const root = platform() === 'win32' ? process.cwd().split(path.sep)[0] : '/';
// repeat until a tsconfig.json is found
while (!existsSync(join(tsconfigPath, 'tsconfig.json'))) {
while (!existsSync(path.join(tsconfigPath, 'tsconfig.json'))) {
if (tsconfigPath === root) {
throw new Error(
`Reached file system root ${root} while searching for 'tsconfig.json' in ${startPath}!`,
@@ -313,72 +45,12 @@ export function getTsconfigPath(startPath: string): string {
}
// pop last directory
const tsconfigPathParts = tsconfigPath.split(sep);
const tsconfigPathParts = tsconfigPath.split(path.sep);
tsconfigPathParts.pop();
tsconfigPath = tsconfigPathParts.join(sep);
tsconfigPath = tsconfigPathParts.join(path.sep);
}
Logger.info(`Using 'tsconfig.json' from ${tsconfigPath}.`);
return tsconfigPath;
}
/**
* Converts a comma separated string into a string array
*
* @param val Comma separated string
*/
export function toArray(val: string): string[] {
return val.split(',');
}
/**
* Creates the full name of a lightweight type recursively
*
* @param type Type to get the full name of
*/
export function getFullTypeName(type: LightweightType): string {
// init name
let fullName: string = type.name;
if (type.isTypeParameter) {
// type parameters are a sink
return fullName;
}
if (type.isLiteral) {
// literals are a sink
return `'${fullName}'`;
}
if (type.isUnion && type.specificationTypes.length > 0) {
const tempNames: string[] = [];
for (const easyType of type.specificationTypes) {
tempNames.push(getFullTypeName(easyType));
}
// since unions can't be applied to other types, it is a sink.
return tempNames.join(' | ');
}
// check if type is generic and has a type attached
if (type.isTyped && type.genericsTypes.length > 0) {
const tempNames: string[] = [];
for (const easyType of type.genericsTypes) {
tempNames.push(getFullTypeName(easyType));
}
fullName = `${fullName}<${tempNames.join(', ')}>`;
}
// check if type is array
if (type.isArray) {
fullName += '[]';
}
return fullName;
}
/**
* Creates sentence cased string
*
* @param str The string to capitalize
*/
export function capitalize(str?: string): string {
// tslint:disable-next-line: newline-per-chained-call
return `${str?.charAt(0).toUpperCase()}${str?.slice(1).toLowerCase()}`;
}

View File

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

83
src/easy-ast/ast-util.ts Normal file
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 {flatMap, keyBy, isEmpty} from 'lodash';
import {TypeFlags} from 'typescript';
import {LightweightAliasDefinition} from './types/lightweight-alias-definition';
import {LightweightClassDefinition} from './types/lightweight-class-definition';
import {LightweightDefinition} from './types/lightweight-definition';
import {LightweightDefinitionKind} from './types/lightweight-definition-kind';
import {LightweightProject} from './types/lightweight-project';
import {LightweightType} from './types/lightweight-type';
/**
* 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 (isEmpty(type.genericsTypes)) {
return `${type.value}<${type.genericsTypes?.map(expandTypeValue).join(', ')}>`;
}
return type.value?.toString();
}
export function definitionsOf(project: LightweightProject): Record<string, LightweightDefinition> {
return keyBy(flatMap(project, Object.values), 'name');
}
export function isPrimitiveType(type: {flags: TypeFlags}): boolean {
return (type.flags & TypeFlags.NonPrimitive) === 0;
}
export function isLiteralType(type: {flags: TypeFlags}): boolean {
return (type.flags & TypeFlags.Literal) !== 0;
}
export function isEnumLiteralType(type: {flags: TypeFlags}): boolean {
return (type.flags & TypeFlags.EnumLiteral) !== 0;
}
export function isStringLiteralType(type: {flags: TypeFlags}): boolean {
return (type.flags & TypeFlags.StringLiteral) !== 0;
}
export function isUnionOrIntersectionType(type: {flags: TypeFlags}): boolean {
return (type.flags & TypeFlags.UnionOrIntersection) !== 0;
}
export function isUnionType(type: {flags: TypeFlags}): boolean {
return (type.flags & 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: TypeFlags}): boolean {
return (type.flags & TypeFlags.TypeVariable) !== 0;
}

274
src/easy-ast/easy-ast.ts Normal file
View File

@@ -0,0 +1,274 @@
/* 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 {flatMap, groupBy, keyBy, mapValues} from 'lodash';
import * as ts from 'typescript';
import {
ClassDeclaration,
ClassElement,
EnumDeclaration,
InterfaceDeclaration,
isArrayTypeNode,
isClassDeclaration,
isEnumDeclaration,
isIndexSignatureDeclaration,
isPropertyDeclaration,
isTypeLiteralNode,
isTypeReferenceNode,
NodeArray,
Program,
SourceFile,
SyntaxKind,
Type,
TypeAliasDeclaration,
TypeChecker,
TypeElement,
TypeFlags,
TypeLiteralNode,
TypeNode,
} from 'typescript';
import {cleanupEmpty, mapNotNil, rejectNil} from '../util/collections';
import {expandPathToFilesSync} from '../util/io';
import {
extractComment,
filterChildrenTo,
filterNodeTo,
getModifiers,
isArrayLikeType,
isClassLikeNode,
isEnumLikeNode,
isProperty,
resolvePropertyName,
resolveTypeName,
} from './ast-internal-util';
import {isEnumLiteralType, isTypeVariable} from './ast-util';
import {LightweightAliasDefinition} from './types/lightweight-alias-definition';
import {LightweightClassDefinition} from './types/lightweight-class-definition';
import {LightweightDefinition} from './types/lightweight-definition';
import {LightweightDefinitionKind} from './types/lightweight-definition-kind';
import {LightweightProject} from './types/lightweight-project';
import {LightweightType} from './types/lightweight-type';
import path from 'path';
import {LightweightProperty} from './types/lightweight-property';
/**
* 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: Program;
readonly sourceFiles: readonly SourceFile[];
readonly typeChecker: 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: EnumDeclaration | TypeAliasDeclaration): LightweightAliasDefinition {
return cleanupEmpty({
comment: this.includeComments ? extractComment(enumLike) : undefined,
name: enumLike.name.getText() ?? 'ERROR',
kind: LightweightDefinitionKind.ALIAS_LIKE,
modifiers: getModifiers(enumLike.getText(), isEnumDeclaration(enumLike) ? 'enum' : 'type'),
type: 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: ClassDeclaration | InterfaceDeclaration): LightweightClassDefinition {
const heritages = mapValues(
groupBy(classLike.heritageClauses, it => it.token),
heritages => flatMap(heritages, it => it.types),
);
return cleanupEmpty({
comment: this.includeComments ? extractComment(classLike) : undefined,
name: classLike.name?.escapedText ?? 'ERROR',
kind: LightweightDefinitionKind.CLASS_LIKE,
modifiers: getModifiers(classLike.getText(), 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 NodeArray<ClassElement | TypeElement>,
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: NodeArray<ClassElement | TypeElement>): Record<string, LightweightProperty> {
return keyBy(
filterNodeTo(members as NodeArray<ClassElement | 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 TypeLiteralNode)?.members),
optional: isPropertyDeclaration(property)
? typeof property.questionToken !== 'undefined'
? true
: undefined
: 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?: TypeNode): LightweightType {
if (typeNode?.kind === SyntaxKind.ConditionalType) {
return {value: 'UNSUPPORTED_CONDITIONAL_TYPE', flags: TypeFlags.Unknown};
}
if (isArrayLikeType(typeNode)) {
const elementType = isArrayTypeNode(typeNode) ? typeNode.elementType : typeNode.typeArguments?.[0]!;
const out = this.lightweightTypeFromType(
this.typeChecker.getTypeFromTypeNode(elementType),
elementType,
);
out.isArray = true;
return out;
}
const isReference =
typeof typeNode !== 'undefined' && isTypeReferenceNode(typeNode) && !isEnumLiteralType(type);
const isTypeLiteral = typeof typeNode !== 'undefined' && 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: 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 flatMap(this.convert(), it => it.values);
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 StApps
* 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.
@@ -13,32 +13,20 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {LightweightDefinitionBase} from './lightweight-definition';
import {LightweightDefinitionKind} from './lightweight-definition-kind';
import {LightweightType} from './lightweight-type';
/**
* @indexable
* Represents an enum definition
*/
export interface AggInherited extends Foo {
export interface LightweightAliasDefinition extends LightweightDefinitionBase {
/**
* No tag here :)
* Kind
*/
bar: 'some thing';
kind: LightweightDefinitionKind.ALIAS_LIKE;
type: ThingType.AggInheritedOverwritten;
}
interface Foo {
/**
* @aggregatable
* Enumeration or union values
*/
bar: string;
type?: LightweightType;
}
export const aggInheritedOverwrittenTest: MapAggTestOptions = {
name: ThingType.AggInherited,
agg: {
fields: ['bar'],
},
};

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 StApps
* 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.
@@ -13,44 +13,42 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {LightweightDefinition} from './lightweight-definition';
import {LightweightProperty} from './lightweight-property';
import {LightweightDefinitionBase} from './lightweight-definition';
import {LightweightDefinitionKind} from './lightweight-definition-kind';
import {LightweightIndexSignature, LightweightProperty} from './lightweight-property';
import {LightweightType} from './lightweight-type';
/**
* Represents a class definition
*/
export class LightweightClassDefinition extends LightweightDefinition {
export interface LightweightClassDefinition extends LightweightDefinitionBase {
/**
* String values of the extended definitions
*/
public extendedDefinitions: string[];
extendedDefinitions?: LightweightType[];
/**
* String values of the implemented definitions
*/
public implementedDefinitions: string[];
implementedDefinitions?: LightweightType[];
/**
* Index signatures
*/
indexSignatures?: Record<string, LightweightIndexSignature>;
/**
* Kind
*/
kind: LightweightDefinitionKind.CLASS_LIKE;
/**
* Properties of the definition
*/
public properties: LightweightProperty[];
/**
* The definition type
* e.g. `interface`/[`abstract`] `class`
*/
public type: string;
properties?: Record<string, LightweightProperty>;
/**
* Generic type parameters of this class
*/
public typeParameters: string[];
constructor(name: string, type: string) {
super(name);
this.type = type;
this.properties = [];
this.extendedDefinitions = [];
this.implementedDefinitions = [];
this.typeParameters = [];
}
typeParameters?: string[];
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 StApps
* 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.
@@ -12,28 +12,37 @@
* 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;
import {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
/**
* Short summary of the comment
*/
shortSummary?: string;
/**
* Tags of the comment
*/
tags?: LightweightCommentTag[];
}
/**
* @indexable
* Lightweight comment tag
*/
export interface AggInheritedGlobal extends Foo {
type: ThingType.AggInheritedGlobal;
}
interface Foo {
export interface LightweightCommentTag {
/**
* @aggregatable global
* The name of the tag
*/
bar: string;
}
name: string;
export const aggInheritedGlobalTest: MapAggTestOptions = {
name: ThingType.AggInheritedGlobal,
agg: {
globals: ['bar'],
},
};
/**
* The parameters of the tag
*/
parameters?: string[];
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018-2019 StApps
* 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.
@@ -12,6 +12,8 @@
* 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 function testFunction(): boolean {
return true;
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';
import {LightweightComment} from './lightweight-comment';
import {LightweightClassDefinition} from './lightweight-class-definition';
import {LightweightAliasDefinition} from './lightweight-alias-definition';
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,100 @@
/*
* 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 {assign, cloneDeep, flatMap, fromPairs, trimEnd} from 'lodash';
import {mapNotNil} from '../../util/collections';
import {definitionsOf, isLightweightClass} from '../ast-util';
import {lightweightProjectFromPath} from '../easy-ast';
import {LightweightClassDefinition} from './lightweight-class-definition';
import {LightweightDefinition} from './lightweight-definition';
/**
* Build an index for a lightweight project
*/
function buildIndex(project: LightweightProject): Record<string, string> {
return fromPairs(
flatMap(project, (definitions, file) => Object.keys(definitions).map(definition => [definition, file])),
);
}
/**
* 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 assign(
mapNotNil(
[...(classLike.implementedDefinitions ?? []), ...(classLike.extendedDefinitions ?? [])],
extension => {
const object = this.definitions[extension.referenceName ?? ''];
return (deep ?? true) && isLightweightClass(object)
? this.applyInheritance(object)
: cloneDeep(object);
},
),
cloneDeep(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 (typeof fsPath === 'undefined') {
return undefined;
}
const module = await import(findCompiledModule ? `${trimEnd(fsPath, 'd.ts')}.js` : fsPath);
return new module[name]() as T;
}
}
export interface LightweightFile {
[definitionName: string]: LightweightDefinition;
}
export interface LightweightProject {
[sourcePath: string]: LightweightFile;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 StApps
* 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.
@@ -12,43 +12,42 @@
* 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';
import {LightweightType} from './lightweight-type';
/**
* Represents a property definition
*/
export class LightweightProperty {
export interface LightweightProperty {
/**
* Is the property inherited from another definition
* The comment of the property
*/
public inherited: boolean;
comment?: LightweightComment;
/**
* Name of the property
*/
public name: string;
name: string;
/**
* Is the property marked as optional
*/
public optional: boolean;
optional?: true;
/**
* A record of properties if the property happens to be a type literal
*/
properties?: Record<string, LightweightProperty>;
/**
* Type of the property
*/
public type: LightweightType;
/**
* Constructor for LightweightProperty
*
* @param name Name of the property
* @param type Type of the property
* @param optional Is the property optional
*/
constructor(name: string, type: LightweightType, optional = true) {
this.name = name;
this.optional = optional;
this.inherited = false;
this.type = type;
}
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

@@ -1,812 +0,0 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Logger} from '@openstapps/logger';
import merge from 'deepmerge';
import {stringify} from 'flatted';
import {DeclarationReflection, ProjectReflection, SignatureReflection} from 'typedoc';
import {
ArrayType,
Comment,
CommentTag,
IntrinsicType,
ReferenceType,
ReflectionType,
StringLiteralType,
Type,
TypeParameterType,
UnionType,
} from 'typedoc/dist/lib/models';
import {AggregationSchema, ESNestedAggregation} from './mappings/aggregation-definitions';
import {fieldmap, filterableMap, filterableTagName} from './mappings/definitions/fieldmap';
import {premaps} from './mappings/definitions/premap';
import {settings} from './mappings/definitions/settings';
import {dynamicTypes, ElasticsearchDataType, typemap} from './mappings/definitions/typemap';
import {
ElasticsearchDynamicTemplate,
ElasticsearchObject,
ElasticsearchTemplateCollection,
ElasticsearchType,
ElasticsearchValue,
} from './mappings/mapping-definitions';
let dynamicTemplates: ElasticsearchDynamicTemplate[] = [];
let errors: string[] = [];
let showErrors = true;
let aggregations: AggregationSchema = {};
const indexableTag = 'indexable';
const aggregatableTag = 'aggregatable';
const aggregatableTagParameterGlobal = 'global';
const inheritTagsName = 'inherittags';
// clamp printed object to 1000 chars to keep error messages readable
const maxErrorObjectChars = 1000;
let ignoredTagsList = ['indexable', 'validatable', inheritTagsName];
let inheritTagsMap: { [path: string]: CommentTag[]; } = {};
/**
* Gets all interfaces that have an @indexable tag
*
* @param projectReflection the project reflection from which to extract the indexable interfaces
*/
export function getAllIndexableInterfaces(projectReflection: ProjectReflection): DeclarationReflection[] {
let indexableInterfaces: DeclarationReflection[] = [];
if (!Array.isArray(projectReflection.children) || projectReflection.children.length === 0) {
throw new Error('No DeclarationReflections found. Please check your input path');
}
// push all declaration reflections into one array
projectReflection.children.forEach((declarationReflection) => {
if (Array.isArray(declarationReflection.children)) {
indexableInterfaces = indexableInterfaces.concat(declarationReflection.children);
}
});
// filter all declaration reflections with an @indexable tag
indexableInterfaces = indexableInterfaces.filter((declarationReflection) => {
if (
typeof declarationReflection.comment === 'undefined' ||
typeof declarationReflection.comment.tags === 'undefined'
) {
return false;
}
return typeof declarationReflection.comment.tags.find((commentTag) => {
return commentTag.tagName === indexableTag;
}) !== 'undefined';
});
return indexableInterfaces;
}
/**
* Composes error messages, that are readable and contain a certain minimum of information
*
* @param path the path where the error took place
* @param topTypeName the name of the SCThingType
* @param typeName the name of the object, with which something went wrong
* @param object the object or name
* @param message the error message
*/
function composeErrorMessage(path: string, topTypeName: string, typeName: string, object: string, message: string) {
const error = `At "${topTypeName}::${path.substr(0, path.length - 1)}" for ${typeName} "${trimString(object, maxErrorObjectChars)}": ${message}`;
errors.push(error);
if (showErrors) {
// tslint:disable-next-line:no-floating-promises
void Logger.error(error);
}
}
/**
* Trims a string to a readable size and appends "..."
*
* @param value the string to trim
* @param maxLength the maximum allowed length before it is clamped
*/
function trimString(value: string, maxLength: number): string {
return value.length > maxLength ?
`${value.substring(0, maxLength)}...` :
value;
}
/**
* Gets the Reflections and names for Generics in a ReferenceType of a DeclarationReflection
*
* Warning to future maintainers: The code for generics doesn't account for depth. when there is a new generic, it will
* override the previous one, if there isn't, it will just continue passing it down.
*
* @param type the ReferenceType of a DeclarationReflection
* @param out the previous reflection, it then overrides all parameters or keeps old ones
* @param topTypeName the name of the object, with which something went wrong
* @param path the current path to the object we are in
* @param tags any tags attached to the type
*/
function getReflectionGeneric(type: ReferenceType,
out: Map<string, ElasticsearchValue>,
topTypeName: string,
path: string,
tags: CommentTag[]): Map<string, ElasticsearchValue> {
if (typeof type.typeArguments !== 'undefined'
&& type.reflection instanceof DeclarationReflection
&& typeof type.reflection.typeParameters !== 'undefined') {
for (let i = 0; i < type.reflection.typeParameters.length; i++) {
if (i < type.typeArguments.length) {
out
.set(type.reflection.typeParameters[i].name, handleType(type.typeArguments[i], out, topTypeName, path, tags));
} else {
// this can happen due to a bug in TypeDoc https://github.com/TypeStrong/typedoc/issues/1061
// we have no way to know the type here, so we have to use this.
out.set(type.reflection.typeParameters[i].name, {
dynamic: true,
properties: {},
});
Logger.warn(`Type "${type.name}": Defaults of generics (Foo<T = any>) currently don't work due to a bug` +
` in TypeDoc. It has been replaced by a dynamic type.`);
}
}
}
return out;
}
/**
* Handles a ReferenceType that has no value
*
* Most of the times that is an external type.
*
* @param ref the ReferenceType
* @param generics the generics from levels above, so we can use them without having access to the parent
* @param path the current path to the object we are in
* @param topTypeName the name of the SCThingType
* @param tags any tags attached to the type
*/
function handleExternalType(ref: ReferenceType, generics: Map<string, ElasticsearchValue>,
path: string, topTypeName: string, tags: CommentTag[]): ElasticsearchValue {
for (const premap of Object.keys(premaps)) {
if (premap === ref.name) {
return readFieldTags(premaps[premap], path, topTypeName, tags);
}
}
if (ref.name === 'Array') { // basically an external type, but Array is quite common, especially with generics
if (typeof ref.typeArguments === 'undefined' || typeof ref.typeArguments[0] === 'undefined') {
composeErrorMessage(path, topTypeName, 'Array with generics', 'array', 'Failed to parse');
return {type: ElasticsearchDataType.parse_error};
}
return readFieldTags(
handleType(
ref.typeArguments[0], getReflectionGeneric(
ref, new Map(generics), path, topTypeName, tags),
path, topTypeName, tags),
path, topTypeName, tags);
}
if (ref.name === '__type') { // empty object
return {
dynamic: 'strict',
properties: {},
};
}
composeErrorMessage(path, topTypeName, 'external type', ref.name, 'Missing pre-map');
return readFieldTags({type: ElasticsearchDataType.missing_premap}, path, topTypeName, tags);
}
/**
* Handles an object
*
* @param decl the DeclarationReflection of the object
* @param generics the generics from levels above, so we can use them without having access to the parent
* @param path the current path to the object we are in
* @param topTypeName the name of the SCThingType
* @param inheritedTags the inherited tags
*/
function handleDeclarationReflection(decl: DeclarationReflection,
generics: Map<string, ElasticsearchValue>,
path: string,
topTypeName: string,
inheritedTags?: CommentTag[]):
ElasticsearchValue {
// check if we have an object referencing a generic
if (generics.has(decl.name)) { // if the object name is the same as the generic name
return readFieldTags(generics.get(decl.name) as ElasticsearchObject | ElasticsearchType, path, topTypeName,
decl.comment?.tags ?? []);
// use the value defined by the generic
}
// start the actual handling process
const out: ElasticsearchObject = {
dynamic: 'strict',
properties: {},
};
let empty = true;
// first check if there are any index signatures, so for example `[name: string]: Foo`
if (typeof decl.indexSignature !== 'undefined') {
out.dynamic = true;
if (typeof decl.indexSignature.type !== 'undefined') {
empty = false;
const template: ElasticsearchDynamicTemplate = {};
template[decl.name] = {
mapping: handleType(
decl.indexSignature.type,
new Map(generics), path, topTypeName,
getCommentTags(decl.indexSignature, path, topTypeName),
),
match: '*',
match_mapping_type: '*',
path_match: `${path}*`,
};
dynamicTemplates.push(template);
}
}
if (decl.kindString === 'Enumeration') {
return readTypeTags('string', path, topTypeName, getCommentTags(decl, path, topTypeName, inheritedTags));
}
// check all the children, so in this case we are dealing with an OBJECT
if (typeof decl.children !== 'undefined' && decl.children.length > 0) {
for (const child of decl.children) {
empty = false;
out.properties[child.name] =
handleDeclarationReflection(child, new Map(generics), `${path}${child.name}.`, topTypeName);
}
} else if (decl.type instanceof Type) { // if the object is a type, so we are dealing with a PROPERTY
// get inherited tags
const tags = (inheritedTags ?? []).length > 0 ? inheritedTags! : getCommentTags(decl, path, topTypeName);
return handleType(decl.type, new Map(generics), path, topTypeName, tags);
} else if (decl.kindString === 'Enumeration member') {
return readTypeTags(typeof decl.defaultValue, path, topTypeName,
getCommentTags(decl, path, topTypeName, inheritedTags));
}
if (empty) {
composeErrorMessage(path, topTypeName, 'object', decl.name, 'Empty object');
}
return readFieldTags(out, path, topTypeName, getCommentTags(decl, path, topTypeName));
}
/**
* Reads all comment tags, including inherited ones
*
* @param decl the DeclarationReflection to read the tags from
* @param path the path on which the comments lie
* @param topTypeName the name of the SCThingType
* @param inheritedTags any tags that might have been inherited by a parent
* @param breakId the id of the previous reflection to prevent infinite recursion in some cases
*/
function getCommentTags(
decl: DeclarationReflection | SignatureReflection,
path: string,
topTypeName: string,
inheritedTags: CommentTag[] = [],
// tslint:disable-next-line:no-unnecessary-initializer
breakId: number | undefined = undefined,
): CommentTag[] {
if (decl.id === breakId) {
return [];
}
let out: CommentTag[] = decl.comment instanceof Comment ?
typeof decl.comment.tags !== 'undefined' ? decl.comment.tags : inheritedTags : inheritedTags;
if (decl.overwrites instanceof ReferenceType && decl.overwrites.reflection instanceof DeclarationReflection) {
out = arrayPriorityJoin(
getCommentTags(decl.overwrites.reflection, path, topTypeName, inheritedTags, decl.id), out);
}
if (decl.inheritedFrom instanceof ReferenceType && decl.inheritedFrom.reflection instanceof DeclarationReflection) {
out = arrayPriorityJoin(
getCommentTags(decl.inheritedFrom.reflection, path, topTypeName, inheritedTags, decl.id), out);
}
saveCommentTags(out, path, topTypeName);
const inheritTag = out.find(((value) => value.tagName === inheritTagsName));
if (typeof inheritTag !== 'undefined') {
out = arrayPriorityJoin(out, retrieveCommentTags(inheritTag.text.trim(), path, topTypeName));
}
return out;
}
/**
* Saves all comment tags to the map
*
* @param tags all tags to be saved (@see and @[inheritTags] will be stripped)
* @param path the path of field
* @param topTypeName the name of the SCThingType
*/
function saveCommentTags(tags: CommentTag[], path: string, topTypeName: string) {
inheritTagsMap[`${topTypeName}::${path.substr(0, path.length - 1)}`] =
tags.filter(((value) => value.tagName !== 'see' && value.tagName !== inheritTagsName));
}
/**
* Retrieves any saved tags
*
* @param path the path to the original field
* @param currentPath the current path to the object we are in
* @param topTypeName the name of the SCThingType
*/
function retrieveCommentTags(path: string, currentPath: string, topTypeName: string): CommentTag[] {
if (!(path in inheritTagsMap)) {
composeErrorMessage(currentPath, topTypeName, path, 'Comment', 'Referenced path to tags does not exist!');
return [];
}
return inheritTagsMap[path];
}
/**
* Joins two arrays of CommentTags, but overrides all original CommentTags with the same tagName
*
* @param originals the original array
* @param overrider the array that should be appended and provide the override values
*/
function arrayPriorityJoin(originals: CommentTag[], overrider: CommentTag[]): CommentTag[] {
const out: CommentTag[] = overrider;
originals.forEach((original) => {
const result = overrider.find((element) => original.tagName === element.tagName);
// no support for multiple tags with the same name
if (!(result instanceof CommentTag)) {
out.push(original);
}
});
return out;
}
/**
* Handles UnionTypes
*
* Put into a separate function as it is a little bit more complex
* Works fairly reliable, although there are issues with primitive union types, which don't work at all (And never will)
*
* @param type the type object
* @param generics the generics from levels above, so we can use them without having access to the parent
* @param path the current path to the object we are in
* @param topTypeName the name of the SCThingType
* @param tags any tags attached to the type
*/
function handleUnionType(type: UnionType,
generics: Map<string, ElasticsearchValue>,
path: string,
topTypeName: string,
tags: CommentTag[]): ElasticsearchValue {
const list: ElasticsearchValue[] = [];
for (const subType of type.types) {
if (subType instanceof IntrinsicType && subType.name === 'undefined') {
continue;
}
list.push(handleType(subType, new Map(generics), path, topTypeName, tags));
}
if (list.length > 0) {
let out = list[0];
for (const item of list) {
out = merge<ElasticsearchValue>(out, item);
}
return out;
}
composeErrorMessage(path, topTypeName, 'Union Type', stringify(list),
'Empty union type. This is likely not a user error.');
return {type: ElasticsearchDataType.parse_error};
}
/**
* Serves as a kind of distributor for the different types, should not contain any specific code
*
* @param type the type object
* @param generics the generics from levels above, so we can use them without having access to the parent
* @param path the current path to the object we are in
* @param topTypeName the name of the SCThingType
* @param tags any tags attached to the type
*/
function handleType(type: Type, generics: Map<string, ElasticsearchValue>, path: string, topTypeName: string,
tags: CommentTag[]):
ElasticsearchValue {
// logger.log((type as any).name);
if (type instanceof ArrayType) { // array is irrelevant in Elasticsearch, so just go with the element type
const esType = handleType(type.elementType, new Map(generics), path, topTypeName, tags);
// also merge tags of the array to the element type
// filter out the type tags lazily, this can lead to double messages for "Not implemented tag"
let newTags = tags;
if ('type' in esType) {
newTags = tags.filter((tag) => {
return !(tag.tagName === esType.type);
});
}
return readFieldTags(esType, path, topTypeName, newTags);
}
if (type.type === 'stringLiteral') { // a string literal, usually for type
return readTypeTags(type.type, path, topTypeName, tags);
}
if (type instanceof IntrinsicType) { // the absolute default type, like strings
return readTypeTags(type.name, path, topTypeName, tags);
}
if (type instanceof UnionType) { // the union type...
return handleUnionType(type, new Map(generics), path, topTypeName, tags);
}
if (type instanceof ReferenceType) {
if (typeof premaps[type.name] === 'undefined' && typeof type.reflection !== 'undefined') {
// there is really no way to make this typesafe, every element in DeclarationReflection is optional.
return handleDeclarationReflection(type.reflection as DeclarationReflection,
getReflectionGeneric(type, new Map(generics), path, topTypeName, tags), path, topTypeName, tags);
}
return handleExternalType(type, new Map(generics), path, topTypeName, tags);
}
if (type instanceof TypeParameterType) {
// check if we have an object referencing a generic
if (generics.has(type.name)) {
return generics.get(type.name) as ElasticsearchObject | ElasticsearchType;
}
composeErrorMessage(path, topTypeName, 'Generic', type.name, 'Missing reflection, please report!');
return {type: ElasticsearchDataType.parse_error};
}
if (type instanceof ReflectionType) {
return readFieldTags(handleDeclarationReflection(type.declaration, new Map(generics), path, topTypeName),
path, topTypeName, tags);
}
composeErrorMessage(path, topTypeName, 'type', stringify(type), 'Not implemented type');
return {type: ElasticsearchDataType.parse_error};
}
/**
* Adds an aggregatable to the aggregations list
*
* @param path the current path
* @param topTypeName the name of the top type
* @param global whether the topTypeName will be used
*/
function addAggregatable(path: string, topTypeName: string, global: boolean) {
// push type.path and remove the '.' at the end of the path
if (global) {
const prop = path.slice(0, -1)
.split('.')
.pop() as string; // cannot be undefined
return (aggregations['@all'] as ESNestedAggregation).aggs[prop.split('.')
.pop() as string] = {
terms: {
field: `${prop}.raw`,
size: 1000,
},
};
}
const property = path.slice(0, -1);
return (aggregations[topTypeName] as ESNestedAggregation).aggs[property] = {
terms: {
field: `${property}.raw`,
size: 1000,
},
};
}
/**
* Reads all tags related to Elasticsearch fields from the fieldMap
*
* @param prev the previous ElasticsearchValue, for example and object
* @param path the current path to the object we are in
* @param topTypeName the name of the SCThingType
* @param tags tags attached to the value
* @param dataType the ElasticsearchDataType, for checking if a tag is a type tag
*/
function readFieldTags(prev: ElasticsearchValue,
path: string,
topTypeName: string,
tags: CommentTag[],
dataType?: string): ElasticsearchValue {
for (const tag of tags) {
if (tag.tagName === aggregatableTag) {
addAggregatable(path, topTypeName, tag.text.trim() === aggregatableTagParameterGlobal);
}
if (!ignoredTagsList.includes(tag.tagName)) {
if (typeof fieldmap[tag.tagName] !== 'undefined') {
if (typeof prev.fields === 'undefined') {
// create in case it doesn't exist
prev.fields = {};
}
if (tag.text.trim() === '') {
// merge fields
prev.fields = {...prev.fields, ...fieldmap[tag.tagName].default};
} else if (typeof fieldmap[tag.tagName][tag.text.trim()] !== 'undefined') {
// merge fields
prev.fields = {...prev.fields, ...fieldmap[tag.tagName][tag.text.trim()]};
} else if (!fieldmap[tag.tagName].ignore.includes(tag.text.trim())) {
// when there is an unidentified tag
composeErrorMessage(path, topTypeName, 'tag', tag.tagName, `Not implemented tag param "${tag.text.trim()}"`);
}
} else if (tag.tagName === filterableTagName) {
if (typeof prev.fields === 'undefined') {
prev.fields = {};
}
if ('type' in prev) {
const type = filterableMap[prev.type];
if (typeof type !== 'undefined') {
// merge fields
prev.fields = {...prev.fields, ...{raw: {type: type}}};
} else {
composeErrorMessage(path, topTypeName, 'tag', tag.tagName, `Not implemented for ${prev.type}`);
}
} else {
composeErrorMessage(path, topTypeName, 'tag', tag.tagName, 'Not applicable for object types');
}
} else if (typeof dataType === 'undefined' || typeof typemap[dataType][tag.tagName] === 'undefined') {
composeErrorMessage(path, topTypeName, 'tag', tag.tagName, `Not implemented tag`);
}
}
}
return prev;
}
/**
* Reads all types related to Elasticsearch fields from the fieldMap
*
* @param type the type of the value
* @param path the current path to the object we are in
* @param topTypeName the name of the SCThingType
* @param tags tags attached to the value
*/
function readTypeTags(type: string, path: string, topTypeName: string, tags: CommentTag[]): ElasticsearchValue {
let out: ElasticsearchValue = {type: ElasticsearchDataType.parse_error};
if (typeof typemap[type] !== 'undefined') { // first look if the value has a definition in the typemap
for (let i = tags.length - 1; i >= 0; i--) {
if (!ignoredTagsList.includes(tags[i].tagName) && typeof typemap[type][tags[i].tagName] !== 'undefined') {
// if we have a tag that indicates a type
if (out.type !== ElasticsearchDataType.parse_error) {
composeErrorMessage(path, topTypeName, 'type', type,
`Type conflict; "${typemap[type][tags[i].tagName]}" would override "${out.type}"`);
out.type = ElasticsearchDataType.type_conflict;
continue;
}
out.type = typemap[type][tags[i].tagName];
}
}
if (out.type === ElasticsearchDataType.parse_error) {
out.type = typemap[type].default;
}
out = readFieldTags(out, path, topTypeName, tags, type);
return out;
}
if (dynamicTypes.includes(type)) { // Elasticsearch dynamic type TODO: doesn't work for direct types
return {
dynamic: true,
properties: {},
};
}
composeErrorMessage(path, topTypeName, 'type', type, 'Not implemented type');
return out;
}
/**
* Reset the state
*
* This is kind of a suboptimal solution and should be changed in the future.
* https://gitlab.com/openstapps/core-tools/-/issues/49
*
* @param resetInheritTags whether inherited tags should be reset as well
*/
function reset(resetInheritTags = true) {
errors = [];
dynamicTemplates = [];
aggregations = {
'@all': {
aggs: {},
filter: {
match_all: {},
},
},
};
if (resetInheritTags) {
inheritTagsMap = {};
}
}
/**
* Takes a project reflection and generates an ElasticsearchTemplate from it
*
* Serves as the entry point for getting the mapping, so if you just want to get the mapping files for Elasticsearch,
* you can do so by calling this function, `RETURNED_VALUE.template` contains the mapping in a fashion that is directly
* readable by Elasticsearch.
*
* @param projectReflection a reflection of the project you want to get the ES Mappings from
* @param ignoredTags the tag names for which the error output should be suppressed
* @param showErrorOutput whether to print all errors in the command line or not
* @param interfaceFilter only parse specific interfaces, this is for testing purposes
*/
export function generateTemplate(projectReflection: ProjectReflection,
ignoredTags: string[],
showErrorOutput = true,
interfaceFilter: string[] = []):
// tslint:disable-next-line:completed-docs
{ aggregations: AggregationSchema; errors: string[]; mappings: ElasticsearchTemplateCollection; } {
reset();
showErrors = showErrorOutput;
ignoredTagsList = ['indexable', 'validatable', inheritTagsName];
ignoredTagsList.push.apply(ignoredTagsList, ignoredTags);
const indexableInterfaces = getAllIndexableInterfaces(projectReflection);
const out: ElasticsearchTemplateCollection = {};
for (const _interface of indexableInterfaces) {
// TODO: lots of duplicate code, this all needs to be changed https://gitlab.com/openstapps/core-tools/-/issues/49
if (!Array.isArray(_interface.children) || _interface.children.length === 0) {
throw new Error('Interface needs at least some properties to be indexable');
}
const typeObject = _interface.children.find((declarationReflection) => {
return declarationReflection.name === 'type';
});
if (typeof typeObject === 'undefined' || typeof typeObject.type === 'undefined') {
throw new Error('Interface needs a type to be indexable');
}
let typeName = 'INVALID_TYPE';
if (typeObject.type instanceof ReferenceType) {
if (typeObject.type.reflection instanceof DeclarationReflection
&& typeof typeObject.type.reflection.defaultValue === 'string') {
typeName = typeObject.type.reflection.defaultValue.replace('"', '')
.replace('"', '');
} else {
// tslint:disable-next-line:no-floating-promises
void Logger.error('Your input files seem to be incorrect, or there is a major bug in the mapping generator.');
}
} else if (typeObject.type instanceof StringLiteralType) {
Logger.warn(`The interface ${_interface.name} uses a string literal as type, please use SCThingType.`);
typeName = typeObject.type.value;
} else {
// tslint:disable-next-line:no-floating-promises
void Logger.error(`The interface ${_interface.name} is required to use an SCThingType as a type, please do so.`);
}
// init aggregation schema for type
aggregations[typeName] = {
aggs: {},
filter: {
type: {
value: typeName,
},
},
};
handleDeclarationReflection(_interface, new Map(), '', typeName);
}
// second traversal
reset(false);
for (const _interface of indexableInterfaces) {
if (!Array.isArray(_interface.children) || _interface.children.length === 0) {
throw new Error('Interface needs at least some properties to be indexable');
}
const typeObject = _interface.children.find((declarationReflection) => {
return declarationReflection.name === 'type';
});
if (typeof typeObject === 'undefined' || typeof typeObject.type === 'undefined') {
throw new Error('Interface needs a type to be indexable');
}
let typeName = 'INVALID_TYPE';
if (typeObject.type instanceof ReferenceType) {
if (typeObject.type.reflection instanceof DeclarationReflection
&& typeof typeObject.type.reflection.defaultValue === 'string') {
typeName = typeObject.type.reflection.defaultValue.replace('"', '')
.replace('"', '');
} else {
// tslint:disable-next-line:no-floating-promises
void Logger.error('Your input files seem to be incorrect, or there is a major bug in the mapping generator.');
}
} else if (typeObject.type instanceof StringLiteralType) {
Logger.warn(`The interface ${_interface.name} uses a string literal as type, please use SCThingType.`);
typeName = typeObject.type.value;
} else {
// tslint:disable-next-line:no-floating-promises
void Logger.error(`The interface ${_interface.name} is required to use an SCThingType as a type, please do so.`);
}
// filter out
if (interfaceFilter.length !== 0) {
if (typeof interfaceFilter.find((it) => it === typeName) === 'undefined') {
continue;
}
}
// init aggregation schema for type
aggregations[typeName] = {
aggs: {},
filter: {
type: {
value: typeName,
},
},
};
let typeNameWithoutSpaces = typeName.toLowerCase();
while (typeNameWithoutSpaces.includes(' ')) {
typeNameWithoutSpaces = typeNameWithoutSpaces.replace(' ', '_');
}
const templateName = `template_${typeNameWithoutSpaces}`;
out[templateName] = {
mappings: {
[typeName]: handleDeclarationReflection(_interface, new Map(), '', typeName) as ElasticsearchObject,
},
settings: settings,
template: `stapps_${typeNameWithoutSpaces}*`,
}
;
out[templateName].mappings[typeName].properties.creation_date = {
type: ElasticsearchDataType.date,
};
out[templateName].mappings[typeName].dynamic_templates = dynamicTemplates;
// Set some properties
out[templateName].mappings[typeName]._source = {
excludes: [
'creation_date',
],
};
out[templateName].mappings[typeName].date_detection = false;
dynamicTemplates = [];
if (Object.keys((aggregations[typeName] as ESNestedAggregation).aggs).length === 0) {
delete aggregations[typeName];
}
}
return {aggregations, mappings: out, errors};
}

View File

@@ -1,81 +0,0 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* An elasticsearch bucket aggregation
* @see https://www.elastic.co/guide/en/elasticsearch/reference/5.6/search-aggregations-bucket.html
*/
export interface AggregationSchema {
[aggregationName: string]: ESTermsFilter | ESNestedAggregation;
}
/**
* An elasticsearch terms filter
*/
export interface ESTermsFilter {
/**
* Terms filter definition
*/
terms: {
/**
* Field to apply filter to
*/
field: string;
/**
* Number of results
*/
size?: number;
};
}
/**
* Filter that filters by name of the the field type
*/
export interface ESAggTypeFilter {
/**
* The type of the object to find
*/
type: {
/**
* The name of the type
*/
value: string;
};
}
/**
* Filter that matches everything
*/
export interface ESAggMatchAllFilter {
/**
* Filter that matches everything
*/
match_all: {};
}
/**
* For nested aggregations
*/
export interface ESNestedAggregation {
/**
* Possible nested Aggregations
*/
aggs: AggregationSchema;
/**
* Possible filter for types
*/
filter: ESAggTypeFilter | ESAggMatchAllFilter;
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright (C) 2019-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 {ElasticsearchFieldmap, ElasticsearchFilterableMap} from '../mapping-definitions';
import {ElasticsearchDataType} from './typemap';
export enum analyzers {
ducet_sort = 'ducet_sort',
search_german = 'search_german',
}
export const fieldmap: ElasticsearchFieldmap = {
aggregatable: {
default: {
raw: {
ignore_above: 10000,
type: ElasticsearchDataType.keyword,
},
},
ignore: ['global'],
},
sortable: {
default: {
sort: {
analyzer: analyzers.ducet_sort,
fielddata: true,
type: ElasticsearchDataType.text,
},
},
ducet: {
sort: {
analyzer: analyzers.ducet_sort,
fielddata: true,
type: ElasticsearchDataType.text,
},
},
ignore: ['price'],
},
};
export const filterableTagName = 'filterable';
export const filterableMap: ElasticsearchFilterableMap = {
date: ElasticsearchDataType.keyword,
keyword: ElasticsearchDataType.keyword,
text: ElasticsearchDataType.keyword,
integer: ElasticsearchDataType.integer,
};

View File

@@ -1,69 +0,0 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ElasticsearchPremap} from '../mapping-definitions';
import {ElasticsearchDataType} from './typemap';
export const premaps: ElasticsearchPremap = {
CoordinateReferenceSystem: {
dynamic: true,
properties: {
type: {
type: ElasticsearchDataType.keyword,
},
},
},
LineString: {
precision: '1m',
tree: 'quadtree',
type: ElasticsearchDataType.geo_shape,
},
Point: {
properties: {
type: {
type: ElasticsearchDataType.keyword,
},
coordinates: {
type: ElasticsearchDataType.geo_point,
},
},
dynamic: 'strict',
},
Polygon: {
precision: '1m',
tree: 'quadtree',
type: ElasticsearchDataType.geo_shape,
},
SCISO8601DateRange: {
type: ElasticsearchDataType.date_range,
},
'jsonpatch.OpPatch': {
dynamic: 'strict',
properties: {
from: {
type: ElasticsearchDataType.keyword,
},
op: {
type: ElasticsearchDataType.keyword,
},
path: {
type: ElasticsearchDataType.keyword,
},
value: {
// this is actually an 'any' type, however ES does not really support that.
type: ElasticsearchDataType.keyword,
},
},
},
};

View File

@@ -1,65 +0,0 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ElasticsearchSettings} from '../mapping-definitions';
export const settings: ElasticsearchSettings = {
analysis: {
analyzer: {
ducet_sort: {
filter: [
'german_phonebook',
],
tokenizer: 'keyword',
type: 'custom',
},
search_german: {
filter: [
'lowercase',
'german_stop',
'german_stemmer',
],
tokenizer: 'stapps_ngram',
type: 'custom',
},
},
filter: {
german_phonebook: {
country: 'DE',
language: 'de',
type: 'icu_collation',
variant: '@collation=phonebook',
},
german_stemmer: {
language: 'german',
type: 'stemmer',
},
german_stop: {
stopwords: '_german_',
type: 'stop',
},
},
tokenizer: {
stapps_ngram: {
max_gram: 7,
min_gram: 4,
type: 'ngram',
},
},
},
'mapping.total_fields.limit': 10000,
max_result_window: 30000,
number_of_replicas: 0,
number_of_shards: 1,
};

View File

@@ -1,70 +0,0 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ElasticsearchTypemap} from '../mapping-definitions';
export enum ElasticsearchDataType {
missing_premap = 'MISSING_PREMAP',
parse_error = 'PARSE_ERROR',
type_conflict = 'TYPE_CONFLICT',
text = 'text',
keyword = 'keyword',
date = 'date',
// long = 'long',
// double = 'double',
float = 'float',
boolean = 'boolean',
ip = 'ip',
integer = 'integer',
object = 'object',
nested = 'nested',
geo_point = 'geo_point',
geo_shape = 'geo_shape',
completion = 'completion',
date_range = 'date_range',
// integer_range = 'integer_range',
// float_range = 'float_range',
// long_range = 'long_range',
// double_range = 'double_range',
// ip_range = 'ip_range',
}
export const typemap: ElasticsearchTypemap = {
boolean: {
default: ElasticsearchDataType.boolean,
},
false: {
default: ElasticsearchDataType.boolean,
},
number: {
default: ElasticsearchDataType.integer,
float: ElasticsearchDataType.float,
integer: ElasticsearchDataType.integer,
date: ElasticsearchDataType.date,
},
string: {
default: ElasticsearchDataType.text,
keyword: ElasticsearchDataType.keyword,
text: ElasticsearchDataType.text,
date: ElasticsearchDataType.date,
},
stringLiteral: {
default: ElasticsearchDataType.keyword,
},
true: {
default: ElasticsearchDataType.boolean,
},
};
export const dynamicTypes = ['any', 'unknown'];

View File

@@ -1,324 +0,0 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ElasticsearchDataType} from './definitions/typemap';
// tslint:disable:no-any
/**
* ElasticsearchValue can be either a type or an object.
*
* Both are composed similarly, and can be the value of a propery
* of an Elasticsearch Object.
*/
export type ElasticsearchValue = ElasticsearchType | ElasticsearchObject | ElasticsearchGeoShape;
/**
* The Typemap is used to get the corresponding ElasicsearchDataType for a name provided by the ProjectReflection
*/
export interface ElasticsearchTypemap {
/**
* The `stringLiteral` type must always be provided
*/
stringLiteral: {
/**
* The default can be chosen freely, but must be provided
*/
default: ElasticsearchDataType;
};
/**
* The name of the JS type, so for `number` it would be number
*/
[name: string]: {
/**
* The default ElasticsearchDataType that should be used, if no tag or only not implemented tags are found
*/
default: ElasticsearchDataType;
/**
* The name of the tag, so for `@integer` it would be `integer`
*/
[name: string]: ElasticsearchDataType;
};
}
/**
* The representation of a `DynamicTemplate` in Elasticsearch
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/dynamic-templates.html
*/
export interface ElasticsearchDynamicTemplate {
/**
* The name of the dynamicTemplate
*/
[name: string]: {
/**
* The mapping of the template
*/
mapping: ElasticsearchValue;
/**
* With automatic mapping, we use `path_match` more or less out of convenience and because it is least error-prone
*
* This also means that match should match all ("*") interface names (because we provide the exact path of the
* interface)
*/
match: '*';
/**
* With automatic mapping, we use `path_match` more or less out of convenience and because it is least error-prone
*
* This also means that match_mapping_type should match all ("*") names (because we provide the exact path of the
* interface)
*/
match_mapping_type: '*';
/**
* With automatic mapping, we use `path_match` more or less out of convenience and because it is least error-prone
*/
path_match: string;
};
}
export interface ElasticsearchFilterableMap {
[name: string]: ElasticsearchDataType;
}
/**
* The Fieldmap contains all tag names for fields and the corresponding fields
*
* The Fieldmap works in a similar fashion to the Typemap
*/
export interface ElasticsearchFieldmap {
/**
* The name of the tag, so for `@sortable` it would be `sortable`
*/
[name: string]: {
/**
* The default value if no parameter is provided
*/
default: {
/**
* To allow the usage of `prev.fields = {...prev.fields, ...fieldmap[tag.tagName].default}`
*
* We could also have used `default: any`, but this adds slightly more improved type-safety.
*/
[name: string]: any;
};
/**
* The tag parameters that will be ignored
*
* Some tag parameters might not be important for your implementation, so you can add their names here to not get
* any errors. The `default` will be used in that case.
*/
ignore: string[];
/**
* The parameters of the tag, so for `@sortable ducet` it would be `ducet`
*/
[name: string]: {
/**
* To allow the usage of `prev.fields = {...prev.fields, ...fieldmap[tag.tagName][tag.text.trim()]}`
*
* We could also have used `default: any`, but this adds slightly more improved type-safety.
*/
[name: string]: any;
};
};
}
/**
* A primitive data type
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/mapping-types.html
*/
export interface ElasticsearchType {
/**
* Fields for a type
*
* The fields are optional, they are used for things like sorting, which is not needed for every single type.
*/
fields?: {
[name: string]: any;
};
/**
* The type as an ElasticsearchDataType
*/
type: ElasticsearchDataType;
}
/**
* A GeoShape data type
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/geo-shape.html
*/
export interface ElasticsearchGeoShape {
/**
* Does not exist; here for TypeScript compiler
*/
fields?: undefined;
/**
* This parameter may be used instead of tree_levels to set an appropriate value for the tree_levels parameter.
*
* The value specifies the desired precision and Elasticsearch will calculate the best tree_levels value to honor
* this precision. The value should be a number followed by an optional distance unit. Valid distance units include:
* in, inch, yd, yard, mi, miles, km, kilometers, m,meters, cm,centimeters, mm, millimeters.
*/
precision: string;
/**
* Name of the PrefixTree implementation to be used: geohash for GeohashPrefixTree and quadtree for QuadPrefixTree.
*/
tree: 'quadtree' | 'geohash';
/**
* The type of the object, obviously geo_shape
*/
type: ElasticsearchDataType.geo_shape;
}
/**
* An object data type
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/object.html
*/
export interface ElasticsearchObject {
/**
* Only for the top type
*/
_source?: {
/**
* Fields that should be excluded in the _source field
*/
excludes: [
'creation_date'
];
};
/**
* Whether the creation date should be set automatically
*/
date_detection?: boolean;
/**
* If the object is a dynamic
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/dynamic.html
* The default should be `'strict'`
*/
dynamic: true | false | 'strict';
/**
* dynamic_templates for an object
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/dynamic-templates.html
* This is a more complex topic, before touching this you should really know what you are doing.
*/
dynamic_templates?: ElasticsearchDynamicTemplate[];
/**
* Fields for a type
*
* The fields are optional, they are used for things like sorting, which is not needed for every single type.
*/
fields?: {
[name: string]: any;
};
/**
* Any properties of the object
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/properties.html
*/
properties: {
/**
* Each property can be any Elasticsearch value
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/mapping-types.html
*/
[name: string]: ElasticsearchValue;
};
}
/**
* A collection of Elasticsearch Templates
*/
export interface ElasticsearchTemplateCollection {
[indexName: string]: ElasticsearchTemplate;
}
/**
* An Elasticsearch template
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/mapping.html
* This is what you pass to Elasticsearch
*/
export interface ElasticsearchTemplate {
/**
* This is a pre-defined structure you should use for your mapping
*/
mappings: {
[typeName: string]: ElasticsearchObject;
};
/**
* The settings for Elasticsearch
*/
settings: ElasticsearchSettings;
/**
* The name of the template, for referencing in Elasticsearch
*/
template: string;
}
/**
* A representation of ElasticsearchSettings used in Mappings
*/
export interface ElasticsearchSettings {
/**
* The settings
*/
[name: string]: any;
/**
* This is where any analyzers go
*
* https://www.elastic.co/guide/en/elasticsearch/reference/5.6/analysis-analyzers.html
*/
analysis: {
[name: string]: any;
};
}
/**
* A premap for a specific value in a ProjectReflection
*
* This is meant to be used for external types. To aid performance, you usually should not include external libs in the
* ProjectReflection. This means that there is no way the generator can generate a mapping for it, so you can use the
* premaps to map out a type manually.
*/
export interface ElasticsearchPremap {
/**
* The name of the type with the corresponding map
*
* So for `const a: B` the name would be `B`
*/
[name: string]: ElasticsearchValue;
}

View File

@@ -1,5 +1,6 @@
/* eslint-disable unicorn/error-message */
/*
* Copyright (C) 2019 StApps
* 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.
@@ -15,37 +16,13 @@
import {Logger} from '@openstapps/logger';
import del from 'del';
import {existsSync} from 'fs';
import {basename, dirname, join} from 'path';
import {cwd} from 'process';
import {globPromisified, readFilePromisified, unlinkPromisified, writeFilePromisified} from './common';
import {JavaScriptModule} from './types/pack';
import path from 'path';
const PACK_IDENTIFIER = '/* PACKED BY @openstapps/pack */';
/**
* A JavaScript module representation to sort a list of them by dependencies
*/
interface JavaScriptModule {
/**
* Content of the module
*/
content: string;
/**
* List of names of dependencies
*/
dependencies: string[];
/**
* Directory the module is in
*/
directory: string;
/**
* The name of the module
*/
name: string;
}
/**
* Pack cli.js
*
@@ -55,32 +32,30 @@ interface JavaScriptModule {
* Furthermore it checks that no shebang line is present and that it does not export anything.
*/
async function packCliJs(): Promise<void> {
const path = join(cwd(), 'lib', 'cli.js');
const cliPath = path.join(cwd(), 'lib', 'cli.js');
if (!existsSync(path)) {
if (!existsSync(cliPath)) {
return;
}
Logger.info('Adjusting JavaScript CLI...');
const buffer = await readFilePromisified(path);
const buffer = await readFilePromisified(cliPath);
const content = buffer.toString();
if (content.indexOf('#!/') === 0) {
throw new Error('`cli.js` must not contain a shebang line! It is added by this script.');
}
let internalRequire: string | null = null;
let internalRequire: string | undefined;
const adjustedContent = content
.split('\n')
.map((line, lineNumber) => {
// check for exports (cli.js is not allowed to export anything)
if (Array.isArray(line.match(/^\s*((exports)|(module\.exports))/))) {
throw new Error(
`Line '${lineNumber}' in 'cli.js' exports something. cli.js is not for exporting. Line was:
${line}`,
throw new TypeError(
`Line '${lineNumber}' in 'cli.js' exports something. cli.js is not for exporting. Line was:\n${line}`,
);
}
@@ -89,16 +64,14 @@ ${line}`,
const match = line.match(/^(\s*)(const|var) ([a-z0-9_]*) = require\(("[^"]+"|'[^']+')\);$/i);
if (match !== null) {
// tslint:disable-next-line:no-magic-numbers
const importedName = match[3];
// tslint:disable-next-line:no-magic-numbers
// eslint-disable-next-line unicorn/prefer-string-slice
const moduleName = match[4].substring(1, match[4].length - 1);
// if it begins with '.' and not ends with json
if (/^[.]{1,2}\/(?!.*\.json$).*$/i.test(moduleName)) {
// is the first internal require
if (internalRequire !== null) {
if (internalRequire) {
return `const ${importedName} = ${internalRequire};`;
}
@@ -113,20 +86,18 @@ ${line}`,
})
.join('\n');
return writeFilePromisified(path, `#!/usr/bin/env node
${adjustedContent}`);
return writeFilePromisified(cliPath, `#!/usr/bin/env node\n\n${adjustedContent}`);
}
/**
* Get a list containing the contents of all type definition files
*/
async function getAllTypeDefinitions(): Promise<string[]> {
const fileNames = await globPromisified(join(cwd(), '*(lib|src)', '**', '*.d.ts'), {
const fileNames = await globPromisified(path.join(cwd(), '*(lib|src)', '**', '*.d.ts'), {
ignore: [
join(cwd(), 'lib', 'doc', '**', '*.d.ts'),
join(cwd(), 'lib', 'test', '**', '*.d.ts'),
join(cwd(), 'lib', 'cli.d.ts'),
path.join(cwd(), 'lib', 'doc', '**', '*.d.ts'),
path.join(cwd(), 'lib', 'test', '**', '*.d.ts'),
path.join(cwd(), 'lib', 'cli.d.ts'),
],
});
@@ -143,24 +114,24 @@ async function getAllTypeDefinitions(): Promise<string[]> {
async function packTypeDefinitions(): Promise<void> {
Logger.info('Packing TypeScript definition files...');
const path = join(cwd(), 'lib', 'index.d.ts');
const indexPath = path.join(cwd(), 'lib', 'index.d.ts');
await deleteFileIfExistingAndPacked(path);
await deleteFileIfExistingAndPacked(indexPath);
const typeDefinitions = await getAllTypeDefinitions();
// pack TypeScript definition files
const imports: { [k: string]: string[]; } = {};
const imports: {[k: string]: string[]} = {};
const referenceLines: string[] = [];
let allDefinitions = typeDefinitions
// concat them separated by new lines
// concat them separated by new lines
.join('\n\n\n\n\n')
// split all lines
.split('\n')
.map((line) => {
if (line.indexOf('export =') !== -1) {
.map(line => {
if (line.includes('export =')) {
throw new Error('`export =` is not allowed by pack. Use named imports instead.');
}
@@ -174,15 +145,12 @@ async function packTypeDefinitions(): Promise<void> {
const match = line.match(/^import {([^}].*)} from '([^'].*)';$/);
if (match !== null) {
// tslint:disable-next-line:no-magic-numbers - extract module
const module = match[2];
// extract imported objects
const importedObjects = match[1]
.split(',')
.map((object) => {
return object.trim();
});
const importedObjects = match[1].split(',').map(object => {
return object.trim();
});
// add list of already imported objects for module
if (typeof imports[module] === 'undefined') {
@@ -191,12 +159,12 @@ async function packTypeDefinitions(): Promise<void> {
// count already imported objects and objects to import now
const objectsToImport: string[] = [];
importedObjects.forEach((object) => {
if (imports[module].indexOf(object) === -1) {
for (const object of importedObjects) {
if (!imports[module].includes(object)) {
imports[module].push(object);
objectsToImport.push(object);
}
});
}
// replace import line
if (objectsToImport.length === 0) {
@@ -209,7 +177,7 @@ async function packTypeDefinitions(): Promise<void> {
return line;
})
// filter lines which contain "local" imports
.filter((line) => {
.filter(line => {
return line.match(/^import .* from '\./) === null;
})
// concat all lines separated by new lines
@@ -223,9 +191,12 @@ ${allDefinitions}`;
}
// write packed TypeScript definition files
return writeFilePromisified(path, `${PACK_IDENTIFIER}
return writeFilePromisified(
indexPath,
`${PACK_IDENTIFIER}
${allDefinitions}`);
${allDefinitions}`,
);
}
}
@@ -233,18 +204,17 @@ ${allDefinitions}`);
* Get all JavaScript modules
*/
async function getAllJavaScriptModules(): Promise<JavaScriptModule[]> {
const fileNames = await globPromisified(join(cwd(), 'lib', '**', '*.js'), {
const fileNames = await globPromisified(path.join(cwd(), 'lib', '**', '*.js'), {
ignore: [
join(cwd(), 'lib', 'doc', '**', '*.js'),
join(cwd(), 'lib', 'test', '*.js'),
join(cwd(), 'lib', 'cli.js'),
path.join(cwd(), 'lib', 'doc', '**', '*.js'),
path.join(cwd(), 'lib', 'test', '*.js'),
path.join(cwd(), 'lib', 'cli.js'),
],
});
const promises = fileNames.map(async (fileName: string) => {
const fileContent = await readFilePromisified(fileName, 'utf8');
const directory = dirname(fileName)
.replace(new RegExp(`^${join(cwd(), 'lib')}`), '');
const directory = path.dirname(fileName).replace(new RegExp(`^${path.join(cwd(), 'lib')}`), '');
return {
content: `(function() {
@@ -253,7 +223,7 @@ ${fileContent}
`,
dependencies: getAllInternalDependencies(fileContent),
directory: directory,
name: basename(fileName, '.js'),
name: path.basename(fileName, '.js'),
};
});
@@ -264,37 +234,33 @@ ${fileContent}
* Pack all javascript files
*/
async function packJavaScriptFiles(): Promise<void> {
const path = join(cwd(), 'lib', 'index.js');
const indexPath = path.join(cwd(), 'lib', 'index.js');
Logger.info('Packing JavaScript files...');
await deleteFileIfExistingAndPacked(path);
await deleteFileIfExistingAndPacked(indexPath);
// topologically sort the modules (sort by dependencies)
const jsModules = topologicalSort(await getAllJavaScriptModules());
let wholeCode = jsModules
// convert modules to strings
.map((module) => {
// convert modules to strings
.map(module => {
module.content = module.content
.split('\n')
.map((line) => {
.map(line => {
const match = line.match(
/^(\s*)(const|var) ([a-z0-9_]*) = ((require\("([^"]+)"\))|(require\('([^']+)'\)));$/i,
);
// replace lines with internal requires
if (match !== null) {
// tslint:disable-next-line:no-magic-numbers - match[6] or match[8] contain the modulePath
if (typeof match[6] === 'undefined') {
// tslint:disable-next-line:no-magic-numbers
match[6] = match[8];
}
const whiteSpace = (typeof match[1] === 'string' && match[1].length > 0) ? match[1] : '';
// tslint:disable-next-line:no-magic-numbers
const whiteSpace = typeof match[1] === 'string' && match[1].length > 0 ? match[1] : '';
const importedName = match[3];
// tslint:disable-next-line:no-magic-numbers
const modulePath = match[6];
// leave line unchanged if it is a "global" import
@@ -303,11 +269,11 @@ async function packJavaScriptFiles(): Promise<void> {
}
// replace internal requires with `module.exports`
if (existsSync(join(cwd(), 'lib', module.directory, `${modulePath}.js`))) {
if (existsSync(path.join(cwd(), 'lib', module.directory, `${modulePath}.js`))) {
return `${whiteSpace}const ${importedName} = module.exports;`;
}
if (existsSync(join(cwd(), 'src', module.directory, modulePath))) {
if (existsSync(path.join(cwd(), 'src', module.directory, modulePath))) {
return `${whiteSpace} const ${importedName} = require(../src/${modulePath});`;
}
@@ -326,7 +292,7 @@ ${module.content}`;
// split all lines
.split('\n')
// filter lines
.filter((line) => {
.filter(line => {
// remove strict usage
if (line === '"use strict";') {
return false;
@@ -356,9 +322,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
${wholeCode}`;
// write packed JavaScript files
return writeFilePromisified(path, `${PACK_IDENTIFIER}
return writeFilePromisified(
indexPath,
`${PACK_IDENTIFIER}
${wholeCode}`);
${wholeCode}`,
);
}
}
@@ -378,8 +347,8 @@ async function deleteFileIfExistingAndPacked(path: string): Promise<void> {
return unlinkPromisified(path);
}
} catch (err) {
if (err.code === 'ENOENT') {
} catch (error) {
if (error.code === 'ENOENT') {
return;
}
}
@@ -392,12 +361,13 @@ async function deleteFileIfExistingAndPacked(path: string): Promise<void> {
*/
function getAllInternalDependencies(moduleContent: string): string[] {
// match all const <name> = require(<moduleName>);
const requireLines =
moduleContent.match(/^\s*(const|var) [a-z0-9_]* = require\("([^"]+)"\)|require\('([^']+)'\);$/gmi);
const requireLines = moduleContent.match(
/^\s*(const|var) [a-z0-9_]* = require\("([^"]+)"\)|require\('([^']+)'\);$/gim,
);
if (Array.isArray(requireLines)) {
return requireLines
.map((requireLine) => {
.map(requireLine => {
const matches = requireLine.match(/require\("([^"]+)"\)|require\('([^']+)'\);$/i);
// previously matched require line does not contain a require?!
@@ -408,13 +378,13 @@ function getAllInternalDependencies(moduleContent: string): string[] {
// return only the moduleName
return matches[1];
})
.filter((moduleName) => {
.filter(moduleName => {
// filter out internal modules beginning with './' and not ending with '.json'
return /^[.]{1,2}\/(?!.*\.json$).*$/i.test(moduleName);
})
.map((internalModuleName) => {
.map(internalModuleName => {
// cut './' from the name
return internalModuleName.substring('./'.length);
return internalModuleName.slice('./'.length);
});
}
@@ -427,6 +397,7 @@ function getAllInternalDependencies(moduleContent: string): string[] {
* @param modules Modules to sort
*/
function topologicalSort(modules: JavaScriptModule[]): JavaScriptModule[] {
// eslint-disable-next-line unicorn/prefer-module,@typescript-eslint/no-var-requires
const topoSort = require('toposort');
// vertices are modules, an edge from a to b means that b depends on a
@@ -434,23 +405,21 @@ function topologicalSort(modules: JavaScriptModule[]): JavaScriptModule[] {
const nodes: string[] = [];
// add all edges
modules.forEach((module) => {
module.dependencies.forEach((dependencyPath) => {
for (const module of modules) {
for (const dependencyPath of module.dependencies) {
// add edge from dependency to our module
edges.push([basename(dependencyPath), module.name]);
});
edges.push([path.basename(dependencyPath), module.name]);
}
nodes.push(module.name);
});
}
// sort graph and return as an array of sorted modules
return topoSort
.array(nodes, edges)
.map((moduleName: string) => {
return modules.find((module) => {
return module.name === moduleName;
});
return topoSort.array(nodes, edges).map((moduleName: string) => {
return modules.find(module => {
return module.name === moduleName;
});
});
}
/**
@@ -460,11 +429,7 @@ export async function pack() {
Logger.log(`Packing project in ${process.cwd()}...`);
// run all tasks in parallel
const promises: Array<Promise<void>> = [
packCliJs(),
packTypeDefinitions(),
packJavaScriptFiles(),
];
const promises: Array<Promise<void>> = [packCliJs(), packTypeDefinitions(), packJavaScriptFiles()];
await Promise.all(promises);
@@ -476,18 +441,24 @@ export async function pack() {
'lib/*',
// keep packed files
'!lib/index.d.ts', '!lib/index.js',
'!lib/index.d.ts',
'!lib/index.js',
// keep converted schema files
'!lib/schema', '!lib/schema/*.json',
'!lib/schema',
'!lib/schema/*.json',
// keep documentation
'!lib/doc', '!lib/doc/*', '!lib/doc/**/*',
'!lib/doc',
'!lib/doc/*',
'!lib/doc/**/*',
// keep cli
'!lib/cli.js',
// keep tests
'!lib/test', '!lib/test/*', '!lib/test/**/*',
'!lib/test',
'!lib/test/*',
'!lib/test/**/*',
]);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 StApps
* 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.
@@ -16,14 +16,14 @@
/**
* This is a simple interface declaration for
* testing the schema generation and validation.
*
*
* @validatable
*/
export interface Foo {
/**
* Dummy parameter
*/
lorem: 'ipsum';
lorem: 'lorem' | 'ipsum';
/**
* String literal type property
@@ -33,6 +33,6 @@ export interface Foo {
/**
* This is a simple type declaration for
* usage in the Foo interace.
* usage in the Foo interface.
*/
export type FooType = 'Foo';

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018-2019 StApps
* Copyright (C) 2018-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.
@@ -12,182 +12,139 @@
* 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 {asyncPool} from '@krlwlfrt/async-pool/lib/async-pool';
import {Logger} from '@openstapps/logger';
import {assign, filter, map} from 'lodash';
import {OpenAPIV3} from 'openapi-types';
import {basename, dirname, join} from 'path';
import {ProjectReflection} from 'typedoc';
import {Type} from 'typedoc/dist/lib/models';
import {capitalize, NodeWithMetaInformation, RouteWithMetaInformation} from './common';
import {isLightweightClass} from './easy-ast/ast-util';
import {LightweightProjectWithIndex} from './easy-ast/types/lightweight-project';
import {RouteInstanceWithMeta, RouteWithMetaInformation} from './types/routes';
import {rejectNil} from './util/collections';
import {capitalize} from './util/string';
import path from 'path';
import {lightweightProjectFromPath} from './easy-ast/easy-ast';
/**
* Gather relevant information of routes
*
* This gathers the information for all routes that implement the abstract class SCAbstractRoute.
* Furthermore it instantiates every route and adds it to the information.
*
* @param reflection Contents of the JSON representation which Typedoc generates
*/
export async function gatherRouteInformation(reflection: ProjectReflection): Promise<RouteWithMetaInformation[]> {
const routes: RouteWithMetaInformation[] = [];
export async function gatherRouteInformation(path: string): Promise<RouteWithMetaInformation[]> {
const project = new LightweightProjectWithIndex(lightweightProjectFromPath(path));
if (!Array.isArray(reflection.children)) {
throw new Error('Project reflection doesn\'t contain any modules.');
}
// tslint:disable-next-line:no-magic-numbers
await asyncPool(2, reflection.children, async (module) => {
if (Array.isArray(module.children) && module.children.length > 0) {
// tslint:disable-next-line:no-magic-numbers
await asyncPool(2, module.children, (async (node) => {
if (Array.isArray(node.extendedTypes) && node.extendedTypes.length > 0) {
if (node.extendedTypes.some((extendedType) => {
// tslint:disable-next-line:completed-docs
return (extendedType as (Type & { name: string; })).name === 'SCAbstractRoute';
})) {
Logger.info(`Found ${node.name} in ${module.originalName}.`);
if (Array.isArray(module.originalName.match(/\.d\.ts$/))) {
module.originalName = join(dirname(module.originalName), basename(module.originalName, '.d.ts'));
Logger.info(`Using compiled version of module in ${module.originalName}.`);
}
const importedModule = await import(module.originalName);
const route = new importedModule[node.name]();
// tslint:disable-next-line: no-any
const errors = route.errorNames.map((error: any) => {
const scError = new importedModule[error.name]();
scError.name = error.name;
return scError;
});
route.responseBodyDescription = module.children!.find(element => element.name === route.responseBodyName)?.comment?.shortText;
route.requestBodyDescription = module.children!.find(element => element.name === route.requestBodyName)?.comment?.shortText;
route.errors = errors;
routes.push({description: node.comment!, name: node.name, route});
}
// find all classes that implement the SCAbstractRoute
return rejectNil(
await Promise.all(
map(filter(project.definitions, isLightweightClass), async node => {
if (!node.extendedDefinitions?.some(it => it.referenceName === 'SCAbstractRoute')) {
return undefined;
}
}));
}
});
if (routes.length === 0) {
throw new Error('No route information found.');
}
const instantiatedRoute = (await project.instantiateDefinitionByName(
node.name,
)) as RouteInstanceWithMeta;
// instantiate all errors
instantiatedRoute.errors = await Promise.all(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
instantiatedRoute.errorNames.map(async (error: any) =>
// eslint-disable-next-line @typescript-eslint/ban-types
assign((await project.instantiateDefinitionByName(error.name)) as object, {name: error.name}),
),
);
instantiatedRoute.responseBodyDescription =
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
project.definitions[instantiatedRoute.responseBodyName]?.comment?.shortSummary!;
instantiatedRoute.requestBodyDescription =
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
project.definitions[instantiatedRoute.requestBodyName]?.comment?.shortSummary!;
return routes;
}
/**
* Get link for a node
*
* @param name Name of the node
* @param node Node itself
*/
export function getLinkForNode(name: string, node: NodeWithMetaInformation): string {
let link = 'https://openstapps.gitlab.io/core/';
const module = node.module
.toLowerCase()
.split('/')
.join('_');
if (node.type === 'Type alias') {
link += 'modules/';
link += `_${module}_`;
link += `.html#${name.toLowerCase()}`;
return link;
}
let type = 'classes';
if (node.type !== 'Class') {
type = `${node.type.toLowerCase()}s`;
}
link += `${type}/`;
link += `_${module}_`;
link += `.${name.toLowerCase()}.html`;
return link;
return {
description: {
shortText: node.comment?.shortSummary,
text: node.comment?.description,
},
name: node.name!,
route: instantiatedRoute,
};
}),
),
);
}
/**
* Generate documentation snippet for one route
*
* @param routeWithInfo A route instance with its meta information
* @param outDirSchemasPath Path to directory that will contain relevant schemas for the route
* @param outDirectorySchemasPath Path to directory that will contain relevant schemas for the route
* @param schemasToCopy Schemas identified as relevant for this route
* @param tagsToKeep Tags / keywords that can be used for grouping routes
*/
export function generateOpenAPIForRoute(routeWithInfo: RouteWithMetaInformation,
outDirSchemasPath: string,
schemasToCopy: string[],
tagsToKeep: string[]): OpenAPIV3.PathItemObject {
export function generateOpenAPIForRoute(
routeWithInfo: RouteWithMetaInformation,
outDirectorySchemasPath: string,
schemasToCopy: string[],
tagsToKeep: string[],
): OpenAPIV3.PathItemObject {
const route = routeWithInfo.route;
const path: OpenAPIV3.PathItemObject = {};
const openapiPath: OpenAPIV3.PathItemObject = {};
schemasToCopy.push(route.requestBodyName, route.responseBodyName);
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)] = {
summary: capitalize(routeWithInfo.description.shortText?.replace(/(Route to |Route for )/gmi, '')),
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods] = {
summary: capitalize(routeWithInfo.description.shortText?.replace(/(Route to |Route for )/gim, '')),
description: routeWithInfo.description.text,
requestBody: {
description: route.responseBodyDescription ?? undefined,
content: {
'application/json': {
schema: {
$ref: join(outDirSchemasPath, `${route.requestBodyName}.json`),
$ref: path.join(outDirectorySchemasPath, `${route.requestBodyName}.json`),
},
},
},
},
parameters: [{
name: 'X-StApps-Version',
in: 'header',
schema: {
type: 'string',
example: '2.0.0',
parameters: [
{
name: 'X-StApps-Version',
in: 'header',
schema: {
type: 'string',
example: '2.0.0',
},
required: true,
},
required: true,
}],
],
responses: {},
tags: routeWithInfo.tags?.filter(value => tagsToKeep.includes(value)),
};
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)]!.responses![route.statusCodeSuccess] = {
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]!.responses![route.statusCodeSuccess] = {
description: route.responseBodyDescription,
content: {
'application/json': {
schema: {
$ref: join(outDirSchemasPath, `${route.responseBodyName}.json`),
$ref: path.join(outDirectorySchemasPath, `${route.responseBodyName}.json`),
},
},
},
};
route.errors.forEach(error => {
for (const error of route.errors) {
schemasToCopy.push(error.name);
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)]!.responses![error.statusCode] = {
description: error.message ?? capitalize(error.name.replace(/([A-Z][a-z])/g,' $1')
.replace('SC ', '')),
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]!.responses![error.statusCode] = {
description: error.message ?? capitalize(error.name.replace(/([A-Z][a-z])/g, ' $1').replace('SC ', '')),
content: {
'application/json': {
schema: {
$ref: join(outDirSchemasPath, `${error.name}.json`),
$ref: path.join(outDirectorySchemasPath, `${error.name}.json`),
},
},
},
};
});
}
if (typeof route.obligatoryParameters === 'object') {
for (const [parameter, schemaDefinition] of Object.entries(route.obligatoryParameters)) {
const openapiParam: OpenAPIV3.ParameterObject = {
const openapiParameter: OpenAPIV3.ParameterObject = {
in: 'path',
name: parameter,
required: true,
@@ -196,9 +153,9 @@ export function getLinkForNode(name: string, node: NodeWithMetaInformation): str
$ref: `schemas/SCSearchResponse.json#/definitions/${schemaDefinition}`,
},
};
path[(route.method.toLowerCase() as OpenAPIV3.HttpMethods)]?.parameters?.push(openapiParam);
openapiPath[route.method.toLowerCase() as OpenAPIV3.HttpMethods]?.parameters?.push(openapiParameter);
}
}
return path;
return openapiPath;
}

View File

@@ -14,13 +14,16 @@
*/
import Ajv from 'ajv';
import {JSONSchema7 as JSONSchema} from 'json-schema';
import {join} from 'path';
import {filter} from 'lodash';
import {Config, DEFAULT_CONFIG, Definition, SchemaGenerator} from 'ts-json-schema-generator';
import {createFormatter} from 'ts-json-schema-generator/dist/factory/formatter';
import {createParser} from 'ts-json-schema-generator/dist/factory/parser';
import {createProgram} from 'ts-json-schema-generator/dist/factory/program';
import {ProjectReflection} from 'typedoc';
import {getTsconfigPath, isSchemaWithDefinitions} from './common';
import {getTsconfigPath} from './common';
import {definitionsOf, isLightweightClass} from './easy-ast/ast-util';
import {lightweightProjectFromPath} from './easy-ast/easy-ast';
import {isSchemaWithDefinitions} from './util/guards';
import path from 'path';
/**
* StAppsCore converter
@@ -41,15 +44,15 @@ export class Converter {
/**
* Create a new converter
*
* @param path Path to the project
* @param projectPath Path to the project
*/
constructor(path: string) {
constructor(projectPath: string) {
// set config for schema generator
const config: Config = {
...DEFAULT_CONFIG,
sortProps: true,
topRef: false,
tsconfig: join(getTsconfigPath(path), 'tsconfig.json'),
tsconfig: path.join(getTsconfigPath(projectPath), 'tsconfig.json'),
type: 'SC',
};
@@ -57,14 +60,11 @@ export class Converter {
const program = createProgram(config);
// create generator
this.generator = new SchemaGenerator(
program,
createParser(program, config),
createFormatter(config),
);
this.generator = new SchemaGenerator(program, createParser(program, config), createFormatter(config));
// create Ajv instance
this.schemaValidator = new Ajv();
// eslint-disable-next-line @typescript-eslint/no-var-requires,unicorn/prefer-module
this.schemaValidator.addMetaSchema(require('ajv/lib/refs/json-schema-draft-06.json'));
}
@@ -93,9 +93,9 @@ export class Converter {
delete selfReference.$id;
// add self reference to definitions
schema.definitions[`SC${type}`] = {
schema.definitions![`SC${type}`] = {
...{},
...selfReference as unknown as Definition,
...(selfReference as unknown as Definition),
};
}
@@ -108,32 +108,10 @@ export class Converter {
}
/**
* Get a list of validatable types from a reflection
*
* @param projectReflection Reflection to get validatable types from
* Get a list of validatable types from an API extractor file
*/
export function getValidatableTypesFromReflection(projectReflection: ProjectReflection): string[] {
const validatableTypes: string[] = [];
if (typeof projectReflection.children === 'undefined') {
throw new Error('Project reflection doesn\'t contain any modules.');
}
// iterate over modules
projectReflection.children.forEach((module) => {
if (Array.isArray(module.children) && module.children.length > 0) {
// iterate over types
module.children.forEach((type) => {
// check if type has annotation @validatable
if (typeof type.comment === 'object'
&& Array.isArray(type.comment.tags)
&& type.comment.tags.findIndex((tag) => tag.tagName === 'validatable') >= 0) {
// add type to list
validatableTypes.push(type.name);
}
});
}
});
return validatableTypes;
export function getValidatableTypesInPath(path: string): string[] {
return filter(definitionsOf(lightweightProjectFromPath(path)), isLightweightClass)
.filter(type => type.comment?.tags?.find(it => it.name === 'validatable'))
.map(type => type.name);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 StApps
* Copyright (C) 2018-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.
@@ -13,24 +13,27 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
/**
* @indexable
* A JavaScript module representation to sort a list of them by dependencies
*/
export interface AggGlobal {
export interface JavaScriptModule {
/**
* @aggregatable global
* Content of the module
*/
foo: string;
content: string;
type: ThingType.AggGlobal;
/**
* List of names of dependencies
*/
dependencies: string[];
/**
* Directory the module is in
*/
directory: string;
/**
* The name of the module
*/
name: string;
}
export const aggGlobalTest: MapAggTestOptions = {
name: ThingType.AggGlobal,
agg: {
globals: ['foo'],
},
};

97
src/types/routes.d.ts vendored Normal file
View File

@@ -0,0 +1,97 @@
/*
* Copyright (C) 2018-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/>.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export type SCRoute = any;
export interface RouteInstanceWithMeta extends SCRoute {
/**
* Possible errors on a route
*/
errors: SCErrorResponse[];
/**
* Description of the request body
*/
requestBodyDescription: string;
/**
* Description of the response body
*/
responseBodyDescription: string;
}
/**
* A route instance with its relevant meta information
*/
export interface RouteWithMetaInformation {
/**
* Description of the route
*/
description: {
/**
* Short text of the description - title
*/
shortText?: string;
/**
* Text of the description
*/
text?: string;
};
/**
* Name of the route
*/
name: string;
/**
* Instance of the route
*/
route: RouteInstanceWithMeta;
/**
* Possible tags/keywords the route can be associated with
*/
tags?: [string];
}
/**
* A node with its relevant meta information
*/
export interface NodeWithMetaInformation {
/**
* Module the node belongs to
*/
module: string;
/**
* Type of the node
*/
type: string;
}
/**
* A generic error that can be returned by the backend if somethings fails during the processing of a request
*/
export interface SCErrorResponse extends Error {
/**
* Additional data that describes the error
*/
additionalData?: unknown;
/**
* HTTP status code to return this error with
*/
statusCode: number;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 StApps
* Copyright (C) 2018-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.
@@ -12,19 +12,15 @@
* 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 {JSONSchema7 as JSONSchema} from 'json-schema';
import {Definition} from 'ts-json-schema-generator';
import {LightweightDefinition} from './lightweight-definition';
/**
* Represents an enum definition
* A schema with definitions
*/
export class LightweightEnumDefinition extends LightweightDefinition {
interface SchemaWithDefinitions extends JSONSchema {
/**
* Enumeration or union values
* Definitions of the schema
*/
public values: string[];
constructor(name: string) {
super(name);
this.values = [];
}
definitions?: {[name: string]: Definition};
}

87
src/types/validator.d.ts vendored Normal file
View File

@@ -0,0 +1,87 @@
/*
* Copyright (C) 2018-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/>.
*/
/**
* The validation result
*/
export interface ValidationResult {
/**
* A list of errors that occurred
*/
errors: ValidationError[];
/**
* whether the validation was successful
*/
valid: boolean;
}
/**
* An error that occurred while validating
*
* This is a duplicate of the ValidationError in core/protocol/errors/validation because of incompatibilities
* between TypeDoc and TypeScript
*/
export interface ValidationError {
/**
* JSON schema path
*/
dataPath: string;
/**
* The instance
*/
instance: unknown;
/**
* The message
*
* Provided by https://www.npmjs.com/package/better-ajv-errors
*/
message: string;
/**
* Name of the error
*/
name: string;
/**
* Path within the Schema
*/
schemaPath: string;
/**
* Suggestion to fix the occurring error
*
* Provided by https://www.npmjs.com/package/better-ajv-errors
*/
suggestion?: string;
}
/**
* An expected error
*/
export interface ExpectedValidationError extends ValidationError {
/**
* Whether or not the error is expected
*/
expected: boolean;
}
/**
* A map of files and their expected validation errors
*/
export interface ExpectedValidationErrors {
[fileName: string]: ExpectedValidationError[];
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 StApps
* 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.
@@ -15,12 +15,13 @@
import {Logger} from '@openstapps/logger';
import {createWriteStream} from 'fs';
import * as request from 'got';
import {getFullTypeName} from '../common';
import {LightweightClassDefinition} from './model/lightweight-class-definition';
import {LightweightDefinition} from './model/lightweight-definition';
import {LightweightEnumDefinition} from './model/lightweight-enum-definition';
import {LightweightProperty} from './model/lightweight-property';
import {LightweightType} from './model/lightweight-type';
import {forEach, map, isEmpty} from 'lodash';
import {expandTypeValue, isLightweightClass, isUnionOrIntersectionType} from '../easy-ast/ast-util';
import {LightweightAliasDefinition} from '../easy-ast/types/lightweight-alias-definition';
import {LightweightClassDefinition} from '../easy-ast/types/lightweight-class-definition';
import {LightweightDefinition} from '../easy-ast/types/lightweight-definition';
import {LightweightProperty} from '../easy-ast/types/lightweight-property';
import {LightweightType} from '../easy-ast/types/lightweight-type';
import {UMLConfig} from './uml-config';
/**
@@ -28,7 +29,7 @@ import {UMLConfig} from './uml-config';
* to valid PlantUML Code, which will then be encoded, converted by the plantuml server
* and saved as a .svg file in directory, in which this method was called
*
* @param definitions all type definitons of the project
* @param definitions all type definitions of the project
* @param config contains information on how the PlantUML should be generated
* @param plantUmlBaseURL Hostname of the PlantUML-Server
*/
@@ -37,49 +38,29 @@ export async function createDiagram(
config: UMLConfig,
plantUmlBaseURL: string,
): Promise<string> {
// when non definitions were specified use all
if (config.definitions.length === 0) {
config.definitions = [];
definitions.forEach((definition) => {
config.definitions.push(definition.name);
});
}
config.definitions = map(definitions, 'name');
// when providing definitions and either showing associations or inheritance the
// inherited definitions will be added automatically
if (config.showInheritance) {
const inheritedDefinitions = gatherTypeAssociations(
// TODO: showInheritance
/*const inheritedDefinitions = gatherTypeAssociations(
definitions,
config.definitions,
);
config.definitions = config.definitions.concat(inheritedDefinitions);
);*/
// config.definitions = config.definitions.concat(inheritedDefinitions);
}
let modelPlantUMLCode = '';
// creates a UML definition for every specified definition name
// however if no definitions were provided all definitions will be transformed
for (const definition of definitions) {
if (
config.definitions.length > 0 &&
!config.definitions.includes(definition.name)
) {
// current definition not specified
continue;
}
// either the definitions are empty or the definition was specified, proceed
let definitionPlantUMLCode = '';
if (definition instanceof LightweightClassDefinition) {
definitionPlantUMLCode = createPlantUMLCodeForClass(config, definition);
} else if (definition instanceof LightweightEnumDefinition) {
definitionPlantUMLCode = createPlantUMLCodeForEnum(config, definition);
} else {
continue;
}
modelPlantUMLCode += definitionPlantUMLCode;
}
const modelPlantUMLCode = map(
definitions.filter(it => !config.definitions.includes(it.name)),
definition =>
isLightweightClass(definition)
? createPlantUMLCodeForClass(config, definition)
: createPlantUMLCodeForEnum(config, definition),
).join('');
return createDiagramFromString(modelPlantUMLCode, plantUmlBaseURL, config.outputFileName);
}
@@ -97,6 +78,7 @@ export async function createDiagramFromString(
plantUmlBaseURL: string,
outputFile = `Diagram-${new Date().toISOString()}`,
) {
// eslint-disable-next-line @typescript-eslint/no-var-requires,unicorn/prefer-module
const plantumlEncoder = require('plantuml-encoder');
const plantUMLCode = plantumlEncoder.encode(`@startuml\n${modelPlantUMLCode}\n@enduml`);
const url = `${plantUmlBaseURL}/svg/${plantUMLCode}`;
@@ -108,17 +90,18 @@ export async function createDiagramFromString(
await Logger.error(`Plantuml Server responded with an error.\n${response.statusMessage}`);
throw new Error('Response not okay');
}
} catch (e) {
Logger.log(`Please try using the public plantuml server:\nhttp://www.plantuml.com/plantuml/svg/${plantUMLCode}`);
throw e;
} catch (error) {
Logger.log(
`Please try using the public plantuml server:\nhttp://www.plantuml.com/plantuml/svg/${plantUMLCode}`,
);
throw error;
}
// attach file extension
const fileName = `${outputFile}.svg`;
try {
createWriteStream(fileName)
.write(response.body);
createWriteStream(fileName).write(response.body);
Logger.log(`Writen data to file: ${fileName}`);
} catch (e) {
} catch {
throw new Error('Could not write file. Are you missing permissions?');
}
@@ -126,12 +109,13 @@ export async function createDiagramFromString(
}
/**
* Recursivly iterates over all types, to find implemented generic types and parents
* Recursively iterates over all types, to find implemented generic types and parents
*
* @param definitions all type definitons of the project
* @param definitions all type definitions of the project
* @param abstractionNames currently known string values of inherited classes
*/
function gatherTypeAssociations(
/*function gatherTypeAssociations(
definitions: LightweightDefinition[],
abstractionNames: string[],
): string[] {
@@ -140,7 +124,7 @@ function gatherTypeAssociations(
const declaration = definitions.find(
(definition) => definition.name === name,
);
if (declaration instanceof LightweightClassDefinition) {
if (isLightweightClass(declaration)) {
const currentAbstractions: string[] = declaration.extendedDefinitions.concat(
declaration.implementedDefinitions,
);
@@ -153,7 +137,7 @@ function gatherTypeAssociations(
}
return abstractions;
}
}*/
/**
* Collects all reference information of this type.
@@ -164,25 +148,22 @@ function gatherTypeAssociations(
*/
function getReferenceTypes(type: LightweightType): string[] {
const types: string[] = [];
if (type.isReference) {
types.push(type.name);
if (typeof type.referenceName !== 'undefined') {
types.push(type.referenceName);
}
if (type.isTyped && type.genericsTypes.length > 0) {
for (const specificType of type.genericsTypes) {
forEach(type.genericsTypes, specificType => {
for (const value of getReferenceTypes(specificType)) {
types.push(value);
}
});
if ((isUnionOrIntersectionType(type) && isEmpty(type.specificationTypes)) || type.isArray) {
forEach(type.specificationTypes, specificType => {
for (const value of getReferenceTypes(specificType)) {
types.push(value);
}
}
}
if (
(type.isUnion && type.specificationTypes.length > 0) ||
(type.isArray && type.specificationTypes.length > 0)
) {
for (const specificType of type.specificationTypes) {
for (const value of getReferenceTypes(specificType)) {
types.push(value);
}
}
});
}
return types;
@@ -194,57 +175,54 @@ function getReferenceTypes(type: LightweightType): string[] {
* @param config Configuration for how the UML should be tweaked
* @param readerClass Class or interface representation
*/
function createPlantUMLCodeForClass(
config: UMLConfig,
readerClass: LightweightClassDefinition,
): string {
function createPlantUMLCodeForClass(config: UMLConfig, readerClass: LightweightClassDefinition): string {
// create the definition header, what type the definition is, it's name and it's inheritance
let model = `${readerClass.type} ${readerClass.name}`;
let model = `${readerClass.modifiers} ${readerClass.name}`;
if (readerClass.typeParameters.length > 0) {
model += `<${readerClass.typeParameters.join(', ')}>`;
if (readerClass.typeParameters?.length ?? 0 > 0) {
model += `<${readerClass.typeParameters!.join(', ')}>`;
}
if (config.showInheritance && readerClass.extendedDefinitions.length > 0) {
if (config.showInheritance && (readerClass.extendedDefinitions?.length ?? 0 > 0)) {
// PlantUML will automatically create links, when using extends
model += ` extends ${readerClass.extendedDefinitions.join(', ')}`;
model += ` extends ${readerClass.extendedDefinitions!.join(', ')}`;
}
if (config.showInheritance && readerClass.implementedDefinitions.length > 0) {
// PlantUML will automatically create links, when using implenents
model += ` implements ${readerClass.implementedDefinitions.join(', ')}`;
if (config.showInheritance && (readerClass.implementedDefinitions?.length ?? 0 > 0)) {
// PlantUML will automatically create links, when using implements
model += ` implements ${readerClass.implementedDefinitions!.join(', ')}`;
}
model += '{';
// add the properties to the definition body
if (config.showProperties) {
for (const property of readerClass.properties) {
forEach(readerClass.properties, property => {
if (property.optional && !config.showOptionalProperties) {
// don't show optional attributes
continue;
return;
}
if (property.inherited && !config.showInheritedProperties) {
/*if (property.inherited && !config.showInheritedProperties) {
// don't show inherited properties
continue;
}
}*/
model += `\n\t${createPropertyLine(property)}`;
}
});
}
// close the definition body
model += '\n}\n';
// add associations from properties with references
for (const property of readerClass.properties) {
forEach(readerClass.properties, property => {
const types: string[] = getReferenceTypes(property.type);
for (const type of types) {
if ( config.showAssociations) {
if (property.inherited && !config.showInheritedProperties) {
if (config.showAssociations) {
/*if (property.inherited && !config.showInheritedProperties) {
continue;
}
}*/
model += `${readerClass.name} -up-> ${type} : ${property.name} >\n`;
}
}
}
});
return model;
}
@@ -255,17 +233,14 @@ function createPlantUMLCodeForClass(
* @param config Configuration for how the UML should be tweaked
* @param readerEnum Enum/-like representation
*/
function createPlantUMLCodeForEnum(
config: UMLConfig,
readerEnum: LightweightEnumDefinition,
): string {
function createPlantUMLCodeForEnum(config: UMLConfig, readerEnum: LightweightAliasDefinition): string {
// create enum header
let model = `enum ${readerEnum.name} {`;
// add values
if (config.showEnumValues) {
for (const value of readerEnum.values) {
forEach(readerEnum.type?.specificationTypes, value => {
model += `\n\t${value.toString()}`;
}
});
}
model += '\n}\n';
@@ -276,7 +251,7 @@ function createPlantUMLCodeForEnum(
* Creates a property PlantUML Line
*/
function createPropertyLine(property: LightweightProperty): string {
const prefix = `${(property.inherited ? '/ ' : '')}${(property.optional ? '? ' : '')}`;
const prefix = `${/*(property.inherited ? '/ ' : */ ''}${property.optional ? '? ' : ''}`;
return `${prefix}${property.name} : ${getFullTypeName(property.type)}`;
return `${prefix}${property.name} : ${expandTypeValue(property.type)}`;
}

View File

@@ -1,85 +0,0 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
/**
* Describes an easy to use type definition.
*/
export class LightweightType {
/**
* Contains all types inside of <> brackets
*/
genericsTypes: LightweightType[];
/**
* Does the type have generic-parameters
*/
hasTypeInformation = false;
/**
* Does the type represent an array type
*/
isArray = false;
/**
* Does the type represent a literal type
*/
isLiteral = false;
/**
* Does the type represent a primitive type
*/
isPrimitive = false;
/**
* Does the type contain a reference to
*/
isReference = false;
/**
* Is the type a reflection and not avaiblabe at compile time
*/
isReflection = false;
/**
* Does the type have type parameters
*/
isTyped = false;
/**
* Is the type a typed parameter
*/
isTypeParameter = false;
/**
* Is the type a union type
*/
isUnion = false;
/**
* Name of the type
*/
name: string;
/**
* Type specifications, if the type is combined by either an array, union or a typeOperator
*/
specificationTypes: LightweightType[];
constructor() {
this.specificationTypes = [];
this.genericsTypes = [];
this.name = '';
}
}

View File

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

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 StApps
* 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.

37
src/util/collections.ts Normal file
View File

@@ -0,0 +1,37 @@
/*
* 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 {omitBy, isNil, reject, isEmpty, isArray, isObject} from 'lodash';
/**
* Filters only defined elements
*/
export function rejectNil<T>(array: Array<T | undefined | null>): T[] {
return reject(array, isNil) 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 {
return omitBy(object, it => isNil(it) || ((isObject(it) || isArray(it)) && isEmpty(it))) as T;
}

35
src/util/guards.ts Normal file
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 {JSONSchema7 as JSONSchema} from 'json-schema';
import {SchemaWithDefinitions} from '../types/schema';
/**
* Guard for if a JSON schema is in fact a schema with definitions
*/
export function isSchemaWithDefinitions(schema: JSONSchema): schema is SchemaWithDefinitions {
return typeof schema.definitions !== 'undefined';
}
/**
* Guard method for determining if an object (a thing) has a type property with a type of string
*/
export function isThingWithType(thing: unknown): thing is {type: string} {
return (
typeof thing === 'object' &&
thing !== null &&
'type' in thing &&
typeof (thing as {type: unknown}).type === 'string'
);
}

38
src/util/io.ts Normal file
View File

@@ -0,0 +1,38 @@
/*
* 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 {readdirSync, statSync} from 'fs';
import {flatMap} from 'lodash';
import path from 'path';
/**
* 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()
? flatMap(readdirSync(fullPath), fragment =>
expandPathToFilesSync(path.resolve(sourcePath, fragment), accept),
)
: [fullPath].filter(accept);
}
/**
* Take a Windows path and make a Unix path out of it
*/
export function toUnixPath(pathString: string): string {
return pathString.replace(/\\/g, '/');
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2019 StApps
* 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.
@@ -12,17 +12,9 @@
* 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 any definition without specifics
* Creates sentence cased string
*/
export abstract class LightweightDefinition {
/**
* Name of the definiton
*/
public name: string;
constructor(name: string) {
this.name = name;
}
export function capitalize(string?: string): string {
return `${string?.charAt(0).toUpperCase()}${string?.slice(1).toLowerCase()}`;
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2018-2019 StApps
* Copyright (C) 2018-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.
@@ -12,38 +12,31 @@
* 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 {asyncPool} from '@krlwlfrt/async-pool/lib/async-pool';
import {Logger} from '@openstapps/logger';
import Ajv from 'ajv';
import betterAjvErrors from 'better-ajv-errors';
import {PathLike} from 'fs';
import {JSONSchema7} from 'json-schema';
import * as mustache from 'mustache';
import {basename, join, resolve} from 'path';
import {Schema} from 'ts-json-schema-generator';
import {
ExpectedValidationErrors,
globPromisified,
isThingWithType,
readFilePromisified,
ValidationError,
ValidationResult,
writeFilePromisified,
} from './common';
import {globPromisified, readFilePromisified, writeFilePromisified} from './common';
import {ExpectedValidationErrors, ValidationError, ValidationResult} from './types/validator';
import {isThingWithType} from './util/guards';
import path from 'path';
/**
* StAppsCore validator
*/
export class Validator {
/**
* JSON Schema Validator
*/
private readonly ajv = Ajv({verbose: true, jsonPointers: true, extendRefs: true});
/**
* Map of schema names to schemas
*/
private readonly schemas: { [type: string]: Schema; } = {};
private readonly schemas: {[type: string]: Schema} = {};
/**
* A wrapper function for Ajv that transforms the error into the compatible old error
@@ -58,27 +51,28 @@ export class Validator {
/**
* Feed the schema files to the validator
*
* @param schemaDir Path to directory that contains schema files
* @param schemaDirectory Path to directory that contains schema files
*/
public async addSchemas(schemaDir: PathLike): Promise<string[]> {
const schemaFiles = await globPromisified(join(schemaDir.toString(), '*.json'));
public async addSchemas(schemaDirectory: PathLike): Promise<string[]> {
const schemaFiles = await globPromisified(path.join(schemaDirectory.toString(), '*.json'));
if (schemaFiles.length === 0) {
throw new Error(`No schema files in ${schemaDir.toString()}!`);
throw new Error(`No schema files in ${schemaDirectory.toString()}!`);
}
Logger.log(`Adding schemas from ${schemaDir} to validator.`);
Logger.log(`Adding schemas from ${schemaDirectory} to validator.`);
// tslint:disable-next-line:no-magic-numbers - iterate over schema files
await asyncPool(2, schemaFiles, async (file: string) => {
// read schema file
const buffer = await readFilePromisified(file);
await Promise.all(
schemaFiles.map(async (file: string) => {
// read schema file
const buffer = await readFilePromisified(file);
// add schema to map
this.schemas[basename(file, '.json')] = JSON.parse(buffer.toString());
// add schema to map
this.schemas[path.basename(file, '.json')] = JSON.parse(buffer.toString());
Logger.info(`Added ${file} to validator.`);
});
Logger.info(`Added ${file} to validator.`);
}),
);
return schemaFiles;
}
@@ -93,11 +87,10 @@ export class Validator {
if (typeof schema === 'undefined') {
if (isThingWithType(instance)) {
// schema name can be inferred from type string
// tslint:disable-next-line: completed-docs
const schemaSuffix = (instance as { type: string; }).type.split(' ')
const schemaSuffix = (instance as {type: string}).type
.split(' ')
.map((part: string) => {
return part.substr(0, 1)
.toUpperCase() + part.substr(1);
return part.slice(0, 1).toUpperCase() + part.slice(1);
})
.join('');
const schemaName = `SC${schemaSuffix}`;
@@ -109,7 +102,7 @@ export class Validator {
if (typeof schema === 'string') {
// if you want to access a schema that is contained in the validator object
if (typeof this.schemas[schema] !== 'object') {
throw new Error(`No schema available for ${schema}.`);
throw new TypeError(`No schema available for ${schema}.`);
}
// schema will be cached
@@ -136,26 +129,31 @@ function fromAjvResult(
instance: unknown,
ajvInstance: Ajv.Ajv,
): ValidationResult {
// tslint:disable-next-line
// @ts-ignore function can return void, which at runtime will be undefined. TS doesn't allow to assign void to undefined
const betterErrorObject: betterAjvErrors.IOutputError[] | undefined =
betterAjvErrors(schema, instance, ajvInstance.errors, {format: 'js', indent: null});
// @ts-expect-error function can return void, which at runtime will be undefined. TS doesn't allow to assign void to undefined
const betterErrorObject: betterAjvErrors.IOutputError[] | undefined = betterAjvErrors(
schema,
instance,
ajvInstance.errors,
// eslint-disable-next-line unicorn/no-null
{format: 'js', indent: null},
);
return {
errors: ajvInstance.errors?.map((ajvError, index) => {
errors:
ajvInstance.errors?.map((ajvError, index) => {
const error: ValidationError = {
dataPath: ajvError.dataPath,
instance: instance,
// eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain
message: betterErrorObject?.[index].error!,
name: ajvError.keyword,
schemaPath: ajvError.schemaPath,
suggestion: betterErrorObject?.[index].suggestion,
};
// (validationError as ValidationError).humanReadableError = betterErrorCLI?.[index] as unknown as string;
const error: ValidationError = {
dataPath: ajvError.dataPath,
instance: instance,
message: betterErrorObject?.[index].error!,
name: ajvError.keyword,
schemaPath: ajvError.schemaPath,
suggestion: betterErrorObject?.[index].suggestion,
};
// (validationError as ValidationError).humanReadableError = betterErrorCLI?.[index] as unknown as string;
return error;
}) ?? [],
return error;
}) ?? [],
valid: typeof result === 'boolean' ? result : false,
};
}
@@ -163,19 +161,22 @@ function fromAjvResult(
/**
* Validate all test files in the given resources directory against schema files in the given (schema) directory
*
* @param schemaDir The directory where the JSON schema files are
* @param resourcesDir The directory where the test files are
* @param schemaDirectory The directory where the JSON schema files are
* @param resourcesDirectory The directory where the test files are
*/
export async function validateFiles(schemaDir: string, resourcesDir: string): Promise<ExpectedValidationErrors> {
export async function validateFiles(
schemaDirectory: string,
resourcesDirectory: string,
): Promise<ExpectedValidationErrors> {
// instantiate new validator
const v = new Validator();
await v.addSchemas(schemaDir);
await v.addSchemas(schemaDirectory);
// get list of files to test
const testFiles = await globPromisified(join(resourcesDir, '*.json'));
const testFiles = await globPromisified(path.join(resourcesDirectory, '*.json'));
if (testFiles.length === 0) {
throw new Error(`No test files in ${resourcesDir}!`);
throw new Error(`No test files in ${resourcesDirectory}!`);
}
Logger.log(`Found ${testFiles.length} file(s) to test.`);
@@ -183,68 +184,68 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
// map of errors per file
const errors: ExpectedValidationErrors = {};
// tslint:disable-next-line:no-magic-numbers - iterate over files to test
await asyncPool(2, testFiles, async (testFile: string) => {
const testFileName = basename(testFile);
await Promise.all(
testFiles.map(async (testFile: string) => {
const testFileName = path.basename(testFile);
const buffer = await readFilePromisified(join(resourcesDir, testFileName));
const buffer = await readFilePromisified(path.join(resourcesDirectory, testFileName));
// read test description from file
const testDescription = JSON.parse(buffer.toString());
// read test description from file
const testDescription = JSON.parse(buffer.toString());
// validate instance from test description
const result = v.validate(testDescription.instance, testDescription.schema);
// validate instance from test description
const result = v.validate(testDescription.instance, testDescription.schema);
// list of expected errors for this test description
const expectedErrors: string[] = [];
expectedErrors.push.apply(expectedErrors, testDescription.errorNames);
// list of expected errors for this test description
const expectedErrors: string[] = [...testDescription.errorNames];
// number of unexpected errors
let unexpectedErrors = 0;
// number of unexpected errors
let unexpectedErrors = 0;
if (result.errors.length > 0) {
errors[testFileName] = [];
if (result.errors.length > 0) {
errors[testFileName] = [];
// iterate over errors
for (const error of result.errors) {
const errorIndex = expectedErrors.indexOf(error.name);
let expected = false;
// iterate over errors
for (const error of result.errors) {
const errorIndex = expectedErrors.indexOf(error.name);
let expected = false;
if (errorIndex >= 0) {
expectedErrors.splice(errorIndex, 1);
expected = true;
} else {
unexpectedErrors++;
await Logger.error(`Unexpected error ${error.name} in ${testFile}`);
if (errorIndex >= 0) {
expectedErrors.splice(errorIndex, 1);
expected = true;
} else {
unexpectedErrors++;
await Logger.error(`Unexpected error ${error.name} in ${testFile}`);
}
// add error to list of errors
errors[testFileName].push({
...error,
expected,
});
}
// add error to list of errors
errors[testFileName].push({
...error,
expected,
});
}
}
if (expectedErrors.length > 0) {
for (const error of expectedErrors) {
await Logger.error(`Extraneous expected error '${error}' in ${testFile}.`);
if (expectedErrors.length > 0) {
for (const error of expectedErrors) {
await Logger.error(`Extraneous expected error '${error}' in ${testFile}.`);
errors[testFileName].push({
dataPath: 'undefined',
expected: false,
instance: undefined,
// instance: testDescription.instance,
message: 'undefined',
name: `expected ${error}`,
schemaPath: 'undefined',
suggestion: 'undefined',
});
errors[testFileName].push({
dataPath: 'undefined',
expected: false,
instance: undefined,
// instance: testDescription.instance,
message: 'undefined',
name: `expected ${error}`,
schemaPath: 'undefined',
suggestion: 'undefined',
});
}
} else if (unexpectedErrors === 0) {
Logger.info(`Successfully validated ${testFile}.`);
}
} else if (unexpectedErrors === 0) {
Logger.info(`Successfully validated ${testFile}.`);
}
});
}),
);
return errors;
}
@@ -256,10 +257,12 @@ export async function validateFiles(schemaDir: string, resourcesDir: string): Pr
* @param errors Errors that occurred in validation
*/
export async function writeReport(reportPath: PathLike, errors: ExpectedValidationErrors): Promise<void> {
let buffer = await readFilePromisified(resolve(__dirname, '..', 'resources', 'file.html.mustache'));
// eslint-disable-next-line unicorn/prefer-module
let buffer = await readFilePromisified(path.resolve(__dirname, '..', 'resources', 'file.html.mustache'));
const fileTemplate = buffer.toString();
buffer = await readFilePromisified(resolve(__dirname, '..', 'resources', 'error.html.mustache'));
// eslint-disable-next-line unicorn/prefer-module
buffer = await readFilePromisified(path.resolve(__dirname, '..', 'resources', 'error.html.mustache'));
const errorTemplate = buffer.toString();
let output = '';
@@ -271,19 +274,17 @@ export async function writeReport(reportPath: PathLike, errors: ExpectedValidati
let fileOutput = '';
errors[fileName].forEach((error, idx) => {
for (const [index, error] of errors[fileName].entries()) {
fileOutput += mustache.render(errorTemplate, {
idx: idx + 1,
// tslint:disable-next-line:no-magic-numbers
instance: JSON.stringify(error.instance, null, 2),
idx: index + 1,
instance: JSON.stringify(error.instance, undefined, 2),
message: error.message,
name: error.name,
schemaPath: error.schemaPath,
status: (error.expected) ? 'alert-success' : 'alert-danger',
status: error.expected ? 'alert-success' : 'alert-danger',
suggestion: error.suggestion,
});
});
}
output += mustache.render(fileTemplate, {
errors: fileOutput,
@@ -291,13 +292,17 @@ export async function writeReport(reportPath: PathLike, errors: ExpectedValidati
});
}
buffer = await readFilePromisified(resolve(__dirname, '..', 'resources', 'report.html.mustache'));
// eslint-disable-next-line unicorn/prefer-module
buffer = await readFilePromisified(path.resolve(__dirname, '..', 'resources', 'report.html.mustache'));
const reportTemplate = buffer.toString();
await writeFilePromisified(reportPath, mustache.render(reportTemplate, {
report: output,
timestamp: (new Date()).toISOString(),
}));
await writeFilePromisified(
reportPath,
mustache.render(reportTemplate, {
report: output,
timestamp: new Date().toISOString(),
}),
);
Logger.ok(`Wrote report to ${reportPath}.`);
}

View File

@@ -1,71 +0,0 @@
/*
* Copyright (C) 2020 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Logger} from '@openstapps/logger';
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {MapAggTest} from './mapping-model/MapAggTest';
import {aggArrayTest} from './mapping-model/aggregations/src/agg-array';
import {aggNestedTest} from './mapping-model/aggregations/src/agg-nested';
import {aggGlobalTest} from './mapping-model/aggregations/src/agg-global';
import {aggGlobalNestedTest} from './mapping-model/aggregations/src/agg-global-nested';
import {aggInheritedTest} from './mapping-model/aggregations/src/agg-inherited';
import {aggInheritedGlobalTest} from './mapping-model/aggregations/src/agg-inherited-global';
import {aggInheritedOverwrittenTest} from './mapping-model/aggregations/src/agg-inherited-overwritten';
process.on('unhandledRejection', (error: unknown) => {
if (error instanceof Error) {
void Logger.error('UNHANDLED REJECTION', error.stack);
}
process.exit(1);
});
const magAppInstance = new MapAggTest('aggregations');
@suite(timeout(20000), slow(10000))
export class AggregationsSpec {
@test
async 'Aggregation tag should propagate on arrays'() {
magAppInstance.testInterfaceAgainstPath(aggArrayTest);
}
@test
async 'Should work on nested properties'() {
magAppInstance.testInterfaceAgainstPath(aggNestedTest);
}
@test
async 'Global option should work'() {
magAppInstance.testInterfaceAgainstPath(aggGlobalTest);
}
@test
async 'Global aggregations when nested'() {
magAppInstance.testInterfaceAgainstPath(aggGlobalNestedTest);
}
@test
async 'Inherited aggregations should work'() {
magAppInstance.testInterfaceAgainstPath(aggInheritedTest);
}
@test
async 'Inherited global aggregations should work'() {
magAppInstance.testInterfaceAgainstPath(aggInheritedGlobalTest);
}
@test
async 'Inherited aggregations should work when overwritten'() {
magAppInstance.testInterfaceAgainstPath(aggInheritedOverwrittenTest);
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-module */
/*
* Copyright (C) 2018-2019 StApps
* This program is free software: you can redistribute it and/or modify it
@@ -20,16 +21,15 @@ import {getTsconfigPath} from '../src/common';
process.on('unhandledRejection', (reason: unknown): void => {
if (reason instanceof Error) {
Logger.error('UNHANDLED REJECTION', reason.stack);
void Logger.error('UNHANDLED REJECTION', reason.stack);
}
process.exit(1);
});
@suite(timeout(20000), slow(10000))
@suite(timeout(20_000), slow(10_000))
export class CommonSpec {
@test
async getTsconfigPath() {
expect(getTsconfigPath(__dirname)).to.be.equal(cwd());
}
}

View File

@@ -1,3 +1,4 @@
/* eslint-disable unicorn/prefer-module */
/*
* Copyright (C) 2018-2019 StApps
* This program is free software: you can redistribute it and/or modify it
@@ -13,19 +14,19 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {expect} from 'chai';
import {resolve} from 'path';
import {existsSync, unlinkSync} from 'fs';
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {getProjectReflection} from '../src/common';
import {createDiagram, createDiagramFromString} from '../src/uml/create-diagram';
import {UMLConfig} from '../src/uml/uml-config';
import {readDefinitions} from '../src/uml/read-definitions';
import {LightweightDefinition} from '../src/uml/model/lightweight-definition';
import {LightweightDefinition} from '../src/easy-ast/types/lightweight-definition';
import nock = require('nock');
import {lightweightDefinitionsFromPath} from '../src/easy-ast/easy-ast';
import path from 'path';
@suite(timeout(15000), slow(5000))
@suite(timeout(15_000), slow(5000))
export class CreateDiagramSpec {
plantUmlConfig: UMLConfig;
definitions: LightweightDefinition[];
constructor() {
@@ -39,21 +40,20 @@ export class CreateDiagramSpec {
showProperties: true,
};
const projectReflection = getProjectReflection('./test/model', true);
this.definitions = readDefinitions(projectReflection);
this.definitions = lightweightDefinitionsFromPath('./test/model');
}
@test
async shouldRefuseRequest() {
const testPlantUmlCode: string = 'class Test{\n}';
const testPlantUmlCode = 'class Test{\n}';
try {
await createDiagramFromString(testPlantUmlCode, "http://plantuml:8080");
} catch (e) {
await createDiagramFromString(testPlantUmlCode, 'http://plantuml:8080');
} catch (error) {
expect([
new Error('getaddrinfo ENOTFOUND plantuml plantuml:8080').message,
new Error('getaddrinfo EAI_AGAIN plantuml plantuml:8080').message,
new Error('getaddrinfo ENOTFOUND plantuml').message,
]).to.include(e.message);
]).to.include(error.message);
}
}
@@ -69,18 +69,18 @@ export class CreateDiagramSpec {
nock('http://plantuml:8080')
.persist()
.get(() => true)
.reply(200, 'This will be the file content')
.reply(200, 'This will be the file content');
let fileName = await createDiagram(this.definitions, this.plantUmlConfig, "http://plantuml:8080");
let filePath = resolve(__dirname, '..', fileName);
let fileName = await createDiagram(this.definitions, this.plantUmlConfig, 'http://plantuml:8080');
let filePath = path.resolve(__dirname, '..', fileName);
expect(await existsSync(filePath)).to.equal(true);
await unlinkSync(fileName);
this.plantUmlConfig.showAssociations = false;
this.plantUmlConfig.showInheritance = false;
fileName = await createDiagram(this.definitions, this.plantUmlConfig, "http://plantuml:8080");
filePath = resolve(__dirname, '..', fileName);
fileName = await createDiagram(this.definitions, this.plantUmlConfig, 'http://plantuml:8080');
filePath = path.resolve(__dirname, '..', fileName);
expect(await existsSync(filePath)).to.equal(true);
await unlinkSync(fileName);

37
test/easy-ast.spec.ts Normal file
View File

@@ -0,0 +1,37 @@
/*
* 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/io';
import {EasyAstSpecType} from './easy-ast/easy-ast-spec-type';
import {lightweightProjectFromPath} from '../src/easy-ast/easy-ast';
import {expect} from 'chai';
import {omitBy} from 'lodash';
describe('Easy AST', async () => {
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, () => {
const project = omitBy(lightweightProjectFromPath(file, true)[toUnixPath(file)], (_value, key) =>
key.startsWith('$'),
);
expect(project).to.be.deep.equal(test.expected);
});
} catch (error) {
console.error(error);
}
}
});

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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
/**
* 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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 StApps
* 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.
@@ -12,13 +12,9 @@
* 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';
export enum ThingType {
AggArray = 'agg array',
AggGlobal = 'agg global',
AggGlobalNested = 'agg global nested',
AggNested = 'agg nested',
AggInherited = 'agg inherited',
AggInheritedGlobal = 'agg inherited global',
AggInheritedOverwritten = 'agg inherited overwritten',
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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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,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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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';
import {LightweightDefinitionKind} from '../../src/easy-ast/types/lightweight-definition-kind';
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

@@ -1,135 +0,0 @@
/*
* Copyright (C) 2020 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 {generateTemplate} from '../../src/mapping';
import {resolve} from "path";
import {expect} from "chai";
import {ProjectReflection} from 'typedoc';
import {getProjectReflection} from '../../src/common';
import {AggregationSchema, ESNestedAggregation} from '../../src/mappings/aggregation-definitions';
import {MapAggTestOptions, MinimalMappingDescription} from './MapAggTestOptions';
import {ElasticsearchTemplateCollection} from '../../src/mappings/mapping-definitions';
import {settings} from '../../src/mappings/definitions/settings';
import {ElasticsearchDataType} from '../../src/mappings/definitions/typemap';
export class MapAggTest {
mapping_model_path!: string;
reflection!: ProjectReflection;
constructor(dir: string) {
this.mapping_model_path = resolve(__dirname, dir);
this.reflection = getProjectReflection(this.mapping_model_path);
}
testInterfaceAgainstPath(options: MapAggTestOptions) {
const template = generateTemplate(this.reflection, options.ignoredTags ?? [], false, [options.name]);
if (typeof options.err !== 'undefined') {
for (const error of template.errors) {
expect(options.err).to.include(error, "Unexpected Error!")
}
} else {
expect(template.errors).to.be.deep.equal([], 'Unexpected Error!');
}
if (typeof options.agg !== 'undefined') {
const expectedAggSchema = MapAggTest.buildAggregation(options.name, options.agg.fields, options.agg.globals)
expect(template.aggregations).to.be.deep.equal(expectedAggSchema, 'Aggregation schema not equal!');
}
if (typeof options.map !== 'undefined') {
const expectedMappingSchema = MapAggTest.buildMapping(options.name, options.map);
expect(template.mappings).to.be.deep.equal(expectedMappingSchema, 'Mapping schema not equal!');
}
}
static buildAggregation(name: string, fields?: string[], globals?: string[]): AggregationSchema {
const out: AggregationSchema = {
'@all': {
aggs: {},
filter: {
match_all: {}
}
},
};
for (const global of globals ?? []) {
(out['@all']! as ESNestedAggregation).aggs[global] = {
terms: {
field: `${global}.raw`,
size: 1000,
}
}
}
if (typeof fields === 'undefined' || fields.length === 0) {
return out;
}
out[name] = {
aggs: {},
filter: {
type: {
value: name,
}
}
}
for (const field of fields) {
(out[name]! as ESNestedAggregation).aggs[field] = {
terms: {
field: `${field}.raw`,
size: 1000,
}
}
}
return out;
}
static buildMapping(name: string, map: MinimalMappingDescription): ElasticsearchTemplateCollection {
let typeNameWithoutSpaces = name.toLowerCase();
while (typeNameWithoutSpaces.includes(' ')) {
typeNameWithoutSpaces = typeNameWithoutSpaces.replace(' ', '_');
}
const out: ElasticsearchTemplateCollection = {};
const templateName = `template_${typeNameWithoutSpaces}`;
out[templateName] = {
mappings: {},
settings: settings,
template: `stapps_${typeNameWithoutSpaces}*`,
}
const maps = map.maps ?? {};
maps.type = {
type: ElasticsearchDataType.text
}
maps.creation_date = {
type: ElasticsearchDataType.date
}
out[templateName].mappings[name] = {
_source: {
excludes: [
'creation_date'
]
},
date_detection: false,
dynamic: 'strict',
properties: maps,
dynamic_templates: map.dynamicTemplates ?? [],
}
return out;
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright (C) 2020 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 {
ElasticsearchDynamicTemplate,
ElasticsearchValue
} from '../../src/mappings/mapping-definitions';
export interface MapAggTestOptions {
name: string;
agg?: {
fields?: string[];
globals?: string[];
}
map?: MinimalMappingDescription;
err?: string[];
ignoredTags?: string[];
}
export interface MinimalMappingDescription {
maps?: {
[name: string]: ElasticsearchValue;
};
dynamicTemplates?: ElasticsearchDynamicTemplate[];
}

View File

@@ -1,37 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
/**
* @indexable
*/
export interface AggArray {
/**
* @aggregatable
*/
array: Foo[];
type: ThingType.AggArray;
}
type Foo = 'A' | 'B' | 'C';
export const aggArrayTest: MapAggTestOptions = {
name: ThingType.AggArray,
agg: {
fields: ['array'],
},
};

View File

@@ -1,38 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
/**
* @indexable
*/
export interface AggGlobalNested {
nested: {
/**
* @aggregatable global
*/
foo: string;
};
type: ThingType.AggGlobalNested;
}
export const aggGlobalNestedTest: MapAggTestOptions = {
name: ThingType.AggGlobalNested,
agg: {
globals: ['foo'],
},
};

View File

@@ -1,39 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
/**
* @indexable
*/
export interface AggInherited extends Foo {
type: ThingType.AggInherited;
}
interface Foo {
/**
* @aggregatable
*/
bar: string;
}
export const aggInheritedTest: MapAggTestOptions = {
name: ThingType.AggInherited,
agg: {
fields: ['bar'],
},
};

View File

@@ -1,38 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
/**
* @indexable
*/
export interface AggNested {
nested: {
/**
* @aggregatable
*/
foo: string;
};
type: ThingType.AggNested;
}
export const aggNestedTest: MapAggTestOptions = {
name: ThingType.AggNested,
agg: {
fields: ['nested.foo'],
},
};

View File

@@ -1,3 +0,0 @@
{
"extends": "../../../node_modules/@openstapps/configuration/tsconfig.json"
}

View File

@@ -1,44 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
/**
* @indexable
*/
export interface AnyUnknown {
foo: any;
bar: unknown;
type: ThingType.AnyUnknown
}
export const anyUnknownTest: MapAggTestOptions = {
name: ThingType.AnyUnknown,
map: {
maps: {
foo: {
dynamic: true,
properties: {}
},
bar: {
dynamic: true,
properties: {}
}
}
}
};

View File

@@ -1,51 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @date
*/
export type SCISO8601Date = string
/**
* @indexable
*/
export interface DateAndRange {
/**
* @date
*/
directDate: string;
dateAlias: SCISO8601Date;
type: ThingType.Date
}
export const dateAndRangeTest: MapAggTestOptions = {
name: ThingType.Date,
map: {
maps: {
directDate: {
type: ElasticsearchDataType.date,
},
dateAlias: {
type: ElasticsearchDataType.date,
},
}
}
};

View File

@@ -1,50 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface DefaultGeneric {
foo: InterfaceWithDefaultGeneric;
type: ThingType.DefaultGeneric;
}
interface InterfaceWithDefaultGeneric<T = number> {
bar: T;
}
export const defaultGenericTest: MapAggTestOptions = {
name: ThingType.DefaultGeneric,
map: {
maps: {
foo: {
dynamic: 'strict',
properties: {
bar: {
type: ElasticsearchDataType.parse_error
}
}
}
}
},
err: [
`At "${ThingType.DefaultGeneric}::foo.bar" for Generic "T": Missing reflection, please report!`
]
};

View File

@@ -1,55 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface DoubleTypeConflict {
/**
* @keyword
* @text
*/
stringDoubleTypeConflict: string;
/**
* @integer
* @float
*/
numberDoubleTypeConflict: number;
type: ThingType.DoubleTypeConflict;
}
export const doubleTypeConflictTest: MapAggTestOptions = {
name: ThingType.DoubleTypeConflict,
map: {
maps: {
stringDoubleTypeConflict: {
type: ElasticsearchDataType.type_conflict
},
numberDoubleTypeConflict: {
type: ElasticsearchDataType.type_conflict
}
}
},
err: [
`At "${ThingType.DoubleTypeConflict}::stringDoubleTypeConflict" for type "string": Type conflict; "keyword" would override "text"`,
`At "${ThingType.DoubleTypeConflict}::numberDoubleTypeConflict" for type "number": Type conflict; "integer" would override "float"`
]
};

View File

@@ -1,55 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface Enum {
foo: Bar,
bar: Baz,
type: ThingType.Enum;
}
enum Bar {
a,
b,
c,
}
enum Baz {
d = 'd',
e = 'e',
f = 'f',
}
export const enumTest: MapAggTestOptions = {
name: ThingType.Enum,
map: {
maps: {
foo: {
type: ElasticsearchDataType.text
},
bar: {
type: ElasticsearchDataType.text
}
}
}
};

View File

@@ -1,89 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
export type FilterableEnumType = 'a' | 'b' | 'c';
/**
* @indexable
*/
export interface FilterableTag {
/**
* @text
* @filterable
*/
foo: string;
/**
* @keyword
* @filterable
*/
bar: string;
/**
* @filterable
*/
baz: 'some literal'
/**
* @filterable
*/
buz: FilterableEnumType
type: ThingType.FilterableTag
}
export const filterableTagTest: MapAggTestOptions = {
name: ThingType.FilterableTag,
map: {
maps: {
foo: {
type: ElasticsearchDataType.text,
fields: {
raw: {
type: ElasticsearchDataType.keyword
}
}
},
bar: {
type: ElasticsearchDataType.keyword,
fields: {
raw: {
type: ElasticsearchDataType.keyword
}
}
},
baz: {
type: ElasticsearchDataType.keyword,
fields: {
raw: {
type: ElasticsearchDataType.keyword
}
}
},
buz: {
type: ElasticsearchDataType.keyword,
fields: {
raw: {
type: ElasticsearchDataType.keyword
}
}
}
}
}
};

View File

@@ -1,60 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface Generics {
foo: InterfaceWithDefaultGeneric<number>;
baz: InterfaceWithStringGeneric<string>
type: ThingType.Generics;
}
interface InterfaceWithDefaultGeneric<T = number> {
bar: T;
}
interface InterfaceWithStringGeneric<T> {
bar: T;
}
export const genericTest: MapAggTestOptions = {
name: ThingType.Generics,
map: {
maps: {
foo: {
dynamic: 'strict',
properties: {
bar: {
type: ElasticsearchDataType.integer
}
}
},
baz: {
dynamic: 'strict',
properties: {
bar: {
type: ElasticsearchDataType.text
}
}
}
}
}
};

View File

@@ -1,41 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface ImpossibleUnion {
foo: 'a' | 1 | boolean;
type: ThingType.ImpossibleUnion
}
export const impossibleUnionTest: MapAggTestOptions = {
name: ThingType.ImpossibleUnion,
map: {
maps: {
foo: {
type: ElasticsearchDataType.boolean
}
}
},
err: [
`At "${ThingType.ImpossibleUnion}::foo" for type "[{"type":"1","name":"2"},"unknown","1"]": Not implemented type`
]
};

View File

@@ -1,72 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface DoubleTypeConflict {
/**
* @keyword
*/
keywordNumber: number;
/**
* @text
*/
textNumber: number;
/**
* @integer
*/
integerString: string;
/**
* @float
*/
floatString: string;
type: ThingType.IncompatibleType;
}
export const incompatibleTypeTest: MapAggTestOptions = {
name: ThingType.IncompatibleType,
map: {
maps: {
keywordNumber: {
type: ElasticsearchDataType.integer
},
textNumber: {
type: ElasticsearchDataType.integer
},
integerString: {
type: ElasticsearchDataType.text
},
floatString: {
type: ElasticsearchDataType.text
}
}
},
err: [
`At "${ThingType.IncompatibleType}::keywordNumber" for tag "keyword": Not implemented tag`,
`At "${ThingType.IncompatibleType}::textNumber" for tag "text": Not implemented tag`,
`At "${ThingType.IncompatibleType}::floatString" for tag "float": Not implemented tag`,
`At "${ThingType.IncompatibleType}::integerString" for tag "integer": Not implemented tag`,
]
};

View File

@@ -1,64 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface IndexSignature {
foo: {
[key: string]: {
bar: number;
baz: string;
}
}
type: ThingType.IndexSignature;
}
export const indexSignatureTest: MapAggTestOptions = {
name: ThingType.IndexSignature,
map: {
maps: {
foo: {
dynamic: true,
properties: {}
}
},
dynamicTemplates: [
{
__type: {
mapping: {
dynamic: 'strict',
properties: {
bar: {
type: ElasticsearchDataType.integer
},
baz: {
type: ElasticsearchDataType.text
}
}
},
match: '*',
match_mapping_type: '*',
path_match: 'foo.*'
}
}
]
}
};

View File

@@ -1,56 +0,0 @@
/*
* Copyright (C) 2020 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 {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ThingType} from './types';
/**
* @indexable
*/
export interface InheritTags {
/**
* @inheritTags inherit tags::bar.baz
*/
foo: number,
bar: {
/**
* @float
*/
baz: number
}
type: ThingType.InheritTags;
}
export const inheritTagsTest: MapAggTestOptions = {
name: ThingType.InheritTags,
map: {
maps: {
foo: {
type: ElasticsearchDataType.float
},
bar: {
dynamic: 'strict',
properties: {
baz: {
type: ElasticsearchDataType.float
}
}
},
}
}
};

View File

@@ -1,56 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface InheritedProperty extends Bar{
foo: number,
type: ThingType.InheritedProperty;
}
interface Bar {
/**
* @keyword
*/
bar: string;
/**
* @float
*/
baz: number;
}
export const inheritedPropertyTest: MapAggTestOptions = {
name: ThingType.InheritedProperty,
map: {
maps: {
foo: {
type: ElasticsearchDataType.integer
},
bar: {
type: ElasticsearchDataType.keyword
},
baz: {
type: ElasticsearchDataType.float
}
}
}
};

View File

@@ -1,44 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface InvalidTag {
/**
* @anInvalidTag
*/
foo: string;
type: ThingType.InvalidTag;
}
export const invalidTagTest: MapAggTestOptions = {
name: ThingType.InvalidTag,
map: {
maps: {
foo: {
type: ElasticsearchDataType.text
}
}
},
err: [
`At "${ThingType.InvalidTag}::foo" for tag "aninvalidtag": Not implemented tag`
]
};

View File

@@ -1,81 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface MapExplicitTypes {
/**
* @integer
*/
esInteger: number;
/**
* @float
*/
esFloat: number;
/**
* @keyword
*/
esKeyword: string;
/**
* @text
*/
esText: string;
/**
* @date
*/
esEpochMsDate: number
/**
* @date
*/
esStringDate: string
type: ThingType.MapExplicitTypes;
}
export const mapExplicitTypesTest: MapAggTestOptions = {
name: ThingType.MapExplicitTypes,
map: {
maps: {
esInteger: {
type: ElasticsearchDataType.integer
},
esFloat: {
type: ElasticsearchDataType.float
},
esKeyword: {
type: ElasticsearchDataType.keyword
},
esText: {
type: ElasticsearchDataType.text
},
esEpochMsDate: {
type: ElasticsearchDataType.date
},
esStringDate: {
type: ElasticsearchDataType.date
},
}
}
};

View File

@@ -1,44 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface MissingPremap {
// it really doesn't matter what we use here, as long as it is an external dependency
// if you get an error here because you removed a dependency, feel free to change it around
// to your heart's content
foo: HTMLAllCollection;
type: ThingType.MissingPremap;
}
export const missingPremapTest: MapAggTestOptions = {
name: ThingType.MissingPremap,
map: {
maps: {
foo: {
type: ElasticsearchDataType.missing_premap
}
}
},
err: [
`At "${ThingType.MissingPremap}::foo" for external type "HTMLAllCollection": Missing pre-map`
]
};

View File

@@ -1,70 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface Nested {
foo: {
depth1_1: {
depth2_1: {
depth3_1: number;
/**
* @keyword
*/
depth3_2: string;
}
depth2_2: boolean;
}
}
type: ThingType.Nested;
}
export const nestedTest: MapAggTestOptions = {
name: ThingType.Nested,
map: {
maps: {
foo: {
dynamic: 'strict',
properties: {
depth1_1: {
dynamic: 'strict',
properties: {
depth2_1: {
dynamic: 'strict',
properties: {
depth3_1: {
type: ElasticsearchDataType.integer
},
depth3_2: {
type: ElasticsearchDataType.keyword
}
}
},
depth2_2: {
type: ElasticsearchDataType.boolean
}
}
}
}
}
}
}
};

View File

@@ -1,63 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface ObjectUnion {
foo: Boo | Buu;
type: ThingType.ObjectUnion
}
// we can't name them Bar or Baz, look here for more info:
// https://gitlab.com/openstapps/core-tools/-/issues/48
// or here
// https://github.com/TypeStrong/typedoc/issues/1373
interface Boo {
a: boolean;
intersection: string;
}
interface Buu {
b: number;
intersection: string;
}
export const objectUnionTest: MapAggTestOptions = {
name: ThingType.ObjectUnion,
map: {
maps: {
foo: {
dynamic: 'strict',
properties: {
a: {
type: ElasticsearchDataType.boolean
},
b: {
type: ElasticsearchDataType.integer
},
intersection: {
type: ElasticsearchDataType.text
}
}
}
}
}
};

View File

@@ -1,67 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface PairedTags {
/**
* @keyword
* @filterable
*/
foo: string;
/**
* @text
* @filterable
* @sortable
*/
bar: string;
type: ThingType.PairedTags;
}
export const pairedTagsTest: MapAggTestOptions = {
name: ThingType.PairedTags,
map: {
maps: {
foo: {
type: ElasticsearchDataType.keyword,
fields: {
raw: {
type: ElasticsearchDataType.keyword
}
}
},
bar: {
type: ElasticsearchDataType.text,
fields: {
raw: {
type: ElasticsearchDataType.keyword
},
sort: {
analyzer: 'ducet_sort',
fielddata: true,
type: ElasticsearchDataType.text
}
}
}
}
}
};

View File

@@ -1,58 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface SensibleDefaults {
numberDefault: number;
stringDefault: string;
booleanDefault: boolean;
stringLiteralDefault: 'Hey there!';
booleanTrueLiteralDefault: true;
booleanFalseLiteralDefault: false;
type: ThingType.SensibleDefaultType;
}
export const sensibleDefaultsTest: MapAggTestOptions = {
name: ThingType.SensibleDefaultType,
map: {
maps: {
numberDefault: {
type: ElasticsearchDataType.integer
},
stringDefault: {
type: ElasticsearchDataType.text
},
booleanDefault: {
type: ElasticsearchDataType.boolean
},
stringLiteralDefault: {
type: ElasticsearchDataType.keyword
},
booleanTrueLiteralDefault: {
type: ElasticsearchDataType.boolean
},
booleanFalseLiteralDefault: {
type: ElasticsearchDataType.boolean
}
}
}
};

View File

@@ -1,78 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface SortableTag {
/**
* @sortable
*/
foo: string;
/**
* @sortable ducet
*/
bar: string;
/**
* @sortable
*/
baz: number;
type: ThingType.SortableTag
}
export const sortableTagTest: MapAggTestOptions = {
name: ThingType.SortableTag,
map: {
maps: {
foo: {
type: ElasticsearchDataType.text,
fields: {
sort: {
analyzer: 'ducet_sort',
fielddata: true,
type: 'text'
}
}
},
bar: {
type: ElasticsearchDataType.text,
fields: {
sort: {
analyzer: 'ducet_sort',
fielddata: true,
type: 'text'
}
}
},
baz: {
type: ElasticsearchDataType.integer,
fields: {
sort: {
analyzer: 'ducet_sort',
fielddata: true,
type: 'text'
}
}
}
}
}
};

View File

@@ -1,63 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface TagsIgnoreCase {
/**
* @inheritTags inherit tags::bar.baz
*/
camelCase: number,
/**
* @inherittags inherit tags::bar.baz
*/
lowerCase: number,
bar: {
/**
* @float
*/
baz: number
}
type: ThingType.TagsIgnoreCase;
}
export const tagsIgnoreCaseTest: MapAggTestOptions = {
name: ThingType.TagsIgnoreCase,
map: {
maps: {
camelCase: {
type: ElasticsearchDataType.float
},
lowerCase: {
type: ElasticsearchDataType.float
},
bar: {
dynamic: 'strict',
properties: {
baz: {
type: ElasticsearchDataType.float
}
}
},
}
}
};

View File

@@ -1,67 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface TypeAlias {
/**
*
*/
textProperty: ATextAlias,
/**
*
*/
keywordProperty: AKeywordAlias,
/**
* @keyword
*/
overriddenTextAsKeyword: ATextAlias
type: ThingType.TypeAlias;
}
/**
* @text
*/
type ATextAlias = string;
/**
* @keyword
*/
type AKeywordAlias = string;
export const typeAliasTest: MapAggTestOptions = {
name: ThingType.TypeAlias,
map: {
maps: {
textProperty: {
type: ElasticsearchDataType.text,
},
keywordProperty: {
type: ElasticsearchDataType.keyword,
},
overriddenTextAsKeyword: {
type: ElasticsearchDataType.keyword,
},
}
}
};

View File

@@ -1,42 +0,0 @@
/*
* 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
export interface SCISO8601DateRange {
bar: string;
}
/**
* @indexable
*/
export interface TypeOverrides {
foo: SCISO8601DateRange;
type: ThingType.TypeOverrides
}
export const typeOverridesTest: MapAggTestOptions = {
name: ThingType.TypeOverrides,
map: {
maps: {
foo: {
type: ElasticsearchDataType.date_range,
},
}
}
};

View File

@@ -1,44 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface TypeQuery {
foo: typeof Bar;
type: ThingType.TypeQuery;
}
enum Bar {
'a',
'b',
'c'
}
export const typeQueryTest: MapAggTestOptions = {
name: ThingType.TypeQuery,
map: {
maps: {
foo: {
type: ElasticsearchDataType.text
}
}
}
};

View File

@@ -1,66 +0,0 @@
/*
* Copyright (C) 2020 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 {ThingType} from './types';
import {MapAggTestOptions} from '../../MapAggTestOptions';
import {ElasticsearchDataType} from '../../../../src/mappings/definitions/typemap';
/**
* @indexable
*/
export interface TypeWrapperInheritance {
/**
* @float
*/
numberWrapper: NumberWrapper;
/**
* @keyword
*/
stringWrapper: StringWrapper;
/**
* @text
*/
stringLiteralWrapper: StringLiteralWrapper;
type: ThingType.TypeWrapperInheritance
}
type NumberWrapper = number;
type StringWrapper = string;
type StringLiteralWrapper = 'foo';
export const typeWrapperInheritanceTest: MapAggTestOptions = {
name: ThingType.TypeWrapperInheritance,
map: {
maps: {
foo: {
dynamic: 'strict',
properties: {
numberWrapper: {
type: ElasticsearchDataType.float
},
stringWrapper: {
type: ElasticsearchDataType.keyword
},
stringLiteralWrapper: {
type: ElasticsearchDataType.text
}
}
}
}
}
};

View File

@@ -1,42 +0,0 @@
/*
* Copyright (C) 2020-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 ThingType {
MapExplicitTypes = 'map explicit types',
DoubleTypeConflict = 'double type conflict',
IncompatibleType = 'incompatible type',
SensibleDefaultType = 'sensible default',
InvalidTag = 'invalid tag',
MissingPremap = 'missing premap',
DefaultGeneric = 'default generic',
Generics = 'generics',
Nested = 'nested',
IndexSignature = 'index signature',
ImpossibleUnion = 'impossible union',
TypeQuery = 'type query',
ObjectUnion = 'object union',
TypeWrapperInheritance = 'type wrapper inheritance',
SortableTag = 'sortable tag',
Enum = 'enum',
InheritedProperty = 'inherited property',
PairedTags = 'paired tags',
FilterableTag = 'filterable tag',
AnyUnknown = 'any unknown',
Date = 'date',
InheritTags = 'inherit tags',
TagsIgnoreCase = 'tags ignore case',
TypeAlias = 'type alias',
TypeOverrides = 'type overrides',
}

View File

@@ -1,3 +0,0 @@
{
"extends": "../../../node_modules/@openstapps/configuration/tsconfig.json"
}

View File

@@ -1,183 +0,0 @@
/*
* Copyright (C) 2020-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 {Logger} from '@openstapps/logger';
import {slow, suite, test, timeout} from '@testdeck/mocha';
import {MapAggTest} from './mapping-model/MapAggTest';
import {inheritTagsTest} from './mapping-model/mappings/src/inherit-tags';
import {mapExplicitTypesTest} from './mapping-model/mappings/src/map-explicit-types';
import {doubleTypeConflictTest} from './mapping-model/mappings/src/double-type-conflict';
import {incompatibleTypeTest} from './mapping-model/mappings/src/incompatible-type';
import {sensibleDefaultsTest} from './mapping-model/mappings/src/sensible-defaults';
import {invalidTagTest} from './mapping-model/mappings/src/invalid-tag';
import {missingPremapTest} from './mapping-model/mappings/src/missing-premap';
import {defaultGenericTest} from './mapping-model/mappings/src/default-generics';
import {genericTest} from './mapping-model/mappings/src/generics';
import {nestedTest} from './mapping-model/mappings/src/nested';
import {indexSignatureTest} from './mapping-model/mappings/src/index-signature';
import {impossibleUnionTest} from './mapping-model/mappings/src/impossible-union';
import {objectUnionTest} from './mapping-model/mappings/src/object-union';
import {sortableTagTest} from './mapping-model/mappings/src/sortable-tag';
import {enumTest} from './mapping-model/mappings/src/enum';
import {inheritedPropertyTest} from './mapping-model/mappings/src/inherited-property';
import {pairedTagsTest} from './mapping-model/mappings/src/paired-tags';
import {filterableTagTest} from './mapping-model/mappings/src/filterable-tag';
import {anyUnknownTest} from './mapping-model/mappings/src/any-unknown';
import {tagsIgnoreCaseTest} from './mapping-model/mappings/src/tags-ignore-case';
import {typeAliasTest} from './mapping-model/mappings/src/type-alias';
import {dateAndRangeTest} from './mapping-model/mappings/src/date';
import {typeOverridesTest} from './mapping-model/mappings/src/type-overrides';
process.on('unhandledRejection', (error: unknown) => {
if (error instanceof Error) {
void Logger.error('UNHANDLED REJECTION', error.stack);
}
process.exit(1);
});
const magAppInstance = new MapAggTest('mappings');
@suite(timeout(20000), slow(10000))
export class MappingSpec {
@test
async 'Any or unknown should create a dynamic field'() {
magAppInstance.testInterfaceAgainstPath(anyUnknownTest);
}
@test
async 'Filterable tag should add raw field to strings'() {
magAppInstance.testInterfaceAgainstPath(filterableTagTest);
}
@test
async 'Tags should be able to be paired'() {
magAppInstance.testInterfaceAgainstPath(pairedTagsTest);
}
@test
async 'Inherited properties should inherit tags'() {
magAppInstance.testInterfaceAgainstPath(inheritedPropertyTest);
}
@test
async 'Tags should ignore case'() {
magAppInstance.testInterfaceAgainstPath(tagsIgnoreCaseTest);
}
@test
async 'Emums should work'() {
// Known issue: Enums only use text
// https://gitlab.com/openstapps/core-tools/-/issues/46
magAppInstance.testInterfaceAgainstPath(enumTest);
}
@test
async 'Sortable tag should work'() {
magAppInstance.testInterfaceAgainstPath(sortableTagTest);
}
/*
https://gitlab.com/openstapps/core-tools/-/merge_requests/29
@test
async 'Wrapper types should inherit tags'() {
this.testInterfaceAgainstPath(typeWrapperInheritanceTest);
}*/
@test
async 'Inherit tags tag should work'() {
magAppInstance.testInterfaceAgainstPath(inheritTagsTest);
}
@test
async 'Object union types should work'() {
magAppInstance.testInterfaceAgainstPath(objectUnionTest);
}
/*
https://gitlab.com/openstapps/core-tools/-/issues/47
@test
async 'Type queries should work'() {
magAppInstance.testInterfaceAgainstPath(typeQueryTest);
}*/
@test
async 'Type alias annotations should work'(){
magAppInstance.testInterfaceAgainstPath(typeAliasTest);
}
@test
async 'Impossible union should cause an error'() {
magAppInstance.testInterfaceAgainstPath(impossibleUnionTest);
}
@test
async 'Index Signatures should work'() {
magAppInstance.testInterfaceAgainstPath(indexSignatureTest);
}
@test
async 'Nested properties should work'() {
magAppInstance.testInterfaceAgainstPath(nestedTest);
}
@test
async 'Generics should work'() {
magAppInstance.testInterfaceAgainstPath(genericTest);
}
@test
async 'Missing premap should cause an error'() {
magAppInstance.testInterfaceAgainstPath(missingPremapTest);
}
@test
async 'Default generics should fail (if they don\'t that\'s actually brilliant)'() {
magAppInstance.testInterfaceAgainstPath(defaultGenericTest);
}
@test
async 'Explicit type annotations should work'() {
magAppInstance.testInterfaceAgainstPath(mapExplicitTypesTest);
}
@test
async 'Double type annotations should cause an error'() {
magAppInstance.testInterfaceAgainstPath(doubleTypeConflictTest);
}
@test
async 'Incompatible type annotations should cause an error and use defaults'() {
magAppInstance.testInterfaceAgainstPath(incompatibleTypeTest);
}
@test
async 'Primitive types should have sensible defaults'() {
magAppInstance.testInterfaceAgainstPath(sensibleDefaultsTest);
}
@test
async 'Invalid tags should cause an error'() {
magAppInstance.testInterfaceAgainstPath(invalidTagTest);
}
@test
async 'Dates and date ranges should have the correct type'() {
magAppInstance.testInterfaceAgainstPath(dateAndRangeTest);
}
@test
async 'Premaps should support non-external types'() {
magAppInstance.testInterfaceAgainstPath(typeOverridesTest);
}
}

View File

@@ -1,492 +0,0 @@
/*
* Copyright (C) 2019 StApps
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {LightweightClassDefinition} from '../../src/uml/model/lightweight-class-definition';
import {LightweightDefinition} from '../../src/uml/model/lightweight-definition';
import {LightweightEnumDefinition} from '../../src/uml/model/lightweight-enum-definition';
export const generatedModel: Array<LightweightDefinition | LightweightClassDefinition | LightweightEnumDefinition> = [
{
name: 'TestClass',
type: 'class',
properties: [
{
name: 'test2',
optional: false,
inherited: false,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: true,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'T',
},
},
{
name: 'test4',
optional: false,
inherited: false,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: true,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'TestFirstUnion',
},
},
],
extendedDefinitions: [],
implementedDefinitions: [],
typeParameters: ['T'],
},
{
name: 'TestSecondClass',
type: 'class',
properties: [
{
name: 'test2',
optional: false,
inherited: true,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: true,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'string',
},
},
{
name: 'test4',
optional: false,
inherited: true,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: true,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'TestFirstUnion',
},
},
],
extendedDefinitions: ['TestClass'],
implementedDefinitions: [],
typeParameters: [],
},
{
name: 'TestFirstEnum',
values: ['TEST1', 'TEST2', 'TEST3'],
},
{
name: 'TestSecondEnum',
values: ['TEST1 = "one"', 'TEST2 = "two"', 'TEST3 = "three"'],
},
{
name: 'TestInterface',
type: 'interface',
properties: [
{
name: 'articleBody',
optional: false,
inherited: false,
type: {
hasTypeInformation: false,
isArray: true,
isLiteral: false,
isPrimitive: false,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [
{
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: true,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'string',
},
],
genericsTypes: [],
name: 'string',
},
},
{
name: 'categorySpecificValues',
optional: true,
inherited: false,
type: {
hasTypeInformation: false,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: false,
isReflection: true,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'object',
},
},
{
name: 'inputType',
optional: false,
inherited: false,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: true,
isPrimitive: false,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'multipleChoice',
},
},
{
name: 'maintainer',
optional: false,
inherited: false,
type: {
hasTypeInformation: false,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: true,
specificationTypes: [
{
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: true,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'TestThirdUnion',
},
{
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: true,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'TestFirstEnum',
},
],
genericsTypes: [],
name: '',
},
},
{
name: 'remainingAttendeeCapacity',
optional: true,
inherited: false,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: true,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'number',
},
},
{
name: 'test1',
optional: false,
inherited: false,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: false,
isReflection: false,
isTyped: true,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [
{
hasTypeInformation: false,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: true,
specificationTypes: [
{
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: true,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'TestThirdUnion',
},
{
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: true,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'TestFirstEnum',
},
],
genericsTypes: [],
name: '',
},
],
name: 'Array',
},
},
{
name: 'test2',
optional: false,
inherited: false,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: true,
isReflection: false,
isTyped: true,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [
{
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: true,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'string',
},
],
name: 'TestClass',
},
},
{
name: 'test3',
optional: false,
inherited: false,
type: {
hasTypeInformation: false,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: true,
specificationTypes: [
{
hasTypeInformation: true,
isArray: false,
isLiteral: true,
isPrimitive: false,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'test1',
},
{
hasTypeInformation: true,
isArray: false,
isLiteral: true,
isPrimitive: false,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'test2',
},
],
genericsTypes: [],
name: '',
},
},
{
name: 'test4',
optional: false,
inherited: false,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: true,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'TestSecondClass',
},
},
{
name: 'universityRole',
optional: false,
inherited: false,
type: {
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: true,
isReference: false,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [
{
hasTypeInformation: true,
isArray: false,
isLiteral: false,
isPrimitive: false,
isReference: true,
isReflection: false,
isTyped: false,
isTypeParameter: false,
isUnion: false,
specificationTypes: [],
genericsTypes: [],
name: 'TestFirstEnum',
},
],
genericsTypes: [],
name: 'keyof TestFirstEnum',
},
},
],
extendedDefinitions: [],
implementedDefinitions: [],
typeParameters: [],
},
{
name: 'TestSecondInterface',
type: 'interface',
properties: [],
extendedDefinitions: [],
implementedDefinitions: [],
typeParameters: [],
},
{
name: 'TestFirstUnion',
values: ['test1', 'test2'],
},
{
name: 'TestFourthUnion',
values: [],
},
{
name: 'TestSecondUnion',
values: ['test3'],
},
{
name: 'TestThirdUnion',
values: ['TestFirstUnion', 'TestSecondUnion'],
},
];

View File

@@ -16,6 +16,7 @@ import {TestFirstUnion} from './test-union';
export class TestClass<T> {
test2: T;
test4: TestFirstUnion;
constructor(type: T) {

View File

@@ -18,7 +18,7 @@ import {TestThirdUnion} from './test-union';
export interface TestInterface {
articleBody: string[];
categorySpecificValues?: { [s: string]: string };
categorySpecificValues?: {[s: string]: string};
inputType: 'multipleChoice';
maintainer: TestThirdUnion | TestFirstEnum;
remainingAttendeeCapacity?: number;

View File

@@ -19,6 +19,4 @@ export type TestSecondUnion = 'test3';
export type TestThirdUnion = TestFirstUnion | TestSecondUnion;
export type TestFourthUnion<T extends TestFirstUnion> = T extends TestFirstUnion
? TestFirstUnion
: never;
export type TestFourthUnion<T extends TestFirstUnion> = T extends TestFirstUnion ? TestFirstUnion : never;

Some files were not shown because too many files have changed in this diff Show More