[kotlin] Nested enum naming fix, and naming options via CLI (#6858)

* [kotlin] Nested enum naming fix, and naming options via CLI

* [kotlin] Add option test for enum property naming

* [kotlin] Escape all reserved/keywords for enums
This commit is contained in:
Jim Schubert 2017-11-02 05:35:11 -04:00 committed by wing328
parent 86b266b02f
commit e7594c42d0
6 changed files with 140 additions and 10 deletions

View File

@ -146,6 +146,10 @@ public class CodegenConstants {
public static final String DOTNET_FRAMEWORK_DESC = "The target .NET framework version.";
public static enum MODEL_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, original}
public static enum ENUM_PROPERTY_NAMING_TYPE {camelCase, PascalCase, snake_case, original, UPPERCASE}
public static final String ENUM_PROPERTY_NAMING = "enumPropertyNaming";
public static final String ENUM_PROPERTY_NAMING_DESC = "Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'";
public static final String MODEL_NAME_PREFIX = "modelNamePrefix";
public static final String MODEL_NAME_PREFIX_DESC = "Prefix that will be prepended to all model names. Default is the empty string.";

View File

@ -23,6 +23,7 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
protected String packageName = "io.swagger.client";
protected String apiDocPath = "docs/";
protected String modelDocPath = "docs/";
protected CodegenConstants.ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.camelCase;
/**
* Constructs an instance of `KotlinClientCodegen`.
@ -56,17 +57,26 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
));
// this includes hard reserved words defined by https://github.com/JetBrains/kotlin/blob/master/core/descriptors/src/org/jetbrains/kotlin/renderer/KeywordStringsGenerated.java
// as well as select soft (contextual) keywords
// as well as keywords from https://kotlinlang.org/docs/reference/keyword-reference.html
reservedWords = new HashSet<String>(Arrays.asList(
"abstract",
"annotation",
"as",
"break",
"case",
"catch",
"class",
"companion",
"const",
"constructor",
"continue",
"crossinline",
"data",
"delegate",
"do",
"else",
"enum",
"external",
"false",
"final",
"finally",
@ -74,20 +84,33 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
"fun",
"if",
"in",
"infix",
"init",
"inline",
"inner",
"interface",
"internal",
"is",
"it",
"lateinit",
"lazy",
"noinline",
"null",
"object",
"open",
"operator",
"out",
"override",
"package",
"private",
"protected",
"public",
"reified",
"return",
"sealed",
"super",
"suspend",
"tailrec",
"this",
"throw",
"true",
@ -96,6 +119,7 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
"typeof",
"val",
"var",
"vararg",
"when",
"while"
));
@ -149,12 +173,17 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
importMapping.put("LocalDate", "java.time.LocalDate");
importMapping.put("LocalTime", "java.time.LocalTime");
specialCharReplacements.put(";", "Semicolon");
cliOptions.clear();
cliOptions.add(new CliOption(CodegenConstants.SOURCE_FOLDER, CodegenConstants.SOURCE_FOLDER_DESC).defaultValue(sourceFolder));
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Client package name (e.g. io.swagger).").defaultValue(this.packageName));
cliOptions.add(new CliOption(CodegenConstants.GROUP_ID, "Client package's organization (i.e. maven groupId).").defaultValue(groupId));
cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_ID, "Client artifact id (name of generated jar).").defaultValue(artifactId));
cliOptions.add(new CliOption(CodegenConstants.ARTIFACT_VERSION, "Client package version.").defaultValue(artifactVersion));
CliOption enumPropertyNamingOpt = new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC);
cliOptions.add(enumPropertyNamingOpt.defaultValue(enumPropertyNaming.name()));
}
public CodegenType getTag() {
@ -193,6 +222,10 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey(CodegenConstants.ENUM_PROPERTY_NAMING)) {
setEnumPropertyNaming((String) additionalProperties.get(CodegenConstants.ENUM_PROPERTY_NAMING));
}
if (additionalProperties.containsKey(CodegenConstants.SOURCE_FOLDER)) {
this.setSourceFolder((String) additionalProperties.get(CodegenConstants.SOURCE_FOLDER));
} else {
@ -255,6 +288,27 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
supportingFiles.add(new SupportingFile("infrastructure/Errors.kt.mustache", infrastructureFolder, "Errors.kt"));
}
/**
* Sets the naming convention for Kotlin enum properties
*
* @param enumPropertyNamingType The string representation of the naming convention, as defined by {@link CodegenConstants.ENUM_PROPERTY_NAMING_TYPE}
*/
public void setEnumPropertyNaming(final String enumPropertyNamingType) {
try {
this.enumPropertyNaming = CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.valueOf(enumPropertyNamingType);
} catch (IllegalArgumentException ex) {
StringBuilder sb = new StringBuilder(enumPropertyNamingType + " is an invalid enum property naming option. Please choose from:");
for (CodegenConstants.ENUM_PROPERTY_NAMING_TYPE t : CodegenConstants.ENUM_PROPERTY_NAMING_TYPE.values()) {
sb.append("\n ").append(t.name());
}
throw new RuntimeException(sb.toString());
}
}
public CodegenConstants.ENUM_PROPERTY_NAMING_TYPE getEnumPropertyNaming() {
return this.enumPropertyNaming;
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
@ -386,4 +440,69 @@ public class KotlinClientCodegen extends DefaultCodegen implements CodegenConfig
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
return postProcessModelsEnum(super.postProcessModels(objs));
}
/**
* Return the sanitized variable name for enum
*
* @param value enum variable name
* @param datatype data type
* @return the sanitized variable name for enum
*/
@Override
public String toEnumVarName(String value, String datatype) {
String modified;
if (value.length() == 0) {
modified = "EMPTY";
} else {
modified = value;
for (Map.Entry<String, String> specialCharacters : specialCharReplacements.entrySet()) {
// Underscore is the only special character we'll allow
if (!specialCharacters.getKey().equals("_")) {
modified = modified.replaceAll("\\Q" + specialCharacters.getKey() + "\\E", specialCharacters.getValue());
}
}
// Fallback, replace unknowns with underscore.
modified = modified.replaceAll("\\W+", "_");
if (modified.matches("\\d.*")) {
modified = "_" + modified;
}
// _, __, and ___ are reserved in Kotlin. Treat all names with only underscores consistently, regardless of count.
if (modified.matches("^_*$")) {
modified = modified.replaceAll("\\Q_\\E", "Underscore");
}
}
switch (getEnumPropertyNaming()) {
case original:
// NOTE: This is provided as a last-case allowance, but will still result in reserved words being escaped.
modified = value;
break;
case camelCase:
// NOTE: Removes hyphens and underscores
modified = camelize(modified, true);
break;
case PascalCase:
// NOTE: Removes hyphens and underscores
String result = camelize(modified);
modified = result.substring(0, 1).toUpperCase() + result.substring(1);
break;
case snake_case:
// NOTE: Removes hyphens
modified = underscore(modified);
break;
case UPPERCASE:
modified = modified.toUpperCase();
break;
}
if (reservedWords.contains(modified)) {
// TODO: Allow enum escaping as an option (e.g. backticks vs append/prepend underscore vs match model property escaping).
return String.format("`%s`", modified);
}
return modified;
}
}

View File

@ -12,11 +12,14 @@ data class {{classname}} (
{{/-last}}{{/optionalVars}}
) {
{{#hasEnums}}{{#vars}}{{#isEnum}}
enum class {{nameInCamelCase}}(val value: {{datatype}}) {
{{#_enum}}
{{{this}}}({{#isString}}"{{/isString}}{{{this}}}{{#isString}}"{{/isString}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{/_enum}}
/**
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{nameInCamelCase}}(val value: {{dataType}}){
{{#allowableValues}}{{#enumVars}}
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{/enumVars}}{{/allowableValues}}
}
{{/isEnum}}{{/vars}}{{/hasEnums}}
}
}

View File

@ -1,9 +1,9 @@
/**
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{classname}}(val value: {{dataType}}){
{{#allowableValues}}{{#enumVars}}
{{name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{/enumVars}}{{/allowableValues}}
}
}

View File

@ -36,6 +36,8 @@ public class KotlinClientCodegenOptionsTest extends AbstractOptionsTest {
times = 1;
codegen.setSourceFolder(KotlinClientCodegenOptionsProvider.SOURCE_FOLDER);
times = 1;
codegen.setEnumPropertyNaming(KotlinClientCodegenOptionsProvider.ENUM_PROPERTY_NAMING);
times = 1;
}};
}
}

View File

@ -12,6 +12,7 @@ public class KotlinClientCodegenOptionsProvider implements OptionsProvider {
public static final String ARTIFACT_ID = "swagger-kotlin-test";
public static final String GROUP_ID = "io.swagger.tests";
public static final String SOURCE_FOLDER = "./generated/kotlin";
public static final String ENUM_PROPERTY_NAMING = "camelCase";
@Override
public String getLanguage() {
@ -27,6 +28,7 @@ public class KotlinClientCodegenOptionsProvider implements OptionsProvider {
.put(CodegenConstants.ARTIFACT_ID, ARTIFACT_ID)
.put(CodegenConstants.GROUP_ID, GROUP_ID)
.put(CodegenConstants.SOURCE_FOLDER, SOURCE_FOLDER)
.put(CodegenConstants.ENUM_PROPERTY_NAMING, ENUM_PROPERTY_NAMING)
.build();
}