diff --git a/factory/program.ts b/factory/program.ts index 3e7ffd1e0..cf8f9402d 100644 --- a/factory/program.ts +++ b/factory/program.ts @@ -1,44 +1,47 @@ import * as glob from "glob"; -import * as path from "path"; -import ts from "typescript"; +import * as path from "node:path"; import normalize from "normalize-path"; - -import { CompletedConfig, Config } from "../src/Config.js"; -import { DiagnosticError } from "../src/Error/DiagnosticError.js"; -import { LogicError } from "../src/Error/LogicError.js"; -import { NoRootNamesError } from "../src/Error/NoRootNamesError.js"; -import { NoTSConfigError } from "../src/Error/NoTSConfigError.js"; +import ts from "typescript"; +import type { CompletedConfig, Config } from "../src/Config.js"; +import { BuildError } from "../src/Error/Errors.js"; function loadTsConfigFile(configFile: string) { const raw = ts.sys.readFile(configFile); - if (raw) { - const config = ts.parseConfigFileTextToJson(configFile, raw); - if (config.error) { - throw new DiagnosticError([config.error]); - } else if (!config.config) { - throw new LogicError(`Invalid parsed config file "${configFile}"`); - } + if (!raw) { + throw new BuildError({ + messageText: `Cannot read config file "${configFile}"`, + }); + } - const parseResult = ts.parseJsonConfigFileContent( - config.config, - ts.sys, - path.resolve(path.dirname(configFile)), - {}, - configFile, - ); - parseResult.options.noEmit = true; - delete parseResult.options.out; - delete parseResult.options.outDir; - delete parseResult.options.outFile; - delete parseResult.options.declaration; - delete parseResult.options.declarationDir; - delete parseResult.options.declarationMap; + const config = ts.parseConfigFileTextToJson(configFile, raw); - return parseResult; - } else { - throw new NoTSConfigError(); + if (config.error) { + throw new BuildError(config.error); } + + if (!config.config) { + throw new BuildError({ + messageText: `Invalid parsed config file "${configFile}"`, + }); + } + + const parseResult = ts.parseJsonConfigFileContent( + config.config, + ts.sys, + path.resolve(path.dirname(configFile)), + {}, + configFile, + ); + parseResult.options.noEmit = true; + delete parseResult.options.out; + delete parseResult.options.outDir; + delete parseResult.options.outFile; + delete parseResult.options.declaration; + delete parseResult.options.declarationDir; + delete parseResult.options.declarationMap; + + return parseResult; } function getTsConfig(config: Config) { @@ -67,15 +70,21 @@ export function createProgram(config: CompletedConfig): ts.Program { const rootNames = rootNamesFromPath.length ? rootNamesFromPath : tsconfig.fileNames; if (!rootNames.length) { - throw new NoRootNamesError(); + throw new BuildError({ + messageText: "No input files", + }); } const program: ts.Program = ts.createProgram(rootNames, tsconfig.options); if (!config.skipTypeCheck) { const diagnostics = ts.getPreEmitDiagnostics(program); + if (diagnostics.length) { - throw new DiagnosticError(diagnostics); + throw new BuildError({ + messageText: "Type check error", + relatedInformation: [...diagnostics], + }); } } diff --git a/index.ts b/index.ts index 0ec7cf8c6..f4a723d42 100644 --- a/index.ts +++ b/index.ts @@ -1,20 +1,12 @@ export * from "./src/Error/BaseError.js"; -export * from "./src/Error/DiagnosticError.js"; -export * from "./src/Error/LogicError.js"; -export * from "./src/Error/NoRootNamesError.js"; -export * from "./src/Error/NoRootTypeError.js"; -export * from "./src/Error/NoTSConfigError.js"; -export * from "./src/Error/UnknownNodeError.js"; -export * from "./src/Error/UnknownTypeError.js"; +export * from "./src/Error/Errors.js"; export * from "./src/Config.js"; export * from "./src/Utils/allOfDefinition.js"; -export * from "./src/Utils/assert.js"; export * from "./src/Utils/deepMerge.js"; export * from "./src/Utils/derefType.js"; export * from "./src/Utils/extractLiterals.js"; -export * from "./src/Utils/formatError.js"; export * from "./src/Utils/hasJsDocTag.js"; export * from "./src/Utils/intersectionOfArrays.js"; export * from "./src/Utils/isAssignableTo.js"; diff --git a/src/ChainNodeParser.ts b/src/ChainNodeParser.ts index bd8c2c409..069b6fa00 100644 --- a/src/ChainNodeParser.ts +++ b/src/ChainNodeParser.ts @@ -1,9 +1,9 @@ -import ts from "typescript"; -import { UnknownNodeError } from "./Error/UnknownNodeError.js"; -import { MutableParser } from "./MutableParser.js"; -import { Context } from "./NodeParser.js"; -import { SubNodeParser } from "./SubNodeParser.js"; -import { BaseType } from "./Type/BaseType.js"; +import type ts from "typescript"; +import { UnknownNodeError } from "./Error/Errors.js"; +import type { MutableParser } from "./MutableParser.js"; +import type { Context } from "./NodeParser.js"; +import type { SubNodeParser } from "./SubNodeParser.js"; +import type { BaseType } from "./Type/BaseType.js"; import { ReferenceType } from "./Type/ReferenceType.js"; export class ChainNodeParser implements SubNodeParser, MutableParser { @@ -32,7 +32,7 @@ export class ChainNodeParser implements SubNodeParser, MutableParser { const contextCacheKey = context.getCacheKey(); let type = typeCache.get(contextCacheKey); if (!type) { - type = this.getNodeParser(node, context).createType(node, context, reference); + type = this.getNodeParser(node).createType(node, context, reference); if (!(type instanceof ReferenceType)) { typeCache.set(contextCacheKey, type); } @@ -40,13 +40,13 @@ export class ChainNodeParser implements SubNodeParser, MutableParser { return type; } - protected getNodeParser(node: ts.Node, context: Context): SubNodeParser { + protected getNodeParser(node: ts.Node): SubNodeParser { for (const nodeParser of this.nodeParsers) { if (nodeParser.supportsNode(node)) { return nodeParser; } } - throw new UnknownNodeError(node, context.getReference()); + throw new UnknownNodeError(node); } } diff --git a/src/ChainTypeFormatter.ts b/src/ChainTypeFormatter.ts index 068e95f98..b19b02d23 100644 --- a/src/ChainTypeFormatter.ts +++ b/src/ChainTypeFormatter.ts @@ -1,8 +1,8 @@ -import { UnknownTypeError } from "./Error/UnknownTypeError.js"; -import { MutableTypeFormatter } from "./MutableTypeFormatter.js"; -import { Definition } from "./Schema/Definition.js"; -import { SubTypeFormatter } from "./SubTypeFormatter.js"; -import { BaseType } from "./Type/BaseType.js"; +import { UnknownTypeError } from "./Error/Errors.js"; +import type { MutableTypeFormatter } from "./MutableTypeFormatter.js"; +import type { Definition } from "./Schema/Definition.js"; +import type { SubTypeFormatter } from "./SubTypeFormatter.js"; +import type { BaseType } from "./Type/BaseType.js"; export class ChainTypeFormatter implements SubTypeFormatter, MutableTypeFormatter { public constructor(protected typeFormatters: SubTypeFormatter[]) {} diff --git a/src/Error/BaseError.ts b/src/Error/BaseError.ts index 1a247585a..24d46c6f4 100644 --- a/src/Error/BaseError.ts +++ b/src/Error/BaseError.ts @@ -1,5 +1,63 @@ +import ts from "typescript"; + +export type PartialDiagnostic = Omit & { + file?: ts.SourceFile; + start?: number; + length?: number; + + /** If we should populate `file`, `source`, `start` and `length` with this node information */ + node?: ts.Node; + + /** @default Error */ + category?: ts.DiagnosticCategory; +}; + +const isTTY = process.env.TTY || process.stdout.isTTY; + +/** + * Base error for ts-json-schema-generator + */ export abstract class BaseError extends Error { - public constructor(message?: string) { - super(message); + readonly diagnostic: ts.Diagnostic; + + constructor(diagnostic: PartialDiagnostic) { + super(ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")); + this.diagnostic = BaseError.createDiagnostic(diagnostic); + } + + static createDiagnostic(diagnostic: PartialDiagnostic): ts.Diagnostic { + // Swap the node for the file, source, start and length properties + // sourceless nodes cannot be referenced in the diagnostic + if (diagnostic.node && diagnostic.node.pos !== -1) { + diagnostic.file = diagnostic.node.getSourceFile(); + diagnostic.start = diagnostic.node.getStart(); + diagnostic.length = diagnostic.node.getWidth(); + + diagnostic.node = undefined; + } + + // @ts-expect-error - Differentiates from errors from the TypeScript compiler + // error TSJ - 100: message + diagnostic.code = `J - ${diagnostic.code}`; + + return Object.assign( + { + category: ts.DiagnosticCategory.Error, + file: undefined, + length: 0, + start: 0, + }, + diagnostic, + ); + } + + format() { + const formatter = isTTY ? ts.formatDiagnosticsWithColorAndContext : ts.formatDiagnostics; + + return formatter([this.diagnostic], { + getCanonicalFileName: (fileName) => fileName, + getCurrentDirectory: () => "", + getNewLine: () => "\n", + }); } } diff --git a/src/Error/DiagnosticError.ts b/src/Error/DiagnosticError.ts deleted file mode 100644 index 2c8e00608..000000000 --- a/src/Error/DiagnosticError.ts +++ /dev/null @@ -1,14 +0,0 @@ -import ts from "typescript"; -import { BaseError } from "./BaseError.js"; - -export class DiagnosticError extends BaseError { - public constructor(private diagnostics: readonly ts.Diagnostic[]) { - super( - diagnostics.map((diagnostic) => ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n")).join("\n\n"), - ); - } - - public getDiagnostics(): readonly ts.Diagnostic[] { - return this.diagnostics; - } -} diff --git a/src/Error/Errors.ts b/src/Error/Errors.ts new file mode 100644 index 000000000..d0c11f80c --- /dev/null +++ b/src/Error/Errors.ts @@ -0,0 +1,103 @@ +import ts from "typescript"; +import { type PartialDiagnostic, BaseError } from "./BaseError.js"; +import type { BaseType } from "../Type/BaseType.js"; +import type { JSONSchema7 } from "json-schema"; + +export class UnknownNodeError extends BaseError { + constructor(readonly node: ts.Node) { + super({ + code: 100, + node, + messageText: `Unknown node of kind "${ts.SyntaxKind[node.kind]}"`, + }); + } +} + +export class UnknownTypeError extends BaseError { + constructor(readonly type: BaseType) { + super({ + code: 101, + messageText: `Unknown type "${type.getId()}"`, + }); + } +} + +export class RootlessError extends BaseError { + constructor(readonly fullName: string) { + super({ + code: 102, + messageText: `No root type "${fullName}" found`, + }); + } +} + +export class MultipleDefinitionsError extends BaseError { + constructor( + readonly name: string, + readonly defA: BaseType, + readonly defB?: BaseType, + ) { + super({ + code: 103, + messageText: `Type "${name}" has multiple definitions.`, + }); + } +} + +export class LogicError extends BaseError { + constructor( + readonly node: ts.Node, + messageText: string, + ) { + super({ + code: 104, + messageText, + node, + }); + } +} + +export class ExpectationFailedError extends BaseError { + constructor( + messageText: string, + readonly node?: ts.Node, + ) { + super({ + code: 105, + messageText, + node, + }); + } +} + +export class JsonTypeError extends BaseError { + constructor( + messageText: string, + readonly type: BaseType, + ) { + super({ + code: 106, + messageText, + }); + } +} + +export class DefinitionError extends BaseError { + constructor( + messageText: string, + readonly definition: JSONSchema7, + ) { + super({ + code: 107, + messageText, + }); + } +} +export class BuildError extends BaseError { + constructor(diag: Omit) { + super({ + code: 108, + ...diag, + }); + } +} diff --git a/src/Error/LogicError.ts b/src/Error/LogicError.ts deleted file mode 100644 index 6288e444a..000000000 --- a/src/Error/LogicError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { BaseError } from "./BaseError.js"; - -export class LogicError extends BaseError { - public constructor(private msg: string) { - super(msg); - } -} diff --git a/src/Error/NoRootNamesError.ts b/src/Error/NoRootNamesError.ts deleted file mode 100644 index 12524b987..000000000 --- a/src/Error/NoRootNamesError.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BaseError } from "./BaseError.js"; - -export class NoRootNamesError extends BaseError { - public get name(): string { - return "NoRootNamesError"; - } - public get message(): string { - return `No source files found`; - } -} diff --git a/src/Error/NoRootTypeError.ts b/src/Error/NoRootTypeError.ts deleted file mode 100644 index 60ad8a021..000000000 --- a/src/Error/NoRootTypeError.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { BaseError } from "./BaseError.js"; - -export class NoRootTypeError extends BaseError { - public constructor(private type: string) { - super(`No root type "${type}" found`); - } - - public getType(): string { - return this.type; - } -} diff --git a/src/Error/NoTSConfigError.ts b/src/Error/NoTSConfigError.ts deleted file mode 100644 index fdeaa5724..000000000 --- a/src/Error/NoTSConfigError.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { BaseError } from "./BaseError.js"; - -export class NoTSConfigError extends BaseError { - public get name(): string { - return "NoTSConfigError"; - } - public get message(): string { - return `No tsconfig file found`; - } -} diff --git a/src/Error/UnknownNodeError.ts b/src/Error/UnknownNodeError.ts deleted file mode 100644 index f8ea184fc..000000000 --- a/src/Error/UnknownNodeError.ts +++ /dev/null @@ -1,23 +0,0 @@ -import ts from "typescript"; -import { BaseError } from "./BaseError.js"; - -export class UnknownNodeError extends BaseError { - public constructor( - private node: ts.Node, - private reference?: ts.Node, - ) { - super( - `Unknown node "${node.getSourceFile() ? node.getFullText() : ""}" of kind "${ - ts.SyntaxKind[node.kind] - }"`, - ); - } - - public getNode(): ts.Node { - return this.node; - } - - public getReference(): ts.Node | undefined { - return this.reference; - } -} diff --git a/src/Error/UnknownTypeError.ts b/src/Error/UnknownTypeError.ts deleted file mode 100644 index fec91fc18..000000000 --- a/src/Error/UnknownTypeError.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { BaseType } from "../Type/BaseType.js"; -import { BaseError } from "./BaseError.js"; - -export class UnknownTypeError extends BaseError { - public constructor(private type: BaseType) { - super(`Unknown type "${type.getId()}"`); - } - - public getType(): BaseType { - return this.type; - } -} diff --git a/src/NodeParser/IndexedAccessTypeNodeParser.ts b/src/NodeParser/IndexedAccessTypeNodeParser.ts index 2544ae1c5..d7a481350 100644 --- a/src/NodeParser/IndexedAccessTypeNodeParser.ts +++ b/src/NodeParser/IndexedAccessTypeNodeParser.ts @@ -1,8 +1,7 @@ import ts from "typescript"; -import { LogicError } from "../Error/LogicError.js"; -import { Context, NodeParser } from "../NodeParser.js"; -import { SubNodeParser } from "../SubNodeParser.js"; -import { BaseType } from "../Type/BaseType.js"; +import type { Context, NodeParser } from "../NodeParser.js"; +import type { SubNodeParser } from "../SubNodeParser.js"; +import type { BaseType } from "../Type/BaseType.js"; import { LiteralType } from "../Type/LiteralType.js"; import { NeverType } from "../Type/NeverType.js"; import { NumberType } from "../Type/NumberType.js"; @@ -12,6 +11,7 @@ import { TupleType } from "../Type/TupleType.js"; import { UnionType } from "../Type/UnionType.js"; import { derefType } from "../Utils/derefType.js"; import { getTypeByKey } from "../Utils/typeKeys.js"; +import { LogicError } from "../Error/Errors.js"; export class IndexedAccessTypeNodeParser implements SubNodeParser { public constructor( @@ -62,6 +62,7 @@ export class IndexedAccessTypeNodeParser implements SubNodeParser { const propertyTypes = indexTypes.map((type) => { if (!(type instanceof LiteralType || type instanceof StringType || type instanceof NumberType)) { throw new LogicError( + node, `Unexpected type "${type.getId()}" (expected "LiteralType.js" or "StringType.js" or "NumberType.js")`, ); } @@ -70,14 +71,17 @@ export class IndexedAccessTypeNodeParser implements SubNodeParser { if (!propertyType) { if (type instanceof NumberType && objectType instanceof TupleType) { return new UnionType(objectType.getTypes()); - } else if (type instanceof LiteralType) { + } + + if (type instanceof LiteralType) { if (objectType instanceof ReferenceType) { return objectType; } - throw new LogicError(`Invalid index "${type.getValue()}" in type "${objectType.getId()}"`); - } else { - throw new LogicError(`No additional properties in type "${objectType.getId()}"`); + + throw new LogicError(node, `Invalid index "${type.getValue()}" in type "${objectType.getId()}"`); } + + throw new LogicError(node, `No additional properties in type "${objectType.getId()}"`); } return propertyType; diff --git a/src/NodeParser/IntersectionNodeParser.ts b/src/NodeParser/IntersectionNodeParser.ts index d94416a34..d6f934aae 100644 --- a/src/NodeParser/IntersectionNodeParser.ts +++ b/src/NodeParser/IntersectionNodeParser.ts @@ -1,18 +1,19 @@ import ts from "typescript"; -import { Context, NodeParser } from "../NodeParser.js"; -import { SubNodeParser } from "../SubNodeParser.js"; -import { BaseType } from "../Type/BaseType.js"; +import { ExpectationFailedError } from "../Error/Errors.js"; +import type { Context, NodeParser } from "../NodeParser.js"; +import type { SubNodeParser } from "../SubNodeParser.js"; +import type { BaseType } from "../Type/BaseType.js"; import { IntersectionType } from "../Type/IntersectionType.js"; -import { PrimitiveType } from "../Type/PrimitiveType.js"; -import { UnionType } from "../Type/UnionType.js"; -import { derefType } from "../Utils/derefType.js"; -import { uniqueTypeArray } from "../Utils/uniqueTypeArray.js"; -import { UndefinedType } from "../Type/UndefinedType.js"; +import { LiteralType } from "../Type/LiteralType.js"; import { NeverType } from "../Type/NeverType.js"; import { ObjectType } from "../Type/ObjectType.js"; +import { PrimitiveType } from "../Type/PrimitiveType.js"; import { StringType } from "../Type/StringType.js"; -import { LiteralType } from "../Type/LiteralType.js"; +import { UndefinedType } from "../Type/UndefinedType.js"; +import { UnionType } from "../Type/UnionType.js"; import { isLiteralUnion } from "../TypeFormatter/LiteralUnionTypeFormatter.js"; +import { derefType } from "../Utils/derefType.js"; +import { uniqueTypeArray } from "../Utils/uniqueTypeArray.js"; export class IntersectionNodeParser implements SubNodeParser { public constructor( @@ -69,12 +70,13 @@ function derefAndFlattenUnions(type: BaseType): BaseType[] { export function translate(types: BaseType[]): BaseType { types = uniqueTypeArray(types); - if (types.length == 1) { + if (types.length === 1) { return types[0]; } const unions = types.map(derefAndFlattenUnions); const result: BaseType[] = []; + function process(i: number, t: BaseType[] = []) { for (const type of unions[i]) { let currentTypes = [...t, type]; @@ -101,13 +103,16 @@ export function translate(types: BaseType[]): BaseType { } } } + process(0); if (result.length === 1) { return result[0]; - } else if (result.length > 1) { + } + + if (result.length > 1) { return new UnionType(result); } - throw new Error("Could not translate intersection to union."); + throw new ExpectationFailedError("Could not translate intersection to union."); } diff --git a/src/NodeParser/IntrinsicNodeParser.ts b/src/NodeParser/IntrinsicNodeParser.ts index 9377dcfdd..2acaab4b2 100644 --- a/src/NodeParser/IntrinsicNodeParser.ts +++ b/src/NodeParser/IntrinsicNodeParser.ts @@ -1,10 +1,10 @@ import ts from "typescript"; -import { Context } from "../NodeParser.js"; -import { SubNodeParser } from "../SubNodeParser.js"; -import { BaseType } from "../Type/BaseType.js"; +import { LogicError } from "../Error/Errors.js"; +import type { Context } from "../NodeParser.js"; +import type { SubNodeParser } from "../SubNodeParser.js"; +import type { BaseType } from "../Type/BaseType.js"; import { LiteralType } from "../Type/LiteralType.js"; import { UnionType } from "../Type/UnionType.js"; -import assert from "../Utils/assert.js"; import { extractLiterals } from "../Utils/extractLiterals.js"; export const intrinsicMethods: Record string) | undefined> = { @@ -21,7 +21,11 @@ export class IntrinsicNodeParser implements SubNodeParser { public createType(node: ts.KeywordTypeNode, context: Context): BaseType { const methodName = getParentName(node); const method = intrinsicMethods[methodName]; - assert(method, `Unknown intrinsic method: ${methodName}`); + + if (!method) { + throw new LogicError(node, `Unknown intrinsic method: ${methodName}`); + } + const literals = extractLiterals(context.getArguments()[0]) .map(method) .map((literal) => new LiteralType(literal)); @@ -34,6 +38,10 @@ export class IntrinsicNodeParser implements SubNodeParser { function getParentName(node: ts.KeywordTypeNode): string { const parent = node.parent; - assert(ts.isTypeAliasDeclaration(parent), "Only intrinsics part of a TypeAliasDeclaration are supported."); + + if (!ts.isTypeAliasDeclaration(parent)) { + throw new LogicError(node, "Only intrinsics part of a TypeAliasDeclaration are supported."); + } + return parent.name.text; } diff --git a/src/NodeParser/MappedTypeNodeParser.ts b/src/NodeParser/MappedTypeNodeParser.ts index 44194cc17..b0856521f 100644 --- a/src/NodeParser/MappedTypeNodeParser.ts +++ b/src/NodeParser/MappedTypeNodeParser.ts @@ -1,5 +1,5 @@ import ts from "typescript"; -import { LogicError } from "../Error/LogicError.js"; +import { ExpectationFailedError } from "../Error/Errors.js"; import { Context, NodeParser } from "../NodeParser.js"; import { SubNodeParser } from "../SubNodeParser.js"; import { AnnotatedType } from "../Type/AnnotatedType.js"; @@ -43,10 +43,14 @@ export class MappedTypeNodeParser implements SubNodeParser { this.getProperties(node, keyListType, context), this.getAdditionalProperties(node, keyListType, context), ); - } else if (keyListType instanceof LiteralType) { + } + + if (keyListType instanceof LiteralType) { // Key type resolves to single known property return new ObjectType(id, [], this.getProperties(node, new UnionType([keyListType]), context), false); - } else if ( + } + + if ( keyListType instanceof StringType || keyListType instanceof NumberType || keyListType instanceof SymbolType @@ -78,17 +82,22 @@ export class MappedTypeNodeParser implements SubNodeParser { } } return resultType; - } else if (keyListType instanceof EnumType) { + } + + if (keyListType instanceof EnumType) { return new ObjectType(id, [], this.getValues(node, keyListType, context), false); - } else if (keyListType instanceof NeverType) { + } + + if (keyListType instanceof NeverType) { return new ObjectType(id, [], [], false); - } else { - throw new LogicError( - `Unexpected key type "${ - constraintType ? constraintType.getId() : constraintType - }" for type "${node.getText()}" (expected "UnionType" or "StringType")`, - ); } + + throw new ExpectationFailedError( + `Unexpected key type "${ + constraintType ? constraintType.getId() : constraintType + }" for this node. (expected "UnionType" or "StringType")`, + node, + ); } protected mapKey(node: ts.MappedTypeNode, rawKey: LiteralType, context: Context): BaseType { @@ -148,14 +157,15 @@ export class MappedTypeNodeParser implements SubNodeParser { context: Context, ): BaseType | boolean { const key = keyListType.getTypes().filter((type) => !(type instanceof LiteralType))[0]; + if (key) { return ( this.childNodeParser.createType(node.type!, this.createSubContext(node, key, context)) ?? this.additionalProperties ); - } else { - return this.additionalProperties; } + + return this.additionalProperties; } protected createSubContext( @@ -165,10 +175,10 @@ export class MappedTypeNodeParser implements SubNodeParser { ): Context { const subContext = new Context(node); - parentContext.getParameters().forEach((parentParameter) => { + for (const parentParameter of parentContext.getParameters()) { subContext.pushParameter(parentParameter); subContext.pushArgument(parentContext.getArgument(parentParameter)); - }); + } subContext.pushParameter(node.typeParameter.name.text); subContext.pushArgument(key); diff --git a/src/NodeParser/PrefixUnaryExpressionNodeParser.ts b/src/NodeParser/PrefixUnaryExpressionNodeParser.ts index 13e11ec12..6f5b7ae97 100644 --- a/src/NodeParser/PrefixUnaryExpressionNodeParser.ts +++ b/src/NodeParser/PrefixUnaryExpressionNodeParser.ts @@ -1,7 +1,8 @@ import ts from "typescript"; -import { Context, NodeParser } from "../NodeParser.js"; -import { SubNodeParser } from "../SubNodeParser.js"; -import { BaseType } from "../Type/BaseType.js"; +import { ExpectationFailedError } from "../Error/Errors.js"; +import type { Context, NodeParser } from "../NodeParser.js"; +import type { SubNodeParser } from "../SubNodeParser.js"; +import type { BaseType } from "../Type/BaseType.js"; import { LiteralType } from "../Type/LiteralType.js"; export class PrefixUnaryExpressionNodeParser implements SubNodeParser { @@ -13,6 +14,7 @@ export class PrefixUnaryExpressionNodeParser implements SubNodeParser { public createType(node: ts.PrefixUnaryExpression, context: Context): BaseType { const operand = this.childNodeParser.createType(node.operand, context); + if (operand instanceof LiteralType) { switch (node.operator) { case ts.SyntaxKind.PlusToken: @@ -23,13 +25,14 @@ export class PrefixUnaryExpressionNodeParser implements SubNodeParser { return new LiteralType(~operand.getValue()); case ts.SyntaxKind.ExclamationToken: return new LiteralType(!operand.getValue()); - default: - throw new Error(`Unsupported prefix unary operator: ${node.operator}`); } - } else { - throw new Error( - `Expected operand to be "LiteralType" but is "${operand ? operand.constructor.name : operand}"`, - ); + + throw new ExpectationFailedError("Unsupported prefix unary operator", node); } + + throw new ExpectationFailedError( + `Expected operand to be "LiteralType" but is "${operand ? operand.constructor.name : operand}"`, + node, + ); } } diff --git a/src/NodeParser/PromiseNodeParser.ts b/src/NodeParser/PromiseNodeParser.ts index 84d91468e..08320bff0 100644 --- a/src/NodeParser/PromiseNodeParser.ts +++ b/src/NodeParser/PromiseNodeParser.ts @@ -1,4 +1,5 @@ import ts from "typescript"; +import { ExpectationFailedError } from "../Error/Errors.js"; import { Context, type NodeParser } from "../NodeParser.js"; import type { SubNodeParser } from "../SubNodeParser.js"; import { AliasType } from "../Type/AliasType.js"; @@ -63,9 +64,7 @@ export class PromiseNodeParser implements SubNodeParser { const awaitedNode = this.typeChecker.typeToTypeNode(awaitedType, undefined, ts.NodeBuilderFlags.IgnoreErrors); if (!awaitedNode) { - throw new Error( - `Could not find awaited node for type ${node.pos === -1 ? "" : node.getText()}`, - ); + throw new ExpectationFailedError("Could not find awaited node", node); } const baseNode = this.childNodeParser.createType(awaitedNode, new Context(node)); @@ -87,7 +86,10 @@ export class PromiseNodeParser implements SubNodeParser { ) { if (ts.isExpressionWithTypeArguments(node)) { if (!ts.isHeritageClause(node.parent)) { - throw new Error("Expected ExpressionWithTypeArguments to have a HeritageClause parent"); + throw new ExpectationFailedError( + "Expected ExpressionWithTypeArguments to have a HeritageClause parent", + node.parent, + ); } return node.parent.parent.name?.getText(); diff --git a/src/NodeParser/StringTemplateLiteralNodeParser.ts b/src/NodeParser/StringTemplateLiteralNodeParser.ts index 0ecf642f4..78f1e80b2 100644 --- a/src/NodeParser/StringTemplateLiteralNodeParser.ts +++ b/src/NodeParser/StringTemplateLiteralNodeParser.ts @@ -1,12 +1,12 @@ import ts from "typescript"; -import { UnknownTypeError } from "../Error/UnknownTypeError.js"; -import { Context, NodeParser } from "../NodeParser.js"; -import { SubNodeParser } from "../SubNodeParser.js"; -import { BaseType } from "../Type/BaseType.js"; +import type { Context, NodeParser } from "../NodeParser.js"; +import type { SubNodeParser } from "../SubNodeParser.js"; +import type { BaseType } from "../Type/BaseType.js"; import { LiteralType } from "../Type/LiteralType.js"; import { StringType } from "../Type/StringType.js"; import { UnionType } from "../Type/UnionType.js"; import { extractLiterals } from "../Utils/extractLiterals.js"; +import { UnknownTypeError } from "../Error/Errors.js"; export class StringTemplateLiteralNodeParser implements SubNodeParser { public constructor(protected childNodeParser: NodeParser) {} diff --git a/src/NodeParser/TypeofNodeParser.ts b/src/NodeParser/TypeofNodeParser.ts index cd5ee6200..03009e11c 100644 --- a/src/NodeParser/TypeofNodeParser.ts +++ b/src/NodeParser/TypeofNodeParser.ts @@ -1,14 +1,14 @@ import ts from "typescript"; -import { LogicError } from "../Error/LogicError.js"; -import { Context, NodeParser } from "../NodeParser.js"; -import { SubNodeParser } from "../SubNodeParser.js"; -import { BaseType } from "../Type/BaseType.js"; +import type { Context, NodeParser } from "../NodeParser.js"; +import type { SubNodeParser } from "../SubNodeParser.js"; +import type { BaseType } from "../Type/BaseType.js"; import { ObjectType, ObjectProperty } from "../Type/ObjectType.js"; -import { ReferenceType } from "../Type/ReferenceType.js"; +import type { ReferenceType } from "../Type/ReferenceType.js"; import { getKey } from "../Utils/nodeKey.js"; import { LiteralType } from "../Type/LiteralType.js"; import { NeverType } from "../Type/NeverType.js"; import { FunctionType } from "../Type/FunctionType.js"; +import { LogicError } from "../Error/Errors.js"; export class TypeofNodeParser implements SubNodeParser { public constructor( @@ -32,10 +32,15 @@ export class TypeofNodeParser implements SubNodeParser { // avoids crashes on globalThis but we really shoulodn't try to make a schema for globalThis return new NeverType(); } - throw new LogicError(`No value declaration found for symbol "${symbol.name}"`); - } else if (ts.isEnumDeclaration(valueDec)) { + + throw new LogicError(node, `No value declaration found for symbol "${symbol.name}"`); + } + + if (ts.isEnumDeclaration(valueDec)) { return this.createObjectFromEnum(valueDec, context, reference); - } else if ( + } + + if ( ts.isVariableDeclaration(valueDec) || ts.isPropertySignature(valueDec) || ts.isPropertyDeclaration(valueDec) @@ -43,18 +48,26 @@ export class TypeofNodeParser implements SubNodeParser { let initializer: ts.Expression | undefined; if (valueDec.type) { return this.childNodeParser.createType(valueDec.type, context); - } else if ((initializer = (valueDec as ts.VariableDeclaration | ts.PropertyDeclaration)?.initializer)) { + } + + if ((initializer = (valueDec as ts.VariableDeclaration | ts.PropertyDeclaration)?.initializer)) { return this.childNodeParser.createType(initializer, context); } - } else if (ts.isClassDeclaration(valueDec)) { + } + + if (ts.isClassDeclaration(valueDec)) { return this.childNodeParser.createType(valueDec, context); - } else if (ts.isPropertyAssignment(valueDec)) { + } + + if (ts.isPropertyAssignment(valueDec)) { return this.childNodeParser.createType(valueDec.initializer, context); - } else if (valueDec.kind === ts.SyntaxKind.FunctionDeclaration) { + } + + if (valueDec.kind === ts.SyntaxKind.FunctionDeclaration) { return new FunctionType(valueDec); } - throw new LogicError(`Invalid type query "${valueDec.getFullText()}" (ts.SyntaxKind = ${valueDec.kind})`); + throw new LogicError(valueDec, `Invalid type query for this declaration. (ts.SyntaxKind = ${valueDec.kind})`); } protected createObjectFromEnum(node: ts.EnumDeclaration, context: Context, reference?: ReferenceType): ObjectType { @@ -67,6 +80,7 @@ export class TypeofNodeParser implements SubNodeParser { let type: BaseType | null = null; const properties = node.members.map((member) => { const name = member.name.getText(); + if (member.initializer) { type = this.childNodeParser.createType(member.initializer, context); } else if (type === null) { @@ -74,8 +88,9 @@ export class TypeofNodeParser implements SubNodeParser { } else if (type instanceof LiteralType && typeof type.getValue() === "number") { type = new LiteralType(+type.getValue() + 1); } else { - throw new LogicError(`Enum initializer missing for "${name}"`); + throw new LogicError(member.name, `Enum initializer missing for "${name}"`); } + return new ObjectProperty(name, type, true); }); diff --git a/src/SchemaGenerator.ts b/src/SchemaGenerator.ts index 2d3fbfcaf..c07a9e3e5 100644 --- a/src/SchemaGenerator.ts +++ b/src/SchemaGenerator.ts @@ -1,6 +1,6 @@ import ts from "typescript"; import type { Config } from "./Config.js"; -import { NoRootTypeError } from "./Error/NoRootTypeError.js"; +import { MultipleDefinitionsError, RootlessError } 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"; @@ -76,7 +76,7 @@ export class SchemaGenerator { return allTypes.get(fullName)!; } - throw new NoRootTypeError(fullName); + throw new RootlessError(fullName); } protected getRootTypeDefinition(rootType: BaseType): Definition { return this.typeFormatter.getDefinition(rootType); @@ -104,7 +104,11 @@ export class SchemaGenerator { const childId = child.getId().replace(/def-/g, ""); if (previousId && childId !== previousId) { - throw new Error(`Type "${name}" has multiple definitions.`); + throw new MultipleDefinitionsError( + name, + child, + children.find((c) => c.getId() === previousId), + ); } ids.set(name, childId); } diff --git a/src/Type/ReferenceType.ts b/src/Type/ReferenceType.ts index 74bd9946e..9cd54d87c 100644 --- a/src/Type/ReferenceType.ts +++ b/src/Type/ReferenceType.ts @@ -1,3 +1,4 @@ +import { JsonTypeError } from "../Error/Errors.js"; import { BaseType } from "./BaseType.js"; export class ReferenceType extends BaseType { @@ -9,8 +10,9 @@ export class ReferenceType extends BaseType { public getId(): string { if (this.id == null) { - throw new Error("Reference type ID not set yet"); + throw new JsonTypeError("Reference type ID not set yet", this); } + return this.id; } @@ -20,8 +22,9 @@ export class ReferenceType extends BaseType { public getName(): string { if (this.name == null) { - throw new Error("Reference type name not set yet"); + throw new JsonTypeError("Reference type name not set yet", this); } + return this.name; } @@ -31,8 +34,9 @@ export class ReferenceType extends BaseType { public getType(): BaseType { if (this.type == null) { - throw new Error("Reference type not set yet"); + throw new JsonTypeError("Reference type not set yet", this); } + return this.type; } diff --git a/src/TypeFormatter/AnnotatedTypeFormatter.ts b/src/TypeFormatter/AnnotatedTypeFormatter.ts index 06c18c8f7..885746727 100644 --- a/src/TypeFormatter/AnnotatedTypeFormatter.ts +++ b/src/TypeFormatter/AnnotatedTypeFormatter.ts @@ -1,9 +1,10 @@ -import { Definition } from "../Schema/Definition.js"; -import { SubTypeFormatter } from "../SubTypeFormatter.js"; +import { JsonTypeError } from "../Error/Errors.js"; +import type { Definition } from "../Schema/Definition.js"; +import type { SubTypeFormatter } from "../SubTypeFormatter.js"; import { AnnotatedType } from "../Type/AnnotatedType.js"; -import { BaseType } from "../Type/BaseType.js"; +import type { BaseType } from "../Type/BaseType.js"; import { UnionType } from "../Type/UnionType.js"; -import { TypeFormatter } from "../TypeFormatter.js"; +import type { TypeFormatter } from "../TypeFormatter.js"; import { derefType } from "../Utils/derefType.js"; export function makeNullable(def: Definition): Definition { @@ -55,13 +56,14 @@ export class AnnotatedTypeFormatter implements SubTypeFormatter { const annotations = type.getAnnotations(); if ("discriminator" in annotations) { - const derefed = derefType(type.getType()); - if (derefed instanceof UnionType) { - derefed.setDiscriminator(annotations.discriminator); + const deref = derefType(type.getType()); + if (deref instanceof UnionType) { + deref.setDiscriminator(annotations.discriminator); delete annotations.discriminator; } else { - throw new Error( - `Cannot assign discriminator tag to type: ${derefed.getName()}. This tag can only be assigned to union types.`, + throw new JsonTypeError( + `Cannot assign discriminator tag to type: ${deref.getName()}. This tag can only be assigned to union types.`, + deref, ); } } diff --git a/src/TypeFormatter/PrimitiveUnionTypeFormatter.ts b/src/TypeFormatter/PrimitiveUnionTypeFormatter.ts index 92b2cc65f..0ff73d3da 100644 --- a/src/TypeFormatter/PrimitiveUnionTypeFormatter.ts +++ b/src/TypeFormatter/PrimitiveUnionTypeFormatter.ts @@ -1,8 +1,8 @@ -import { LogicError } from "../Error/LogicError.js"; -import { Definition } from "../Schema/Definition.js"; -import { RawTypeName } from "../Schema/RawType.js"; -import { SubTypeFormatter } from "../SubTypeFormatter.js"; -import { BaseType } from "../Type/BaseType.js"; +import { JsonTypeError } from "../Error/Errors.js"; +import type { Definition } from "../Schema/Definition.js"; +import type { RawTypeName } from "../Schema/RawType.js"; +import type { SubTypeFormatter } from "../SubTypeFormatter.js"; +import type { BaseType } from "../Type/BaseType.js"; import { BooleanType } from "../Type/BooleanType.js"; import { NullType } from "../Type/NullType.js"; import { NumberType } from "../Type/NumberType.js"; @@ -27,17 +27,24 @@ export class PrimitiveUnionTypeFormatter implements SubTypeFormatter { protected isPrimitiveUnion(type: UnionType): boolean { return type.getTypes().every((item) => item instanceof PrimitiveType); } + protected getPrimitiveType(item: BaseType): RawTypeName { if (item instanceof StringType) { return "string"; - } else if (item instanceof NumberType) { + } + + if (item instanceof NumberType) { return "number"; - } else if (item instanceof BooleanType) { + } + + if (item instanceof BooleanType) { return "boolean"; - } else if (item instanceof NullType) { + } + + if (item instanceof NullType) { return "null"; } - throw new LogicError("Unexpected code branch"); + throw new JsonTypeError("Unexpected code branch", item); } } diff --git a/src/TypeFormatter/UnionTypeFormatter.ts b/src/TypeFormatter/UnionTypeFormatter.ts index 790f57436..adb5dcc0f 100644 --- a/src/TypeFormatter/UnionTypeFormatter.ts +++ b/src/TypeFormatter/UnionTypeFormatter.ts @@ -9,6 +9,7 @@ import { TypeFormatter } from "../TypeFormatter.js"; import { derefType } from "../Utils/derefType.js"; import { getTypeByKey } from "../Utils/typeKeys.js"; import { uniqueArray } from "../Utils/uniqueArray.js"; +import { JsonTypeError } from "../Error/Errors.js"; type DiscriminatorType = "json-schema" | "open-api"; @@ -27,10 +28,15 @@ export class UnionTypeFormatter implements SubTypeFormatter { .filter((item) => !(derefType(item) instanceof NeverType)) .map((item) => this.childTypeFormatter.getDefinition(item)); } + private getJsonSchemaDiscriminatorDefinition(type: UnionType): Definition { const definitions = this.getTypeDefinitions(type); const discriminator = type.getDiscriminator(); - if (!discriminator) throw new Error("discriminator is undefined"); + + if (!discriminator) { + throw new JsonTypeError("discriminator is undefined", type); + } + const kindTypes = type .getTypes() .filter((item) => !(derefType(item) instanceof NeverType)) @@ -38,9 +44,10 @@ export class UnionTypeFormatter implements SubTypeFormatter { const undefinedIndex = kindTypes.findIndex((item) => item === undefined); - if (undefinedIndex != -1) { - throw new Error( + if (undefinedIndex !== -1) { + throw new JsonTypeError( `Cannot find discriminator keyword "${discriminator}" in type ${type.getTypes()[undefinedIndex].getName()}.`, + type, ); } @@ -63,8 +70,9 @@ export class UnionTypeFormatter implements SubTypeFormatter { const duplicates = kindValues.filter((item, index) => kindValues.indexOf(item) !== index); if (duplicates.length > 0) { - throw new Error( + throw new JsonTypeError( `Duplicate discriminator values: ${duplicates.join(", ")} in type ${JSON.stringify(type.getName())}.`, + type, ); } @@ -79,7 +87,11 @@ export class UnionTypeFormatter implements SubTypeFormatter { private getOpenApiDiscriminatorDefinition(type: UnionType): Definition { const oneOf = this.getTypeDefinitions(type); const discriminator = type.getDiscriminator(); - if (!discriminator) throw new Error("discriminator is undefined"); + + if (!discriminator) { + throw new JsonTypeError("discriminator is undefined", type); + } + return { type: "object", discriminator: { propertyName: discriminator }, diff --git a/src/Utils/assert.ts b/src/Utils/assert.ts deleted file mode 100644 index 771380f5f..000000000 --- a/src/Utils/assert.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { LogicError } from "../Error/LogicError.js"; - -export default function assert(value: unknown, message: string): asserts value { - if (!value) { - throw new LogicError(message); - } -} diff --git a/src/Utils/extractLiterals.ts b/src/Utils/extractLiterals.ts index 20f9d283f..6d545ccb4 100644 --- a/src/Utils/extractLiterals.ts +++ b/src/Utils/extractLiterals.ts @@ -1,6 +1,6 @@ -import { UnknownTypeError } from "../Error/UnknownTypeError.js"; +import { UnknownTypeError } from "../Error/Errors.js"; import { AliasType } from "../Type/AliasType.js"; -import { BaseType } from "../Type/BaseType.js"; +import type { BaseType } from "../Type/BaseType.js"; import { BooleanType } from "../Type/BooleanType.js"; import { DefinitionType } from "../Type/DefinitionType.js"; import { EnumType } from "../Type/EnumType.js"; @@ -19,16 +19,20 @@ function* _extractLiterals(type: BaseType): Iterable { yield dereffedType.getValue().toString(); return; } + if (dereffedType instanceof UnionType || dereffedType instanceof EnumType) { for (const t of dereffedType.getTypes()) { yield* _extractLiterals(t); } + return; } + if (dereffedType instanceof AliasType || dereffedType instanceof DefinitionType) { yield* _extractLiterals(dereffedType.getType()); return; } + if (dereffedType instanceof BooleanType) { yield* _extractLiterals(new UnionType([new LiteralType("true"), new LiteralType("false")])); return; diff --git a/src/Utils/formatError.ts b/src/Utils/formatError.ts deleted file mode 100644 index 9c7d369d4..000000000 --- a/src/Utils/formatError.ts +++ /dev/null @@ -1,37 +0,0 @@ -import * as path from "path"; -import ts from "typescript"; - -import { BaseError } from "../Error/BaseError.js"; -import { DiagnosticError } from "../Error/DiagnosticError.js"; -import { UnknownNodeError } from "../Error/UnknownNodeError.js"; - -function getNodeLocation(node: ts.Node): [string, number, number] { - const sourceFile = node.getSourceFile(); - if (!sourceFile) { - return ["", 0, 0]; - } - - const lineAndChar: ts.LineAndCharacter = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart(sourceFile)); - return [sourceFile.fileName, lineAndChar.line + 1, lineAndChar.character]; -} - -export function formatError(error: BaseError): string { - if (error instanceof DiagnosticError) { - const rootDir: string = process.cwd().split(path.sep)[0] || "/"; - return ts.formatDiagnostics(error.getDiagnostics(), { - getCanonicalFileName: (fileName: string) => fileName, - getCurrentDirectory: () => rootDir, - getNewLine: () => "\n", - }); - } else if (error instanceof UnknownNodeError) { - const unknownNode: ts.Node = error.getReference() || error.getNode(); - const nodeFullText: string = unknownNode.getFullText().trim().split("\n")[0].trim(); - const [sourceFile, lineNumber, charPos]: [string, number, number] = getNodeLocation(unknownNode); - return ( - `${error.name}: Unknown node "${nodeFullText}" (ts.SyntaxKind = ${error.getNode().kind}) ` + - `at ${sourceFile}(${lineNumber},${charPos})\n` - ); - } - - return `${error.name}: ${error.message}\n`; -} diff --git a/src/Utils/removeUnreachable.ts b/src/Utils/removeUnreachable.ts index f8a4967fc..b8218d55a 100644 --- a/src/Utils/removeUnreachable.ts +++ b/src/Utils/removeUnreachable.ts @@ -1,6 +1,7 @@ -import { JSONSchema7Definition } from "json-schema"; -import { Definition } from "../Schema/Definition.js"; -import { StringMap } from "./StringMap.js"; +import type { JSONSchema7Definition } from "json-schema"; +import { DefinitionError } from "../Error/Errors.js"; +import type { Definition } from "../Schema/Definition.js"; +import type { StringMap } from "./StringMap.js"; const DEFINITION_OFFSET = "#/definitions/".length; @@ -21,9 +22,11 @@ function addReachable( } reachable.add(typeName); const refDefinition = definitions[typeName]; + if (!refDefinition) { - throw new Error(`Encountered a reference to a missing definition: "${definition.$ref}". This is a bug.`); + throw new DefinitionError("Encountered a reference to a missing definition, this is a bug.", definition); } + addReachable(refDefinition, definitions, reachable); } else if (definition.anyOf) { for (const def of definition.anyOf) { diff --git a/src/Utils/typeName.ts b/src/Utils/typeName.ts index f522b03e9..063ead8e9 100644 --- a/src/Utils/typeName.ts +++ b/src/Utils/typeName.ts @@ -1,4 +1,5 @@ -import { RawType, RawTypeName } from "../Schema/RawType.js"; +import { ExpectationFailedError } from "../Error/Errors.js"; +import type { RawType, RawTypeName } from "../Schema/RawType.js"; export function typeName(value: RawType): RawTypeName { if (value === null) { @@ -6,15 +7,18 @@ export function typeName(value: RawType): RawTypeName { } const type = typeof value; + if (type === "string" || type === "number" || type === "boolean") { return type; } if (Array.isArray(value)) { return "array"; - } else if (type === "object") { + } + + if (type === "object") { return "object"; - } else { - throw new Error(`JavaScript type "${type}" can't be converted to JSON type name`); } + + throw new ExpectationFailedError(`JavaScript type "typeof " can't be converted to JSON type name`); } diff --git a/test/unit/assert.test.ts b/test/unit/assert.test.ts deleted file mode 100644 index 91e5c7566..000000000 --- a/test/unit/assert.test.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { LogicError } from "../../src/Error/LogicError.js"; -import assert from "../../src/Utils/assert.js"; - -describe("validate assert", () => { - it.each` - value - ${"hello"} - ${1} - ${true} - ${{}} - `("success $value", ({ value }) => { - expect(() => assert(value, "message")).not.toThrow(); - }); - - it.each` - value - ${""} - ${0} - ${false} - ${undefined} - ${null} - `("fail $value", ({ value }) => { - expect(() => assert(value, "failed to be true")).toThrowError(LogicError); - }); -}); diff --git a/ts-json-schema-generator.ts b/ts-json-schema-generator.ts index 255d0e539..aea4a7e95 100644 --- a/ts-json-schema-generator.ts +++ b/ts-json-schema-generator.ts @@ -1,12 +1,10 @@ -import { mkdirSync, writeFileSync } from "node:fs"; -import { dirname } from "node:path"; import { Command, Option } from "commander"; +import fs, { mkdirSync, writeFileSync } from "node:fs"; +import { dirname } from "node:path"; import stableStringify from "safe-stable-stringify"; import { createGenerator } from "./factory/generator.js"; import type { Config } from "./src/Config.js"; import { BaseError } from "./src/Error/BaseError.js"; -import { formatError } from "./src/Utils/formatError.js"; -import fs from "node:fs"; const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")); @@ -92,9 +90,9 @@ try { } } catch (error) { if (error instanceof BaseError) { - process.stderr.write(formatError(error)); + process.stderr.write(error.format()); process.exit(1); - } else { - throw error; } + + throw error; }