From c7c1baa512d016df1b2dcf17f597256fad4bbbf2 Mon Sep 17 00:00:00 2001 From: alexchexes Date: Mon, 23 Jun 2025 16:27:25 +0100 Subject: [PATCH 01/10] add tests (with pre-fix schemas snapshots) and semi-working fix that works not in all cases --- src/ExposeNodeParser.ts | 50 ++++++++++++++++++- test/valid-data-other.test.ts | 7 +++ .../generic-mapped-complex-all/main.ts | 22 ++++++++ .../schema-pre-fix.json | 32 ++++++++++++ .../generic-mapped-complex-all/schema.json | 26 ++++++++++ .../generic-mapped-complex-star/main.ts | 22 ++++++++ .../schema-pre-fix.json | 47 +++++++++++++++++ .../generic-mapped-complex-star/schema.json | 47 +++++++++++++++++ test/valid-data/generic-valueof-all/main.ts | 8 +++ .../generic-valueof-all/schema-pre-fix.json | 16 ++++++ .../generic-valueof-all/schema.json | 13 +++++ test/valid-data/generic-valueof-star/main.ts | 8 +++ .../generic-valueof-star/schema-pre-fix.json | 16 ++++++ .../generic-valueof-star/schema.json | 16 ++++++ 14 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 test/valid-data/generic-mapped-complex-all/main.ts create mode 100644 test/valid-data/generic-mapped-complex-all/schema-pre-fix.json create mode 100644 test/valid-data/generic-mapped-complex-all/schema.json create mode 100644 test/valid-data/generic-mapped-complex-star/main.ts create mode 100644 test/valid-data/generic-mapped-complex-star/schema-pre-fix.json create mode 100644 test/valid-data/generic-mapped-complex-star/schema.json create mode 100644 test/valid-data/generic-valueof-all/main.ts create mode 100644 test/valid-data/generic-valueof-all/schema-pre-fix.json create mode 100644 test/valid-data/generic-valueof-all/schema.json create mode 100644 test/valid-data/generic-valueof-star/main.ts create mode 100644 test/valid-data/generic-valueof-star/schema-pre-fix.json create mode 100644 test/valid-data/generic-valueof-star/schema.json diff --git a/src/ExposeNodeParser.ts b/src/ExposeNodeParser.ts index 171a67d5f..7ca736d1f 100644 --- a/src/ExposeNodeParser.ts +++ b/src/ExposeNodeParser.ts @@ -6,6 +6,10 @@ import { DefinitionType } from "./Type/DefinitionType.js"; import type { ReferenceType } from "./Type/ReferenceType.js"; import { hasJsDocTag } from "./Utils/hasJsDocTag.js"; import { symbolAtNode } from "./Utils/symbolAtNode.js"; +import { AliasType } from "./Type/AliasType.js"; +import { derefAliasedType, isDeepLiteralUnion } from "./Utils/derefType.js"; +import { ObjectType } from "./Type/ObjectType.js"; +import { IntersectionType } from "./Type/IntersectionType.js"; export class ExposeNodeParser implements SubNodeParser { public constructor( @@ -22,7 +26,7 @@ export class ExposeNodeParser implements SubNodeParser { public createType(node: ts.Node, context: Context, reference?: ReferenceType): BaseType { const baseType = this.subNodeParser.createType(node, context, reference); - if (!this.isExportNode(node)) { + if (!this.isExportNode(node) || this.isFromLib(node) || this.shouldInline(node, baseType, context)) { return baseType; } @@ -49,4 +53,48 @@ export class ExposeNodeParser implements SubNodeParser { return argumentIds.length ? `${fullName}<${argumentIds.join(",")}>` : fullName; } + + private isFromLib(node: ts.Node): boolean { + const sourceFile = node.getSourceFile(); + if (!sourceFile) { + return false; + } + return /[\\/]typescript[\\/]lib[\\/]/i.test(sourceFile.fileName); + } + + private shouldInline(node: ts.Node, type: BaseType, context: Context): boolean { + if (!ts.isTypeAliasDeclaration(node)) { + return false; + } + if (!(type instanceof AliasType)) { + return false; + } + if (!node.typeParameters?.length) { + return false; + } + + const localSymbol: ts.Symbol = (node as any).localSymbol; + const isExported = localSymbol ? "exportSymbol" in localSymbol : false; + if (isExported) { + return false; + } + + const actual = derefAliasedType(type.getType()); + if (isDeepLiteralUnion(actual)) { + return true; + } + + // Inline non-exported generics producing structural object types to avoid + // unwieldy definition names like `Alias` when expose: all + if (actual instanceof ObjectType || actual instanceof IntersectionType) { + return true; + } + + // Inline when any generic argument is structural (e.g. `structure-xyz`) + if (context.getArguments().some((arg) => /^structure-/.test(arg?.getName()))) { + return true; + } + + return false; + } } diff --git a/test/valid-data-other.test.ts b/test/valid-data-other.test.ts index 81bfab963..f94222616 100644 --- a/test/valid-data-other.test.ts +++ b/test/valid-data-other.test.ts @@ -57,6 +57,13 @@ describe("valid-data-other", () => { it("generic-nested", assertValidSchema("generic-nested", "MyObject")); it("generic-prefixed-number", assertValidSchema("generic-prefixed-number", "MyObject")); it("generic-void", assertValidSchema("generic-void", "MyObject")); + it("generic-mapped-complex-star", assertValidSchema("generic-mapped-complex-star", "*", { encodeRefs: false })); + it( + "generic-mapped-complex-all", + assertValidSchema("generic-mapped-complex-all", "MyType", { encodeRefs: false, expose: "all" }), + ); + it("generic-valueof-all", assertValidSchema("generic-valueof-all", "MyType", { expose: "all" })); + it("generic-valueof-star", assertValidSchema("generic-valueof-star", "*")); it("nullable-null", assertValidSchema("nullable-null", "MyObject")); diff --git a/test/valid-data/generic-mapped-complex-all/main.ts b/test/valid-data/generic-mapped-complex-all/main.ts new file mode 100644 index 000000000..dd3cd9702 --- /dev/null +++ b/test/valid-data/generic-mapped-complex-all/main.ts @@ -0,0 +1,22 @@ +type Merge = { + [K in keyof A as K extends keyof B ? never : K]: A[K]; +} & B; + +type Simplify = { + [K in keyof T]: T[K]; +} & {}; + +type OverrideSimple = Simplify>; + +type Base = { + foo: string; + bar: number; +}; + +export type MyType = OverrideSimple< + Base, + { + bar: string; + baz: boolean; + } +>; diff --git a/test/valid-data/generic-mapped-complex-all/schema-pre-fix.json b/test/valid-data/generic-mapped-complex-all/schema-pre-fix.json new file mode 100644 index 000000000..694c377e2 --- /dev/null +++ b/test/valid-data/generic-mapped-complex-all/schema-pre-fix.json @@ -0,0 +1,32 @@ +{ + "$ref": "#/definitions/MyType", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyType": { + "$ref": "#/definitions/OverrideSimple" + }, + "OverrideSimple": { + "$ref": "#/definitions/Simplify>" + }, + "Simplify>": { + "additionalProperties": false, + "properties": { + "bar": { + "type": "string" + }, + "baz": { + "type": "boolean" + }, + "foo": { + "type": "string" + } + }, + "required": [ + "bar", + "baz", + "foo" + ], + "type": "object" + } + } +} diff --git a/test/valid-data/generic-mapped-complex-all/schema.json b/test/valid-data/generic-mapped-complex-all/schema.json new file mode 100644 index 000000000..e0754d330 --- /dev/null +++ b/test/valid-data/generic-mapped-complex-all/schema.json @@ -0,0 +1,26 @@ +{ + "$ref": "#/definitions/MyType", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyType": { + "additionalProperties": false, + "properties": { + "bar": { + "type": "string" + }, + "baz": { + "type": "boolean" + }, + "foo": { + "type": "string" + } + }, + "required": [ + "bar", + "baz", + "foo" + ], + "type": "object" + } + } +} diff --git a/test/valid-data/generic-mapped-complex-star/main.ts b/test/valid-data/generic-mapped-complex-star/main.ts new file mode 100644 index 000000000..e50364156 --- /dev/null +++ b/test/valid-data/generic-mapped-complex-star/main.ts @@ -0,0 +1,22 @@ +export type Merge = { + [K in keyof A as K extends keyof B ? never : K]: A[K]; +} & B; + +export type Simplify = { + [K in keyof T]: T[K]; +} & {}; + +export type OverrideSimple = Simplify>; + +export type Base = { + foo: string; + bar: number; +}; + +export type MyType = OverrideSimple< + Base, + { + bar: string; + baz: boolean; + } +>; diff --git a/test/valid-data/generic-mapped-complex-star/schema-pre-fix.json b/test/valid-data/generic-mapped-complex-star/schema-pre-fix.json new file mode 100644 index 000000000..d3236588c --- /dev/null +++ b/test/valid-data/generic-mapped-complex-star/schema-pre-fix.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Base": { + "additionalProperties": false, + "properties": { + "bar": { + "type": "number" + }, + "foo": { + "type": "string" + } + }, + "required": [ + "foo", + "bar" + ], + "type": "object" + }, + "MyType": { + "$ref": "#/definitions/OverrideSimple" + }, + "OverrideSimple": { + "$ref": "#/definitions/Simplify>" + }, + "Simplify>": { + "additionalProperties": false, + "properties": { + "bar": { + "type": "string" + }, + "baz": { + "type": "boolean" + }, + "foo": { + "type": "string" + } + }, + "required": [ + "bar", + "baz", + "foo" + ], + "type": "object" + } + } +} diff --git a/test/valid-data/generic-mapped-complex-star/schema.json b/test/valid-data/generic-mapped-complex-star/schema.json new file mode 100644 index 000000000..d3236588c --- /dev/null +++ b/test/valid-data/generic-mapped-complex-star/schema.json @@ -0,0 +1,47 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Base": { + "additionalProperties": false, + "properties": { + "bar": { + "type": "number" + }, + "foo": { + "type": "string" + } + }, + "required": [ + "foo", + "bar" + ], + "type": "object" + }, + "MyType": { + "$ref": "#/definitions/OverrideSimple" + }, + "OverrideSimple": { + "$ref": "#/definitions/Simplify>" + }, + "Simplify>": { + "additionalProperties": false, + "properties": { + "bar": { + "type": "string" + }, + "baz": { + "type": "boolean" + }, + "foo": { + "type": "string" + } + }, + "required": [ + "bar", + "baz", + "foo" + ], + "type": "object" + } + } +} diff --git a/test/valid-data/generic-valueof-all/main.ts b/test/valid-data/generic-valueof-all/main.ts new file mode 100644 index 000000000..73b2e93be --- /dev/null +++ b/test/valid-data/generic-valueof-all/main.ts @@ -0,0 +1,8 @@ +const RuntimeObject = { + FOO: "foo-val", + BAR: "bar-val", +} as const; + +type ValueOf = T[keyof T]; + +export type MyType = ValueOf; diff --git a/test/valid-data/generic-valueof-all/schema-pre-fix.json b/test/valid-data/generic-valueof-all/schema-pre-fix.json new file mode 100644 index 000000000..6f6c8be3f --- /dev/null +++ b/test/valid-data/generic-valueof-all/schema-pre-fix.json @@ -0,0 +1,16 @@ +{ + "$ref": "#/definitions/MyType", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyType": { + "$ref": "#/definitions/ValueOf%3Cobject-984382748-21-65-984382748-21-74-984382748-5-74-984382748-0-74-984382748-0-75-984382748-0-160%3E" + }, + "ValueOf": { + "enum": [ + "foo-val", + "bar-val" + ], + "type": "string" + } + } +} diff --git a/test/valid-data/generic-valueof-all/schema.json b/test/valid-data/generic-valueof-all/schema.json new file mode 100644 index 000000000..ef4037017 --- /dev/null +++ b/test/valid-data/generic-valueof-all/schema.json @@ -0,0 +1,13 @@ +{ + "$ref": "#/definitions/MyType", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyType": { + "enum": [ + "foo-val", + "bar-val" + ], + "type": "string" + } + } +} diff --git a/test/valid-data/generic-valueof-star/main.ts b/test/valid-data/generic-valueof-star/main.ts new file mode 100644 index 000000000..884c3f894 --- /dev/null +++ b/test/valid-data/generic-valueof-star/main.ts @@ -0,0 +1,8 @@ +export const RuntimeObject = { + FOO: "foo-val", + BAR: "bar-val", +} as const; + +export type ValueOf = T[keyof T]; + +export type MyType = ValueOf; diff --git a/test/valid-data/generic-valueof-star/schema-pre-fix.json b/test/valid-data/generic-valueof-star/schema-pre-fix.json new file mode 100644 index 000000000..cb2ff5db5 --- /dev/null +++ b/test/valid-data/generic-valueof-star/schema-pre-fix.json @@ -0,0 +1,16 @@ +{ + "$ref": "#/definitions/MyType", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyType": { + "$ref": "#/definitions/ValueOf%3Cobject-1465228025-28-72-1465228025-28-81-1465228025-12-81-1465228025-6-81-1465228025-0-82-1465228025-0-174%3E" + }, + "ValueOf": { + "enum": [ + "foo-val", + "bar-val" + ], + "type": "string" + } + } +} diff --git a/test/valid-data/generic-valueof-star/schema.json b/test/valid-data/generic-valueof-star/schema.json new file mode 100644 index 000000000..cb2ff5db5 --- /dev/null +++ b/test/valid-data/generic-valueof-star/schema.json @@ -0,0 +1,16 @@ +{ + "$ref": "#/definitions/MyType", + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "MyType": { + "$ref": "#/definitions/ValueOf%3Cobject-1465228025-28-72-1465228025-28-81-1465228025-12-81-1465228025-6-81-1465228025-0-82-1465228025-0-174%3E" + }, + "ValueOf": { + "enum": [ + "foo-val", + "bar-val" + ], + "type": "string" + } + } +} From 7c1ebba86e262866f2ae599505cd0668ea682c4f Mon Sep 17 00:00:00 2001 From: alexchexes Date: Mon, 23 Jun 2025 21:11:13 +0100 Subject: [PATCH 02/10] fix: inline internal structure generics even when they're exported --- src/ExposeNodeParser.ts | 17 +++++++++-------- .../generic-mapped-complex-star/schema.json | 6 ------ .../valid-data/generic-valueof-star/schema.json | 3 --- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/src/ExposeNodeParser.ts b/src/ExposeNodeParser.ts index 7ca736d1f..ea92a6d60 100644 --- a/src/ExposeNodeParser.ts +++ b/src/ExposeNodeParser.ts @@ -75,26 +75,27 @@ export class ExposeNodeParser implements SubNodeParser { const localSymbol: ts.Symbol = (node as any).localSymbol; const isExported = localSymbol ? "exportSymbol" in localSymbol : false; - if (isExported) { + + const actual = derefAliasedType(type.getType()); + const hasStructuralArg = context + .getArguments() + .some((arg) => /^(structure|object|alias|def-alias)-/.test(arg?.getName() ?? "")); + + if (isExported && !hasStructuralArg) { return false; } - const actual = derefAliasedType(type.getType()); if (isDeepLiteralUnion(actual)) { return true; } - // Inline non-exported generics producing structural object types to avoid - // unwieldy definition names like `Alias` when expose: all - if (actual instanceof ObjectType || actual instanceof IntersectionType) { + if (!isExported && (actual instanceof ObjectType || actual instanceof IntersectionType)) { return true; } - // Inline when any generic argument is structural (e.g. `structure-xyz`) - if (context.getArguments().some((arg) => /^structure-/.test(arg?.getName()))) { + if (hasStructuralArg) { return true; } - return false; } } diff --git a/test/valid-data/generic-mapped-complex-star/schema.json b/test/valid-data/generic-mapped-complex-star/schema.json index d3236588c..b86d06624 100644 --- a/test/valid-data/generic-mapped-complex-star/schema.json +++ b/test/valid-data/generic-mapped-complex-star/schema.json @@ -18,12 +18,6 @@ "type": "object" }, "MyType": { - "$ref": "#/definitions/OverrideSimple" - }, - "OverrideSimple": { - "$ref": "#/definitions/Simplify>" - }, - "Simplify>": { "additionalProperties": false, "properties": { "bar": { diff --git a/test/valid-data/generic-valueof-star/schema.json b/test/valid-data/generic-valueof-star/schema.json index cb2ff5db5..ef4037017 100644 --- a/test/valid-data/generic-valueof-star/schema.json +++ b/test/valid-data/generic-valueof-star/schema.json @@ -3,9 +3,6 @@ "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { "MyType": { - "$ref": "#/definitions/ValueOf%3Cobject-1465228025-28-72-1465228025-28-81-1465228025-12-81-1465228025-6-81-1465228025-0-82-1465228025-0-174%3E" - }, - "ValueOf": { "enum": [ "foo-val", "bar-val" From d6bee00fb3fd0d44d0b5c424c53f37061ad7ee05 Mon Sep 17 00:00:00 2001 From: alexchexes Date: Mon, 23 Jun 2025 21:26:50 +0100 Subject: [PATCH 03/10] cleanup --- test/valid-data-other.test.ts | 9 +--- .../generic-mapped-complex-all/main.ts | 22 ---------- .../schema-pre-fix.json | 32 --------------- .../generic-mapped-complex-all/schema.json | 26 ------------ .../generic-mapped-complex-star/schema.json | 41 ------------------- .../valid-data/generic-mapped-complex/main.ts | 14 +++++++ .../schema.json} | 8 ++-- .../util.ts} | 13 ------ test/valid-data/generic-valueof-all/main.ts | 8 ---- .../generic-valueof-all/schema-pre-fix.json | 16 -------- .../generic-valueof-star/schema-pre-fix.json | 16 -------- .../generic-valueof-star/schema.json | 13 ------ .../main.ts | 0 .../schema.json | 0 14 files changed, 20 insertions(+), 198 deletions(-) delete mode 100644 test/valid-data/generic-mapped-complex-all/main.ts delete mode 100644 test/valid-data/generic-mapped-complex-all/schema-pre-fix.json delete mode 100644 test/valid-data/generic-mapped-complex-all/schema.json delete mode 100644 test/valid-data/generic-mapped-complex-star/schema.json create mode 100644 test/valid-data/generic-mapped-complex/main.ts rename test/valid-data/{generic-mapped-complex-star/schema-pre-fix.json => generic-mapped-complex/schema.json} (59%) rename test/valid-data/{generic-mapped-complex-star/main.ts => generic-mapped-complex/util.ts} (56%) delete mode 100644 test/valid-data/generic-valueof-all/main.ts delete mode 100644 test/valid-data/generic-valueof-all/schema-pre-fix.json delete mode 100644 test/valid-data/generic-valueof-star/schema-pre-fix.json delete mode 100644 test/valid-data/generic-valueof-star/schema.json rename test/valid-data/{generic-valueof-star => generic-valueof}/main.ts (100%) rename test/valid-data/{generic-valueof-all => generic-valueof}/schema.json (100%) diff --git a/test/valid-data-other.test.ts b/test/valid-data-other.test.ts index f94222616..c27c8d0af 100644 --- a/test/valid-data-other.test.ts +++ b/test/valid-data-other.test.ts @@ -57,13 +57,8 @@ describe("valid-data-other", () => { it("generic-nested", assertValidSchema("generic-nested", "MyObject")); it("generic-prefixed-number", assertValidSchema("generic-prefixed-number", "MyObject")); it("generic-void", assertValidSchema("generic-void", "MyObject")); - it("generic-mapped-complex-star", assertValidSchema("generic-mapped-complex-star", "*", { encodeRefs: false })); - it( - "generic-mapped-complex-all", - assertValidSchema("generic-mapped-complex-all", "MyType", { encodeRefs: false, expose: "all" }), - ); - it("generic-valueof-all", assertValidSchema("generic-valueof-all", "MyType", { expose: "all" })); - it("generic-valueof-star", assertValidSchema("generic-valueof-star", "*")); + it("generic-mapped-complex", assertValidSchema("generic-mapped-complex", "*", { encodeRefs: true })); + it("generic-valueof", assertValidSchema("generic-valueof", "*", { encodeRefs: true })); it("nullable-null", assertValidSchema("nullable-null", "MyObject")); diff --git a/test/valid-data/generic-mapped-complex-all/main.ts b/test/valid-data/generic-mapped-complex-all/main.ts deleted file mode 100644 index dd3cd9702..000000000 --- a/test/valid-data/generic-mapped-complex-all/main.ts +++ /dev/null @@ -1,22 +0,0 @@ -type Merge = { - [K in keyof A as K extends keyof B ? never : K]: A[K]; -} & B; - -type Simplify = { - [K in keyof T]: T[K]; -} & {}; - -type OverrideSimple = Simplify>; - -type Base = { - foo: string; - bar: number; -}; - -export type MyType = OverrideSimple< - Base, - { - bar: string; - baz: boolean; - } ->; diff --git a/test/valid-data/generic-mapped-complex-all/schema-pre-fix.json b/test/valid-data/generic-mapped-complex-all/schema-pre-fix.json deleted file mode 100644 index 694c377e2..000000000 --- a/test/valid-data/generic-mapped-complex-all/schema-pre-fix.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "$ref": "#/definitions/MyType", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "MyType": { - "$ref": "#/definitions/OverrideSimple" - }, - "OverrideSimple": { - "$ref": "#/definitions/Simplify>" - }, - "Simplify>": { - "additionalProperties": false, - "properties": { - "bar": { - "type": "string" - }, - "baz": { - "type": "boolean" - }, - "foo": { - "type": "string" - } - }, - "required": [ - "bar", - "baz", - "foo" - ], - "type": "object" - } - } -} diff --git a/test/valid-data/generic-mapped-complex-all/schema.json b/test/valid-data/generic-mapped-complex-all/schema.json deleted file mode 100644 index e0754d330..000000000 --- a/test/valid-data/generic-mapped-complex-all/schema.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "$ref": "#/definitions/MyType", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "MyType": { - "additionalProperties": false, - "properties": { - "bar": { - "type": "string" - }, - "baz": { - "type": "boolean" - }, - "foo": { - "type": "string" - } - }, - "required": [ - "bar", - "baz", - "foo" - ], - "type": "object" - } - } -} diff --git a/test/valid-data/generic-mapped-complex-star/schema.json b/test/valid-data/generic-mapped-complex-star/schema.json deleted file mode 100644 index b86d06624..000000000 --- a/test/valid-data/generic-mapped-complex-star/schema.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "Base": { - "additionalProperties": false, - "properties": { - "bar": { - "type": "number" - }, - "foo": { - "type": "string" - } - }, - "required": [ - "foo", - "bar" - ], - "type": "object" - }, - "MyType": { - "additionalProperties": false, - "properties": { - "bar": { - "type": "string" - }, - "baz": { - "type": "boolean" - }, - "foo": { - "type": "string" - } - }, - "required": [ - "bar", - "baz", - "foo" - ], - "type": "object" - } - } -} diff --git a/test/valid-data/generic-mapped-complex/main.ts b/test/valid-data/generic-mapped-complex/main.ts new file mode 100644 index 000000000..5b963f3f5 --- /dev/null +++ b/test/valid-data/generic-mapped-complex/main.ts @@ -0,0 +1,14 @@ +import { OverrideSimple } from "./util"; + +export type Base = { + foo: string; + bar: number; +}; + +export type MyType = OverrideSimple< + Base, + { + bar: string; + baz: boolean; + } +>; diff --git a/test/valid-data/generic-mapped-complex-star/schema-pre-fix.json b/test/valid-data/generic-mapped-complex/schema.json similarity index 59% rename from test/valid-data/generic-mapped-complex-star/schema-pre-fix.json rename to test/valid-data/generic-mapped-complex/schema.json index d3236588c..3a278c29d 100644 --- a/test/valid-data/generic-mapped-complex-star/schema-pre-fix.json +++ b/test/valid-data/generic-mapped-complex/schema.json @@ -18,12 +18,12 @@ "type": "object" }, "MyType": { - "$ref": "#/definitions/OverrideSimple" + "$ref": "#/definitions/OverrideSimple%3CBase%2Cstructure-303496744-147-202-303496744-121-204-303496744-99-205-303496744-0-206%3E" }, - "OverrideSimple": { - "$ref": "#/definitions/Simplify>" + "OverrideSimple": { + "$ref": "#/definitions/Simplify%3CMerge%3CBase%2Cstructure-303496744-147-202-303496744-121-204-303496744-99-205-303496744-0-206%3E%3E" }, - "Simplify>": { + "Simplify>": { "additionalProperties": false, "properties": { "bar": { diff --git a/test/valid-data/generic-mapped-complex-star/main.ts b/test/valid-data/generic-mapped-complex/util.ts similarity index 56% rename from test/valid-data/generic-mapped-complex-star/main.ts rename to test/valid-data/generic-mapped-complex/util.ts index e50364156..e7de1f2c2 100644 --- a/test/valid-data/generic-mapped-complex-star/main.ts +++ b/test/valid-data/generic-mapped-complex/util.ts @@ -7,16 +7,3 @@ export type Simplify = { } & {}; export type OverrideSimple = Simplify>; - -export type Base = { - foo: string; - bar: number; -}; - -export type MyType = OverrideSimple< - Base, - { - bar: string; - baz: boolean; - } ->; diff --git a/test/valid-data/generic-valueof-all/main.ts b/test/valid-data/generic-valueof-all/main.ts deleted file mode 100644 index 73b2e93be..000000000 --- a/test/valid-data/generic-valueof-all/main.ts +++ /dev/null @@ -1,8 +0,0 @@ -const RuntimeObject = { - FOO: "foo-val", - BAR: "bar-val", -} as const; - -type ValueOf = T[keyof T]; - -export type MyType = ValueOf; diff --git a/test/valid-data/generic-valueof-all/schema-pre-fix.json b/test/valid-data/generic-valueof-all/schema-pre-fix.json deleted file mode 100644 index 6f6c8be3f..000000000 --- a/test/valid-data/generic-valueof-all/schema-pre-fix.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$ref": "#/definitions/MyType", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "MyType": { - "$ref": "#/definitions/ValueOf%3Cobject-984382748-21-65-984382748-21-74-984382748-5-74-984382748-0-74-984382748-0-75-984382748-0-160%3E" - }, - "ValueOf": { - "enum": [ - "foo-val", - "bar-val" - ], - "type": "string" - } - } -} diff --git a/test/valid-data/generic-valueof-star/schema-pre-fix.json b/test/valid-data/generic-valueof-star/schema-pre-fix.json deleted file mode 100644 index cb2ff5db5..000000000 --- a/test/valid-data/generic-valueof-star/schema-pre-fix.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "$ref": "#/definitions/MyType", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "MyType": { - "$ref": "#/definitions/ValueOf%3Cobject-1465228025-28-72-1465228025-28-81-1465228025-12-81-1465228025-6-81-1465228025-0-82-1465228025-0-174%3E" - }, - "ValueOf": { - "enum": [ - "foo-val", - "bar-val" - ], - "type": "string" - } - } -} diff --git a/test/valid-data/generic-valueof-star/schema.json b/test/valid-data/generic-valueof-star/schema.json deleted file mode 100644 index ef4037017..000000000 --- a/test/valid-data/generic-valueof-star/schema.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "$ref": "#/definitions/MyType", - "$schema": "http://json-schema.org/draft-07/schema#", - "definitions": { - "MyType": { - "enum": [ - "foo-val", - "bar-val" - ], - "type": "string" - } - } -} diff --git a/test/valid-data/generic-valueof-star/main.ts b/test/valid-data/generic-valueof/main.ts similarity index 100% rename from test/valid-data/generic-valueof-star/main.ts rename to test/valid-data/generic-valueof/main.ts diff --git a/test/valid-data/generic-valueof-all/schema.json b/test/valid-data/generic-valueof/schema.json similarity index 100% rename from test/valid-data/generic-valueof-all/schema.json rename to test/valid-data/generic-valueof/schema.json From 434d7b53196670ccc109fe8e4debe285127ffd88 Mon Sep 17 00:00:00 2001 From: alexchexes Date: Mon, 23 Jun 2025 21:38:14 +0100 Subject: [PATCH 04/10] re-generate schema --- test/valid-data/generic-mapped-complex/schema.json | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/valid-data/generic-mapped-complex/schema.json b/test/valid-data/generic-mapped-complex/schema.json index 3a278c29d..b86d06624 100644 --- a/test/valid-data/generic-mapped-complex/schema.json +++ b/test/valid-data/generic-mapped-complex/schema.json @@ -18,12 +18,6 @@ "type": "object" }, "MyType": { - "$ref": "#/definitions/OverrideSimple%3CBase%2Cstructure-303496744-147-202-303496744-121-204-303496744-99-205-303496744-0-206%3E" - }, - "OverrideSimple": { - "$ref": "#/definitions/Simplify%3CMerge%3CBase%2Cstructure-303496744-147-202-303496744-121-204-303496744-99-205-303496744-0-206%3E%3E" - }, - "Simplify>": { "additionalProperties": false, "properties": { "bar": { From c3b2c94178d6c51ba8079c1fad01f96b7d48dc86 Mon Sep 17 00:00:00 2001 From: alexchexes Date: Mon, 23 Jun 2025 21:54:59 +0100 Subject: [PATCH 05/10] replace simplified 'Override' with a type-fest one. Bug is found --- .../valid-data/generic-mapped-complex/main.ts | 5 ++- .../generic-mapped-complex/schema.json | 7 ++-- .../valid-data/generic-mapped-complex/util.ts | 32 +++++++++++++++---- 3 files changed, 30 insertions(+), 14 deletions(-) diff --git a/test/valid-data/generic-mapped-complex/main.ts b/test/valid-data/generic-mapped-complex/main.ts index 5b963f3f5..10158945f 100644 --- a/test/valid-data/generic-mapped-complex/main.ts +++ b/test/valid-data/generic-mapped-complex/main.ts @@ -1,14 +1,13 @@ -import { OverrideSimple } from "./util"; +import { OverrideProperties } from "./util"; export type Base = { foo: string; bar: number; }; -export type MyType = OverrideSimple< +export type MyType = OverrideProperties< Base, { bar: string; - baz: boolean; } >; diff --git a/test/valid-data/generic-mapped-complex/schema.json b/test/valid-data/generic-mapped-complex/schema.json index b86d06624..f2a709b57 100644 --- a/test/valid-data/generic-mapped-complex/schema.json +++ b/test/valid-data/generic-mapped-complex/schema.json @@ -18,21 +18,20 @@ "type": "object" }, "MyType": { + "$ref": "#/definitions/Simplify%3C(alias-1249507601-457-604-1249507601-0-1064%3Cdef-alias-1249507601-129-293-1249507601-0-1064%3Cdef-alias-303496744-44-103-303496744-0-192%3E%2Calias-1249507601-129-293-1249507601-0-1064%3Cstructure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192%3E%3E%26alias-1249507601-457-604-1249507601-0-1064%3Cdef-alias-1249507601-293-457-1249507601-0-1064%3Cdef-alias-303496744-44-103-303496744-0-192%3E%2Calias-1249507601-293-457-1249507601-0-1064%3Cstructure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192%3E%3E)%3E" + }, + "Simplify<(alias-1249507601-457-604-1249507601-0-1064,alias-1249507601-129-293-1249507601-0-1064>&alias-1249507601-457-604-1249507601-0-1064,alias-1249507601-293-457-1249507601-0-1064>)>": { "additionalProperties": false, "properties": { "bar": { "type": "string" }, - "baz": { - "type": "boolean" - }, "foo": { "type": "string" } }, "required": [ "bar", - "baz", "foo" ], "type": "object" diff --git a/test/valid-data/generic-mapped-complex/util.ts b/test/valid-data/generic-mapped-complex/util.ts index e7de1f2c2..cf3309dee 100644 --- a/test/valid-data/generic-mapped-complex/util.ts +++ b/test/valid-data/generic-mapped-complex/util.ts @@ -1,9 +1,27 @@ -export type Merge = { - [K in keyof A as K extends keyof B ? never : K]: A[K]; -} & B; +// types are from https://github.com/sindresorhus/type-fest -export type Simplify = { - [K in keyof T]: T[K]; -} & {}; +export type Simplify = { [KeyType in keyof T]: T[KeyType] } & {}; -export type OverrideSimple = Simplify>; +export type PickIndexSignature = { + [KeyType in keyof ObjectType as {} extends Record ? KeyType : never]: ObjectType[KeyType]; +}; + +export type OmitIndexSignature = { + [KeyType in keyof ObjectType as {} extends Record ? never : KeyType]: ObjectType[KeyType]; +}; + +type SimpleMerge = { + [Key in keyof Destination as Key extends keyof Source ? never : Key]: Destination[Key]; +} & Source; + +export type Merge = Simplify< + SimpleMerge, PickIndexSignature> & + SimpleMerge, OmitIndexSignature> +>; + +export type OverrideProperties< + TOriginal, + TOverride extends Partial> & { + [Key in keyof TOverride]: Key extends keyof TOriginal ? TOverride[Key] : never; + }, +> = Merge; From 45a530babee9de064591aa4b37fb96f6b062dfa6 Mon Sep 17 00:00:00 2001 From: alexchexes Date: Mon, 23 Jun 2025 22:18:38 +0100 Subject: [PATCH 06/10] fix regex that detects internal structures --- src/ExposeNodeParser.ts | 2 +- test/valid-data/generic-mapped-complex/schema.json | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ExposeNodeParser.ts b/src/ExposeNodeParser.ts index ea92a6d60..670baf22f 100644 --- a/src/ExposeNodeParser.ts +++ b/src/ExposeNodeParser.ts @@ -79,7 +79,7 @@ export class ExposeNodeParser implements SubNodeParser { const actual = derefAliasedType(type.getType()); const hasStructuralArg = context .getArguments() - .some((arg) => /^(structure|object|alias|def-alias)-/.test(arg?.getName() ?? "")); + .some((arg) => /(structure|object|alias|def-alias)-/.test(arg?.getName() ?? "")); if (isExported && !hasStructuralArg) { return false; diff --git a/test/valid-data/generic-mapped-complex/schema.json b/test/valid-data/generic-mapped-complex/schema.json index f2a709b57..f3513f943 100644 --- a/test/valid-data/generic-mapped-complex/schema.json +++ b/test/valid-data/generic-mapped-complex/schema.json @@ -18,9 +18,6 @@ "type": "object" }, "MyType": { - "$ref": "#/definitions/Simplify%3C(alias-1249507601-457-604-1249507601-0-1064%3Cdef-alias-1249507601-129-293-1249507601-0-1064%3Cdef-alias-303496744-44-103-303496744-0-192%3E%2Calias-1249507601-129-293-1249507601-0-1064%3Cstructure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192%3E%3E%26alias-1249507601-457-604-1249507601-0-1064%3Cdef-alias-1249507601-293-457-1249507601-0-1064%3Cdef-alias-303496744-44-103-303496744-0-192%3E%2Calias-1249507601-293-457-1249507601-0-1064%3Cstructure-303496744-155-188-303496744-125-190-303496744-103-191-303496744-0-192%3E%3E)%3E" - }, - "Simplify<(alias-1249507601-457-604-1249507601-0-1064,alias-1249507601-129-293-1249507601-0-1064>&alias-1249507601-457-604-1249507601-0-1064,alias-1249507601-293-457-1249507601-0-1064>)>": { "additionalProperties": false, "properties": { "bar": { From 1622f18f974b54c85a43a2fe77bf59f449881596 Mon Sep 17 00:00:00 2001 From: alexchexes Date: Mon, 23 Jun 2025 22:27:06 +0100 Subject: [PATCH 07/10] cleanup --- test/valid-data-other.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/valid-data-other.test.ts b/test/valid-data-other.test.ts index c27c8d0af..e07febf9d 100644 --- a/test/valid-data-other.test.ts +++ b/test/valid-data-other.test.ts @@ -57,8 +57,8 @@ describe("valid-data-other", () => { it("generic-nested", assertValidSchema("generic-nested", "MyObject")); it("generic-prefixed-number", assertValidSchema("generic-prefixed-number", "MyObject")); it("generic-void", assertValidSchema("generic-void", "MyObject")); - it("generic-mapped-complex", assertValidSchema("generic-mapped-complex", "*", { encodeRefs: true })); - it("generic-valueof", assertValidSchema("generic-valueof", "*", { encodeRefs: true })); + it("generic-mapped-complex", assertValidSchema("generic-mapped-complex", "*")); + it("generic-valueof", assertValidSchema("generic-valueof", "*")); it("nullable-null", assertValidSchema("nullable-null", "MyObject")); From 095a8ebdf44a5e0080a3e1d8bd434de8d78e78ef Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 24 Jun 2025 10:16:09 +0100 Subject: [PATCH 08/10] Update src/ExposeNodeParser.ts Co-authored-by: Dominik Moritz --- src/ExposeNodeParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExposeNodeParser.ts b/src/ExposeNodeParser.ts index 670baf22f..6d1c16aad 100644 --- a/src/ExposeNodeParser.ts +++ b/src/ExposeNodeParser.ts @@ -69,7 +69,7 @@ export class ExposeNodeParser implements SubNodeParser { if (!(type instanceof AliasType)) { return false; } - if (!node.typeParameters?.length) { + if (node.typeParameters?.length == 0) { return false; } From dcf93891f3288f6e5de62d0110ac6f0b93315b9e Mon Sep 17 00:00:00 2001 From: alexchexes Date: Tue, 24 Jun 2025 12:09:43 +0100 Subject: [PATCH 09/10] Revert 095a8e; undefined == 0 is false --- src/ExposeNodeParser.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ExposeNodeParser.ts b/src/ExposeNodeParser.ts index 6d1c16aad..670baf22f 100644 --- a/src/ExposeNodeParser.ts +++ b/src/ExposeNodeParser.ts @@ -69,7 +69,7 @@ export class ExposeNodeParser implements SubNodeParser { if (!(type instanceof AliasType)) { return false; } - if (node.typeParameters?.length == 0) { + if (!node.typeParameters?.length) { return false; } From 705f1a41e64804fd2b849fda43e436838741c3ca Mon Sep 17 00:00:00 2001 From: alexchexes Date: Sun, 6 Jul 2025 16:10:40 +0100 Subject: [PATCH 10/10] add 'generic-mapped-reused' test case that is not passes right now as desired: 1) don't inline reused 2) proper naming TODO: - Add test case that does the same but with different non-exported objects in other file (to ensure collisions are resolved) - Add test case that does the same but uses object in place instead of 'Base' or 'Patch' object --- test/valid-data-other.test.ts | 1 + test/valid-data/generic-mapped-reused/main.ts | 15 ++++++ .../generic-mapped-reused/schema.json | 49 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 test/valid-data/generic-mapped-reused/main.ts create mode 100644 test/valid-data/generic-mapped-reused/schema.json diff --git a/test/valid-data-other.test.ts b/test/valid-data-other.test.ts index e07febf9d..4d0b3415d 100644 --- a/test/valid-data-other.test.ts +++ b/test/valid-data-other.test.ts @@ -59,6 +59,7 @@ describe("valid-data-other", () => { it("generic-void", assertValidSchema("generic-void", "MyObject")); it("generic-mapped-complex", assertValidSchema("generic-mapped-complex", "*")); it("generic-valueof", assertValidSchema("generic-valueof", "*")); + it("generic-mapped-reused", assertValidSchema("generic-mapped-reused", "*", { encodeRefs: false })); it("nullable-null", assertValidSchema("nullable-null", "MyObject")); diff --git a/test/valid-data/generic-mapped-reused/main.ts b/test/valid-data/generic-mapped-reused/main.ts new file mode 100644 index 000000000..a23ae1d24 --- /dev/null +++ b/test/valid-data/generic-mapped-reused/main.ts @@ -0,0 +1,15 @@ +export type MyHelper = { + [K in keyof A as K extends keyof B ? never : K]: A[K]; +} & B; + +type Base = { foo: string; bar: number }; +type Patch = { bar: string; baz: boolean }; + +type Resolved = MyHelper; // ← `Resolved` NOT EXPORTED + +export interface Foo { + beta: Resolved; +} +export interface Bar { + gamma: Resolved; +} diff --git a/test/valid-data/generic-mapped-reused/schema.json b/test/valid-data/generic-mapped-reused/schema.json new file mode 100644 index 000000000..074350edf --- /dev/null +++ b/test/valid-data/generic-mapped-reused/schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "Bar": { + "additionalProperties": false, + "properties": { + "gamma": { + "$ref": "#/definitions/MyHelper" + } + }, + "required": [ + "gamma" + ], + "type": "object" + }, + "Foo": { + "additionalProperties": false, + "properties": { + "beta": { + "$ref": "#/definitions/MyHelper" + } + }, + "required": [ + "beta" + ], + "type": "object" + }, + "MyHelper": { + "additionalProperties": false, + "properties": { + "bar": { + "type": "string" + }, + "baz": { + "type": "boolean" + }, + "foo": { + "type": "string" + } + }, + "required": [ + "bar", + "baz", + "foo" + ], + "type": "object" + } + } +}