Skip to content

Commit

Permalink
[typescript] Clean up modelPropertyNaming across generators (#5427)
Browse files Browse the repository at this point in the history
* [typescript] Clean up modelPropertyNaming across generators
Fixes #2976

Generators without runtime models conversion use "original" property naming by default. It's still possible to change it via cli options

Generators with runtime conversion keep using "camelCase"

* Refactoring: use enum instead of string for modelPropertyNaming

* Restore the original camelCase for var names, decouple it from property names

* Swap toParamName and toVarName logic (looks like I've mistaken them)

* Regenerate docs

* Remove a no longer used private method
  • Loading branch information
amakhrov committed Feb 28, 2020
1 parent a4fc319 commit 0edb628
Show file tree
Hide file tree
Showing 17 changed files with 82 additions and 63 deletions.
2 changes: 1 addition & 1 deletion docs/generators/javascript-flowtyped.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sidebar_label: javascript-flowtyped
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|enumNameSuffix|Suffix that will be appended to all enum names. A special 'v4-compat' value enables the backward-compatible behavior (as pre v4.2.3)| |v4-compat|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original|
|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null|
|npmRepository|Use this property to set an url your private npmRepo in the package.json| |null|
|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/typescript-angular.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ sidebar_label: typescript-angular
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|fileNaming|Naming convention for the output files: 'camelCase', 'kebab-case'.| |camelCase|
|modelFileSuffix|The suffix of the file of the generated model (model<suffix>.ts).| |null|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original|
|modelSuffix|The suffix of the generated model.| |null|
|ngVersion|The version of Angular.| |9.0.0|
|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/typescript-angularjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sidebar_label: typescript-angularjs
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|enumNameSuffix|Suffix that will be appended to all enum names. A special 'v4-compat' value enables the backward-compatible behavior (as pre v4.2.3)| |v4-compat|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original|
|nullSafeAdditionalProps|Set to make additional properties types declare that their indexer may return undefined| |false|
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/typescript-aurelia.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sidebar_label: typescript-aurelia
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|enumNameSuffix|Suffix that will be appended to all enum names. A special 'v4-compat' value enables the backward-compatible behavior (as pre v4.2.3)| |v4-compat|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original|
|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null|
|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0|
|nullSafeAdditionalProps|Set to make additional properties types declare that their indexer may return undefined| |false|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/typescript-axios.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sidebar_label: typescript-axios
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|enumNameSuffix|Suffix that will be appended to all enum names. A special 'v4-compat' value enables the backward-compatible behavior (as pre v4.2.3)| |v4-compat|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original|
|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null|
|npmRepository|Use this property to set an url of your private npmRepo in the package.json| |null|
|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/typescript-inversify.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sidebar_label: typescript-inversify
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|enumNameSuffix|Suffix that will be appended to all enum names. A special 'v4-compat' value enables the backward-compatible behavior (as pre v4.2.3)| |v4-compat|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original|
|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null|
|npmRepository|Use this property to set an url your private npmRepo in the package.json| |null|
|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/typescript-jquery.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ sidebar_label: typescript-jquery
|enumNameSuffix|Suffix that will be appended to all enum names. A special 'v4-compat' value enables the backward-compatible behavior (as pre v4.2.3)| |v4-compat|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|jqueryAlreadyImported|When using this in legacy app using mix of typescript and javascript, this will only declare jquery and not import it| |false|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original|
|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null|
|npmRepository|Use this property to set an url your private npmRepo in the package.json| |null|
|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0|
Expand Down
2 changes: 1 addition & 1 deletion docs/generators/typescript-rxjs.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ sidebar_label: typescript-rxjs
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|enumNameSuffix|Suffix that will be appended to all enum names. A special 'v4-compat' value enables the backward-compatible behavior (as pre v4.2.3)| |v4-compat|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name. Only change it if you provide your own run-time code for (de-)serialization of models| |original|
|npmName|The name under which you want to publish generated npm package. Required to generate a full package| |null|
|npmRepository|Use this property to set an url your private npmRepo in the package.json| |null|
|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE;
import org.openapitools.codegen.CodegenConstants.MODEL_PROPERTY_NAMING_TYPE;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.utils.ModelUtils;
Expand Down Expand Up @@ -55,14 +56,17 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
public static final String ENUM_NAME_SUFFIX_DESC_CUSTOMIZED = CodegenConstants.ENUM_NAME_SUFFIX_DESC
+ " A special '" + ENUM_NAME_SUFFIX_V4_COMPAT + "' value enables the backward-compatible behavior (as pre v4.2.3)";

public static final String MODEL_PROPERTY_NAMING_DESC_WITH_WARNING = CodegenConstants.MODEL_PROPERTY_NAMING_DESC
+ ". Only change it if you provide your own run-time code for (de-)serialization of models";

public static final String NULL_SAFE_ADDITIONAL_PROPS = "nullSafeAdditionalProps";
public static final String NULL_SAFE_ADDITIONAL_PROPS_DESC = "Set to make additional properties types declare that their indexer may return undefined";

// NOTE: SimpleDateFormat is not thread-safe and may not be static unless it is thread-local
@SuppressWarnings("squid:S5164")
protected static final ThreadLocal<SimpleDateFormat> SNAPSHOT_SUFFIX_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmm", Locale.ROOT));

protected String modelPropertyNaming = "camelCase";
protected MODEL_PROPERTY_NAMING_TYPE modelPropertyNaming = MODEL_PROPERTY_NAMING_TYPE.original;
protected ENUM_PROPERTY_NAMING_TYPE enumPropertyNaming = ENUM_PROPERTY_NAMING_TYPE.PascalCase;
protected Boolean supportsES6 = false;
protected Boolean nullSafeAdditionalProps = false;
Expand Down Expand Up @@ -170,7 +174,7 @@ public AbstractTypeScriptClientCodegen() {

cliOptions.add(new CliOption(CodegenConstants.ENUM_NAME_SUFFIX, ENUM_NAME_SUFFIX_DESC_CUSTOMIZED).defaultValue(this.enumSuffix));
cliOptions.add(new CliOption(CodegenConstants.ENUM_PROPERTY_NAMING, CodegenConstants.ENUM_PROPERTY_NAMING_DESC).defaultValue(this.enumPropertyNaming.name()));
cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC).defaultValue(this.modelPropertyNaming));
cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, MODEL_PROPERTY_NAMING_DESC_WITH_WARNING).defaultValue(this.modelPropertyNaming.name()));
cliOptions.add(new CliOption(CodegenConstants.SUPPORTS_ES6, CodegenConstants.SUPPORTS_ES6_DESC).defaultValue(String.valueOf(this.getSupportsES6())));
this.cliOptions.add(new CliOption(NPM_NAME, "The name under which you want to publish generated npm package." +
" Required to generate a full package"));
Expand All @@ -179,7 +183,12 @@ public AbstractTypeScriptClientCodegen() {
"When setting this property to true, the version will be suffixed with -SNAPSHOT." + this.SNAPSHOT_SUFFIX_FORMAT.get().toPattern(),
false));
this.cliOptions.add(new CliOption(NULL_SAFE_ADDITIONAL_PROPS, NULL_SAFE_ADDITIONAL_PROPS_DESC).defaultValue(String.valueOf(this.getNullSafeAdditionalProps())));
}

protected void supportModelPropertyNaming(MODEL_PROPERTY_NAMING_TYPE defaultModelPropertyNaming) {
removeOption(CodegenConstants.MODEL_PROPERTY_NAMING);
modelPropertyNaming = defaultModelPropertyNaming;
cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC).defaultValue(modelPropertyNaming.name()));
}

@Override
Expand Down Expand Up @@ -274,52 +283,37 @@ public String modelFileFolder() {

@Override
public String toParamName(String name) {
// sanitize name
name = sanitizeName(name, "[^\\w$]");

if ("_".equals(name)) {
name = "_u";
}

// if it's all uppper case, do nothing
if (name.matches("^[A-Z_]*$")) {
return name;
}

name = getNameUsingModelPropertyNaming(name);

// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
name = camelize(name, true);
name = toSafeIdentifier(name);

return name;
}

@Override
public String toVarName(String name) {
name = this.toParamName(name);

// if the property name has any breaking characters such as :, ;, . etc.
// then wrap the name within single quotes.
// my:interface:property: string; => 'my:interface:property': string;
if (propertyHasBreakingCharacters(name)) {
name = "\'" + name + "\'";
name = sanitizeName(name, "[^\\w$]");

if ("_".equals(name)) {
name = "_u";
}

name = getNameUsingModelPropertyNaming(name);
name = toSafeIdentifier(name);

return name;
}

/**
* Checks whether property names have breaking characters like ':', '-'.
* @param str string to check for breaking characters
* @return <code>true</code> if breaking characters are present and <code>false</code> if not
*/
private boolean propertyHasBreakingCharacters(String str) {
final String regex = "^.*[+*:;,.()-]+.*$";
final Pattern pattern = Pattern.compile(regex);
final Matcher matcher = pattern.matcher(str);
return matcher.matches();
private String toSafeIdentifier(String name) {
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
return name;
}

@Override
Expand Down Expand Up @@ -533,32 +527,31 @@ public String toOperationId(String operationId) {
throw new RuntimeException("Empty method name (operationId) not allowed");
}

// method name cannot use reserved keyword or word starting with number, e.g. return or 123return
// append _ at the beginning, e.g. _return or _123return
if (isReservedWord(operationId) || operationId.matches("^\\d.*")) {
return escapeReservedWord(camelize(sanitizeName(operationId), true));
}
operationId = camelize(sanitizeName(operationId), true);
operationId = toSafeIdentifier(operationId);

return camelize(sanitizeName(operationId), true);
return operationId;
}

public void setModelPropertyNaming(String naming) {
if ("original".equals(naming) || "camelCase".equals(naming) ||
"PascalCase".equals(naming) || "snake_case".equals(naming)) {
this.modelPropertyNaming = naming;
} else {
throw new IllegalArgumentException("Invalid model property naming '" +
naming + "'. Must be 'original', 'camelCase', " +
"'PascalCase' or 'snake_case'");
try {
modelPropertyNaming = MODEL_PROPERTY_NAMING_TYPE.valueOf(naming);
} catch (IllegalArgumentException e) {
String values = Stream.of(MODEL_PROPERTY_NAMING_TYPE.values())
.map(value -> "'" + value.name() + "'")
.collect(Collectors.joining(", "));

String msg = String.format(Locale.ROOT, "Invalid model property naming '%s'. Must be one of %s.", naming, values);
throw new IllegalArgumentException(msg);
}
}

public String getModelPropertyNaming() {
return this.modelPropertyNaming;
public MODEL_PROPERTY_NAMING_TYPE getModelPropertyNaming() {
return modelPropertyNaming;
}

private String getNameUsingModelPropertyNaming(String name) {
switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) {
switch (getModelPropertyNaming()) {
case original:
return name;
case camelCase:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -456,7 +456,7 @@ public Map<String, Object> postProcessOperationsWithModels(Map<String, Object> o

// Add the more complicated component instead of just the brace.
CodegenParameter parameter = findPathParameterByName(op, parameterName.toString());
pathBuffer.append(toVarName(parameterName.toString()));
pathBuffer.append(toParamName(parameterName.toString()));
if (parameter != null && parameter.isDateTime) {
pathBuffer.append(".toISOString()");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public TypeScriptFetchClientCodegen() {
typeMapping.put("date", "Date");
typeMapping.put("DateTime", "Date");

supportModelPropertyNaming(CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.camelCase);
this.cliOptions.add(new CliOption(NPM_REPOSITORY, "Use this property to set an url your private npmRepo in the package.json"));
this.cliOptions.add(new CliOption(WITH_INTERFACES, "Setting this property to true will generate interfaces next to the default class implementations.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(USE_SINGLE_REQUEST_PARAMETER, "Setting this property to true will generate functions with a single argument containing all API endpoint parameters instead of one argument per parameter.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.TRUE.toString()));
Expand Down Expand Up @@ -102,8 +103,8 @@ public void setTypescriptThreePlus(Boolean typescriptThreePlus) {
@Override
public void processOpts() {
super.processOpts();
additionalProperties.put("isOriginalModelPropertyNaming", getModelPropertyNaming().equals("original"));
additionalProperties.put("modelPropertyNaming", getModelPropertyNaming());
additionalProperties.put("isOriginalModelPropertyNaming", getModelPropertyNaming() == CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.original);
additionalProperties.put("modelPropertyNaming", getModelPropertyNaming().name());

String sourceDir = "";
if (additionalProperties.containsKey(NPM_NAME)) {
Expand Down
Loading

0 comments on commit 0edb628

Please sign in to comment.