[typescript] Make TypeScriptClientCodegen extend AbstractTypeScriptClientCodegen (#15096)

* Make TypeScriptClientCodegen extend AbstractTypeScriptClientCodegen

* Regenerate samples

* Update docs

* Clean up

* Remove updated toEnumName

* Fix: SUPPORTS_ES6

* Fix: `setSupportsES6` should not be set directly in unit tests

* Set modelPropertyNaming to camelCase
This commit is contained in:
Ween Jiann
2023-04-10 02:43:58 +08:00
committed by GitHub
parent f40433d28f
commit a5bc7f107d
5 changed files with 65 additions and 587 deletions

View File

@@ -21,6 +21,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
|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.| |Enum|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|<dl><dt>**false**</dt><dd>No changes to the enum's are made, this is the default option.</dd><dt>**true**</dt><dd>With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.</dd></dl>|false|
|fileContentDataType|Specifies the type to use for the content of a file - i.e. Blob (Browser, Deno) / Buffer (node)| |Buffer|
|framework|Specify the framework which should be used in the client code.|<dl><dt>**fetch-api**</dt><dd>fetch-api</dd><dt>**jquery**</dt><dd>jquery</dd></dl>|fetch-api|
@@ -30,6 +32,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|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|
|nullSafeAdditionalProps|Set to make additional properties types declare that their indexer may return undefined| |false|
|paramNaming|Naming convention for parameters: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
|platform|Specifies the platform the code should run on. The default is 'node' for the 'request' framework and 'browser' otherwise.|<dl><dt>**browser**</dt><dd>browser</dd><dt>**node**</dt><dd>node</dd><dt>**deno**</dt><dd>deno</dd></dl>|browser|
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|snapshot|When setting this property to true, the version will be suffixed with -SNAPSHOT.yyyyMMddHHmm| |false|
@@ -67,11 +71,13 @@ These options may be applied as additional-properties (cli) or configOptions (pl
<li>Long</li>
<li>Map</li>
<li>Object</li>
<li>ReadonlyArray</li>
<li>Set</li>
<li>String</li>
<li>any</li>
<li>boolean</li>
<li>number</li>
<li>object</li>
<li>string</li>
</ul>
@@ -156,7 +162,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
### Client Modification Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|BasePath||ToolingExtension
|BasePath||ToolingExtension
|Authorizations|✗|ToolingExtension
|UserAgent|✗|ToolingExtension
|MockServer|✗|ToolingExtension
@@ -201,7 +207,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
### Documentation Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Readme||ToolingExtension
|Readme||ToolingExtension
|Model|✓|ToolingExtension
|Api|✓|ToolingExtension
@@ -221,7 +227,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|MultiServer|✗|OAS3
|ParameterizedServer|✗|OAS3
|ParameterStyling|✗|OAS3
|Callbacks||OAS3
|Callbacks||OAS3
|LinkObjects|✗|OAS3
### Parameter Feature
@@ -250,14 +256,14 @@ These options may be applied as additional-properties (cli) or configOptions (pl
### Security Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|BasicAuth||OAS2,OAS3
|ApiKey||OAS2,OAS3
|BasicAuth||OAS2,OAS3
|ApiKey||OAS2,OAS3
|OpenIDConnect|✗|OAS3
|BearerToken||OAS3
|OAuth2_Implicit||OAS2,OAS3
|OAuth2_Password||OAS2,OAS3
|OAuth2_ClientCredentials||OAS2,OAS3
|OAuth2_AuthorizationCode||OAS2,OAS3
|BearerToken||OAS3
|OAuth2_Implicit||OAS2,OAS3
|OAuth2_Password||OAS2,OAS3
|OAuth2_ClientCredentials||OAS2,OAS3
|OAuth2_AuthorizationCode||OAS2,OAS3
### Wire Format Feature
| Name | Supported | Defined By |

View File

@@ -25,10 +25,10 @@ import io.swagger.v3.oas.models.parameters.Parameter;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.openapitools.codegen.*;
import org.openapitools.codegen.CodegenConstants.ENUM_PROPERTY_NAMING_TYPE;
import org.openapitools.codegen.CodegenConstants.MODEL_PROPERTY_NAMING_TYPE;
import org.openapitools.codegen.CodegenConstants.PARAM_NAMING_TYPE;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.ModelsMap;
@@ -46,7 +46,8 @@ import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static org.openapitools.codegen.languages.AbstractTypeScriptClientCodegen.ParameterExpander.ParamStyle.*;
import static org.openapitools.codegen.languages.AbstractTypeScriptClientCodegen.ParameterExpander.ParamStyle.form;
import static org.openapitools.codegen.languages.AbstractTypeScriptClientCodegen.ParameterExpander.ParamStyle.simple;
import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
@@ -392,10 +393,7 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
setParamNaming((String) additionalProperties.get(CodegenConstants.PARAM_NAMING));
}
if (additionalProperties.containsKey(CodegenConstants.SUPPORTS_ES6)) {
setSupportsES6(Boolean.valueOf(additionalProperties.get(CodegenConstants.SUPPORTS_ES6).toString()));
additionalProperties.put("supportsES6", getSupportsES6());
}
setSupportsES6(convertPropertyToBooleanAndWriteBack(CodegenConstants.SUPPORTS_ES6));
if (additionalProperties.containsKey(NULL_SAFE_ADDITIONAL_PROPS)) {
setNullSafeAdditionalProps(Boolean.valueOf(additionalProperties.get(NULL_SAFE_ADDITIONAL_PROPS).toString()));
@@ -839,7 +837,7 @@ public abstract class AbstractTypeScriptClientCodegen extends DefaultCodegen imp
if ("number".equals(datatype) || "boolean".equals(datatype)) {
return value;
} else {
return "\'" + escapeText(value) + "\'";
return "'" + escapeText(value) + "'";
}
}

View File

@@ -17,17 +17,12 @@
package org.openapitools.codegen.languages;
import com.github.curiousoddman.rgxgen.RgxGen;
import com.google.common.collect.Sets;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.ComposedSchema;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.*;
import org.openapitools.codegen.CodegenDiscriminator.MappedModel;
@@ -40,42 +35,30 @@ import org.openapitools.codegen.model.OperationsMap;
import org.openapitools.codegen.utils.ModelUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.github.curiousoddman.rgxgen.RgxGen;
import com.github.curiousoddman.rgxgen.config.RgxGenOption;
import com.github.curiousoddman.rgxgen.config.RgxGenProperties;
import java.io.File;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import static org.openapitools.codegen.utils.CamelizeOption.LOWERCASE_FIRST_LETTER;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.openapitools.codegen.utils.OnceLogger.once;
public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenConfig {
public class TypeScriptClientCodegen extends AbstractTypeScriptClientCodegen implements CodegenConfig {
private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptClientCodegen.class);
private static final String X_DISCRIMINATOR_TYPE = "x-discriminator-value";
private static final String UNDEFINED_VALUE = "undefined";
private static final String FRAMEWORK_SWITCH = "framework";
private static final String FRAMEWORK_SWITCH_DESC = "Specify the framework which should be used in the client code.";
private static final String[] FRAMEWORKS = { "fetch-api", "jquery" };
private static final String[] FRAMEWORKS = {"fetch-api", "jquery"};
private static final String PLATFORM_SWITCH = "platform";
private static final String PLATFORM_SWITCH_DESC = "Specifies the platform the code should run on. The default is 'node' for the 'request' framework and 'browser' otherwise.";
private static final String[] PLATFORMS = { "browser", "node", "deno" };
private static final String[] PLATFORMS = {"browser", "node", "deno"};
private static final String IMPORT_FILE_EXTENSION_SWITCH = "importFileExtension";
private static final String IMPORT_FILE_EXTENSION_SWITCH_DESC = "File extension to use with relative imports. Set it to '.js' or '.mjs' when using [ESM](https://nodejs.org/api/esm.html). Defaults to '.ts' when 'platform' is set to 'deno'.";
private static final String FILE_CONTENT_DATA_TYPE= "fileContentDataType";
private static final String FILE_CONTENT_DATA_TYPE = "fileContentDataType";
private static final String FILE_CONTENT_DATA_TYPE_DESC = "Specifies the type to use for the content of a file - i.e. Blob (Browser, Deno) / Buffer (node)";
private static final String USE_RXJS_SWITCH = "useRxJS";
private static final String USE_RXJS_SWITCH_DESC = "Enable this to internally use rxjs observables. If disabled, a stub is used instead. This is required for the 'angular' framework.";
@@ -85,27 +68,17 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
private static final String USE_OBJECT_PARAMS_SWITCH = "useObjectParameters";
private static final String USE_OBJECT_PARAMS_DESC = "Use aggregate parameter objects as function arguments for api operations instead of passing each parameter as a separate function argument.";
private final Map<String, String> frameworkToHttpLibMap;
// NPM Options
private static final String SNAPSHOT = "snapshot";
@SuppressWarnings("squid:S5164")
protected static final ThreadLocal<SimpleDateFormat> SNAPSHOT_SUFFIX_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMddHHmm", Locale.ROOT));
private static final String NPM_REPOSITORY = "npmRepository";
private static final String NPM_NAME = "npmName";
private static final String NPM_VERSION = "npmVersion";
// NPM Option Values
protected String npmRepository = null;
protected String snapshot = null;
protected String npmName = null;
protected String npmVersion = "1.0.0";
protected String modelPropertyNaming = "camelCase";
protected HashSet<String> languageGenericTypes;
private DateTimeFormatter iso8601Date = DateTimeFormatter.ISO_DATE;
private DateTimeFormatter iso8601DateTime = DateTimeFormatter.ISO_DATE_TIME;
private final DateTimeFormatter iso8601Date = DateTimeFormatter.ISO_DATE;
private final DateTimeFormatter iso8601DateTime = DateTimeFormatter.ISO_DATE_TIME;
public TypeScriptClientCodegen() {
super();
@@ -114,88 +87,23 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
this.frameworkToHttpLibMap.put("fetch-api", "isomorphic-fetch");
this.frameworkToHttpLibMap.put("jquery", "jquery");
this.generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata).stability(Stability.EXPERIMENTAL).build();
outputFolder = "generated-code" + File.separator + "typescript";
embeddedTemplateDir = templateDir = "typescript";
supportsInheritance = true;
// NOTE: TypeScript uses camel cased reserved words, while models are title cased. We don't want lowercase comparisons.
reservedWords.addAll(Arrays.asList(
// local variable names used in API methods (endpoints)
"varLocalPath", "queryParameters", "headerParams", "formParams", "useFormData", "varLocalDeferred",
"requestOptions", "from",
"from",
// Typescript reserved words
"abstract", "await", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "constructor", "continue", "debugger", "default", "delete", "do", "double", "else", "enum", "export", "extends", "false", "final", "finally", "float", "for", "function", "goto", "if", "implements", "import", "in", "instanceof", "int", "interface", "let", "long", "native", "new", "null", "package", "private", "protected", "public", "return", "short", "static", "super", "switch", "synchronized", "this", "throw", "transient", "true", "try", "typeof", "var", "void", "volatile", "while", "with", "yield"));
"constructor"));
languageSpecificPrimitives = new HashSet<>(Arrays.asList(
"string",
"String",
"boolean",
"Boolean",
"Double",
"Integer",
"Long",
"Float",
"Object",
"Array",
"Date",
"number",
"any",
"File",
"Error",
"Map",
"Set"
));
languageGenericTypes = new HashSet<>(Arrays.asList(
"Array"
));
instantiationTypes.put("array", "Array");
typeMapping = new HashMap<>();
typeMapping.put("Array", "Array");
typeMapping.put("array", "Array");
typeMapping.put("List", "Array");
typeMapping.put("boolean", "boolean");
typeMapping.put("string", "string");
typeMapping.put("int", "number");
typeMapping.put("float", "number");
typeMapping.put("number", "number");
typeMapping.put("long", "number");
typeMapping.put("short", "number");
typeMapping.put("char", "string");
typeMapping.put("double", "number");
typeMapping.put("object", "any");
typeMapping.put("integer", "number");
typeMapping.put("Map", "any");
typeMapping.put("map", "any");
typeMapping.put("Set", "Set");
typeMapping.put("set", "Set");
typeMapping.put("date", "string");
typeMapping.put("DateTime", "Date");
typeMapping.put("binary", "any");
typeMapping.put("File", "any");
typeMapping.put("file", "any");
typeMapping.put("ByteArray", "string");
typeMapping.put("UUID", "string");
typeMapping.put("Error", "Error");
typeMapping.put("AnyType", "any");
cliOptions.add(new CliOption(NPM_NAME, "The name under which you want to publish generated npm package." +
" Required to generate a full package"));
cliOptions.add(new CliOption(NPM_VERSION, "The version of your npm package. If not provided, using the version from the OpenAPI specification file.").defaultValue(this.getNpmVersion()));
cliOptions.add(new CliOption(NPM_REPOSITORY, "Use this property to set an url your private npmRepo in the package.json"));
cliOptions.add(CliOption.newBoolean(SNAPSHOT,
"When setting this property to true, the version will be suffixed with -SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.get().toPattern(),
false));
cliOptions.add(new CliOption(CodegenConstants.MODEL_PROPERTY_NAMING, CodegenConstants.MODEL_PROPERTY_NAMING_DESC).defaultValue("camelCase"));
cliOptions.add(new CliOption(CodegenConstants.SUPPORTS_ES6, CodegenConstants.SUPPORTS_ES6_DESC).defaultValue("false"));
cliOptions.add(new CliOption(TypeScriptClientCodegen.FILE_CONTENT_DATA_TYPE, TypeScriptClientCodegen.FILE_CONTENT_DATA_TYPE_DESC).defaultValue("Buffer"));
cliOptions.add(new CliOption(TypeScriptClientCodegen.USE_RXJS_SWITCH, TypeScriptClientCodegen.USE_RXJS_SWITCH_DESC).defaultValue("false"));
cliOptions.add(new CliOption(TypeScriptClientCodegen.USE_OBJECT_PARAMS_SWITCH, TypeScriptClientCodegen.USE_OBJECT_PARAMS_DESC).defaultValue("false"));
@@ -203,20 +111,23 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
cliOptions.add(new CliOption(TypeScriptClientCodegen.IMPORT_FILE_EXTENSION_SWITCH, TypeScriptClientCodegen.IMPORT_FILE_EXTENSION_SWITCH_DESC));
CliOption frameworkOption = new CliOption(TypeScriptClientCodegen.FRAMEWORK_SWITCH, TypeScriptClientCodegen.FRAMEWORK_SWITCH_DESC);
for (String option: TypeScriptClientCodegen.FRAMEWORKS) {
for (String option : TypeScriptClientCodegen.FRAMEWORKS) {
frameworkOption.addEnum(option, option);
}
frameworkOption.defaultValue(FRAMEWORKS[0]);
cliOptions.add(frameworkOption);
CliOption platformOption = new CliOption(TypeScriptClientCodegen.PLATFORM_SWITCH, TypeScriptClientCodegen.PLATFORM_SWITCH_DESC);
for (String option: TypeScriptClientCodegen.PLATFORMS) {
for (String option : TypeScriptClientCodegen.PLATFORMS) {
platformOption.addEnum(option, option);
}
platformOption.defaultValue(PLATFORMS[0]);
cliOptions.add(platformOption);
// Set property naming to camelCase
supportModelPropertyNaming(CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.camelCase);
// Git
supportingFiles.add(new SupportingFile(".gitignore.mustache", "", ".gitignore"));
supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
@@ -274,42 +185,12 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
this.npmVersion = npmVersion;
}
@Override
public CodegenType getTag() {
return CodegenType.CLIENT;
}
@Override
public void preprocessOpenAPI(OpenAPI openAPI) {
if (additionalProperties.containsKey(NPM_NAME)) {
// If no npmVersion is provided in additional properties, version from API specification is used.
// If none of them is provided then fallbacks to default version
if (additionalProperties.containsKey(NPM_VERSION)) {
this.setNpmVersion(additionalProperties.get(NPM_VERSION).toString());
} else if (openAPI.getInfo() != null && openAPI.getInfo().getVersion() != null) {
this.setNpmVersion(openAPI.getInfo().getVersion());
}
if (additionalProperties.containsKey(SNAPSHOT) && Boolean.parseBoolean(additionalProperties.get(SNAPSHOT).toString())) {
if (npmVersion.toUpperCase(Locale.ROOT).matches("^.*-SNAPSHOT$")) {
this.setNpmVersion(npmVersion + "." + SNAPSHOT_SUFFIX_FORMAT.get().format(new Date()));
} else {
this.setNpmVersion(npmVersion + "-SNAPSHOT." + SNAPSHOT_SUFFIX_FORMAT.get().format(new Date()));
}
}
additionalProperties.put(NPM_VERSION, npmVersion);
}
}
@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
final Object propFramework = additionalProperties.get(FRAMEWORK_SWITCH);
Map<String, Boolean> frameworks = new HashMap<>();
for (String framework: FRAMEWORKS) {
for (String framework : FRAMEWORKS) {
frameworks.put(framework, framework.equals(propFramework));
}
objs.put("framework", propFramework);
@@ -331,7 +212,7 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
OperationMap operationsMap = operations.getOperations();
List<CodegenOperation> operationList = operationsMap.getOperation();
for (CodegenOperation operation: operationList) {
for (CodegenOperation operation : operationList) {
List<CodegenResponse> responses = operation.responses;
operation.returnType = this.getReturnType(responses);
}
@@ -340,12 +221,13 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
/**
* Returns the correct return type based on all 2xx HTTP responses defined for an operation.
*
* @param responses all CodegenResponses defined for one operation
* @return TypeScript return type
*/
private String getReturnType(List<CodegenResponse> responses) {
Set<String> returnTypes = new HashSet<>();
for (CodegenResponse response: responses) {
for (CodegenResponse response : responses) {
if (response.is2xx) {
if (response.dataType != null) {
returnTypes.add(response.dataType);
@@ -362,18 +244,21 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
return String.join(" | ", returnTypes);
}
@Override
public String escapeReservedWord(String name) {
if (this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "_" + name;
}
@Override
public String toParamName(String name) {
// should be the same as variable name
return toVarName(name);
// sanitize name
name = sanitizeName(name);
if ("_".equals(name)) {
name = "_u";
}
// if it's all upper case, do nothing
if (name.matches("^[A-Z_]*$")) {
return name;
}
return super.toParamName(name);
}
@Override
@@ -390,22 +275,7 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
return name;
}
name = getNameUsingModelPropertyNaming(name);
// for reserved word or word starting with number, append _
if (isReservedWord(name) || name.matches("^\\d.*")) {
name = escapeReservedWord(name);
}
return name;
}
@Override
public String toModelName(final String name) {
String fullModelName = name;
fullModelName = addPrefix(fullModelName, modelNamePrefix);
fullModelName = addSuffix(fullModelName, modelNameSuffix);
return toTypescriptTypeName(fullModelName, "Model");
return super.toVarName(name);
}
@Override
@@ -414,283 +284,12 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
return "../" + modelPackage() + "/" + toModelName(name);
}
protected String addPrefix(String name, String prefix) {
if (!StringUtils.isEmpty(prefix)) {
name = prefix + "_" + name;
}
return name;
}
protected String addSuffix(String name, String suffix) {
if (!StringUtils.isEmpty(suffix)) {
name = name + "_" + suffix;
}
return name;
}
protected String toTypescriptTypeName(final String name, String safePrefix) {
ArrayList<String> exceptions = new ArrayList<>(Arrays.asList("\\|", " "));
String sanName = sanitizeName(name, "(?![| ])\\W", exceptions);
sanName = camelize(sanName);
// model name cannot use reserved keyword, e.g. return
// this is unlikely to happen, because we have just camelized the name, while reserved words are usually all lowercase
if (isReservedWord(sanName)) {
String modelName = safePrefix + sanName;
LOGGER.warn("{} (reserved word) cannot be used as model name. Renamed to {}", sanName, modelName);
return modelName;
}
// model name starts with number
if (sanName.matches("^\\d.*")) {
String modelName = safePrefix + sanName; // e.g. 200Response => Model200Response
LOGGER.warn("{} (model name starts with number) cannot be used as model name. Renamed to {}", sanName,
modelName);
return modelName;
}
if (languageSpecificPrimitives.contains(sanName)) {
String modelName = safePrefix + sanName;
LOGGER.warn("{} (model name matches existing language type) cannot be used as a model name. Renamed to {}",
sanName, modelName);
return modelName;
}
return sanName;
}
@Override
public String toModelFilename(String name) {
// should be the same as the model name
return toModelName(name);
}
@Override
protected String getParameterDataType(Parameter parameter, Schema p) {
// handle enums of various data types
Schema inner;
if (ModelUtils.isArraySchema(p)) {
ArraySchema mp1 = (ArraySchema) p;
inner = mp1.getItems();
return this.getSchemaType(p) + "<" + this.getParameterDataType(parameter, inner) + ">";
} else if (ModelUtils.isMapSchema(p)) {
inner = (Schema) p.getAdditionalProperties();
return "{ [key: string]: " + this.getParameterDataType(parameter, inner) + "; }";
} else if (ModelUtils.isStringSchema(p)) {
// Handle string enums
if (p.getEnum() != null) {
return enumValuesToEnumTypeUnion(p.getEnum(), "string");
}
} else if (ModelUtils.isIntegerSchema(p)) {
// Handle integer enums
if (p.getEnum() != null) {
return numericEnumValuesToEnumTypeUnion(new ArrayList<Number>(p.getEnum()));
}
} else if (ModelUtils.isNumberSchema(p)) {
// Handle double enums
if (p.getEnum() != null) {
return numericEnumValuesToEnumTypeUnion(new ArrayList<Number>(p.getEnum()));
}
}
return this.getTypeDeclaration(p);
}
/**
* Converts a list of strings to a literal union for representing enum values as a type.
* Example output: 'available' | 'pending' | 'sold'
*
* @param values list of allowed enum values
* @param dataType either "string" or "number"
* @return a literal union for representing enum values as a type
*/
protected String enumValuesToEnumTypeUnion(List<String> values, String dataType) {
StringBuilder b = new StringBuilder();
boolean isFirst = true;
for (String value : values) {
if (!isFirst) {
b.append(" | ");
}
b.append(toEnumValue(value, dataType));
isFirst = false;
}
return b.toString();
}
/**
* Converts a list of numbers to a literal union for representing enum values as a type.
* Example output: 3 | 9 | 55
*
* @param values a list of numbers
* @return a literal union for representing enum values as a type
*/
protected String numericEnumValuesToEnumTypeUnion(List<Number> values) {
List<String> stringValues = new ArrayList<>();
for (Number value : values) {
stringValues.add(value.toString());
}
return enumValuesToEnumTypeUnion(stringValues, "number");
}
@Override
public String toDefaultValue(Schema p) {
if (ModelUtils.isBooleanSchema(p)) {
return UNDEFINED_VALUE;
} else if (ModelUtils.isDateSchema(p)) {
return UNDEFINED_VALUE;
} else if (ModelUtils.isDateTimeSchema(p)) {
return UNDEFINED_VALUE;
} else if (ModelUtils.isNumberSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
return UNDEFINED_VALUE;
} else if (ModelUtils.isIntegerSchema(p)) {
if (p.getDefault() != null) {
return p.getDefault().toString();
}
return UNDEFINED_VALUE;
} else if (ModelUtils.isStringSchema(p)) {
if (p.getDefault() != null) {
return "'" + (String) p.getDefault() + "'";
}
return UNDEFINED_VALUE;
} else {
return UNDEFINED_VALUE;
}
}
@Override
protected boolean isReservedWord(String word) {
// NOTE: This differs from super's implementation in that TypeScript does _not_ want case insensitive matching.
return reservedWords.contains(word);
}
@Override
public String getSchemaType(Schema p) {
String openAPIType = super.getSchemaType(p);
String type = null;
if (ModelUtils.isComposedSchema(p)) {
return openAPIType;
} else if (typeMapping.containsKey(openAPIType)) {
type = typeMapping.get(openAPIType);
if (languageSpecificPrimitives.contains(type))
return type;
} else
type = openAPIType;
return toModelName(type);
}
@Override
public String toOperationId(String operationId) {
// throw exception if method name is empty
if (StringUtils.isEmpty(operationId)) {
throw new RuntimeException("Empty method name (operationId) not allowed");
}
// method name cannot use reserved keyword, e.g. return
// append _ at the beginning, e.g. _return
if (isReservedWord(operationId)) {
return escapeReservedWord(camelize(sanitizeName(operationId), LOWERCASE_FIRST_LETTER));
}
return camelize(sanitizeName(operationId), LOWERCASE_FIRST_LETTER);
}
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'");
}
}
public String getModelPropertyNaming() {
return this.modelPropertyNaming;
}
public String getNameUsingModelPropertyNaming(String name) {
switch (CodegenConstants.MODEL_PROPERTY_NAMING_TYPE.valueOf(getModelPropertyNaming())) {
case original:
return name;
case camelCase:
return camelize(name, LOWERCASE_FIRST_LETTER);
case PascalCase:
return camelize(name);
case snake_case:
return underscore(name);
default:
throw new IllegalArgumentException("Invalid model property naming '" +
name + "'. Must be 'original', 'camelCase', " +
"'PascalCase' or 'snake_case'");
}
}
@Override
public String toEnumValue(String value, String datatype) {
if ("number".equals(datatype)) {
return value;
} else {
return "\'" + escapeText(value) + "\'";
}
}
@Override
public String toEnumDefaultValue(String value, String datatype) {
return datatype + "_" + value;
}
@Override
public String toEnumVarName(String name, String datatype) {
if (name.length() == 0) {
return "Empty";
}
// for symbol, e.g. $, #
if (getSymbolName(name) != null) {
return camelize(getSymbolName(name));
}
// number
if ("number".equals(datatype)) {
String varName = "NUMBER_" + name;
varName = varName.replaceAll("-", "MINUS_");
varName = varName.replaceAll("\\+", "PLUS_");
varName = varName.replaceAll("\\.", "_DOT_");
return varName;
}
// string
String enumName = sanitizeName(name);
enumName = enumName.replaceFirst("^_", "");
enumName = enumName.replaceFirst("_$", "");
// camelize the enum variable name
// ref: https://basarat.gitbooks.io/typescript/content/docs/enums.html
enumName = camelize(enumName);
if (enumName.matches("\\d.*")) { // starts with number
return "_" + enumName;
} else {
return enumName;
}
}
@Override
public String toEnumName(CodegenProperty property) {
String enumName = toModelName(property.name) + "Enum";
if (enumName.matches("\\d.*")) { // starts with number
return "_" + enumName;
} else {
return enumName;
return "'" + escapeText(value) + "'";
}
}
@@ -738,53 +337,6 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
return tsImports;
}
@Override
public Map<String, ModelsMap> postProcessAllModels(Map<String, ModelsMap> objs) {
Map<String, ModelsMap> result = super.postProcessAllModels(objs);
for (ModelsMap entry : result.values()) {
for (ModelMap mo : entry.getModels()) {
CodegenModel cm = mo.getModel();
if (cm.discriminator != null && cm.children != null) {
for (CodegenModel child : cm.children) {
this.setDiscriminatorValue(child, cm.discriminator.getPropertyName(), this.getDiscriminatorValue(child));
}
}
}
}
return result;
}
private void setDiscriminatorValue(CodegenModel model, String baseName, String value) {
for (CodegenProperty prop : model.allVars) {
if (prop.baseName.equals(baseName)) {
prop.discriminatorValue = value;
}
}
if (model.children != null) {
final boolean newDiscriminator = model.discriminator != null;
for (CodegenModel child : model.children) {
this.setDiscriminatorValue(child, baseName, newDiscriminator ? value : this.getDiscriminatorValue(child));
}
}
}
private String getDiscriminatorValue(CodegenModel model) {
return model.vendorExtensions.containsKey(X_DISCRIMINATOR_TYPE) ?
(String) model.vendorExtensions.get(X_DISCRIMINATOR_TYPE) : model.classname;
}
@Override
public String escapeQuotationMark(String input) {
// remove ', " to avoid code injection
return input.replace("\"", "").replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
return input.replace("*/", "*_/").replace("/*", "/_*");
}
@Override
public String getName() {
return "typescript";
@@ -795,17 +347,10 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
return "Generates a TypeScript client library using Fetch API (beta).";
}
@Override
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey(CodegenConstants.MODEL_PROPERTY_NAMING)) {
setModelPropertyNaming((String) additionalProperties.get(CodegenConstants.MODEL_PROPERTY_NAMING));
}
convertPropertyToBooleanAndWriteBack(CodegenConstants.SUPPORTS_ES6);
// change package names
apiPackage = this.apiPackage + ".apis";
testPackage = this.testPackage + ".tests";
@@ -815,8 +360,8 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
String httpLibName = this.getHttpLibForFramework(additionalProperties.get(FRAMEWORK_SWITCH).toString());
supportingFiles.add(new SupportingFile(
"http" + File.separator + httpLibName + ".mustache",
"http", httpLibName + ".ts"
"http" + File.separator + httpLibName + ".mustache",
"http", httpLibName + ".ts"
));
Object propPlatform = additionalProperties.get(PLATFORM_SWITCH);
@@ -826,7 +371,7 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
}
Map<String, Boolean> platforms = new HashMap<>();
for (String platform: PLATFORMS) {
for (String platform : PLATFORMS) {
platforms.put(platform, platform.equals(propPlatform));
}
additionalProperties.put("platforms", platforms);
@@ -861,14 +406,6 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
}
// NPM Settings
if (additionalProperties.containsKey(NPM_NAME)) {
setNpmName(additionalProperties.get(NPM_NAME).toString());
}
if (additionalProperties.containsKey(NPM_VERSION)) {
setNpmVersion(additionalProperties.get(NPM_VERSION).toString());
}
if (additionalProperties.containsKey(NPM_REPOSITORY)) {
setNpmRepository(additionalProperties.get(NPM_REPOSITORY).toString());
}
@@ -1396,9 +933,7 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
return example;
}
/***
*
* Set the codegenParameter example value
* We have a custom version of this function so we can invoke toExampleValue
*
@@ -1489,64 +1024,6 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
return cp;
}
@Override
public String toAnyOfName(List<String> names, ComposedSchema composedSchema) {
List<String> types = getTypesFromSchemas(composedSchema.getAnyOf());
return String.join(" | ", types);
}
@Override
public String toOneOfName(List<String> names, ComposedSchema composedSchema) {
List<String> types = getTypesFromSchemas(composedSchema.getOneOf());
return String.join(" | ", types);
}
@Override
public String toAllOfName(List<String> names, ComposedSchema composedSchema) {
List<String> types = getTypesFromSchemas(composedSchema.getAllOf());
return String.join(" & ", types);
}
/**
* Extracts the list of type names from a list of schemas.
* Excludes `AnyType` if there are other valid types extracted.
*
* @param schemas list of schemas
* @return list of types
*/
protected List<String> getTypesFromSchemas(List<Schema> schemas) {
List<Schema> filteredSchemas = schemas.size() > 1
? schemas.stream().filter(schema -> !"AnyType".equals(super.getSchemaType(schema))).collect(Collectors.toList())
: schemas;
return filteredSchemas.stream().map(schema -> {
String schemaType = getSchemaType(schema);
if (ModelUtils.isArraySchema(schema)) {
ArraySchema ap = (ArraySchema) schema;
Schema inner = ap.getItems();
schemaType = schemaType + "<" + getSchemaType(inner) + ">";
}
return schemaType;
}).distinct().collect(Collectors.toList());
}
@Override
protected void addImport(CodegenModel m, String type) {
if (type == null) {
return;
}
String[] parts = splitComposedType(type);
for (String s : parts) {
if (needToImport(s)) {
m.imports.add(s);
}
}
}
@Override
protected void addImport(Set<String> importsToBeAddedTo, String type) {
if (type == null) {
@@ -1567,9 +1044,6 @@ public class TypeScriptClientCodegen extends DefaultCodegen implements CodegenCo
* @return list of types
*/
protected String[] splitComposedType(String type) {
return type.replace(" ","").split("[|&<>]");
return type.replace(" ", "").split("[|&<>]");
}
@Override
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.TYPESCRIPT; }
}

View File

@@ -89,7 +89,7 @@ public class TypeScriptAxiosClientCodegenTest {
codegen.additionalProperties().put("npmName", "@openapi/typescript-axios-petstore");
codegen.additionalProperties().put("snapshot", false);
codegen.additionalProperties().put("npmVersion", "1.0.0-SNAPSHOT");
codegen.setSupportsES6(true);
codegen.additionalProperties().put("supportsES6", true);
codegen.processOpts();
@@ -104,7 +104,7 @@ public class TypeScriptAxiosClientCodegenTest {
codegen.additionalProperties().put("npmName", "@openapi/typescript-axios-petstore");
codegen.additionalProperties().put("snapshot", false);
codegen.additionalProperties().put("npmVersion", "1.0.0-SNAPSHOT");
codegen.setSupportsES6(false);
codegen.additionalProperties().put("supportsES6", false);
codegen.processOpts();

View File

@@ -116,7 +116,7 @@ public class TypeScriptFetchClientCodegenTest {
codegen.additionalProperties().put("npmName", "@openapi/typescript-fetch-petstore");
codegen.additionalProperties().put("snapshot", false);
codegen.additionalProperties().put("npmVersion", "1.0.0-SNAPSHOT");
codegen.setSupportsES6(true);
codegen.additionalProperties().put("supportsES6", true);
codegen.processOpts();
@@ -131,7 +131,7 @@ public class TypeScriptFetchClientCodegenTest {
codegen.additionalProperties().put("npmName", "@openapi/typescript-fetch-petstore");
codegen.additionalProperties().put("snapshot", false);
codegen.additionalProperties().put("npmVersion", "1.0.0-SNAPSHOT");
codegen.setSupportsES6(false);
codegen.additionalProperties().put("supportsES6", false);
codegen.processOpts();