ts-json-schema-generator 2.4.1-next.1 → 2.5.0--canary.2413.011011e.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -21
- package/dist/factory/generator.js +1 -1
- package/dist/factory/generator.js.map +1 -1
- package/dist/factory/parser.js +13 -2
- package/dist/factory/parser.js.map +1 -1
- package/dist/factory/program.js +5 -2
- package/dist/factory/program.js.map +1 -1
- package/dist/package.json +31 -31
- package/dist/src/AnnotationsReader/ExtendedAnnotationsReader.d.ts +2 -1
- package/dist/src/AnnotationsReader/ExtendedAnnotationsReader.js +22 -10
- package/dist/src/AnnotationsReader/ExtendedAnnotationsReader.js.map +1 -1
- package/dist/src/Config.d.ts +5 -2
- package/dist/src/Config.js +1 -0
- package/dist/src/Config.js.map +1 -1
- package/dist/src/Error/BaseError.d.ts +1 -1
- package/dist/src/Error/BaseError.js +1 -2
- package/dist/src/Error/BaseError.js.map +1 -1
- package/dist/src/NodeParser/BinaryExpressionNodeParser.d.ts +13 -0
- package/dist/src/NodeParser/BinaryExpressionNodeParser.js +79 -0
- package/dist/src/NodeParser/BinaryExpressionNodeParser.js.map +1 -0
- package/dist/src/NodeParser/IdentifierNodeParser.d.ts +11 -0
- package/dist/src/NodeParser/IdentifierNodeParser.js +33 -0
- package/dist/src/NodeParser/IdentifierNodeParser.js.map +1 -0
- package/dist/src/NodeParser/MappedTypeNodeParser.js +1 -1
- package/dist/src/NodeParser/MappedTypeNodeParser.js.map +1 -1
- package/dist/src/NodeParser/NewExpressionParser.d.ts +13 -0
- package/dist/src/NodeParser/NewExpressionParser.js +41 -0
- package/dist/src/NodeParser/NewExpressionParser.js.map +1 -0
- package/dist/src/NodeParser/SpreadElementNodeParser.d.ts +10 -0
- package/dist/src/NodeParser/SpreadElementNodeParser.js +21 -0
- package/dist/src/NodeParser/SpreadElementNodeParser.js.map +1 -0
- package/dist/src/NodeParser/TypeOperatorNodeParser.d.ts +1 -1
- package/dist/src/NodeParser/TypeOperatorNodeParser.js +2 -1
- package/dist/src/NodeParser/TypeOperatorNodeParser.js.map +1 -1
- package/dist/src/NodeParser/TypeReferenceNodeParser.js +1 -1
- package/dist/src/NodeParser/TypeReferenceNodeParser.js.map +1 -1
- package/dist/src/SchemaGenerator.d.ts +2 -2
- package/dist/src/SchemaGenerator.js +17 -7
- package/dist/src/SchemaGenerator.js.map +1 -1
- package/dist/src/Type/AnnotatedType.d.ts +1 -3
- package/dist/src/Type/AnnotatedType.js.map +1 -1
- package/dist/src/Type/UnknownType.d.ts +1 -1
- package/dist/src/Type/UnknownType.js +1 -1
- package/dist/src/Type/UnknownType.js.map +1 -1
- package/dist/src/TypeFormatter/AnnotatedTypeFormatter.js +1 -1
- package/dist/src/TypeFormatter/AnnotatedTypeFormatter.js.map +1 -1
- package/dist/src/TypeFormatter/LiteralUnionTypeFormatter.js +10 -5
- package/dist/src/TypeFormatter/LiteralUnionTypeFormatter.js.map +1 -1
- package/dist/src/TypeFormatter/PrimitiveUnionTypeFormatter.js +6 -2
- package/dist/src/TypeFormatter/PrimitiveUnionTypeFormatter.js.map +1 -1
- package/dist/src/Utils/castArray.d.ts +1 -0
- package/dist/src/Utils/castArray.js +10 -0
- package/dist/src/Utils/castArray.js.map +1 -0
- package/dist/src/Utils/getFullDescription.d.ts +2 -0
- package/dist/src/Utils/getFullDescription.js +33 -0
- package/dist/src/Utils/getFullDescription.js.map +1 -0
- package/dist/src/Utils/narrowType.js.map +1 -1
- package/dist/src/Utils/nodeKey.d.ts +1 -1
- package/dist/src/Utils/nodeKey.js.map +1 -1
- package/dist/ts-json-schema-generator.js +7 -3
- package/dist/ts-json-schema-generator.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/factory/generator.ts +1 -1
- package/factory/parser.ts +19 -2
- package/factory/program.ts +7 -3
- package/package.json +31 -31
- package/src/AnnotationsReader/ExtendedAnnotationsReader.ts +25 -10
- package/src/Config.ts +124 -2
- package/src/Error/BaseError.ts +1 -3
- package/src/NodeParser/BinaryExpressionNodeParser.ts +99 -0
- package/src/NodeParser/IdentifierNodeParser.ts +38 -0
- package/src/NodeParser/MappedTypeNodeParser.ts +1 -1
- package/src/NodeParser/NewExpressionParser.ts +46 -0
- package/src/NodeParser/SpreadElementNodeParser.ts +25 -0
- package/src/NodeParser/TypeOperatorNodeParser.ts +2 -2
- package/src/NodeParser/TypeReferenceNodeParser.ts +1 -1
- package/src/SchemaGenerator.ts +26 -9
- package/src/Type/AnnotatedType.ts +1 -3
- package/src/Type/UnknownType.ts +1 -1
- package/src/TypeFormatter/AnnotatedTypeFormatter.ts +2 -2
- package/src/TypeFormatter/LiteralUnionTypeFormatter.ts +12 -6
- package/src/TypeFormatter/PrimitiveUnionTypeFormatter.ts +9 -2
- package/src/Utils/castArray.ts +7 -0
- package/src/Utils/getFullDescription.ts +39 -0
- package/src/Utils/narrowType.ts +1 -6
- package/src/Utils/nodeKey.ts +3 -1
- package/ts-json-schema-generator.ts +12 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-json-schema-generator",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0--canary.2413.011011e.0",
|
|
4
4
|
"description": "Generate JSON schema from your Typescript sources",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ts",
|
|
@@ -49,51 +49,51 @@
|
|
|
49
49
|
"prepublishOnly": "npm run build",
|
|
50
50
|
"release": "npm run build && auto shipit",
|
|
51
51
|
"run": "tsx ts-json-schema-generator.ts",
|
|
52
|
-
"test": "
|
|
53
|
-
"test:
|
|
54
|
-
"test:fast": "cross-env FAST_TEST=1
|
|
55
|
-
"test:update": "cross-env UPDATE_SCHEMA=true
|
|
52
|
+
"test": "tsx --test \"test/**/*.test.ts\"",
|
|
53
|
+
"test:debug": "tsx --inspect-brk --test-concurrency=1 --test",
|
|
54
|
+
"test:fast": "cross-env FAST_TEST=1 tsx --test \"test/**/*.test.ts\"",
|
|
55
|
+
"test:update": "cross-env FAST_TEST=1 UPDATE_SCHEMA=true tsx --test \"test/**/*.test.ts\"",
|
|
56
|
+
"test:coverage": "c8 --reporter lcov --reporter text tsx --test \"test/**/*.test.ts\"",
|
|
56
57
|
"watch": "tsc -w"
|
|
57
58
|
},
|
|
58
59
|
"dependencies": {
|
|
59
60
|
"@types/json-schema": "^7.0.15",
|
|
60
|
-
"
|
|
61
|
-
"
|
|
61
|
+
"@typescript/vfs": "1.6.2",
|
|
62
|
+
"commander": "^14.0.2",
|
|
63
|
+
"glob": "^13.0.0",
|
|
62
64
|
"json5": "^2.2.3",
|
|
63
65
|
"normalize-path": "^3.0.0",
|
|
64
66
|
"safe-stable-stringify": "^2.5.0",
|
|
65
67
|
"tslib": "^2.8.1",
|
|
66
|
-
"typescript": "^5.
|
|
68
|
+
"typescript": "^5.9.3"
|
|
67
69
|
},
|
|
68
70
|
"devDependencies": {
|
|
69
|
-
"@auto-it/conventional-commits": "^11.3.
|
|
70
|
-
"@auto-it/first-time-contributor": "^11.3.
|
|
71
|
-
"@babel/core": "^7.
|
|
72
|
-
"@babel/preset-env": "^7.
|
|
73
|
-
"@babel/preset-typescript": "^7.
|
|
74
|
-
"@eslint/js": "^9.
|
|
71
|
+
"@auto-it/conventional-commits": "^11.3.6",
|
|
72
|
+
"@auto-it/first-time-contributor": "^11.3.6",
|
|
73
|
+
"@babel/core": "^7.28.5",
|
|
74
|
+
"@babel/preset-env": "^7.28.5",
|
|
75
|
+
"@babel/preset-typescript": "^7.28.5",
|
|
76
|
+
"@eslint/js": "^9.39.1",
|
|
75
77
|
"@types/eslint": "^9.6.1",
|
|
76
|
-
"@types/
|
|
77
|
-
"@types/jest": "^29.5.14",
|
|
78
|
-
"@types/node": "^22.13.14",
|
|
78
|
+
"@types/node": "^24.10.1",
|
|
79
79
|
"@types/normalize-path": "^3.0.2",
|
|
80
80
|
"ajv": "^8.17.1",
|
|
81
81
|
"ajv-formats": "^3.0.1",
|
|
82
|
-
"auto": "^11.3.
|
|
83
|
-
"chai": "^
|
|
84
|
-
"cross-env": "^
|
|
85
|
-
"eslint": "9.
|
|
86
|
-
"eslint-config-prettier": "^10.1.
|
|
87
|
-
"eslint-plugin-prettier": "^5.
|
|
88
|
-
"
|
|
89
|
-
"
|
|
90
|
-
"
|
|
91
|
-
"tsx": "^4.
|
|
92
|
-
"typescript-eslint": "^8.
|
|
93
|
-
"vega": "^6.
|
|
94
|
-
"vega-lite": "^6.1
|
|
82
|
+
"auto": "^11.3.6",
|
|
83
|
+
"chai": "^6.2.1",
|
|
84
|
+
"cross-env": "^10.1.0",
|
|
85
|
+
"eslint": "9.39.1",
|
|
86
|
+
"eslint-config-prettier": "^10.1.8",
|
|
87
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
88
|
+
"globals": "^16.5.0",
|
|
89
|
+
"prettier": "^3.7.3",
|
|
90
|
+
"try": "^1.0.1",
|
|
91
|
+
"tsx": "^4.21.0",
|
|
92
|
+
"typescript-eslint": "^8.48.0",
|
|
93
|
+
"vega": "^6.2.0",
|
|
94
|
+
"vega-lite": "^6.4.1"
|
|
95
95
|
},
|
|
96
|
-
"packageManager": "npm@11.
|
|
96
|
+
"packageManager": "npm@11.6.4",
|
|
97
97
|
"engines": {
|
|
98
98
|
"node": ">=18.0.0"
|
|
99
99
|
}
|
|
@@ -2,6 +2,7 @@ import json5 from "json5";
|
|
|
2
2
|
import type ts from "typescript";
|
|
3
3
|
import type { Annotations } from "../Type/AnnotatedType.js";
|
|
4
4
|
import { symbolAtNode } from "../Utils/symbolAtNode.js";
|
|
5
|
+
import { getFullDescription } from "../Utils/getFullDescription.js";
|
|
5
6
|
import { BasicAnnotationsReader } from "./BasicAnnotationsReader.js";
|
|
6
7
|
|
|
7
8
|
export class ExtendedAnnotationsReader extends BasicAnnotationsReader {
|
|
@@ -9,6 +10,7 @@ export class ExtendedAnnotationsReader extends BasicAnnotationsReader {
|
|
|
9
10
|
private typeChecker: ts.TypeChecker,
|
|
10
11
|
extraTags?: Set<string>,
|
|
11
12
|
private markdownDescription?: boolean,
|
|
13
|
+
private fullDescription?: boolean,
|
|
12
14
|
) {
|
|
13
15
|
super(extraTags);
|
|
14
16
|
}
|
|
@@ -44,21 +46,34 @@ export class ExtendedAnnotationsReader extends BasicAnnotationsReader {
|
|
|
44
46
|
return undefined;
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
const annotations: { description?: string; markdownDescription?: string; fullDescription?: string } = {};
|
|
50
|
+
|
|
47
51
|
const comments: ts.SymbolDisplayPart[] = symbol.getDocumentationComment(this.typeChecker);
|
|
48
|
-
if (!comments || !comments.length) {
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
53
|
+
if (comments && comments.length) {
|
|
54
|
+
const markdownDescription = comments
|
|
55
|
+
.map((comment) => comment.text)
|
|
56
|
+
.join(" ")
|
|
57
|
+
.replace(/\r/g, "")
|
|
58
|
+
.trim();
|
|
57
59
|
|
|
58
|
-
|
|
60
|
+
annotations.description = markdownDescription.replace(/(?<=[^\n])\n(?=[^\n*-])/g, " ").trim();
|
|
61
|
+
|
|
62
|
+
if (this.markdownDescription) {
|
|
63
|
+
annotations.markdownDescription = markdownDescription;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
if (this.fullDescription) {
|
|
68
|
+
const fullDescription = getFullDescription(node)?.trim();
|
|
69
|
+
if (fullDescription) {
|
|
70
|
+
annotations.fullDescription = fullDescription;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Object.keys(annotations).length ? annotations : undefined;
|
|
61
75
|
}
|
|
76
|
+
|
|
62
77
|
private getTypeAnnotation(node: ts.Node): Annotations | undefined {
|
|
63
78
|
const symbol = symbolAtNode(node);
|
|
64
79
|
if (!symbol) {
|
package/src/Config.ts
CHANGED
|
@@ -1,32 +1,154 @@
|
|
|
1
|
+
import type ts from "typescript";
|
|
2
|
+
|
|
1
3
|
export interface Config {
|
|
4
|
+
/**
|
|
5
|
+
* Glob pattern(s) for source TypeScript files to process.
|
|
6
|
+
* If not provided, falls back to files from tsconfig.
|
|
7
|
+
*/
|
|
2
8
|
path?: string;
|
|
3
|
-
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Name of the type(s)/interface(s) to generate schema for.
|
|
12
|
+
* Use "*" to generate schemas for all exported types.
|
|
13
|
+
*/
|
|
14
|
+
type?: string | string[];
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Minify the output JSON schema (no whitespace).
|
|
18
|
+
* When false, the schema is pretty-printed with 2-space indentation.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
4
21
|
minify?: boolean;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Sets the `$id` property in the root of the generated schema.
|
|
25
|
+
* Used for schema identification and referencing.
|
|
26
|
+
*/
|
|
5
27
|
schemaId?: string;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Path to a custom tsconfig.json file for TypeScript compilation.
|
|
31
|
+
* If not provided, uses default TypeScript configuration.
|
|
32
|
+
*/
|
|
6
33
|
tsconfig?: string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Controls which types are exposed as definitions in the schema.
|
|
37
|
+
* - "all": Exposes all types except type literals
|
|
38
|
+
* - "none": Exposes no types automatically
|
|
39
|
+
* - "export": Only exposes exported types (respects @internal JSDoc tag)
|
|
40
|
+
* @default "export"
|
|
41
|
+
*/
|
|
7
42
|
expose?: "all" | "none" | "export";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Wraps the root type in a `$ref` definition.
|
|
46
|
+
* When false, inlines the root type definition directly.
|
|
47
|
+
* @default true
|
|
48
|
+
*/
|
|
8
49
|
topRef?: boolean;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Controls how JSDoc comments are parsed and included in the schema.
|
|
53
|
+
* - "none": Ignores all JSDoc annotations
|
|
54
|
+
* - "basic": Parses standard JSON Schema JSDoc tags
|
|
55
|
+
* - "extended": Parses all tags plus descriptions, examples, and type overrides
|
|
56
|
+
* @default "extended"
|
|
57
|
+
*/
|
|
9
58
|
jsDoc?: "none" | "extended" | "basic";
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Adds a `markdownDescription` field alongside `description` in the schema.
|
|
62
|
+
* Preserves markdown formatting including newlines.
|
|
63
|
+
* Only works with `jsDoc: "extended"`.
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
10
66
|
markdownDescription?: boolean;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Includes the complete raw JSDoc comment as `fullDescription` in the schema.
|
|
70
|
+
* Only works with `jsDoc: "extended"`.
|
|
71
|
+
* @default false
|
|
72
|
+
*/
|
|
73
|
+
fullDescription?: boolean;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Sorts object properties alphabetically in the output.
|
|
77
|
+
* @default true
|
|
78
|
+
*/
|
|
11
79
|
sortProps?: boolean;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Controls whether tuples allow additional items beyond their defined length.
|
|
83
|
+
* @default false
|
|
84
|
+
*/
|
|
12
85
|
strictTuples?: boolean;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Skips TypeScript type checking to improve performance.
|
|
89
|
+
* Speeds up generation but may miss type errors.
|
|
90
|
+
* @default false
|
|
91
|
+
*/
|
|
13
92
|
skipTypeCheck?: boolean;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* URI-encodes `$ref` values (e.g., `#/definitions/Foo%3CBar%3E`).
|
|
96
|
+
* When false, uses raw names in reference paths.
|
|
97
|
+
* @default true
|
|
98
|
+
*/
|
|
14
99
|
encodeRefs?: boolean;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Array of additional JSDoc tag names to include in the schema.
|
|
103
|
+
* Custom tags (e.g., `@customProperty`) are parsed and included in output.
|
|
104
|
+
* Values are parsed as JSON5.
|
|
105
|
+
* @default []
|
|
106
|
+
*/
|
|
15
107
|
extraTags?: string[];
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Sets default value for `additionalProperties` on objects without index signatures.
|
|
111
|
+
* When false, objects get `additionalProperties: false` by default.
|
|
112
|
+
* When true, allows additional properties on all objects.
|
|
113
|
+
* @default false
|
|
114
|
+
*/
|
|
16
115
|
additionalProperties?: boolean;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Controls discriminator style for discriminated unions.
|
|
119
|
+
* - "json-schema": Uses `if`/`then`/`allOf` with properties containing discriminator enum
|
|
120
|
+
* - "open-api": Uses OpenAPI 3.x style with `discriminator: { propertyName }` and `oneOf`
|
|
121
|
+
* @default "json-schema"
|
|
122
|
+
*/
|
|
17
123
|
discriminatorType?: "json-schema" | "open-api";
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Controls how function types are handled in the schema.
|
|
127
|
+
* - "fail": Throws error when encountering function types
|
|
128
|
+
* - "comment": Generates schema with `$comment` describing the function signature
|
|
129
|
+
* - "hide": Treats functions as NeverType (excluded from schema)
|
|
130
|
+
* @default "comment"
|
|
131
|
+
*/
|
|
18
132
|
functions?: FunctionOptions;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Pre-compiled TypeScript Program instance to use.
|
|
136
|
+
* Bypasses the default setup of a TypeScript program, and so some configuration options may not be applied.
|
|
137
|
+
* Useful for programmatic usage with existing TypeScript compilation, or for vfs scenarios where you do not want file-system representation.
|
|
138
|
+
*/
|
|
139
|
+
tsProgram?: ts.Program;
|
|
19
140
|
}
|
|
20
141
|
|
|
21
142
|
export type CompletedConfig = Config & typeof DEFAULT_CONFIG;
|
|
22
143
|
|
|
23
144
|
export type FunctionOptions = "fail" | "comment" | "hide";
|
|
24
145
|
|
|
25
|
-
export const DEFAULT_CONFIG: Omit<Required<Config>, "path" | "type" | "schemaId" | "tsconfig"> = {
|
|
146
|
+
export const DEFAULT_CONFIG: Omit<Required<Config>, "path" | "type" | "schemaId" | "tsconfig" | "tsProgram"> = {
|
|
26
147
|
expose: "export",
|
|
27
148
|
topRef: true,
|
|
28
149
|
jsDoc: "extended",
|
|
29
150
|
markdownDescription: false,
|
|
151
|
+
fullDescription: false,
|
|
30
152
|
sortProps: true,
|
|
31
153
|
strictTuples: false,
|
|
32
154
|
skipTypeCheck: false,
|
package/src/Error/BaseError.ts
CHANGED
|
@@ -12,8 +12,6 @@ export type PartialDiagnostic = Omit<ts.Diagnostic, "category" | "file" | "start
|
|
|
12
12
|
category?: ts.DiagnosticCategory;
|
|
13
13
|
};
|
|
14
14
|
|
|
15
|
-
const isTTY = process.env.TTY || process.stdout.isTTY;
|
|
16
|
-
|
|
17
15
|
/**
|
|
18
16
|
* Base error for ts-json-schema-generator
|
|
19
17
|
*/
|
|
@@ -51,7 +49,7 @@ export abstract class BaseError extends Error {
|
|
|
51
49
|
);
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
format() {
|
|
52
|
+
format(isTTY = process.env.TTY || process.stdout.isTTY): string {
|
|
55
53
|
const formatter = isTTY ? ts.formatDiagnosticsWithColorAndContext : ts.formatDiagnostics;
|
|
56
54
|
|
|
57
55
|
return formatter([this.diagnostic], {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { Context, NodeParser } from "../NodeParser.js";
|
|
3
|
+
import type { SubNodeParser } from "../SubNodeParser.js";
|
|
4
|
+
import { AnyType } from "../Type/AnyType.js";
|
|
5
|
+
import type { BaseType } from "../Type/BaseType.js";
|
|
6
|
+
import { BooleanType } from "../Type/BooleanType.js";
|
|
7
|
+
import { LiteralType } from "../Type/LiteralType.js";
|
|
8
|
+
import { NumberType } from "../Type/NumberType.js";
|
|
9
|
+
import { StringType } from "../Type/StringType.js";
|
|
10
|
+
import { UnionType } from "../Type/UnionType.js";
|
|
11
|
+
import { AliasType } from "../Type/AliasType.js";
|
|
12
|
+
|
|
13
|
+
export class BinaryExpressionNodeParser implements SubNodeParser {
|
|
14
|
+
public constructor(protected childNodeParser: NodeParser) {}
|
|
15
|
+
|
|
16
|
+
public supportsNode(node: ts.Node): boolean {
|
|
17
|
+
return node.kind === ts.SyntaxKind.BinaryExpression;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public createType(node: ts.BinaryExpression, context: Context): BaseType {
|
|
21
|
+
const leftType = this.childNodeParser.createType(node.left, context);
|
|
22
|
+
const rightType = this.childNodeParser.createType(node.right, context);
|
|
23
|
+
|
|
24
|
+
if (leftType instanceof AnyType || rightType instanceof AnyType) {
|
|
25
|
+
return new AnyType();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (this.isStringLike(leftType) || this.isStringLike(rightType)) {
|
|
29
|
+
return new StringType();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (this.isDefinitelyNumberLike(leftType) && this.isDefinitelyNumberLike(rightType)) {
|
|
33
|
+
return new NumberType();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (this.isBooleanLike(leftType) && this.isBooleanLike(rightType)) {
|
|
37
|
+
return new BooleanType();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Anything else (objects, any, unknown, weird unions, etc.) return
|
|
41
|
+
// 'string' because at runtime + will usually go through ToPrimitive and
|
|
42
|
+
// end up in the "string concatenation" branch when non-numeric stuff is
|
|
43
|
+
// involved.
|
|
44
|
+
return new StringType();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private isStringLike(type: BaseType): boolean {
|
|
48
|
+
if (type instanceof AliasType) {
|
|
49
|
+
return this.isStringLike(type.getType());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (type instanceof StringType) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (type instanceof LiteralType && type.isString()) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Any union member being string-like is enough.
|
|
61
|
+
if (type instanceof UnionType) {
|
|
62
|
+
return type.getTypes().some((t) => this.isStringLike(t));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
private isBooleanLike(type: BaseType): boolean {
|
|
69
|
+
if (type instanceof BooleanType) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (type instanceof LiteralType && typeof type.getValue() === "boolean") {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private isDefinitelyNumberLike(type: BaseType): boolean {
|
|
81
|
+
if (type instanceof AliasType) {
|
|
82
|
+
return this.isDefinitelyNumberLike(type.getType());
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (type instanceof NumberType) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (type instanceof LiteralType && typeof type.getValue() === "number") {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (type instanceof UnionType) {
|
|
94
|
+
return type.getTypes().every((t) => this.isDefinitelyNumberLike(t));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { Context, NodeParser } from "../NodeParser.js";
|
|
3
|
+
import type { SubNodeParser } from "../SubNodeParser.js";
|
|
4
|
+
import type { BaseType } from "../Type/BaseType.js";
|
|
5
|
+
import { UnknownNodeError } from "../Error/Errors.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Resolves identifiers whose value is a compile-time constant
|
|
9
|
+
*/
|
|
10
|
+
export class IdentifierNodeParser implements SubNodeParser {
|
|
11
|
+
constructor(
|
|
12
|
+
private readonly childNodeParser: NodeParser,
|
|
13
|
+
private readonly checker: ts.TypeChecker,
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
supportsNode(node: ts.Identifier): boolean {
|
|
17
|
+
return node.kind === ts.SyntaxKind.Identifier;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
createType(node: ts.Identifier, context: Context): BaseType {
|
|
21
|
+
const symbol = this.checker.getSymbolAtLocation(node);
|
|
22
|
+
if (!symbol) {
|
|
23
|
+
throw new UnknownNodeError(node);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const decl = symbol.valueDeclaration;
|
|
27
|
+
if (
|
|
28
|
+
decl &&
|
|
29
|
+
ts.isVariableDeclaration(decl) &&
|
|
30
|
+
decl.initializer &&
|
|
31
|
+
ts.getCombinedNodeFlags(decl) & ts.NodeFlags.Const
|
|
32
|
+
) {
|
|
33
|
+
return this.childNodeParser.createType(decl.initializer, context);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
throw new UnknownNodeError(node);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -65,7 +65,7 @@ export class MappedTypeNodeParser implements SubNodeParser {
|
|
|
65
65
|
return type instanceof NeverType ? new NeverType() : new ArrayType(type);
|
|
66
66
|
}
|
|
67
67
|
// Key type widens to `string`
|
|
68
|
-
const type = this.childNodeParser.createType(node.type!, context);
|
|
68
|
+
const type = this.childNodeParser.createType(node.type!, this.createSubContext(node, keyListType, context));
|
|
69
69
|
// const resultType = type instanceof NeverType ? new NeverType() : new ObjectType(id, [], [], type);
|
|
70
70
|
const resultType = new ObjectType(id, [], [], type);
|
|
71
71
|
if (resultType) {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { NodeParser } from "../NodeParser.js";
|
|
3
|
+
import { Context } from "../NodeParser.js";
|
|
4
|
+
import type { SubNodeParser } from "../SubNodeParser.js";
|
|
5
|
+
import type { BaseType } from "../Type/BaseType.js";
|
|
6
|
+
import { UnknownNodeError } from "../Error/Errors.js";
|
|
7
|
+
|
|
8
|
+
export class NewExpressionParser implements SubNodeParser {
|
|
9
|
+
public constructor(
|
|
10
|
+
protected typeChecker: ts.TypeChecker,
|
|
11
|
+
protected childNodeParser: NodeParser,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
public supportsNode(node: ts.NewExpression): boolean {
|
|
15
|
+
return node.kind === ts.SyntaxKind.NewExpression;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public createType(node: ts.NewExpression, context: Context): BaseType {
|
|
19
|
+
const type = this.typeChecker.getTypeAtLocation(node);
|
|
20
|
+
|
|
21
|
+
const symbol = type.symbol || type.aliasSymbol;
|
|
22
|
+
|
|
23
|
+
const decl =
|
|
24
|
+
this.typeChecker.typeToTypeNode(type, node, ts.NodeBuilderFlags.IgnoreErrors) ||
|
|
25
|
+
symbol?.valueDeclaration ||
|
|
26
|
+
symbol?.declarations?.[0];
|
|
27
|
+
|
|
28
|
+
if (!decl) {
|
|
29
|
+
throw new UnknownNodeError(node);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return this.childNodeParser.createType(decl, this.createSubContext(node, context));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
protected createSubContext(node: ts.NewExpression, parentContext: Context): Context {
|
|
36
|
+
const subContext = new Context(node);
|
|
37
|
+
|
|
38
|
+
if (node.arguments) {
|
|
39
|
+
for (const arg of node.arguments) {
|
|
40
|
+
const type = this.childNodeParser.createType(arg, parentContext);
|
|
41
|
+
subContext.pushArgument(type);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return subContext;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import type { Context, NodeParser } from "../NodeParser.js";
|
|
3
|
+
import type { SubNodeParser } from "../SubNodeParser.js";
|
|
4
|
+
import type { ArrayType } from "../Type/ArrayType.js";
|
|
5
|
+
import type { InferType } from "../Type/InferType.js";
|
|
6
|
+
import type { TupleType } from "../Type/TupleType.js";
|
|
7
|
+
import { RestType } from "../Type/RestType.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Handles `...expr` inside an ArrayLiteralExpression.
|
|
11
|
+
* Turns it into RestType so TupleTypeFormatter can emit correct JSON-Schema.
|
|
12
|
+
*/
|
|
13
|
+
export class SpreadElementNodeParser implements SubNodeParser {
|
|
14
|
+
constructor(private readonly childNodeParser: NodeParser) {}
|
|
15
|
+
|
|
16
|
+
supportsNode(node: ts.SpreadElement): boolean {
|
|
17
|
+
return node.kind === ts.SyntaxKind.SpreadElement;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
createType(node: ts.SpreadElement, context: Context) {
|
|
21
|
+
const inner = this.childNodeParser.createType(node.expression, context) as ArrayType | InferType | TupleType;
|
|
22
|
+
|
|
23
|
+
return new RestType(inner);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -2,7 +2,7 @@ import ts from "typescript";
|
|
|
2
2
|
import type { Context, NodeParser } from "../NodeParser.js";
|
|
3
3
|
import type { SubNodeParser } from "../SubNodeParser.js";
|
|
4
4
|
import { ArrayType } from "../Type/ArrayType.js";
|
|
5
|
-
import
|
|
5
|
+
import { BaseType } from "../Type/BaseType.js";
|
|
6
6
|
import { NumberType } from "../Type/NumberType.js";
|
|
7
7
|
import { ObjectType } from "../Type/ObjectType.js";
|
|
8
8
|
import { StringType } from "../Type/StringType.js";
|
|
@@ -28,7 +28,7 @@ export class TypeOperatorNodeParser implements SubNodeParser {
|
|
|
28
28
|
return new NumberType();
|
|
29
29
|
}
|
|
30
30
|
const keys = getTypeKeys(type);
|
|
31
|
-
if (derefed instanceof ObjectType && derefed.getAdditionalProperties()) {
|
|
31
|
+
if (derefed instanceof ObjectType && derefed.getAdditionalProperties() instanceof BaseType) {
|
|
32
32
|
return new UnionType([...keys, new StringType()]);
|
|
33
33
|
}
|
|
34
34
|
|
|
@@ -56,7 +56,7 @@ export class TypeReferenceNodeParser implements SubNodeParser {
|
|
|
56
56
|
return new AnyType();
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
return this.childNodeParser.createType(node.typeArguments[0],
|
|
59
|
+
return this.childNodeParser.createType(node.typeArguments[0], context);
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
if (typeSymbol.name === "Array" || typeSymbol.name === "ReadonlyArray") {
|
package/src/SchemaGenerator.ts
CHANGED
|
@@ -10,6 +10,7 @@ import type { TypeFormatter } from "./TypeFormatter.js";
|
|
|
10
10
|
import type { StringMap } from "./Utils/StringMap.js";
|
|
11
11
|
import { hasJsDocTag } from "./Utils/hasJsDocTag.js";
|
|
12
12
|
import { removeUnreachable } from "./Utils/removeUnreachable.js";
|
|
13
|
+
import { castArray } from "./Utils/castArray.js";
|
|
13
14
|
import { symbolAtNode } from "./Utils/symbolAtNode.js";
|
|
14
15
|
|
|
15
16
|
export class SchemaGenerator {
|
|
@@ -20,8 +21,8 @@ export class SchemaGenerator {
|
|
|
20
21
|
protected readonly config?: Config,
|
|
21
22
|
) {}
|
|
22
23
|
|
|
23
|
-
public createSchema(
|
|
24
|
-
const rootNodes = this.getRootNodes(
|
|
24
|
+
public createSchema(fullNames?: string | string[]): Schema {
|
|
25
|
+
const rootNodes = this.getRootNodes(castArray(fullNames));
|
|
25
26
|
return this.createSchemaFromNodes(rootNodes);
|
|
26
27
|
}
|
|
27
28
|
|
|
@@ -31,8 +32,8 @@ export class SchemaGenerator {
|
|
|
31
32
|
rootType: this.nodeParser.createType(rootNode, new Context()),
|
|
32
33
|
}));
|
|
33
34
|
|
|
34
|
-
const
|
|
35
|
-
|
|
35
|
+
const rootTypeDefinitions = roots.map((root) => this.getRootTypeDefinition(root.rootType, root.rootNode));
|
|
36
|
+
const rootTypeDefinition = rootTypeDefinitions.length === 1 ? rootTypeDefinitions[0] : undefined;
|
|
36
37
|
const definitions: StringMap<Definition> = {};
|
|
37
38
|
|
|
38
39
|
for (const root of roots) {
|
|
@@ -47,7 +48,10 @@ export class SchemaGenerator {
|
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
const reachableDefinitions =
|
|
51
|
+
const reachableDefinitions = rootTypeDefinitions.reduce<StringMap<Definition>>(
|
|
52
|
+
(acc, def) => Object.assign(acc, removeUnreachable(def, definitions)),
|
|
53
|
+
{},
|
|
54
|
+
);
|
|
51
55
|
|
|
52
56
|
return {
|
|
53
57
|
...(this.config?.schemaId ? { $id: this.config.schemaId } : {}),
|
|
@@ -57,9 +61,15 @@ export class SchemaGenerator {
|
|
|
57
61
|
};
|
|
58
62
|
}
|
|
59
63
|
|
|
60
|
-
protected getRootNodes(
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
protected getRootNodes(fullNames: string[] | undefined): ts.Node[] {
|
|
65
|
+
// ["*"] means generate everything.
|
|
66
|
+
if (fullNames && fullNames.includes("*") && fullNames.length > 1) {
|
|
67
|
+
throw new Error("Cannot mix '*' with specific type names");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const generateAll = !fullNames || fullNames.length === 0 || (fullNames.length === 1 && fullNames[0] === "*");
|
|
71
|
+
if (!generateAll) {
|
|
72
|
+
return fullNames.map((name) => this.findNamedNode(name));
|
|
63
73
|
}
|
|
64
74
|
|
|
65
75
|
const rootFileNames = this.program.getRootFileNames();
|
|
@@ -229,11 +239,18 @@ export class SchemaGenerator {
|
|
|
229
239
|
return;
|
|
230
240
|
}
|
|
231
241
|
|
|
232
|
-
|
|
242
|
+
if (node.exportClause) {
|
|
243
|
+
// export { Foo } from './lib' or export { Foo };
|
|
244
|
+
// export * as Foo from './lib' should not import all exports
|
|
245
|
+
ts.forEachChild(node.exportClause, (subnode) => this.inspectNode(subnode, typeChecker, allTypes));
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
233
249
|
if (!node.moduleSpecifier) {
|
|
234
250
|
return;
|
|
235
251
|
}
|
|
236
252
|
|
|
253
|
+
// export * from './lib'
|
|
237
254
|
const symbol = typeChecker.getSymbolAtLocation(node.moduleSpecifier);
|
|
238
255
|
|
|
239
256
|
// should never hit this (maybe type error in user's code)
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { BaseType } from "./BaseType.js";
|
|
2
2
|
import { hash } from "../Utils/nodeKey.js";
|
|
3
3
|
|
|
4
|
-
export
|
|
5
|
-
[name: string]: any;
|
|
6
|
-
}
|
|
4
|
+
export type Annotations = Record<string, unknown>;
|
|
7
5
|
|
|
8
6
|
export class AnnotatedType extends BaseType {
|
|
9
7
|
public constructor(
|