Skip to content
Open
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 @@ -22,6 +22,8 @@
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.DocumentationFeature;
import org.openapitools.codegen.meta.features.SecurityFeature;
Expand All @@ -39,6 +41,8 @@

public class TypeScriptAxiosClientCodegen extends AbstractTypeScriptClientCodegen {

private final Logger LOGGER = LoggerFactory.getLogger(TypeScriptAxiosClientCodegen.class);

public static final String NPM_REPOSITORY = "npmRepository";
public static final String WITH_INTERFACES = "withInterfaces";
public static final String SEPARATE_MODELS_AND_API = "withSeparateModelsAndApi";
Expand All @@ -53,10 +57,13 @@ public class TypeScriptAxiosClientCodegen extends AbstractTypeScriptClientCodege
public static final String AXIOS_VERSION = "axiosVersion";
public static final String DEFAULT_AXIOS_VERSION = "^1.13.5";
public static final String WITH_AWSV4_SIGNATURE = "withAWSV4Signature";
public static final String USE_ERASABLE_SYNTAX = "useErasableSyntax";
public static final String USE_ERASABLE_SYNTAX_DESC = "Use erasable syntax for the generated code, compatible with TypeScript's erasableSyntaxOnly option.";

@Getter @Setter
protected String npmRepository = null;
protected Boolean stringEnums = false;
protected Boolean useErasableSyntax = false;
protected String importFileExtension = "";

@Getter @Setter
Expand Down Expand Up @@ -97,6 +104,7 @@ public TypeScriptAxiosClientCodegen() {
this.cliOptions.add(new CliOption(USE_SQUARE_BRACKETS_IN_ARRAY_NAMES, "Setting this property to true will add brackets to array attribute names, e.g. my_values[].", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(AXIOS_VERSION, "Use this property to override the axios version in package.json").defaultValue(DEFAULT_AXIOS_VERSION));
this.cliOptions.add(new CliOption(WITH_AWSV4_SIGNATURE, "whether to include AWS v4 signature support", SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
this.cliOptions.add(new CliOption(USE_ERASABLE_SYNTAX, USE_ERASABLE_SYNTAX_DESC, SchemaTypeUtil.BOOLEAN_TYPE).defaultValue(Boolean.FALSE.toString()));
// Templates have no mapping between formatted property names and original base names so use only "original" and remove this option
removeOption(CodegenConstants.MODEL_PROPERTY_NAMING);
}
Expand Down Expand Up @@ -177,6 +185,18 @@ public void processOpts() {
additionalProperties.put("importFileExtension", this.importFileExtension);
}

if (additionalProperties.containsKey(USE_ERASABLE_SYNTAX)) {
this.useErasableSyntax = Boolean.parseBoolean(additionalProperties.get(USE_ERASABLE_SYNTAX).toString());
additionalProperties.put(USE_ERASABLE_SYNTAX, this.useErasableSyntax);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: useErasableSyntax is not reconciled with stringEnums, allowing generation of export enum despite claiming erasable-syntax compatibility.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAxiosClientCodegen.java, line 186:

<comment>`useErasableSyntax` is not reconciled with `stringEnums`, allowing generation of `export enum` despite claiming erasable-syntax compatibility.</comment>

<file context>
@@ -177,6 +181,11 @@ public void processOpts() {
 
+        if (additionalProperties.containsKey(USE_ERASABLE_SYNTAX)) {
+            this.useErasableSyntax = Boolean.parseBoolean(additionalProperties.get(USE_ERASABLE_SYNTAX).toString());
+            additionalProperties.put(USE_ERASABLE_SYNTAX, this.useErasableSyntax);
+        }
+
</file context>
Suggested change
additionalProperties.put(USE_ERASABLE_SYNTAX, this.useErasableSyntax);
additionalProperties.put(USE_ERASABLE_SYNTAX, this.useErasableSyntax);
if (Boolean.TRUE.equals(this.useErasableSyntax) && Boolean.TRUE.equals(this.stringEnums)) {
this.stringEnums = false;
additionalProperties.put("stringEnums", false);
}
Fix with Cubic

Copy link
Author

@thobed thobed Mar 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 0acb15c — added a runtime warning when both useErasableSyntax and stringEnums are enabled. The warning explains that enum declarations are not erasable syntax and recommends disabling stringEnums (the default generates erasable-compatible as const objects).

chose a warning over a hard error to avoid breaking existing configs — users may have valid reasons to enable both (e.g. migrating incrementally).

}

if (this.useErasableSyntax && this.stringEnums) {
LOGGER.warn("useErasableSyntax and stringEnums are both enabled. "
+ "TypeScript 'enum' declarations are not erasable syntax and will fail with "
+ "erasableSyntaxOnly. Consider disabling stringEnums (the default generates "
+ "erasable-compatible const objects instead).");
}

if (additionalProperties.containsKey(NPM_NAME)) {
addNpmPackageGeneration();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,45 @@ export interface RequestArgs {

export class BaseAPI {
protected configuration: Configuration | undefined;
{{^useErasableSyntax}}

constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
if (configuration) {
this.configuration = configuration;
this.basePath = configuration.basePath ?? basePath;
}
}
{{/useErasableSyntax}}
{{#useErasableSyntax}}
protected basePath: string;
protected axios: AxiosInstance;

constructor(configuration?: Configuration, basePath: string = BASE_PATH, axios: AxiosInstance = globalAxios) {
this.basePath = basePath;
this.axios = axios;
if (configuration) {
this.configuration = configuration;
this.basePath = configuration.basePath ?? basePath;
}
}
{{/useErasableSyntax}}
};

export class RequiredError extends Error {
{{^useErasableSyntax}}
constructor(public field: string, msg?: string) {
super(msg);
this.name = "RequiredError"
}
{{/useErasableSyntax}}
{{#useErasableSyntax}}
public field: string;
constructor(field: string, msg?: string) {
super(msg);
this.name = "RequiredError"
this.field = field;
}
{{/useErasableSyntax}}
}

interface ServerMap {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.testng.annotations.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
Expand Down Expand Up @@ -178,4 +179,45 @@ public void testDeprecatedArrayAttribute() throws Exception {
// Verify the non-deprecated array property 'nicknames' is also present
TestUtils.assertFileContains(file, "'nicknames'?: Array<string>");
}

@Test(description = "Verify useErasableSyntax generates erasable code in base.ts")
public void testUseErasableSyntaxConfig() throws IOException {
boolean[] options = {true, false};
for (boolean useErasableSyntax : options) {
final File output = Files.createTempDirectory("typescript_axios_erasable_").toFile();
output.deleteOnExit();

final CodegenConfigurator configurator = new CodegenConfigurator()
.setGeneratorName("typescript-axios")
.setInputSpec("src/test/resources/3_0/petstore.yaml")
.addAdditionalProperty("useErasableSyntax", useErasableSyntax)
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));

final ClientOptInput clientOptInput = configurator.toClientOptInput();
final DefaultGenerator generator = new DefaultGenerator();
final List<File> files = generator.opts(clientOptInput).generate();
files.forEach(File::deleteOnExit);

Path baseTsPath = Paths.get(output + "/base.ts");
TestUtils.assertFileExists(baseTsPath);
if (useErasableSyntax) {
// Erasable syntax: no parameter properties, explicit field declarations and assignments
TestUtils.assertFileContains(baseTsPath, "protected basePath: string;");
TestUtils.assertFileContains(baseTsPath, "protected axios: AxiosInstance;");
TestUtils.assertFileContains(baseTsPath, "this.basePath = basePath;");
TestUtils.assertFileContains(baseTsPath, "this.axios = axios;");
TestUtils.assertFileContains(baseTsPath, "public field: string;");
TestUtils.assertFileContains(baseTsPath, "this.field = field;");
// Should NOT contain parameter properties
TestUtils.assertFileNotContains(baseTsPath, "protected basePath: string = BASE_PATH,");
TestUtils.assertFileNotContains(baseTsPath, "protected axios: AxiosInstance = globalAxios");
TestUtils.assertFileNotContains(baseTsPath, "public field: string,");
} else {
// Non-erasable syntax: uses parameter properties
TestUtils.assertFileContains(baseTsPath, "protected basePath: string = BASE_PATH,");
TestUtils.assertFileContains(baseTsPath, "protected axios: AxiosInstance = globalAxios");
TestUtils.assertFileContains(baseTsPath, "constructor(public field: string,");
}
}
}
}