diff --git a/docs/generators/typescript-fetch.md b/docs/generators/typescript-fetch.md
index 20a758ccf3f..63a97cf5360 100644
--- a/docs/generators/typescript-fetch.md
+++ b/docs/generators/typescript-fetch.md
@@ -25,6 +25,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |PascalCase|
|enumPropertyNamingReplaceSpecialChar|Set to true to replace '-' and '+' symbols with 'minus_' and 'plus_' in enum of type string| |false|
|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.|
- **false**
- No changes to the enum's are made, this is the default option.
- **true**
- 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.
|false|
+|fileNaming|Naming convention for the output files: 'PascalCase', 'camelCase', 'kebab-case'.| |PascalCase|
|importFileExtension|File extension to use with relative imports. Set it to '.js' or '.mjs' when using [ESM](https://nodejs.org/api/esm.html).| ||
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|- **true**
- The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.
- **false**
- The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.
|true|
|modelPropertyNaming|Naming convention for the property: 'camelCase', 'PascalCase', 'snake_case' and 'original', which keeps the original name| |camelCase|
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java
index 6bc75f0e639..e6e49222e2b 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java
@@ -26,7 +26,6 @@ import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.servers.Server;
import io.swagger.v3.parser.util.SchemaTypeUtil;
-import java.util.stream.Collectors;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.DocumentationFeature;
import org.openapitools.codegen.meta.features.SecurityFeature;
@@ -38,6 +37,10 @@ import org.openapitools.codegen.utils.ModelUtils;
import java.io.File;
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.*;
public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodegen {
public static final String NPM_REPOSITORY = "npmRepository";
@@ -49,6 +52,10 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
public static final String STRING_ENUMS_DESC = "Generate string enums instead of objects for enum values.";
public static final String IMPORT_FILE_EXTENSION_SWITCH = "importFileExtension";
public 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).";
+ public static final String FILE_NAMING = "fileNaming";
+ public static final String KEBAB_CASE = "kebab-case";
+ public static final String CAMEL_CASE = "camelCase";
+ public static final String PASCAL_CASE = "PascalCase";
protected String npmRepository = null;
protected String importFileExtension = "";
@@ -58,6 +65,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
protected boolean addedModelIndex = false;
protected boolean withoutRuntimeChecks = false;
protected boolean stringEnums = false;
+ protected String fileNaming = PASCAL_CASE;
// "Saga and Record" mode.
public static final String SAGAS_AND_RECORDS = "sagasAndRecords";
@@ -105,6 +113,33 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
this.cliOptions.add(new CliOption(SAGAS_AND_RECORDS, "Setting this property to true will generate additional files for use with redux-saga and immutablejs.", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(STRING_ENUMS, STRING_ENUMS_DESC, SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(IMPORT_FILE_EXTENSION_SWITCH, IMPORT_FILE_EXTENSION_SWITCH_DESC).defaultValue(""));
+ this.cliOptions.add(new CliOption(FILE_NAMING, "Naming convention for the output files: 'PascalCase', 'camelCase', 'kebab-case'.").defaultValue(this.fileNaming));
+ }
+
+ @Override
+ public String toApiFilename(String name) {
+ return convertUsingFileNamingConvention(super.toApiFilename(name));
+ }
+
+ @Override
+ public String toModelFilename(String name) {
+ return convertUsingFileNamingConvention(super.toModelFilename(name));
+ }
+
+ /**
+ * Converts the original name according to the current fileNaming
strategy.
+ *
+ * @param originalName the original name to transform
+ * @return the transformed name
+ */
+ private String convertUsingFileNamingConvention(String originalName) {
+ String name = originalName;
+ if (KEBAB_CASE.equals(fileNaming)) {
+ name = dashize(underscore(name));
+ } else if (CAMEL_CASE.equals(fileNaming)) {
+ name = camelize(name, LOWERCASE_FIRST_LETTER);
+ }
+ return name;
}
@Override
@@ -148,6 +183,20 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
this.stringEnums = stringEnums;
}
+ /**
+ * Set the file naming type.
+ *
+ * @param fileNaming the file naming to use
+ */
+ public void setFileNaming(String fileNaming) {
+ if (PASCAL_CASE.equals(fileNaming) || CAMEL_CASE.equals(fileNaming) || KEBAB_CASE.equals(fileNaming)) {
+ this.fileNaming = fileNaming;
+ } else {
+ throw new IllegalArgumentException("Invalid file naming '" +
+ fileNaming + "'. Must be 'PascalCase', 'camelCase' or 'kebab-case'");
+ }
+ }
+
public Boolean getSagasAndRecords() {
return sagasAndRecords;
}
@@ -250,6 +299,10 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
this.setStringEnums(convertPropertyToBoolean(STRING_ENUMS));
}
+ if (additionalProperties.containsKey(FILE_NAMING)) {
+ this.setFileNaming(additionalProperties.get(FILE_NAMING).toString());
+ }
+
if (!withoutRuntimeChecks) {
this.modelTemplateFiles.put("models.mustache", ".ts");
typeMapping.put("date", "Date");
@@ -367,7 +420,7 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
for (ModelMap model : entry.getModels()) {
ExtendedCodegenModel codegenModel = (ExtendedCodegenModel) model.getModel();
model.put("hasImports", codegenModel.imports.size() > 0);
-
+ model.put("tsImports", toTsImports(codegenModel, parseImports(codegenModel)));
allModels.add(codegenModel);
if (codegenModel.isEntity) {
entityModelClassnames.add(codegenModel.classname);
@@ -401,6 +454,38 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
return result;
}
+ /**
+ * Parse imports
+ */
+ private Set parseImports(CodegenModel cm) {
+ Set newImports = new HashSet<>();
+ if (cm.imports.size() > 0) {
+ for (String name : cm.imports) {
+ if (name.indexOf(" | ") >= 0) {
+ String[] parts = name.split(" \\| ");
+ Collections.addAll(newImports, parts);
+ } else {
+ newImports.add(name);
+ }
+ }
+ }
+ return newImports;
+ }
+
+ private List