Skip to content

Commit

Permalink
feat: add unhandled error to append useful information (#2063)
Browse files Browse the repository at this point in the history
  • Loading branch information
arthurfiorette authored Sep 7, 2024
1 parent b456603 commit d69f592
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 18 deletions.
8 changes: 6 additions & 2 deletions src/ChainNodeParser.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
}
Expand Down
23 changes: 21 additions & 2 deletions src/Error/Errors.ts
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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 BaseError ? cause : new UnhandledError(message, node, cause);
}
}
35 changes: 26 additions & 9 deletions src/SchemaGenerator.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ts from "typescript";
import type { Config } from "./Config.js";
import { MultipleDefinitionsError, RootlessError } 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";
Expand All @@ -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<Definition> = {};

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);
Expand All @@ -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<string, ts.Node>();
Expand All @@ -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<Definition>): void {
const seen = new Set<string>();

Expand Down
16 changes: 11 additions & 5 deletions ts-json-schema-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

0 comments on commit d69f592

Please sign in to comment.