Skip to content

Commit 49df2de

Browse files
feat: added diagnostics to all errors (#1963)
* fix: errors for sourceless nodes * initial promise support * revert * feat: final implementation * fix: imports * code * lint * code * fix: tests * expose internals * i was wrong * fix: entire check * fix: lint * type * somehow tests were getting this out of order * fix: removed unused method * feat: added unnamed class test * fix: test * documented promise unwrapping * Update src/NodeParser/FunctionNodeParser.ts Co-authored-by: Dominik Moritz <[email protected]> * fix: removed allowUnionTypes * chore: style * feat: code * fixes * non tty errors * remove useless test * fix: keep same error message * fix: remove TSJG prefixes * fix yarn * keep exact error message --------- Co-authored-by: Dominik Moritz <[email protected]>
1 parent 4ac21b2 commit 49df2de

33 files changed

+424
-333
lines changed

factory/program.ts

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,47 @@
11
import * as glob from "glob";
2-
import * as path from "path";
3-
import ts from "typescript";
2+
import * as path from "node:path";
43
import normalize from "normalize-path";
5-
6-
import { CompletedConfig, Config } from "../src/Config.js";
7-
import { DiagnosticError } from "../src/Error/DiagnosticError.js";
8-
import { LogicError } from "../src/Error/LogicError.js";
9-
import { NoRootNamesError } from "../src/Error/NoRootNamesError.js";
10-
import { NoTSConfigError } from "../src/Error/NoTSConfigError.js";
4+
import ts from "typescript";
5+
import type { CompletedConfig, Config } from "../src/Config.js";
6+
import { BuildError } from "../src/Error/Errors.js";
117

128
function loadTsConfigFile(configFile: string) {
139
const raw = ts.sys.readFile(configFile);
14-
if (raw) {
15-
const config = ts.parseConfigFileTextToJson(configFile, raw);
1610

17-
if (config.error) {
18-
throw new DiagnosticError([config.error]);
19-
} else if (!config.config) {
20-
throw new LogicError(`Invalid parsed config file "${configFile}"`);
21-
}
11+
if (!raw) {
12+
throw new BuildError({
13+
messageText: `Cannot read config file "${configFile}"`,
14+
});
15+
}
2216

23-
const parseResult = ts.parseJsonConfigFileContent(
24-
config.config,
25-
ts.sys,
26-
path.resolve(path.dirname(configFile)),
27-
{},
28-
configFile,
29-
);
30-
parseResult.options.noEmit = true;
31-
delete parseResult.options.out;
32-
delete parseResult.options.outDir;
33-
delete parseResult.options.outFile;
34-
delete parseResult.options.declaration;
35-
delete parseResult.options.declarationDir;
36-
delete parseResult.options.declarationMap;
17+
const config = ts.parseConfigFileTextToJson(configFile, raw);
3718

38-
return parseResult;
39-
} else {
40-
throw new NoTSConfigError();
19+
if (config.error) {
20+
throw new BuildError(config.error);
4121
}
22+
23+
if (!config.config) {
24+
throw new BuildError({
25+
messageText: `Invalid parsed config file "${configFile}"`,
26+
});
27+
}
28+
29+
const parseResult = ts.parseJsonConfigFileContent(
30+
config.config,
31+
ts.sys,
32+
path.resolve(path.dirname(configFile)),
33+
{},
34+
configFile,
35+
);
36+
parseResult.options.noEmit = true;
37+
delete parseResult.options.out;
38+
delete parseResult.options.outDir;
39+
delete parseResult.options.outFile;
40+
delete parseResult.options.declaration;
41+
delete parseResult.options.declarationDir;
42+
delete parseResult.options.declarationMap;
43+
44+
return parseResult;
4245
}
4346

4447
function getTsConfig(config: Config) {
@@ -67,15 +70,21 @@ export function createProgram(config: CompletedConfig): ts.Program {
6770
const rootNames = rootNamesFromPath.length ? rootNamesFromPath : tsconfig.fileNames;
6871

6972
if (!rootNames.length) {
70-
throw new NoRootNamesError();
73+
throw new BuildError({
74+
messageText: "No input files",
75+
});
7176
}
7277

7378
const program: ts.Program = ts.createProgram(rootNames, tsconfig.options);
7479

7580
if (!config.skipTypeCheck) {
7681
const diagnostics = ts.getPreEmitDiagnostics(program);
82+
7783
if (diagnostics.length) {
78-
throw new DiagnosticError(diagnostics);
84+
throw new BuildError({
85+
messageText: "Type check error",
86+
relatedInformation: [...diagnostics],
87+
});
7988
}
8089
}
8190

index.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
11
export * from "./src/Error/BaseError.js";
2-
export * from "./src/Error/DiagnosticError.js";
3-
export * from "./src/Error/LogicError.js";
4-
export * from "./src/Error/NoRootNamesError.js";
5-
export * from "./src/Error/NoRootTypeError.js";
6-
export * from "./src/Error/NoTSConfigError.js";
7-
export * from "./src/Error/UnknownNodeError.js";
8-
export * from "./src/Error/UnknownTypeError.js";
2+
export * from "./src/Error/Errors.js";
93

104
export * from "./src/Config.js";
115

126
export * from "./src/Utils/allOfDefinition.js";
13-
export * from "./src/Utils/assert.js";
147
export * from "./src/Utils/deepMerge.js";
158
export * from "./src/Utils/derefType.js";
169
export * from "./src/Utils/extractLiterals.js";
17-
export * from "./src/Utils/formatError.js";
1810
export * from "./src/Utils/hasJsDocTag.js";
1911
export * from "./src/Utils/intersectionOfArrays.js";
2012
export * from "./src/Utils/isAssignableTo.js";

src/ChainNodeParser.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import ts from "typescript";
2-
import { UnknownNodeError } from "./Error/UnknownNodeError.js";
3-
import { MutableParser } from "./MutableParser.js";
4-
import { Context } from "./NodeParser.js";
5-
import { SubNodeParser } from "./SubNodeParser.js";
6-
import { BaseType } from "./Type/BaseType.js";
1+
import type ts from "typescript";
2+
import { UnknownNodeError } from "./Error/Errors.js";
3+
import type { MutableParser } from "./MutableParser.js";
4+
import type { Context } from "./NodeParser.js";
5+
import type { SubNodeParser } from "./SubNodeParser.js";
6+
import type { BaseType } from "./Type/BaseType.js";
77
import { ReferenceType } from "./Type/ReferenceType.js";
88

99
export class ChainNodeParser implements SubNodeParser, MutableParser {
@@ -32,21 +32,21 @@ export class ChainNodeParser implements SubNodeParser, MutableParser {
3232
const contextCacheKey = context.getCacheKey();
3333
let type = typeCache.get(contextCacheKey);
3434
if (!type) {
35-
type = this.getNodeParser(node, context).createType(node, context, reference);
35+
type = this.getNodeParser(node).createType(node, context, reference);
3636
if (!(type instanceof ReferenceType)) {
3737
typeCache.set(contextCacheKey, type);
3838
}
3939
}
4040
return type;
4141
}
4242

43-
protected getNodeParser(node: ts.Node, context: Context): SubNodeParser {
43+
protected getNodeParser(node: ts.Node): SubNodeParser {
4444
for (const nodeParser of this.nodeParsers) {
4545
if (nodeParser.supportsNode(node)) {
4646
return nodeParser;
4747
}
4848
}
4949

50-
throw new UnknownNodeError(node, context.getReference());
50+
throw new UnknownNodeError(node);
5151
}
5252
}

src/ChainTypeFormatter.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
import { UnknownTypeError } from "./Error/UnknownTypeError.js";
2-
import { MutableTypeFormatter } from "./MutableTypeFormatter.js";
3-
import { Definition } from "./Schema/Definition.js";
4-
import { SubTypeFormatter } from "./SubTypeFormatter.js";
5-
import { BaseType } from "./Type/BaseType.js";
1+
import { UnknownTypeError } from "./Error/Errors.js";
2+
import type { MutableTypeFormatter } from "./MutableTypeFormatter.js";
3+
import type { Definition } from "./Schema/Definition.js";
4+
import type { SubTypeFormatter } from "./SubTypeFormatter.js";
5+
import type { BaseType } from "./Type/BaseType.js";
66

77
export class ChainTypeFormatter implements SubTypeFormatter, MutableTypeFormatter {
88
public constructor(protected typeFormatters: SubTypeFormatter[]) {}

src/Error/BaseError.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,63 @@
1+
import ts from "typescript";
2+
3+
export type PartialDiagnostic = Omit<ts.Diagnostic, "category" | "file" | "start" | "length"> & {
4+
file?: ts.SourceFile;
5+
start?: number;
6+
length?: number;
7+
8+
/** If we should populate `file`, `source`, `start` and `length` with this node information */
9+
node?: ts.Node;
10+
11+
/** @default Error */
12+
category?: ts.DiagnosticCategory;
13+
};
14+
15+
const isTTY = process.env.TTY || process.stdout.isTTY;
16+
17+
/**
18+
* Base error for ts-json-schema-generator
19+
*/
120
export abstract class BaseError extends Error {
2-
public constructor(message?: string) {
3-
super(message);
21+
readonly diagnostic: ts.Diagnostic;
22+
23+
constructor(diagnostic: PartialDiagnostic) {
24+
super(ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"));
25+
this.diagnostic = BaseError.createDiagnostic(diagnostic);
26+
}
27+
28+
static createDiagnostic(diagnostic: PartialDiagnostic): ts.Diagnostic {
29+
// Swap the node for the file, source, start and length properties
30+
// sourceless nodes cannot be referenced in the diagnostic
31+
if (diagnostic.node && diagnostic.node.pos !== -1) {
32+
diagnostic.file = diagnostic.node.getSourceFile();
33+
diagnostic.start = diagnostic.node.getStart();
34+
diagnostic.length = diagnostic.node.getWidth();
35+
36+
diagnostic.node = undefined;
37+
}
38+
39+
// @ts-expect-error - Differentiates from errors from the TypeScript compiler
40+
// error TSJ - 100: message
41+
diagnostic.code = `J - ${diagnostic.code}`;
42+
43+
return Object.assign(
44+
{
45+
category: ts.DiagnosticCategory.Error,
46+
file: undefined,
47+
length: 0,
48+
start: 0,
49+
},
50+
diagnostic,
51+
);
52+
}
53+
54+
format() {
55+
const formatter = isTTY ? ts.formatDiagnosticsWithColorAndContext : ts.formatDiagnostics;
56+
57+
return formatter([this.diagnostic], {
58+
getCanonicalFileName: (fileName) => fileName,
59+
getCurrentDirectory: () => "",
60+
getNewLine: () => "\n",
61+
});
462
}
563
}

src/Error/DiagnosticError.ts

Lines changed: 0 additions & 14 deletions
This file was deleted.

src/Error/Errors.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import ts from "typescript";
2+
import { type PartialDiagnostic, BaseError } from "./BaseError.js";
3+
import type { BaseType } from "../Type/BaseType.js";
4+
import type { JSONSchema7 } from "json-schema";
5+
6+
export class UnknownNodeError extends BaseError {
7+
constructor(readonly node: ts.Node) {
8+
super({
9+
code: 100,
10+
node,
11+
messageText: `Unknown node of kind "${ts.SyntaxKind[node.kind]}"`,
12+
});
13+
}
14+
}
15+
16+
export class UnknownTypeError extends BaseError {
17+
constructor(readonly type: BaseType) {
18+
super({
19+
code: 101,
20+
messageText: `Unknown type "${type.getId()}"`,
21+
});
22+
}
23+
}
24+
25+
export class RootlessError extends BaseError {
26+
constructor(readonly fullName: string) {
27+
super({
28+
code: 102,
29+
messageText: `No root type "${fullName}" found`,
30+
});
31+
}
32+
}
33+
34+
export class MultipleDefinitionsError extends BaseError {
35+
constructor(
36+
readonly name: string,
37+
readonly defA: BaseType,
38+
readonly defB?: BaseType,
39+
) {
40+
super({
41+
code: 103,
42+
messageText: `Type "${name}" has multiple definitions.`,
43+
});
44+
}
45+
}
46+
47+
export class LogicError extends BaseError {
48+
constructor(
49+
readonly node: ts.Node,
50+
messageText: string,
51+
) {
52+
super({
53+
code: 104,
54+
messageText,
55+
node,
56+
});
57+
}
58+
}
59+
60+
export class ExpectationFailedError extends BaseError {
61+
constructor(
62+
messageText: string,
63+
readonly node?: ts.Node,
64+
) {
65+
super({
66+
code: 105,
67+
messageText,
68+
node,
69+
});
70+
}
71+
}
72+
73+
export class JsonTypeError extends BaseError {
74+
constructor(
75+
messageText: string,
76+
readonly type: BaseType,
77+
) {
78+
super({
79+
code: 106,
80+
messageText,
81+
});
82+
}
83+
}
84+
85+
export class DefinitionError extends BaseError {
86+
constructor(
87+
messageText: string,
88+
readonly definition: JSONSchema7,
89+
) {
90+
super({
91+
code: 107,
92+
messageText,
93+
});
94+
}
95+
}
96+
export class BuildError extends BaseError {
97+
constructor(diag: Omit<PartialDiagnostic, "code">) {
98+
super({
99+
code: 108,
100+
...diag,
101+
});
102+
}
103+
}

src/Error/LogicError.ts

Lines changed: 0 additions & 7 deletions
This file was deleted.

src/Error/NoRootNamesError.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/Error/NoRootTypeError.ts

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)