From 4d37855b9d959f56430dca724f1c6b1d68e222f6 Mon Sep 17 00:00:00 2001 From: Mohamed EL-Habib Date: Fri, 5 Dec 2025 19:51:39 +0100 Subject: [PATCH 1/3] Fix: Keep acronyms uppercase in Java enum constants (#2850) Enum constants containing known acronyms (like "SPA") were being incorrectly converted when using --acronym-style=camel, e.g. "MULTI_SPA_IN_GROUP_REJECTED" became "MULTI_Spa_IN_GROUP_REJECTED". For Java enum constants (UPPER_UNDERSCORE style), acronyms should always remain uppercase regardless of the --acronym-style setting. - Modified javaNameStyle() to use allUpperWordStyle for acronyms in enum constants - Added test case for enum with "SPA" acronym --- .../quicktype-core/src/language/Java/utils.ts | 8 ++-- .../main/java/io/quicktype/MessageCode.java | 42 +++++++++++++++++++ test/inputs/json/priority/enum-spa.json | 10 +++++ 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/java/src/main/java/io/quicktype/MessageCode.java create mode 100644 test/inputs/json/priority/enum-spa.json diff --git a/packages/quicktype-core/src/language/Java/utils.ts b/packages/quicktype-core/src/language/Java/utils.ts index 8e27c72b6e..48827f4c1f 100644 --- a/packages/quicktype-core/src/language/Java/utils.ts +++ b/packages/quicktype-core/src/language/Java/utils.ts @@ -44,13 +44,15 @@ export function javaNameStyle( upperUnderscore ? allUpperWordStyle : startWithUpper - ? firstUpperWordStyle - : allLowerWordStyle, + ? firstUpperWordStyle + : allLowerWordStyle, upperUnderscore ? allUpperWordStyle : firstUpperWordStyle, upperUnderscore || startWithUpper ? allUpperWordStyle : allLowerWordStyle, - acronymsStyle, + // For UPPER_UNDERSCORE style (Java enum constants), always use allUpperWordStyle for acronyms + // to maintain consistency with the naming convention (e.g., XXX_SPA_XXX) + upperUnderscore ? allUpperWordStyle : acronymsStyle, upperUnderscore ? "_" : "", isStartCharacter, ); diff --git a/test/fixtures/java/src/main/java/io/quicktype/MessageCode.java b/test/fixtures/java/src/main/java/io/quicktype/MessageCode.java new file mode 100644 index 0000000000..4d113ae8f6 --- /dev/null +++ b/test/fixtures/java/src/main/java/io/quicktype/MessageCode.java @@ -0,0 +1,42 @@ +package io.quicktype; + +import java.io.IOException; +import com.fasterxml.jackson.annotation.*; + +public class MessageCode { + private Code code; + + @JsonProperty("code") + public Code getCode() { + return code; + } + + @JsonProperty("code") + public void setCode(Code value) { + this.code = value; + } + + public enum Code { + MULTI_SPA_IN_GROUP_REJECTED, SOME_OTHER_VALUE; + + @JsonValue + public String toValue() { + switch (this) { + case MULTI_SPA_IN_GROUP_REJECTED: + return "MULTI_SPA_IN_GROUP_REJECTED"; + case SOME_OTHER_VALUE: + return "SOME_OTHER_VALUE"; + } + return null; + } + + @JsonCreator + public static Code forValue(String value) throws IOException { + if (value.equals("MULTI_SPA_IN_GROUP_REJECTED")) + return MULTI_SPA_IN_GROUP_REJECTED; + if (value.equals("SOME_OTHER_VALUE")) + return SOME_OTHER_VALUE; + throw new IOException("Cannot deserialize Code"); + } + } +} diff --git a/test/inputs/json/priority/enum-spa.json b/test/inputs/json/priority/enum-spa.json new file mode 100644 index 0000000000..ce5b3aeb3d --- /dev/null +++ b/test/inputs/json/priority/enum-spa.json @@ -0,0 +1,10 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "type": "object", + "properties": { + "code": { + "type": "string", + "enum": ["SOME_OTHER_VALUE", "MULTI_SPA_IN_GROUP_REJECTED"] + } + } +} From b1479e5261e0bd7a44163b87503d5111003a6140 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 6 Jul 2026 10:44:39 -0400 Subject: [PATCH 2/3] Remove ineffective test artifacts and formatting churn from #2851 - Revert the re-indentation of the neighboring ternary in Java/utils.ts; keep only the semantic one-line fix (it failed biome check). - Remove test/inputs/json/priority/enum-spa.json: as a JSON sample it ran through every language with default options and never exercised the acronym-style bug. - Remove the hand-written MessageCode.java fixture file, which was never compared against generated output. A real regression check follows in the next commit. Co-Authored-By: Claude Fable 5 --- .../quicktype-core/src/language/Java/utils.ts | 4 +- .../main/java/io/quicktype/MessageCode.java | 42 ------------------- test/inputs/json/priority/enum-spa.json | 10 ----- 3 files changed, 2 insertions(+), 54 deletions(-) delete mode 100644 test/fixtures/java/src/main/java/io/quicktype/MessageCode.java delete mode 100644 test/inputs/json/priority/enum-spa.json diff --git a/packages/quicktype-core/src/language/Java/utils.ts b/packages/quicktype-core/src/language/Java/utils.ts index 48827f4c1f..80c2cfc1b8 100644 --- a/packages/quicktype-core/src/language/Java/utils.ts +++ b/packages/quicktype-core/src/language/Java/utils.ts @@ -44,8 +44,8 @@ export function javaNameStyle( upperUnderscore ? allUpperWordStyle : startWithUpper - ? firstUpperWordStyle - : allLowerWordStyle, + ? firstUpperWordStyle + : allLowerWordStyle, upperUnderscore ? allUpperWordStyle : firstUpperWordStyle, upperUnderscore || startWithUpper ? allUpperWordStyle diff --git a/test/fixtures/java/src/main/java/io/quicktype/MessageCode.java b/test/fixtures/java/src/main/java/io/quicktype/MessageCode.java deleted file mode 100644 index 4d113ae8f6..0000000000 --- a/test/fixtures/java/src/main/java/io/quicktype/MessageCode.java +++ /dev/null @@ -1,42 +0,0 @@ -package io.quicktype; - -import java.io.IOException; -import com.fasterxml.jackson.annotation.*; - -public class MessageCode { - private Code code; - - @JsonProperty("code") - public Code getCode() { - return code; - } - - @JsonProperty("code") - public void setCode(Code value) { - this.code = value; - } - - public enum Code { - MULTI_SPA_IN_GROUP_REJECTED, SOME_OTHER_VALUE; - - @JsonValue - public String toValue() { - switch (this) { - case MULTI_SPA_IN_GROUP_REJECTED: - return "MULTI_SPA_IN_GROUP_REJECTED"; - case SOME_OTHER_VALUE: - return "SOME_OTHER_VALUE"; - } - return null; - } - - @JsonCreator - public static Code forValue(String value) throws IOException { - if (value.equals("MULTI_SPA_IN_GROUP_REJECTED")) - return MULTI_SPA_IN_GROUP_REJECTED; - if (value.equals("SOME_OTHER_VALUE")) - return SOME_OTHER_VALUE; - throw new IOException("Cannot deserialize Code"); - } - } -} diff --git a/test/inputs/json/priority/enum-spa.json b/test/inputs/json/priority/enum-spa.json deleted file mode 100644 index ce5b3aeb3d..0000000000 --- a/test/inputs/json/priority/enum-spa.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "type": "object", - "properties": { - "code": { - "type": "string", - "enum": ["SOME_OTHER_VALUE", "MULTI_SPA_IN_GROUP_REJECTED"] - } - } -} From 6066b9d078edb61347d665b037e91ffe9a043544 Mon Sep 17 00:00:00 2001 From: Mark Probst Date: Mon, 6 Jul 2026 10:50:38 -0400 Subject: [PATCH 3/3] Add regression check for Java enum acronym casing (#2850) The fixture harness cannot catch this bug: mangled enum constants are self-consistent identifiers (the generated code compiles) and (de)serialization uses the raw JSON names (round-tripping succeeds). test/check-java-acronym-names.ts generates Java for an enum containing the known acronym "SPA" under all four --acronym-style settings and asserts on the emitted enum constant *identifier* (extracted from the toValue() switch, since the raw JSON name always appears in string literals even when the identifier is wrong). It runs standalone via ts-node and as a pre-fixture check in test/test.ts, like check-no-node-imports.ts. Verified to fail on unfixed code: acronym-style=camel: got "MULTI_Spa_IN_GROUP_REJECTED" acronym-style=lowerCase: got "MULTI_spa_IN_GROUP_REJECTED" Co-Authored-By: Claude Fable 5 --- test/check-java-acronym-names.ts | 105 +++++++++++++++++++++++++++++++ test/test.ts | 6 ++ 2 files changed, 111 insertions(+) create mode 100644 test/check-java-acronym-names.ts diff --git a/test/check-java-acronym-names.ts b/test/check-java-acronym-names.ts new file mode 100644 index 0000000000..9480553c45 --- /dev/null +++ b/test/check-java-acronym-names.ts @@ -0,0 +1,105 @@ +// Guard: Java enum constants must keep acronyms uppercase for every +// --acronym-style setting. +// +// Java enum constants are rendered in UPPER_UNDERSCORE style. Before the fix +// in https://github.com/glideapps/quicktype/pull/2851 (issue #2850), the +// acronym-style option was still applied to words recognized as acronyms +// (e.g. "SPA"), so with --acronym-style=camel the JSON enum value +// "MULTI_SPA_IN_GROUP_REJECTED" produced the constant +// "MULTI_Spa_IN_GROUP_REJECTED" (and "MULTI_spa_IN_GROUP_REJECTED" with +// lowerCase). +// +// The regular fixture harness cannot catch this: the mangled constants are +// self-consistent identifiers, so the generated code still compiles, and +// (de)serialization uses the raw JSON names, so round-tripping succeeds. +// This check instead generates Java code directly and asserts on the emitted +// *identifier*. Note that we must NOT just check that the output contains +// "MULTI_SPA_IN_GROUP_REJECTED" — the raw JSON name always appears in the +// string literals of toValue()/forValue(), even when the identifier is +// mangled. We extract the identifier from `case : return "";` +// and compare that. + +import { InputData, JSONSchemaInput, quicktype } from "quicktype-core"; + +// "spa" is a known acronym (see Acronyms.const.ts), so acronym styling would +// apply to the SPA word if the fix regressed. +const enumValue = "MULTI_SPA_IN_GROUP_REJECTED"; + +const schema = JSON.stringify({ + $schema: "http://json-schema.org/draft-06/schema#", + type: "object", + properties: { + messageCode: { + type: "string", + enum: [enumValue], + }, + }, + required: ["messageCode"], +}); + +const acronymStyles = ["original", "pascal", "camel", "lowerCase"]; + +async function javaEnumConstantIdentifier( + acronymStyle: string, +): Promise { + const schemaInput = new JSONSchemaInput(undefined); + await schemaInput.addSource({ name: "TopLevel", schema }); + const inputData = new InputData(); + inputData.addInput(schemaInput); + + const result = await quicktype({ + inputData, + lang: "java", + rendererOptions: { "acronym-style": acronymStyle }, + }); + const output = result.lines.join("\n"); + + // Matches e.g. `case MULTI_SPA_IN_GROUP_REJECTED: return "MULTI_SPA_IN_GROUP_REJECTED";` + // in the generated MessageCode.toValue() — the capture is the identifier. + const match = output.match( + new RegExp(`case (\\w+): return "${enumValue}";`), + ); + if (match === null) { + console.error( + `error: could not find the enum constant for "${enumValue}" in the generated Java code (acronym-style=${acronymStyle}):\n\n${output}`, + ); + process.exit(1); + } + + return match[1]; +} + +export async function checkJavaEnumAcronymCasing(): Promise { + const failures: string[] = []; + for (const style of acronymStyles) { + const identifier = await javaEnumConstantIdentifier(style); + if (identifier !== enumValue) { + failures.push( + ` acronym-style=${style}: got "${identifier}", expected "${enumValue}"`, + ); + } + } + + if (failures.length > 0) { + console.error( + `error: Java enum constants must keep acronyms uppercase for every acronym-style (issue #2850): + +${failures.join("\n")} + +javaNameStyle must force allUpperWordStyle for acronyms in UPPER_UNDERSCORE +names — see packages/quicktype-core/src/language/Java/utils.ts and +https://github.com/glideapps/quicktype/pull/2851`, + ); + process.exit(1); + } +} + +// Allow running the check standalone: +// NODE_PATH=`pwd`/node_modules npx ts-node --project test/tsconfig.json test/check-java-acronym-names.ts +if (require.main === module) { + checkJavaEnumAcronymCasing().then(() => { + console.error( + "* Java enum constants keep acronyms uppercase for every acronym-style", + ); + }); +} diff --git a/test/test.ts b/test/test.ts index f9f4e316a0..5bd0b245c1 100755 --- a/test/test.ts +++ b/test/test.ts @@ -5,6 +5,7 @@ import { inParallel } from "./lib/multicore"; import { execAsync, type Sample } from "./utils"; import { type Fixture, allFixtures } from "./fixtures"; import { affectedFixtures, divideParallelJobs } from "./buildkite"; +import { checkJavaEnumAcronymCasing } from "./check-java-acronym-names"; import { checkCoreHasNoNodePrefixedImports } from "./check-no-node-imports"; const exit = require("exit"); @@ -21,6 +22,11 @@ async function main(sources: string[]) { // use "node:"-prefixed imports or it breaks web bundlers (issue #2763). checkCoreHasNoNodePrefixedImports(); + // Regression check for issue #2850: Java enum constants must keep + // acronyms uppercase for every --acronym-style. The fixture harness + // can't catch this (mangled constants still compile and round-trip). + await checkJavaEnumAcronymCasing(); + let fixtures = affectedFixtures(); const fixturesFromCmdline = process.env.FIXTURE; if (fixturesFromCmdline) {