diff --git a/lib/codegen/fromcto/rust/rustvisitor.js b/lib/codegen/fromcto/rust/rustvisitor.js index 92d89568..4cb2ddac 100644 --- a/lib/codegen/fromcto/rust/rustvisitor.js +++ b/lib/codegen/fromcto/rust/rustvisitor.js @@ -304,6 +304,7 @@ class RustVisitor { * @private */ visitClassDeclaration(classDeclaration, parameters) { + this.writeDescription(classDeclaration, parameters, 0); parameters.fileWriter.writeLine( 0, '#[derive(Debug, Clone, Serialize, Deserialize)]' @@ -513,6 +514,8 @@ class RustVisitor { if (field.isOptional?.()) { hashMapType = this.wrapAsOption(hashMapType); } + // Add description for HashMap fields + this.writeDescription(field, parameters, 1); // Generate serde attributes for HashMap with DateTime serialization support parameters.fileWriter.writeLine(1, '#[serde('); @@ -607,6 +610,10 @@ class RustVisitor { } // Handle regular (non-HashMap) fields + + // Add description for regular fields + this.writeDescription(field, parameters, 1); + parameters.fileWriter.writeLine(1, '#[serde('); parameters.fileWriter.writeLine(2, `rename = "${field.name}",`); if (field.isOptional?.()) { @@ -752,6 +759,7 @@ class RustVisitor { if (relationshipDeclaration.isOptional?.()) { type = `Option<${type}>`; } + this.writeDescription(relationshipDeclaration, parameters, 1); // Start serde attribute block parameters.fileWriter.writeLine(1, '#[serde('); @@ -1327,6 +1335,24 @@ class RustVisitor { parameters.fileWriter.closeFile(); } + /** + * Writes the description of a declaration or property as a Rust documentation comment. + * @param {Object} thing - the declaration or property + * @param {Object} parameters - the parameters + * @param {number} indent - the indentation level + * @private + */ + writeDescription(thing, parameters, indent) { + if (thing.getDescription && thing.getDescription()) { + const description = thing.getDescription(); + // Split by newline to handle multi-line comments cleanly + const lines = description.split(/\r?\n/); + lines.forEach(line => { + // Write '/// ' followed by the trimmed line + parameters.fileWriter.writeLine(indent, `/// ${line.trim()}`); + }); + } + } } module.exports = RustVisitor; diff --git a/lib/codegen/fromcto/typescript/typescriptvisitor.js b/lib/codegen/fromcto/typescript/typescriptvisitor.js index 5ffd94d4..dccf5820 100644 --- a/lib/codegen/fromcto/typescript/typescriptvisitor.js +++ b/lib/codegen/fromcto/typescript/typescriptvisitor.js @@ -184,17 +184,23 @@ class TypescriptVisitor { * @private */ visitEnumDeclaration(enumDeclaration, parameters) { + const properties = enumDeclaration.getOwnProperties(); - parameters.fileWriter.writeLine(0, 'export enum ' + enumDeclaration.getName() + ' {'); + if (properties.length === 0) { + // Handle empty enums to ensure valid TypeScript syntax + parameters.fileWriter.writeLine(0, 'export type ' + enumDeclaration.getName() + ' = never;'); + } else { + parameters.fileWriter.writeLine(0, 'export type ' + enumDeclaration.getName() + ' ='); + const values = properties + .map(property => `'${property.getName()}'`) + .join(' | '); - enumDeclaration.getOwnProperties().forEach((property) => { - property.accept(this, parameters); - }); + parameters.fileWriter.writeLine(1, values + ';'); + } - parameters.fileWriter.writeLine(0, '}\n'); + parameters.fileWriter.writeLine(0, ''); return null; } - /** * Visitor design pattern * @param {ClassDeclaration} classDeclaration - the object being visited @@ -271,7 +277,8 @@ class TypescriptVisitor { const subclasses = field.getParent().getModelFile().getModelManager().getType(field.getFullyQualifiedTypeName()).getDirectSubclasses(); if (subclasses && subclasses.length > 0) { const useUnion = !(isEnumRef || isMapRef); - tsType = this.toTsType(field.getType(), !useUnion, useUnion); + const useInterface = !useUnion && !isEnumRef && !isMapRef; + tsType = this.toTsType(field.getType(), useInterface, useUnion); } } @@ -400,4 +407,4 @@ class TypescriptVisitor { } } -module.exports = TypescriptVisitor; +module.exports = TypescriptVisitor; \ No newline at end of file diff --git a/test/codegen/fromcto/typescript/typescriptvisitor.js b/test/codegen/fromcto/typescript/typescriptvisitor.js index 39fe80b9..eaaf987a 100644 --- a/test/codegen/fromcto/typescript/typescriptvisitor.js +++ b/test/codegen/fromcto/typescript/typescriptvisitor.js @@ -442,7 +442,7 @@ describe('TypescriptVisitor', function () { }); describe('visitEnumDeclaration', () => { - it('should write the export enum and call accept on each property', () => { + it('should write the export type and string union', () => { let acceptSpy = sinon.spy(); let param = { @@ -452,20 +452,22 @@ describe('TypescriptVisitor', function () { let mockEnumDeclaration = sinon.createStubInstance(EnumDeclaration); mockEnumDeclaration.isEnum.returns(true); mockEnumDeclaration.getName.returns('Bob'); + mockEnumDeclaration.getOwnProperties.returns([{ - accept: acceptSpy + accept: acceptSpy, + getName: () => 'RED' }, { - accept: acceptSpy + accept: acceptSpy, + getName: () => 'BLUE' }]); typescriptVisitor.visitEnumDeclaration(mockEnumDeclaration, param); - param.fileWriter.writeLine.callCount.should.deep.equal(2); - param.fileWriter.writeLine.withArgs(0, 'export enum Bob {').calledOnce.should.be.ok; - param.fileWriter.writeLine.withArgs(0, '}\n').calledOnce.should.be.ok; - - acceptSpy.withArgs(typescriptVisitor, param).calledTwice.should.be.ok; + param.fileWriter.writeLine.callCount.should.deep.equal(3); + param.fileWriter.writeLine.withArgs(0, 'export type Bob =').calledOnce.should.be.ok; + param.fileWriter.writeLine.withArgs(1, '\'RED\' | \'BLUE\';').calledOnce.should.be.ok; + param.fileWriter.writeLine.withArgs(0, '').calledOnce.should.be.ok; }); });