ts-json-schema-generator 2.5.0-next.8 → 2.5.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.
Files changed (145) hide show
  1. package/dist/factory/parser.js +3 -0
  2. package/dist/factory/parser.js.map +1 -1
  3. package/dist/factory/program.js +6 -3
  4. package/dist/factory/program.js.map +1 -1
  5. package/dist/package.json +28 -33
  6. package/dist/src/AnnotationsReader/BasicAnnotationsReader.js +6 -0
  7. package/dist/src/AnnotationsReader/BasicAnnotationsReader.js.map +1 -1
  8. package/dist/src/AnnotationsReader/ExtendedAnnotationsReader.d.ts +4 -0
  9. package/dist/src/AnnotationsReader/ExtendedAnnotationsReader.js +7 -0
  10. package/dist/src/AnnotationsReader/ExtendedAnnotationsReader.js.map +1 -1
  11. package/dist/src/Config.d.ts +99 -0
  12. package/dist/src/Error/BaseError.d.ts +6 -1
  13. package/dist/src/Error/BaseError.js +8 -2
  14. package/dist/src/Error/BaseError.js.map +1 -1
  15. package/dist/src/Error/Errors.d.ts +3 -0
  16. package/dist/src/Error/Errors.js +5 -0
  17. package/dist/src/Error/Errors.js.map +1 -1
  18. package/dist/src/NodeParser/AnnotatedNodeParser.js +5 -0
  19. package/dist/src/NodeParser/AnnotatedNodeParser.js.map +1 -1
  20. package/dist/src/NodeParser/ArrayNodeParser.js +1 -0
  21. package/dist/src/NodeParser/ArrayNodeParser.js.map +1 -1
  22. package/dist/src/NodeParser/AsExpressionNodeParser.js +1 -0
  23. package/dist/src/NodeParser/AsExpressionNodeParser.js.map +1 -1
  24. package/dist/src/NodeParser/BinaryExpressionNodeParser.d.ts +13 -0
  25. package/dist/src/NodeParser/BinaryExpressionNodeParser.js +84 -0
  26. package/dist/src/NodeParser/BinaryExpressionNodeParser.js.map +1 -0
  27. package/dist/src/NodeParser/CallExpressionParser.js +6 -0
  28. package/dist/src/NodeParser/CallExpressionParser.js.map +1 -1
  29. package/dist/src/NodeParser/ConditionalTypeNodeParser.d.ts +16 -0
  30. package/dist/src/NodeParser/ConditionalTypeNodeParser.js +22 -0
  31. package/dist/src/NodeParser/ConditionalTypeNodeParser.js.map +1 -1
  32. package/dist/src/NodeParser/FunctionNodeParser.js +1 -0
  33. package/dist/src/NodeParser/FunctionNodeParser.js.map +1 -1
  34. package/dist/src/NodeParser/IdentifierNodeParser.d.ts +3 -0
  35. package/dist/src/NodeParser/IdentifierNodeParser.js +3 -0
  36. package/dist/src/NodeParser/IdentifierNodeParser.js.map +1 -1
  37. package/dist/src/NodeParser/IndexedAccessTypeNodeParser.js +4 -1
  38. package/dist/src/NodeParser/IndexedAccessTypeNodeParser.js.map +1 -1
  39. package/dist/src/NodeParser/InterfaceAndClassNodeParser.d.ts +7 -0
  40. package/dist/src/NodeParser/InterfaceAndClassNodeParser.js +10 -0
  41. package/dist/src/NodeParser/InterfaceAndClassNodeParser.js.map +1 -1
  42. package/dist/src/NodeParser/IntersectionNodeParser.d.ts +4 -0
  43. package/dist/src/NodeParser/IntersectionNodeParser.js +8 -0
  44. package/dist/src/NodeParser/IntersectionNodeParser.js.map +1 -1
  45. package/dist/src/NodeParser/MappedTypeNodeParser.js +12 -5
  46. package/dist/src/NodeParser/MappedTypeNodeParser.js.map +1 -1
  47. package/dist/src/NodeParser/ObjectLiteralExpressionNodeParser.js +1 -0
  48. package/dist/src/NodeParser/ObjectLiteralExpressionNodeParser.js.map +1 -1
  49. package/dist/src/NodeParser/PrefixUnaryExpressionNodeParser.js +1 -0
  50. package/dist/src/NodeParser/PrefixUnaryExpressionNodeParser.js.map +1 -1
  51. package/dist/src/NodeParser/PromiseNodeParser.d.ts +3 -0
  52. package/dist/src/NodeParser/PromiseNodeParser.js +19 -1
  53. package/dist/src/NodeParser/PromiseNodeParser.js.map +1 -1
  54. package/dist/src/NodeParser/SpreadElementNodeParser.d.ts +4 -0
  55. package/dist/src/NodeParser/SpreadElementNodeParser.js +4 -0
  56. package/dist/src/NodeParser/SpreadElementNodeParser.js.map +1 -1
  57. package/dist/src/NodeParser/TypeLiteralNodeParser.js +5 -0
  58. package/dist/src/NodeParser/TypeLiteralNodeParser.js.map +1 -1
  59. package/dist/src/NodeParser/TypeOperatorNodeParser.js +1 -0
  60. package/dist/src/NodeParser/TypeOperatorNodeParser.js.map +1 -1
  61. package/dist/src/NodeParser/TypeReferenceNodeParser.js +6 -0
  62. package/dist/src/NodeParser/TypeReferenceNodeParser.js.map +1 -1
  63. package/dist/src/NodeParser/TypeofNodeParser.js +1 -0
  64. package/dist/src/NodeParser/TypeofNodeParser.js.map +1 -1
  65. package/dist/src/SchemaGenerator.js +28 -1
  66. package/dist/src/SchemaGenerator.js.map +1 -1
  67. package/dist/src/Type/AnnotatedType.d.ts +1 -3
  68. package/dist/src/Type/AnnotatedType.js.map +1 -1
  69. package/dist/src/Type/BaseType.d.ts +3 -0
  70. package/dist/src/Type/BaseType.js +3 -0
  71. package/dist/src/Type/BaseType.js.map +1 -1
  72. package/dist/src/Type/ObjectType.js +3 -1
  73. package/dist/src/Type/ObjectType.js.map +1 -1
  74. package/dist/src/Type/UnionType.d.ts +3 -0
  75. package/dist/src/Type/UnionType.js +3 -0
  76. package/dist/src/Type/UnionType.js.map +1 -1
  77. package/dist/src/Type/UnknownType.d.ts +11 -1
  78. package/dist/src/Type/UnknownType.js +8 -1
  79. package/dist/src/Type/UnknownType.js.map +1 -1
  80. package/dist/src/TypeFormatter/AnnotatedTypeFormatter.js +2 -1
  81. package/dist/src/TypeFormatter/AnnotatedTypeFormatter.js.map +1 -1
  82. package/dist/src/TypeFormatter/EnumTypeFormatter.d.ts +3 -0
  83. package/dist/src/TypeFormatter/EnumTypeFormatter.js +7 -0
  84. package/dist/src/TypeFormatter/EnumTypeFormatter.js.map +1 -1
  85. package/dist/src/TypeFormatter/IntersectionTypeFormatter.js +3 -0
  86. package/dist/src/TypeFormatter/IntersectionTypeFormatter.js.map +1 -1
  87. package/dist/src/TypeFormatter/LiteralUnionTypeFormatter.js +7 -0
  88. package/dist/src/TypeFormatter/LiteralUnionTypeFormatter.js.map +1 -1
  89. package/dist/src/TypeFormatter/ReferenceTypeFormatter.js +6 -0
  90. package/dist/src/TypeFormatter/ReferenceTypeFormatter.js.map +1 -1
  91. package/dist/src/TypeFormatter/TupleTypeFormatter.js +12 -5
  92. package/dist/src/TypeFormatter/TupleTypeFormatter.js.map +1 -1
  93. package/dist/src/TypeFormatter/UnionTypeFormatter.js +1 -0
  94. package/dist/src/TypeFormatter/UnionTypeFormatter.js.map +1 -1
  95. package/dist/src/Utils/allOfDefinition.js +5 -0
  96. package/dist/src/Utils/allOfDefinition.js.map +1 -1
  97. package/dist/src/Utils/deepMerge.d.ts +8 -0
  98. package/dist/src/Utils/deepMerge.js +9 -0
  99. package/dist/src/Utils/deepMerge.js.map +1 -1
  100. package/dist/src/Utils/derefType.d.ts +6 -0
  101. package/dist/src/Utils/derefType.js +6 -0
  102. package/dist/src/Utils/derefType.js.map +1 -1
  103. package/dist/src/Utils/intersectionOfArrays.js +1 -0
  104. package/dist/src/Utils/intersectionOfArrays.js.map +1 -1
  105. package/dist/src/Utils/isAssignableTo.d.ts +12 -0
  106. package/dist/src/Utils/isAssignableTo.js +54 -0
  107. package/dist/src/Utils/isAssignableTo.js.map +1 -1
  108. package/dist/src/Utils/modifiers.d.ts +19 -0
  109. package/dist/src/Utils/modifiers.js +19 -0
  110. package/dist/src/Utils/modifiers.js.map +1 -1
  111. package/dist/src/Utils/narrowType.d.ts +12 -0
  112. package/dist/src/Utils/narrowType.js +15 -0
  113. package/dist/src/Utils/narrowType.js.map +1 -1
  114. package/dist/src/Utils/nodeKey.d.ts +1 -1
  115. package/dist/src/Utils/nodeKey.js +7 -1
  116. package/dist/src/Utils/nodeKey.js.map +1 -1
  117. package/dist/src/Utils/preserveAnnotation.d.ts +6 -0
  118. package/dist/src/Utils/preserveAnnotation.js +6 -0
  119. package/dist/src/Utils/preserveAnnotation.js.map +1 -1
  120. package/dist/src/Utils/removeUndefined.d.ts +3 -0
  121. package/dist/src/Utils/removeUndefined.js +3 -0
  122. package/dist/src/Utils/removeUndefined.js.map +1 -1
  123. package/dist/src/Utils/removeUnreachable.js +1 -0
  124. package/dist/src/Utils/removeUnreachable.js.map +1 -1
  125. package/dist/src/Utils/symbolAtNode.js +1 -0
  126. package/dist/src/Utils/symbolAtNode.js.map +1 -1
  127. package/dist/src/Utils/typeKeys.js +1 -0
  128. package/dist/src/Utils/typeKeys.js.map +1 -1
  129. package/dist/ts-json-schema-generator.js +6 -7
  130. package/dist/ts-json-schema-generator.js.map +1 -1
  131. package/dist/tsconfig.tsbuildinfo +1 -1
  132. package/factory/parser.ts +2 -0
  133. package/factory/program.ts +8 -4
  134. package/package.json +28 -33
  135. package/src/Error/BaseError.ts +1 -3
  136. package/src/NodeParser/BinaryExpressionNodeParser.ts +99 -0
  137. package/src/NodeParser/IndexedAccessTypeNodeParser.ts +4 -1
  138. package/src/NodeParser/MappedTypeNodeParser.ts +12 -8
  139. package/src/SchemaGenerator.ts +18 -3
  140. package/src/Type/AnnotatedType.ts +1 -3
  141. package/src/Type/UnknownType.ts +1 -1
  142. package/src/TypeFormatter/AnnotatedTypeFormatter.ts +2 -2
  143. package/src/Utils/narrowType.ts +1 -6
  144. package/src/Utils/nodeKey.ts +3 -1
  145. package/ts-json-schema-generator.ts +2 -11
package/factory/parser.ts CHANGED
@@ -12,6 +12,7 @@ import { AnyTypeNodeParser } from "../src/NodeParser/AnyTypeNodeParser.js";
12
12
  import { ArrayLiteralExpressionNodeParser } from "../src/NodeParser/ArrayLiteralExpressionNodeParser.js";
13
13
  import { ArrayNodeParser } from "../src/NodeParser/ArrayNodeParser.js";
14
14
  import { AsExpressionNodeParser } from "../src/NodeParser/AsExpressionNodeParser.js";
15
+ import { BinaryExpressionNodeParser } from "../src/NodeParser/BinaryExpressionNodeParser.js";
15
16
  import { BooleanLiteralNodeParser } from "../src/NodeParser/BooleanLiteralNodeParser.js";
16
17
  import { BooleanTypeNodeParser } from "../src/NodeParser/BooleanTypeNodeParser.js";
17
18
  import { CallExpressionParser } from "../src/NodeParser/CallExpressionParser.js";
@@ -118,6 +119,7 @@ export function createParser(program: ts.Program, config: CompletedConfig, augme
118
119
  .addNodeParser(new NeverTypeNodeParser())
119
120
  .addNodeParser(new ObjectTypeNodeParser())
120
121
  .addNodeParser(new AsExpressionNodeParser(chainNodeParser))
122
+ .addNodeParser(new BinaryExpressionNodeParser(chainNodeParser))
121
123
  .addNodeParser(new SatisfiesNodeParser(chainNodeParser))
122
124
  .addNodeParser(withJsDoc(new ParameterParser(chainNodeParser)))
123
125
  .addNodeParser(new StringLiteralNodeParser())
@@ -1,9 +1,10 @@
1
1
  import * as path from "node:path";
2
2
  import normalize from "normalize-path";
3
+ import type { CompilerOptions } from "typescript";
3
4
  import ts from "typescript";
4
5
  import type { CompletedConfig, Config } from "../src/Config.js";
5
6
  import { BuildError } from "../src/Error/Errors.js";
6
- import { globSync } from "glob";
7
+ import fs from "node:fs";
7
8
 
8
9
  function loadTsConfigFile(configFile: string) {
9
10
  const raw = ts.sys.readFile(configFile);
@@ -55,16 +56,19 @@ function getTsConfig(config: Config) {
55
56
  noEmit: true,
56
57
  emitDecoratorMetadata: true,
57
58
  experimentalDecorators: true,
58
- target: ts.ScriptTarget.ES5,
59
+ target: ts.ScriptTarget.ES2022,
59
60
  module: ts.ModuleKind.CommonJS,
60
61
  strictNullChecks: false,
61
- },
62
+ skipLibCheck: true,
63
+ skipDefaultLibCheck: true,
64
+ esModuleInterop: true,
65
+ } satisfies CompilerOptions,
62
66
  };
63
67
  }
64
68
 
65
69
  export function createProgram(config: CompletedConfig): ts.Program {
66
70
  const rootNamesFromPath = config.path
67
- ? globSync(normalize(path.resolve(config.path))).map((rootName) => normalize(rootName))
71
+ ? fs.globSync(normalize(path.resolve(config.path))).map((rootName) => normalize(rootName))
68
72
  : [];
69
73
  const tsconfig = getTsConfig(config);
70
74
  const rootNames = rootNamesFromPath.length ? rootNamesFromPath : tsconfig.fileNames;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-json-schema-generator",
3
- "version": "2.5.0-next.8",
3
+ "version": "2.5.0",
4
4
  "description": "Generate JSON schema from your Typescript sources",
5
5
  "keywords": [
6
6
  "ts",
@@ -49,53 +49,48 @@
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": "jest test/ --verbose",
53
- "test:coverage": "npm run jest -- test/ --collectCoverage=true",
54
- "test:fast": "cross-env FAST_TEST=1 jest test/ --verbose",
55
- "test:update": "cross-env UPDATE_SCHEMA=true npm run test:fast",
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
- "commander": "^14.0.0",
61
- "glob": "^13.0.0",
61
+ "commander": "^14.0.2",
62
62
  "json5": "^2.2.3",
63
63
  "normalize-path": "^3.0.0",
64
64
  "safe-stable-stringify": "^2.5.0",
65
65
  "tslib": "^2.8.1",
66
- "typescript": "^5.8.3",
67
- "@typescript/vfs": "1.6.2"
66
+ "typescript": "^5.9.3"
68
67
  },
69
68
  "devDependencies": {
70
- "@auto-it/conventional-commits": "^11.3.0",
71
- "@auto-it/first-time-contributor": "^11.3.0",
72
- "@babel/core": "^7.28.0",
73
- "@babel/preset-env": "^7.28.0",
74
- "@babel/preset-typescript": "^7.27.1",
75
- "@eslint/js": "^9.30.1",
69
+ "@auto-it/conventional-commits": "^11.3.6",
70
+ "@auto-it/first-time-contributor": "^11.3.6",
71
+ "@eslint/js": "^9.39.1",
76
72
  "@types/eslint": "^9.6.1",
77
- "@types/jest": "^30.0.0",
78
- "@types/node": "^24.0.10",
73
+ "@types/node": "^24.10.1",
79
74
  "@types/normalize-path": "^3.0.2",
75
+ "@typescript/vfs": "1.6.2",
80
76
  "ajv": "^8.17.1",
81
77
  "ajv-formats": "^3.0.1",
82
- "auto": "^11.3.0",
83
- "chai": "^6.0.1",
84
- "cross-env": "^10.0.0",
85
- "eslint": "9.39.1",
86
- "eslint-config-prettier": "^10.1.5",
87
- "eslint-plugin-prettier": "^5.5.1",
88
- "globals": "^16.3.0",
89
- "jest": "^30.0.4",
90
- "jest-junit": "^16.0.0",
91
- "prettier": "^3.6.2",
92
- "tsx": "^4.20.3",
93
- "typescript-eslint": "^8.35.1",
94
- "vega": "^6.1.2",
95
- "vega-lite": "^6.2.0"
78
+ "auto": "^11.3.6",
79
+ "c8": "^10.1.3",
80
+ "cross-env": "^10.1.0",
81
+ "eslint": "9.39.2",
82
+ "eslint-config-prettier": "^10.1.8",
83
+ "eslint-plugin-prettier": "^5.5.4",
84
+ "globals": "^17.0.0",
85
+ "prettier": "^3.7.3",
86
+ "try": "^1.0.1",
87
+ "tsx": "^4.21.0",
88
+ "typescript-eslint": "^8.48.0",
89
+ "vega": "^6.2.0",
90
+ "vega-lite": "^6.4.1"
96
91
  },
97
- "packageManager": "npm@11.4.2",
92
+ "packageManager": "npm@11.6.4",
98
93
  "engines": {
99
- "node": ">=18.0.0"
94
+ "node": ">=22.0.0"
100
95
  }
101
96
  }
@@ -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
+ }
@@ -79,7 +79,10 @@ export class IndexedAccessTypeNodeParser implements SubNodeParser {
79
79
  return objectType;
80
80
  }
81
81
 
82
- throw new LogicError(node, `Invalid index "${type.getValue()}" in type "${objectType.getId()}"`);
82
+ // When the indexed property does not exist (e.g. constrained generics with
83
+ // narrower instantiations), treat it as never so optional properties are dropped
84
+ // instead of throwing.
85
+ return new NeverType();
83
86
  }
84
87
 
85
88
  throw new LogicError(node, `No additional properties in type "${objectType.getId()}"`);
@@ -4,6 +4,7 @@ import type { NodeParser } from "../NodeParser.js";
4
4
  import { Context } from "../NodeParser.js";
5
5
  import type { SubNodeParser } from "../SubNodeParser.js";
6
6
  import { AnnotatedType } from "../Type/AnnotatedType.js";
7
+ import { AnyType } from "../Type/AnyType.js";
7
8
  import { ArrayType } from "../Type/ArrayType.js";
8
9
  import type { BaseType } from "../Type/BaseType.js";
9
10
  import { DefinitionType } from "../Type/DefinitionType.js";
@@ -52,18 +53,21 @@ export class MappedTypeNodeParser implements SubNodeParser {
52
53
  return new ObjectType(id, [], this.getProperties(node, new UnionType([keyListType]), context), false);
53
54
  }
54
55
 
56
+ const maybeUnionType = this.childNodeParser.createType(
57
+ node.type!,
58
+ this.createSubContext(node, keyListType, context),
59
+ );
60
+ if (maybeUnionType instanceof UnionType && constraintType?.getId() === "number") {
61
+ // Then we turn it into an array
62
+ return maybeUnionType instanceof NeverType ? new NeverType() : new ArrayType(maybeUnionType);
63
+ }
64
+
55
65
  if (
56
66
  keyListType instanceof StringType ||
57
67
  keyListType instanceof NumberType ||
58
- keyListType instanceof SymbolType
68
+ keyListType instanceof SymbolType ||
69
+ keyListType instanceof AnyType
59
70
  ) {
60
- if (constraintType?.getId() === "number") {
61
- const type = this.childNodeParser.createType(
62
- node.type!,
63
- this.createSubContext(node, keyListType, context),
64
- );
65
- return type instanceof NeverType ? new NeverType() : new ArrayType(type);
66
- }
67
71
  // Key type widens to `string`
68
72
  const type = this.childNodeParser.createType(node.type!, this.createSubContext(node, keyListType, context));
69
73
  // const resultType = type instanceof NeverType ? new NeverType() : new ObjectType(id, [], [], type);
@@ -8,6 +8,7 @@ import type { BaseType } from "./Type/BaseType.js";
8
8
  import { DefinitionType } from "./Type/DefinitionType.js";
9
9
  import type { TypeFormatter } from "./TypeFormatter.js";
10
10
  import type { StringMap } from "./Utils/StringMap.js";
11
+ import { AnnotatedType } from "./Type/AnnotatedType.js";
11
12
  import { hasJsDocTag } from "./Utils/hasJsDocTag.js";
12
13
  import { removeUnreachable } from "./Utils/removeUnreachable.js";
13
14
  import { castArray } from "./Utils/castArray.js";
@@ -124,21 +125,35 @@ export class SchemaGenerator {
124
125
  });
125
126
 
126
127
  const ids = new Map<string, string>();
128
+ const baseIds = new Map<string, string>();
127
129
  for (const child of children) {
128
130
  const name = child.getName();
129
131
  const previousId = ids.get(name);
130
- // remove def prefix from ids to avoid false alarms
131
- // FIXME: we probably shouldn't be doing this as there is probably something wrong with the deduplication
132
+ // Strip def- prefixes from IDs. DefinitionType.getId() returns "def-{innerType.getId()}"
133
+ // and for generic types, nested DefinitionTypes also add def- prefixes. Stripping all
134
+ // of them normalizes the comparison for types that may be wrapped differently.
132
135
  const childId = child.getId().replace(/def-/g, "");
136
+ // Also track the base type ID (without AnnotatedType wrapper) to handle cases where
137
+ // the same type appears with different annotations (e.g., a discriminated union type
138
+ // referenced directly vs from a property - one has @discriminator annotation, one doesn't)
139
+ const innerType = child.getType();
140
+ const baseChildId = (innerType instanceof AnnotatedType ? innerType.getType() : innerType).getId();
141
+ const previousBaseId = baseIds.get(name);
133
142
 
134
143
  if (previousId && childId !== previousId) {
144
+ // Check if the base type (without annotations) matches - if so, it's just
145
+ // annotation differences, not truly different types
146
+ if (previousBaseId === baseChildId) {
147
+ continue;
148
+ }
135
149
  throw new MultipleDefinitionsError(
136
150
  name,
137
151
  child,
138
- children.find((c) => c.getId() === previousId),
152
+ children.find((c) => c.getId().replace(/def-/g, "") === previousId),
139
153
  );
140
154
  }
141
155
  ids.set(name, childId);
156
+ baseIds.set(name, baseChildId);
142
157
  }
143
158
 
144
159
  children.reduce((definitions, child) => {
@@ -1,9 +1,7 @@
1
1
  import { BaseType } from "./BaseType.js";
2
2
  import { hash } from "../Utils/nodeKey.js";
3
3
 
4
- export interface Annotations {
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(
@@ -5,7 +5,7 @@ export class UnknownType extends BaseType {
5
5
  /**
6
6
  * If the source for this UnknownType was from a failed operation than to an actual `unknown` type present in the source code.
7
7
  */
8
- readonly erroredSource: boolean,
8
+ readonly erroredSource = false,
9
9
  ) {
10
10
  super();
11
11
  }
@@ -8,7 +8,7 @@ import type { TypeFormatter } from "../TypeFormatter.js";
8
8
  import { derefType } from "../Utils/derefType.js";
9
9
 
10
10
  export function makeNullable(def: Definition): Definition {
11
- const union: Definition[] | undefined = (def.oneOf as Definition[]) || def.anyOf;
11
+ const union = (def.oneOf || def.anyOf) as Definition[] | undefined;
12
12
  if (union && union.filter((d: Definition) => d.type === "null").length === 0) {
13
13
  union.push({ type: "null" });
14
14
  } else if (def.type && def.type !== "object") {
@@ -58,7 +58,7 @@ export class AnnotatedTypeFormatter implements SubTypeFormatter {
58
58
  if ("discriminator" in annotations) {
59
59
  const deref = derefType(type.getType());
60
60
  if (deref instanceof UnionType) {
61
- deref.setDiscriminator(annotations.discriminator);
61
+ deref.setDiscriminator(annotations.discriminator as string);
62
62
  delete annotations.discriminator;
63
63
  } else {
64
64
  throw new JsonTypeError(
@@ -16,12 +16,7 @@ import { derefType } from "./derefType.js";
16
16
  * kept, when returning false it is removed.
17
17
  * @return The narrowed down type.
18
18
  */
19
- export function narrowType(
20
- type: BaseType,
21
- // TODO: remove the next line
22
- // eslint-disable-next-line no-shadow
23
- predicate: (type: BaseType) => boolean,
24
- ): BaseType {
19
+ export function narrowType(type: BaseType, predicate: (type: BaseType) => boolean): BaseType {
25
20
  const derefed = derefType(type);
26
21
  if (derefed instanceof UnionType || derefed instanceof EnumType) {
27
22
  let changed = false;
@@ -2,7 +2,9 @@ import stringify from "safe-stable-stringify";
2
2
  import type { Node } from "typescript";
3
3
  import type { Context } from "../NodeParser.js";
4
4
 
5
- export function hash(a: string | boolean | number | (string | boolean | number)[] | object): string | number {
5
+ export function hash(
6
+ a: string | boolean | number | (string | boolean | number | Record<string, unknown>)[] | Record<string, unknown>,
7
+ ): string | number {
6
8
  if (typeof a === "number") {
7
9
  return a;
8
10
  }
@@ -11,23 +11,14 @@ import pkg from "./package.json";
11
11
 
12
12
  const args = new Command()
13
13
  .option("-p, --path <path>", "Source file path")
14
- .option(
15
- "-t, --type <name>",
16
- "Type name (can be passed multiple times)",
17
- (value: string, previous: string[] | undefined) => {
18
- if (previous) {
19
- return previous.concat(value);
20
- }
21
- return [value];
22
- },
23
- )
14
+ .option("-t, --type <name...>", "Type name(s)")
24
15
  .option("-i, --id <name>", "$id for generated schema")
25
16
  .option("-f, --tsconfig <path>", "Custom tsconfig.json path")
26
17
  .addOption(
27
18
  new Option("-e, --expose <expose>", "Type exposing").choices(["all", "none", "export"]).default("export"),
28
19
  )
29
20
  .addOption(
30
- new Option("-j, --jsDoc <extended>", "Read JsDoc annotations")
21
+ new Option("-j, --jsDoc <extended>", "Read JSDoc annotations")
31
22
  .choices(["none", "basic", "extended"])
32
23
  .default("extended"),
33
24
  )