Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add unhandled error to append useful information #2063

Merged
merged 5 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
}
Loading