Skip to content

Commit 6c623f2

Browse files
thejeff77claude
andcommitted
[kotlin] Add companionObject option to generate companion objects in data classes
Add a new boolean CLI option `companionObject` (default: false) that generates an empty `companion object { }` on all data class models, enabling users to add companion extensions from outside the generated code. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 2eaa15a commit 6c623f2

3 files changed

Lines changed: 63 additions & 2 deletions

File tree

modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KotlinClientCodegen.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
106106

107107
public static final String GENERATE_ONEOF_ANYOF_WRAPPERS = "generateOneOfAnyOfWrappers";
108108

109+
public static final String COMPANION_OBJECT = "companionObject";
110+
109111
protected static final String VENDOR_EXTENSION_BASE_NAME_LITERAL = "x-base-name-literal";
110112

111113

@@ -123,6 +125,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
123125
@Setter protected boolean mapFileBinaryToByteArray = false;
124126
@Setter protected boolean generateOneOfAnyOfWrappers = true;
125127
@Getter @Setter protected boolean failOnUnknownProperties = false;
128+
@Setter protected boolean companionObject = false;
126129

127130
protected String authFolder;
128131

@@ -288,6 +291,8 @@ public KotlinClientCodegen() {
288291

289292
cliOptions.add(CliOption.newBoolean(GENERATE_ONEOF_ANYOF_WRAPPERS, "Generate oneOf, anyOf schemas as wrappers. Only `jvm-retrofit2`(library) with `gson` or `kotlinx_serialization`(serializationLibrary) support this option."));
290293

294+
cliOptions.add(CliOption.newBoolean(COMPANION_OBJECT, "Whether to generate companion objects in data classes, enabling companion extensions.", false));
295+
291296
CliOption serializationLibraryOpt = new CliOption(CodegenConstants.SERIALIZATION_LIBRARY, SERIALIZATION_LIBRARY_DESC);
292297
cliOptions.add(serializationLibraryOpt.defaultValue(serializationLibrary.name()));
293298

@@ -322,6 +327,10 @@ public boolean getGenerateOneOfAnyOfWrappers() {
322327
return generateOneOfAnyOfWrappers;
323328
}
324329

330+
public boolean getCompanionObject() {
331+
return companionObject;
332+
}
333+
325334
public void setGenerateRoomModels(Boolean generateRoomModels) {
326335
this.generateRoomModels = generateRoomModels;
327336
}
@@ -484,6 +493,12 @@ public void processOpts() {
484493
setFailOnUnknownProperties(false);
485494
}
486495

496+
if (additionalProperties.containsKey(COMPANION_OBJECT)) {
497+
setCompanionObject(convertPropertyToBooleanAndWriteBack(COMPANION_OBJECT));
498+
} else {
499+
additionalProperties.put(COMPANION_OBJECT, companionObject);
500+
}
501+
487502
commonSupportingFiles();
488503

489504
switch (getLibrary()) {
@@ -936,7 +951,7 @@ public ModelsMap postProcessModels(ModelsMap objs) {
936951

937952
for (ModelMap mo : objects.getModels()) {
938953
CodegenModel cm = mo.getModel();
939-
if (getGenerateRoomModels() || getGenerateOneOfAnyOfWrappers()) {
954+
if (getGenerateRoomModels() || getGenerateOneOfAnyOfWrappers() || getCompanionObject()) {
940955
cm.vendorExtensions.put("x-has-data-class-body", true);
941956
}
942957

modules/openapi-generator/src/main/resources/kotlin-client/data_class.mustache

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,8 @@ import {{packageName}}.infrastructure.ITransformForStorage
123123
private const val serialVersionUID: Long = 123
124124
}
125125
{{/serializableModel}}
126-
{{#discriminator}}{{#vars}}{{#required}}
126+
{{^serializableModel}}{{^generateRoomModels}}{{^generateOneOfAnyOfWrappers}}{{#companionObject}} {{#nonPublicApi}}internal {{/nonPublicApi}}{{^nonPublicApi}}{{#explicitApi}}public {{/explicitApi}}{{/nonPublicApi}}companion object { }
127+
{{/companionObject}}{{/generateOneOfAnyOfWrappers}}{{/generateRoomModels}}{{/serializableModel}}{{#discriminator}}{{#vars}}{{#required}}
127128
{{>interface_req_var}}{{/required}}{{^required}}
128129
{{>interface_opt_var}}{{/required}}{{/vars}}{{/discriminator}}
129130
{{#hasEnums}}

modules/openapi-generator/src/test/java/org/openapitools/codegen/kotlin/KotlinClientCodegenModelTest.java

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.util.Map;
4444

4545
import static org.openapitools.codegen.CodegenConstants.*;
46+
import static org.openapitools.codegen.languages.KotlinClientCodegen.COMPANION_OBJECT;
4647
import static org.openapitools.codegen.languages.KotlinClientCodegen.GENERATE_ONEOF_ANYOF_WRAPPERS;
4748

4849
@SuppressWarnings("static-method")
@@ -878,6 +879,50 @@ public void emptyModelKotlinxSerializationTest() throws IOException {
878879
TestUtils.assertFileNotContains(modelKt, "data class EmptyModel");
879880
}
880881

882+
@Test
883+
public void testCompanionObjectAdditionalProperty() {
884+
final KotlinClientCodegen codegen = new KotlinClientCodegen();
885+
886+
// Default case, nothing provided
887+
codegen.processOpts();
888+
889+
ConfigAssert configAssert = new ConfigAssert(codegen.additionalProperties());
890+
// Default to false
891+
configAssert.assertValue(COMPANION_OBJECT, codegen::getCompanionObject, Boolean.FALSE);
892+
893+
// Provide true
894+
codegen.additionalProperties().put(COMPANION_OBJECT, true);
895+
codegen.processOpts();
896+
897+
// Should be true
898+
configAssert.assertValue(COMPANION_OBJECT, codegen::getCompanionObject, Boolean.TRUE);
899+
900+
// Provide false
901+
codegen.additionalProperties().put(COMPANION_OBJECT, false);
902+
codegen.processOpts();
903+
904+
// Should be false
905+
configAssert.assertValue(COMPANION_OBJECT, codegen::getCompanionObject, Boolean.FALSE);
906+
}
907+
908+
@Test
909+
public void testCompanionObjectGeneratesCompanionInModel() throws IOException {
910+
File output = Files.createTempDirectory("test").toFile();
911+
output.deleteOnExit();
912+
913+
final CodegenConfigurator configurator = new CodegenConfigurator()
914+
.setGeneratorName("kotlin")
915+
.addAdditionalProperty(COMPANION_OBJECT, true)
916+
.setInputSpec("src/test/resources/3_0/petstore.yaml")
917+
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
918+
919+
DefaultGenerator generator = new DefaultGenerator();
920+
generator.opts(configurator.toClientOptInput()).generate();
921+
922+
Path petModel = Paths.get(output.getAbsolutePath() + "/src/main/kotlin/org/openapitools/client/models/Pet.kt");
923+
TestUtils.assertFileContains(petModel, "companion object { }");
924+
}
925+
881926
private static class ModelNameTest {
882927
private final String expectedName;
883928
private final String expectedClassName;

0 commit comments

Comments
 (0)