Skip to content

Fixes invalid Kotlin client variable names for reserved words #3993

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,25 +19,17 @@

import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenModel;
import org.openapitools.codegen.CodegenOperation;
import org.openapitools.codegen.CodegenProperty;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.SupportingFile;

import java.io.File;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

public class KotlinClientCodegen extends AbstractKotlinCodegen {

protected static final String VENDOR_EXTENSION_ESCAPED_NAME = "x-escapedName";

protected static final String JVM = "jvm";
protected static final String MULTIPLATFORM = "multiplatform";

Expand All @@ -47,14 +39,6 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
protected String dateLibrary = DateLibrary.JAVA8.value;
protected String collectionType = CollectionType.ARRAY.value;

// https://kotlinlang.org/docs/reference/grammar.html#Identifier
protected static final Pattern IDENTIFIER_PATTERN =
Pattern.compile("[\\p{Ll}\\p{Lm}\\p{Lo}\\p{Lt}\\p{Lu}\\p{Nl}_][\\p{Ll}\\p{Lm}\\p{Lo}\\p{Lt}\\p{Lu}\\p{Nl}\\p{Nd}_]*");

// https://kotlinlang.org/docs/reference/grammar.html#Identifier
protected static final String IDENTIFIER_REPLACEMENTS =
"[.;:/\\[\\]<>]";

public enum DateLibrary {
STRING("string"),
THREETENBP("threetenbp"),
Expand Down Expand Up @@ -212,7 +196,6 @@ public void processOpts() {
supportingFiles.add(new SupportingFile("infrastructure/HttpResponse.kt.mustache", infrastructureFolder, "HttpResponse.kt"));

// multiplatform specific testing files
final String testFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", "/");
supportingFiles.add(new SupportingFile("commonTest/coroutine.mustache", "src/commonTest/kotlin/util", "Coroutine.kt"));
supportingFiles.add(new SupportingFile("iosTest/coroutine.mustache", "src/iosTest/kotlin/util", "Coroutine.kt"));
supportingFiles.add(new SupportingFile("jvmTest/coroutine.mustache", "src/jvmTest/kotlin/util", "Coroutine.kt"));
Expand Down Expand Up @@ -253,58 +236,6 @@ public void processOpts() {
}
}

@Override
public Map<String, Object> postProcessModels(Map<String, Object> objs) {
objs = super.postProcessModels(objs);
return postProcessModelsEscapeNames(objs);
}

@SuppressWarnings("unchecked")
private static Map<String, Object> postProcessModelsEscapeNames(Map<String, Object> objs) {
List<Object> models = (List<Object>) objs.get("models");
for (Object _mo : models) {
Map<String, Object> mo = (Map<String, Object>) _mo;
CodegenModel cm = (CodegenModel) mo.get("model");

if (cm.vars != null) {
for (CodegenProperty var : cm.vars) {
var.vendorExtensions.put(VENDOR_EXTENSION_ESCAPED_NAME, escapeIdentifier(var.name));
}
}
if (cm.requiredVars != null) {
for (CodegenProperty var : cm.requiredVars) {
var.vendorExtensions.put(VENDOR_EXTENSION_ESCAPED_NAME, escapeIdentifier(var.name));
}
}
if (cm.optionalVars != null) {
for (CodegenProperty var : cm.optionalVars) {
var.vendorExtensions.put(VENDOR_EXTENSION_ESCAPED_NAME, escapeIdentifier(var.name));
}
}
}
return objs;
}

private static String escapeIdentifier(String identifier) {

// the kotlin grammar permits a wider set of characters in their identifiers that all target
// platforms permit (namely jvm). in order to remain compatible with target platforms, we
// initially replace all illegal target characters before escaping the identifier if required.
identifier = identifier.replaceAll(IDENTIFIER_REPLACEMENTS, "_");
if (IDENTIFIER_PATTERN.matcher(identifier).matches()) return identifier;
return '`' + identifier + '`';
}

private static void removeDuplicates(List<CodegenProperty> list) {
Set<String> set = new HashSet<>();
Iterator<CodegenProperty> iterator = list.iterator();
while (iterator.hasNext()) {
CodegenProperty item = iterator.next();
if (set.contains(item.name)) iterator.remove();
else set.add(item.name);
}
}

@Override
@SuppressWarnings("unchecked")
public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> objs, List<Object> allModels) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import kotlinx.serialization.internal.CommonEnumSerializer
/**
* {{{description}}}
{{#vars}}
* @param {{{vendorExtensions.x-escapedName}}} {{{description}}}
* @param {{{name}}} {{{description}}}
{{/vars}}
*/
{{#parcelizeModels}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
@SerializedName("{{name}}")
{{/gson}}
{{/jvm}}
{{#multiplatform}}@SerialName(value = "{{name}}") {{/multiplatform}}val {{{vendorExtensions.x-escapedName}}}: {{#isEnum}}{{#isListContainer}}{{#isList}}kotlin.collections.List{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{classname}}.{{{nameInCamelCase}}}>{{/isListContainer}}{{^isListContainer}}{{classname}}.{{{nameInCamelCase}}}{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
{{#multiplatform}}@SerialName(value = "{{baseName}}") {{/multiplatform}}val {{{name}}}: {{#isEnum}}{{#isListContainer}}{{#isList}}kotlin.collections.List{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{classname}}.{{{nameInCamelCase}}}>{{/isListContainer}}{{^isListContainer}}{{classname}}.{{{nameInCamelCase}}}{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
@SerializedName("{{name}}")
{{/gson}}
{{/jvm}}
{{#multiplatform}}@SerialName(value = "{{name}}") @Required {{/multiplatform}}val {{{vendorExtensions.x-escapedName}}}: {{#isEnum}}{{#isListContainer}}{{#isList}}kotlin.collections.List{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{classname}}.{{{nameInCamelCase}}}>{{/isListContainer}}{{^isListContainer}}{{classname}}.{{{nameInCamelCase}}}{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}
{{#multiplatform}}@SerialName(value = "{{baseName}}") @Required {{/multiplatform}}val {{{name}}}: {{#isEnum}}{{#isListContainer}}{{#isList}}kotlin.collections.List{{/isList}}{{^isList}}kotlin.Array{{/isList}}<{{classname}}.{{{nameInCamelCase}}}>{{/isListContainer}}{{^isListContainer}}{{classname}}.{{{nameInCamelCase}}}{{/isListContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}