From 50bfcf3fc17b47e19140e252752cda1ff9ac6fff Mon Sep 17 00:00:00 2001 From: seonwoo_jung <79202163+seonwooj0810@users.noreply.github.com> Date: Wed, 10 Jun 2026 12:20:51 +0900 Subject: [PATCH 1/2] [typescript-fetch] Fix TS2590 in instanceOf guards for wide sanitized-name models The dual name/baseName membership check added in #23497 narrows the `object` parameter on every clause of the type-predicate function. For models with many required properties whose sanitized TS name differs from the JSON baseName (e.g. snake_case APIs), the accumulated candidate union grows past the compiler limit and trips TS2590 ("union type too complex"), introduced in 7.23.0. Read the membership/index-access checks through `value as Record` so TypeScript no longer narrows the predicate on each clause, keeping the dual-key behavior from #23497 while restoring compilable guards. Regenerated affected typescript-fetch samples. Fixes #23980 --- .../main/resources/typescript-fetch/modelGeneric.mustache | 6 +++--- .../typescript-fetch/builds/default-v3.0/models/EnumTest.ts | 2 +- .../builds/default-v3.0/models/FormatTest.ts | 2 +- .../typescript-fetch/builds/oneOf/models/DashedOptionOne.ts | 6 +++--- .../typescript-fetch/builds/oneOf/models/DashedOptionTwo.ts | 6 +++--- .../typescript-fetch/builds/oneOf/models/SnakeOptionOne.ts | 6 +++--- .../typescript-fetch/builds/oneOf/models/SnakeOptionTwo.ts | 6 +++--- .../builds/snakecase-discriminator/models/Animal.ts | 2 +- .../builds/snakecase-discriminator/models/EnumTest.ts | 2 +- .../builds/snakecase-discriminator/models/FormatTest.ts | 2 +- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache b/modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache index 31426dd375e6..4046fed3b286 100644 --- a/modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache +++ b/modules/openapi-generator/src/main/resources/typescript-fetch/modelGeneric.mustache @@ -26,7 +26,7 @@ export function instanceOf{{classname}}(value: object): value is {{classname}} { {{#vars}} {{#required}} {{#hasSanitizedName}} - if ((!('{{name}}' in value) && !('{{baseName}}' in value)) || (value['{{name}}'] === undefined && value['{{baseName}}'] === undefined)) return false; + if ((!('{{name}}' in (value as Record)) && !('{{baseName}}' in (value as Record))) || ((value as Record)['{{name}}'] === undefined && (value as Record)['{{baseName}}'] === undefined)) return false; {{/hasSanitizedName}} {{^hasSanitizedName}} if (!('{{name}}' in value) || value['{{name}}'] === undefined) return false; @@ -37,8 +37,8 @@ export function instanceOf{{classname}}(value: object): value is {{classname}} { {{#-first}} {{#-last}} {{#hasSanitizedName}} - {{#isString}}if (value['{{name}}'] !== '{{.}}' && value['{{baseName}}'] !== '{{.}}') return false;{{/isString}} - {{^isString}}if (value['{{name}}'] !== {{.}} && value['{{baseName}}'] !== {{.}}) return false;{{/isString}} + {{#isString}}if ((value as Record)['{{name}}'] !== '{{.}}' && (value as Record)['{{baseName}}'] !== '{{.}}') return false;{{/isString}} + {{^isString}}if ((value as Record)['{{name}}'] !== {{.}} && (value as Record)['{{baseName}}'] !== {{.}}) return false;{{/isString}} {{/hasSanitizedName}} {{^hasSanitizedName}} {{#isString}}if (value['{{name}}'] !== '{{.}}') return false;{{/isString}} diff --git a/samples/client/petstore/typescript-fetch/builds/default-v3.0/models/EnumTest.ts b/samples/client/petstore/typescript-fetch/builds/default-v3.0/models/EnumTest.ts index 6890904e62b9..13edcbd9d92c 100644 --- a/samples/client/petstore/typescript-fetch/builds/default-v3.0/models/EnumTest.ts +++ b/samples/client/petstore/typescript-fetch/builds/default-v3.0/models/EnumTest.ts @@ -142,7 +142,7 @@ export type EnumTestEnumNumberEnum = typeof EnumTestEnumNumberEnum[keyof typeof * Check if a given object implements the EnumTest interface. */ export function instanceOfEnumTest(value: object): value is EnumTest { - if ((!('enumStringRequired' in value) && !('enum_string_required' in value)) || (value['enumStringRequired'] === undefined && value['enum_string_required'] === undefined)) return false; + if ((!('enumStringRequired' in (value as Record)) && !('enum_string_required' in (value as Record))) || ((value as Record)['enumStringRequired'] === undefined && (value as Record)['enum_string_required'] === undefined)) return false; return true; } diff --git a/samples/client/petstore/typescript-fetch/builds/default-v3.0/models/FormatTest.ts b/samples/client/petstore/typescript-fetch/builds/default-v3.0/models/FormatTest.ts index 822bb60636fb..c80896e71861 100644 --- a/samples/client/petstore/typescript-fetch/builds/default-v3.0/models/FormatTest.ts +++ b/samples/client/petstore/typescript-fetch/builds/default-v3.0/models/FormatTest.ts @@ -122,7 +122,7 @@ export interface FormatTest { */ export function instanceOfFormatTest(value: object): value is FormatTest { if (!('number' in value) || value['number'] === undefined) return false; - if ((!('_byte' in value) && !('byte' in value)) || (value['_byte'] === undefined && value['byte'] === undefined)) return false; + if ((!('_byte' in (value as Record)) && !('byte' in (value as Record))) || ((value as Record)['_byte'] === undefined && (value as Record)['byte'] === undefined)) return false; if (!('date' in value) || value['date'] === undefined) return false; if (!('password' in value) || value['password'] === undefined) return false; return true; diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/models/DashedOptionOne.ts b/samples/client/petstore/typescript-fetch/builds/oneOf/models/DashedOptionOne.ts index 1a37609b9973..a6e019ea9fcd 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/models/DashedOptionOne.ts +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/models/DashedOptionOne.ts @@ -47,10 +47,10 @@ export type DashedOptionOneDiscriminatorFieldEnum = typeof DashedOptionOneDiscri * Check if a given object implements the DashedOptionOne interface. */ export function instanceOfDashedOptionOne(value: object): value is DashedOptionOne { - if ((!('discriminatorField' in value) && !('discriminator-field' in value)) || (value['discriminatorField'] === undefined && value['discriminator-field'] === undefined)) return false; - if (value['discriminatorField'] !== 'dashedOptionOne' && value['discriminator-field'] !== 'dashedOptionOne') return false; + if ((!('discriminatorField' in (value as Record)) && !('discriminator-field' in (value as Record))) || ((value as Record)['discriminatorField'] === undefined && (value as Record)['discriminator-field'] === undefined)) return false; + if ((value as Record)['discriminatorField'] !== 'dashedOptionOne' && (value as Record)['discriminator-field'] !== 'dashedOptionOne') return false; - if ((!('someProperty' in value) && !('some-property' in value)) || (value['someProperty'] === undefined && value['some-property'] === undefined)) return false; + if ((!('someProperty' in (value as Record)) && !('some-property' in (value as Record))) || ((value as Record)['someProperty'] === undefined && (value as Record)['some-property'] === undefined)) return false; return true; } diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/models/DashedOptionTwo.ts b/samples/client/petstore/typescript-fetch/builds/oneOf/models/DashedOptionTwo.ts index 80d2f6424fc8..ca3cd32e44dc 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/models/DashedOptionTwo.ts +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/models/DashedOptionTwo.ts @@ -47,10 +47,10 @@ export type DashedOptionTwoDiscriminatorFieldEnum = typeof DashedOptionTwoDiscri * Check if a given object implements the DashedOptionTwo interface. */ export function instanceOfDashedOptionTwo(value: object): value is DashedOptionTwo { - if ((!('discriminatorField' in value) && !('discriminator-field' in value)) || (value['discriminatorField'] === undefined && value['discriminator-field'] === undefined)) return false; - if (value['discriminatorField'] !== 'dashedOptionTwo' && value['discriminator-field'] !== 'dashedOptionTwo') return false; + if ((!('discriminatorField' in (value as Record)) && !('discriminator-field' in (value as Record))) || ((value as Record)['discriminatorField'] === undefined && (value as Record)['discriminator-field'] === undefined)) return false; + if ((value as Record)['discriminatorField'] !== 'dashedOptionTwo' && (value as Record)['discriminator-field'] !== 'dashedOptionTwo') return false; - if ((!('someProperty' in value) && !('some-property' in value)) || (value['someProperty'] === undefined && value['some-property'] === undefined)) return false; + if ((!('someProperty' in (value as Record)) && !('some-property' in (value as Record))) || ((value as Record)['someProperty'] === undefined && (value as Record)['some-property'] === undefined)) return false; return true; } diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/models/SnakeOptionOne.ts b/samples/client/petstore/typescript-fetch/builds/oneOf/models/SnakeOptionOne.ts index 5e2aa0dac710..fb3a19ecd15d 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/models/SnakeOptionOne.ts +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/models/SnakeOptionOne.ts @@ -47,10 +47,10 @@ export type SnakeOptionOneDiscriminatorFieldEnum = typeof SnakeOptionOneDiscrimi * Check if a given object implements the SnakeOptionOne interface. */ export function instanceOfSnakeOptionOne(value: object): value is SnakeOptionOne { - if ((!('discriminatorField' in value) && !('discriminator_field' in value)) || (value['discriminatorField'] === undefined && value['discriminator_field'] === undefined)) return false; - if (value['discriminatorField'] !== 'snakeOptionOne' && value['discriminator_field'] !== 'snakeOptionOne') return false; + if ((!('discriminatorField' in (value as Record)) && !('discriminator_field' in (value as Record))) || ((value as Record)['discriminatorField'] === undefined && (value as Record)['discriminator_field'] === undefined)) return false; + if ((value as Record)['discriminatorField'] !== 'snakeOptionOne' && (value as Record)['discriminator_field'] !== 'snakeOptionOne') return false; - if ((!('someProperty' in value) && !('some_property' in value)) || (value['someProperty'] === undefined && value['some_property'] === undefined)) return false; + if ((!('someProperty' in (value as Record)) && !('some_property' in (value as Record))) || ((value as Record)['someProperty'] === undefined && (value as Record)['some_property'] === undefined)) return false; return true; } diff --git a/samples/client/petstore/typescript-fetch/builds/oneOf/models/SnakeOptionTwo.ts b/samples/client/petstore/typescript-fetch/builds/oneOf/models/SnakeOptionTwo.ts index a89ac1d76292..f28bb29a556f 100644 --- a/samples/client/petstore/typescript-fetch/builds/oneOf/models/SnakeOptionTwo.ts +++ b/samples/client/petstore/typescript-fetch/builds/oneOf/models/SnakeOptionTwo.ts @@ -47,10 +47,10 @@ export type SnakeOptionTwoDiscriminatorFieldEnum = typeof SnakeOptionTwoDiscrimi * Check if a given object implements the SnakeOptionTwo interface. */ export function instanceOfSnakeOptionTwo(value: object): value is SnakeOptionTwo { - if ((!('discriminatorField' in value) && !('discriminator_field' in value)) || (value['discriminatorField'] === undefined && value['discriminator_field'] === undefined)) return false; - if (value['discriminatorField'] !== 'snakeOptionTwo' && value['discriminator_field'] !== 'snakeOptionTwo') return false; + if ((!('discriminatorField' in (value as Record)) && !('discriminator_field' in (value as Record))) || ((value as Record)['discriminatorField'] === undefined && (value as Record)['discriminator_field'] === undefined)) return false; + if ((value as Record)['discriminatorField'] !== 'snakeOptionTwo' && (value as Record)['discriminator_field'] !== 'snakeOptionTwo') return false; - if ((!('someProperty' in value) && !('some_property' in value)) || (value['someProperty'] === undefined && value['some_property'] === undefined)) return false; + if ((!('someProperty' in (value as Record)) && !('some_property' in (value as Record))) || ((value as Record)['someProperty'] === undefined && (value as Record)['some_property'] === undefined)) return false; return true; } diff --git a/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/Animal.ts b/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/Animal.ts index 790ff584911a..819c813af5f1 100644 --- a/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/Animal.ts +++ b/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/Animal.ts @@ -39,7 +39,7 @@ export interface Animal { * Check if a given object implements the Animal interface. */ export function instanceOfAnimal(value: object): value is Animal { - if ((!('className' in value) && !('class_name' in value)) || (value['className'] === undefined && value['class_name'] === undefined)) return false; + if ((!('className' in (value as Record)) && !('class_name' in (value as Record))) || ((value as Record)['className'] === undefined && (value as Record)['class_name'] === undefined)) return false; return true; } diff --git a/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/EnumTest.ts b/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/EnumTest.ts index 6890904e62b9..13edcbd9d92c 100644 --- a/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/EnumTest.ts +++ b/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/EnumTest.ts @@ -142,7 +142,7 @@ export type EnumTestEnumNumberEnum = typeof EnumTestEnumNumberEnum[keyof typeof * Check if a given object implements the EnumTest interface. */ export function instanceOfEnumTest(value: object): value is EnumTest { - if ((!('enumStringRequired' in value) && !('enum_string_required' in value)) || (value['enumStringRequired'] === undefined && value['enum_string_required'] === undefined)) return false; + if ((!('enumStringRequired' in (value as Record)) && !('enum_string_required' in (value as Record))) || ((value as Record)['enumStringRequired'] === undefined && (value as Record)['enum_string_required'] === undefined)) return false; return true; } diff --git a/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/FormatTest.ts b/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/FormatTest.ts index 822bb60636fb..c80896e71861 100644 --- a/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/FormatTest.ts +++ b/samples/client/petstore/typescript-fetch/builds/snakecase-discriminator/models/FormatTest.ts @@ -122,7 +122,7 @@ export interface FormatTest { */ export function instanceOfFormatTest(value: object): value is FormatTest { if (!('number' in value) || value['number'] === undefined) return false; - if ((!('_byte' in value) && !('byte' in value)) || (value['_byte'] === undefined && value['byte'] === undefined)) return false; + if ((!('_byte' in (value as Record)) && !('byte' in (value as Record))) || ((value as Record)['_byte'] === undefined && (value as Record)['byte'] === undefined)) return false; if (!('date' in value) || value['date'] === undefined) return false; if (!('password' in value) || value['password'] === undefined) return false; return true; From 3b1d30a8ac68c4502a4cc262ab4a053ef2e71c81 Mon Sep 17 00:00:00 2001 From: seonwooj0810 Date: Wed, 10 Jun 2026 17:55:40 +0900 Subject: [PATCH 2/2] [typescript-fetch] Update test expectations for Record casts in instanceOf guards --- .../TypeScriptFetchClientCodegenTest.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java index 094929f2fe53..bc015a30a947 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/typescript/fetch/TypeScriptFetchClientCodegenTest.java @@ -466,25 +466,26 @@ public void testInstanceOfChecksDiscriminatorValue() throws IOException { // SnakeOptionOne: discriminator_field (snake_case baseName) vs discriminatorField (camelCase name) // instanceOf should check both casings for field presence and discriminator value + // (the value is widened to Record to avoid TS2590 on wide models) Path snakeOptionOne = Paths.get(output + "/models/SnakeOptionOne.ts"); TestUtils.assertFileExists(snakeOptionOne); - TestUtils.assertFileContains(snakeOptionOne, "'discriminatorField' in value"); - TestUtils.assertFileContains(snakeOptionOne, "'discriminator_field' in value"); - TestUtils.assertFileContains(snakeOptionOne, "value['discriminatorField'] !== 'snakeOptionOne'"); - TestUtils.assertFileContains(snakeOptionOne, "value['discriminator_field'] !== 'snakeOptionOne'"); + TestUtils.assertFileContains(snakeOptionOne, "'discriminatorField' in (value as Record)"); + TestUtils.assertFileContains(snakeOptionOne, "'discriminator_field' in (value as Record)"); + TestUtils.assertFileContains(snakeOptionOne, "(value as Record)['discriminatorField'] !== 'snakeOptionOne'"); + TestUtils.assertFileContains(snakeOptionOne, "(value as Record)['discriminator_field'] !== 'snakeOptionOne'"); // Also verify the non-enum required field checks both casings - TestUtils.assertFileContains(snakeOptionOne, "'someProperty' in value"); - TestUtils.assertFileContains(snakeOptionOne, "'some_property' in value"); + TestUtils.assertFileContains(snakeOptionOne, "'someProperty' in (value as Record)"); + TestUtils.assertFileContains(snakeOptionOne, "'some_property' in (value as Record)"); // DashedOptionOne: discriminator-field (dashed baseName) vs discriminatorField (camelCase name) Path dashedOptionOne = Paths.get(output + "/models/DashedOptionOne.ts"); TestUtils.assertFileExists(dashedOptionOne); - TestUtils.assertFileContains(dashedOptionOne, "'discriminatorField' in value"); - TestUtils.assertFileContains(dashedOptionOne, "'discriminator-field' in value"); - TestUtils.assertFileContains(dashedOptionOne, "value['discriminatorField'] !== 'dashedOptionOne'"); - TestUtils.assertFileContains(dashedOptionOne, "value['discriminator-field'] !== 'dashedOptionOne'"); - TestUtils.assertFileContains(dashedOptionOne, "'someProperty' in value"); - TestUtils.assertFileContains(dashedOptionOne, "'some-property' in value"); + TestUtils.assertFileContains(dashedOptionOne, "'discriminatorField' in (value as Record)"); + TestUtils.assertFileContains(dashedOptionOne, "'discriminator-field' in (value as Record)"); + TestUtils.assertFileContains(dashedOptionOne, "(value as Record)['discriminatorField'] !== 'dashedOptionOne'"); + TestUtils.assertFileContains(dashedOptionOne, "(value as Record)['discriminator-field'] !== 'dashedOptionOne'"); + TestUtils.assertFileContains(dashedOptionOne, "'someProperty' in (value as Record)"); + TestUtils.assertFileContains(dashedOptionOne, "'some-property' in (value as Record)"); // Numeric singleton enum: value check must NOT quote the literal Path numericModel = Paths.get(output + "/models/NumericSingletonEnumModel.ts");