From 6b411b9f25bab06a9f537c318e683a275f49117f Mon Sep 17 00:00:00 2001 From: Arthur Fiorette Date: Sat, 7 Sep 2024 00:04:57 -0300 Subject: [PATCH 1/5] code --- src/ChainNodeParser.ts | 8 ++++++-- src/Error/Errors.ts | 19 +++++++++++++++++++ src/SchemaGenerator.ts | 35 ++++++++++++++++++++++++++--------- ts-json-schema-generator.ts | 16 +++++++++++----- 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/src/ChainNodeParser.ts b/src/ChainNodeParser.ts index 069b6fa00..3ff25d82a 100644 --- a/src/ChainNodeParser.ts +++ b/src/ChainNodeParser.ts @@ -1,5 +1,5 @@ import type ts from "typescript"; -import { UnknownNodeError } from "./Error/Errors.js"; +import { UnhandledError, UnknownNodeError } from "./Error/Errors.js"; import type { MutableParser } from "./MutableParser.js"; import type { Context } from "./NodeParser.js"; import type { SubNodeParser } from "./SubNodeParser.js"; @@ -32,7 +32,11 @@ export class ChainNodeParser implements SubNodeParser, MutableParser { const contextCacheKey = context.getCacheKey(); let type = typeCache.get(contextCacheKey); if (!type) { - type = this.getNodeParser(node).createType(node, context, reference); + try { + type = this.getNodeParser(node).createType(node, context, reference); + } catch (error) { + throw UnhandledError.from("Unhandled error while creating Base Type.", node, error); + } if (!(type instanceof ReferenceType)) { typeCache.set(contextCacheKey, type); } diff --git a/src/Error/Errors.ts b/src/Error/Errors.ts index 2ef3b78cd..a5e2a1f31 100644 --- a/src/Error/Errors.ts +++ b/src/Error/Errors.ts @@ -102,3 +102,22 @@ export class BuildError extends BaseError { }); } } + +export class UnhandledError extends BaseError { + private constructor( + messageText: string, + node?: ts.Node, + readonly cause?: unknown, + ) { + super({ code: 109, messageText, node }); + } + + /** + * Creates a new error with a cause and optional node information and ensures it is not wrapped in another UnhandledError + */ + static from(message: string, node?: ts.Node, cause?: unknown) { + // This might be called deeply inside the parser chain + // this ensures it doesn't end up with error.cause.cause.cause... + return cause instanceof UnhandledError ? cause : new UnhandledError(message, node, cause); + } +} diff --git a/src/SchemaGenerator.ts b/src/SchemaGenerator.ts index 910edc3f3..08475e56f 100644 --- a/src/SchemaGenerator.ts +++ b/src/SchemaGenerator.ts @@ -1,6 +1,6 @@ import ts from "typescript"; import type { Config } from "./Config.js"; -import { MultipleDefinitionsError, RootlessError } from "./Error/Errors.js"; +import { FailedTypeCreation, MultipleDefinitionsError, RootlessError, UnhandledError } from "./Error/Errors.js"; import { Context, type NodeParser } from "./NodeParser.js"; import type { Definition } from "./Schema/Definition.js"; import type { Schema } from "./Schema/Schema.js"; @@ -26,15 +26,25 @@ export class SchemaGenerator { } public createSchemaFromNodes(rootNodes: ts.Node[]): Schema { - const rootTypes = rootNodes.map((rootNode) => { - return this.nodeParser.createType(rootNode, new Context()); - }); + const roots = rootNodes.map((rootNode) => ({ + rootNode: rootNode, + rootType: this.nodeParser.createType(rootNode, new Context()), + })); - const rootTypeDefinition = rootTypes.length === 1 ? this.getRootTypeDefinition(rootTypes[0]) : undefined; + const rootTypeDefinition = + roots.length === 1 ? this.getRootTypeDefinition(roots[0].rootType, roots[0].rootNode) : undefined; const definitions: StringMap = {}; - for (const rootType of rootTypes) { - this.appendRootChildDefinitions(rootType, definitions); + for (const root of roots) { + try { + this.appendRootChildDefinitions(root.rootType, definitions); + } catch (error) { + throw UnhandledError.from( + "Unhandled error while appending Child Type Definition.", + root.rootNode, + error, + ); + } } const reachableDefinitions = removeUnreachable(rootTypeDefinition, definitions); @@ -60,6 +70,7 @@ export class SchemaGenerator { this.appendTypes(rootSourceFiles, this.program.getTypeChecker(), rootNodes); return [...rootNodes.values()]; } + protected findNamedNode(fullName: string): ts.Node { const typeChecker = this.program.getTypeChecker(); const allTypes = new Map(); @@ -79,9 +90,15 @@ export class SchemaGenerator { throw new RootlessError(fullName); } - protected getRootTypeDefinition(rootType: BaseType): Definition { - return this.typeFormatter.getDefinition(rootType); + + protected getRootTypeDefinition(rootType: BaseType, rootNode: ts.Node): Definition { + try { + return this.typeFormatter.getDefinition(rootType); + } catch (error) { + throw UnhandledError.from("Unhandled error while creating Root Type Definition.", rootNode, error); + } } + protected appendRootChildDefinitions(rootType: BaseType, childDefinitions: StringMap): void { const seen = new Set(); diff --git a/ts-json-schema-generator.ts b/ts-json-schema-generator.ts index ea63ac28b..953929041 100644 --- a/ts-json-schema-generator.ts +++ b/ts-json-schema-generator.ts @@ -86,13 +86,19 @@ try { writeFileSync(args.out, schemaString); } else { // write to stdout - process.stdout.write(`${schemaString}\n`); + console.log(`${schemaString}\n`); } } catch (error) { if (error instanceof BaseError) { - process.stderr.write(error.format()); - process.exit(1); - } + console.error(error.format()); + + if (error.cause) { + console.error(error.cause); + } - throw error; + // Maybe we are being imported by another script + process.exitCode = 1; + } else { + throw error; + } } From b92a38440bc2f16327f6a28a8abb22d858ac6020 Mon Sep 17 00:00:00 2001 From: Arthur Fiorette Date: Sat, 7 Sep 2024 00:05:52 -0300 Subject: [PATCH 2/5] code --- src/Error/Errors.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Error/Errors.ts b/src/Error/Errors.ts index a5e2a1f31..70539485b 100644 --- a/src/Error/Errors.ts +++ b/src/Error/Errors.ts @@ -1,7 +1,7 @@ +import type { JSONSchema7 } from "json-schema"; import ts from "typescript"; -import { type PartialDiagnostic, BaseError } from "./BaseError.js"; import type { BaseType } from "../Type/BaseType.js"; -import type { JSONSchema7 } from "json-schema"; +import { BaseError, type PartialDiagnostic } from "./BaseError.js"; export class UnknownNodeError extends BaseError { constructor(readonly node: ts.Node) { From f5ebddab2a4481a5a28f8975af3ed7b653dfe4e7 Mon Sep 17 00:00:00 2001 From: Arthur Fiorette Date: Sat, 7 Sep 2024 00:12:34 -0300 Subject: [PATCH 3/5] code --- src/NodeParser/BooleanTypeNodeParser.ts | 2 ++ src/SchemaGenerator.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/NodeParser/BooleanTypeNodeParser.ts b/src/NodeParser/BooleanTypeNodeParser.ts index 718172d49..1462b3128 100644 --- a/src/NodeParser/BooleanTypeNodeParser.ts +++ b/src/NodeParser/BooleanTypeNodeParser.ts @@ -9,6 +9,8 @@ export class BooleanTypeNodeParser implements SubNodeParser { return node.kind === ts.SyntaxKind.BooleanKeyword; } public createType(node: ts.KeywordTypeNode, context: Context): BaseType { + let a = undefined; + a.toString(); return new BooleanType(); } } diff --git a/src/SchemaGenerator.ts b/src/SchemaGenerator.ts index 08475e56f..7c990d479 100644 --- a/src/SchemaGenerator.ts +++ b/src/SchemaGenerator.ts @@ -1,6 +1,6 @@ import ts from "typescript"; import type { Config } from "./Config.js"; -import { FailedTypeCreation, MultipleDefinitionsError, RootlessError, UnhandledError } from "./Error/Errors.js"; +import { MultipleDefinitionsError, RootlessError, UnhandledError } from "./Error/Errors.js"; import { Context, type NodeParser } from "./NodeParser.js"; import type { Definition } from "./Schema/Definition.js"; import type { Schema } from "./Schema/Schema.js"; From 9d675d317e9b157192b29c0322250c8bb411428f Mon Sep 17 00:00:00 2001 From: Arthur Fiorette Date: Sat, 7 Sep 2024 00:15:45 -0300 Subject: [PATCH 4/5] code --- src/NodeParser/BooleanTypeNodeParser.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/NodeParser/BooleanTypeNodeParser.ts b/src/NodeParser/BooleanTypeNodeParser.ts index 1462b3128..718172d49 100644 --- a/src/NodeParser/BooleanTypeNodeParser.ts +++ b/src/NodeParser/BooleanTypeNodeParser.ts @@ -9,8 +9,6 @@ export class BooleanTypeNodeParser implements SubNodeParser { return node.kind === ts.SyntaxKind.BooleanKeyword; } public createType(node: ts.KeywordTypeNode, context: Context): BaseType { - let a = undefined; - a.toString(); return new BooleanType(); } } From 403e019da36c2b6c015fc7bcd404ffb5c613d5f9 Mon Sep 17 00:00:00 2001 From: Arthur Fiorette Date: Sat, 7 Sep 2024 00:53:09 -0300 Subject: [PATCH 5/5] code --- src/Error/Errors.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Error/Errors.ts b/src/Error/Errors.ts index 70539485b..ef4161f5d 100644 --- a/src/Error/Errors.ts +++ b/src/Error/Errors.ts @@ -118,6 +118,6 @@ export class UnhandledError extends BaseError { static from(message: string, node?: ts.Node, cause?: unknown) { // This might be called deeply inside the parser chain // this ensures it doesn't end up with error.cause.cause.cause... - return cause instanceof UnhandledError ? cause : new UnhandledError(message, node, cause); + return cause instanceof BaseError ? cause : new UnhandledError(message, node, cause); } }