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
26 changes: 26 additions & 0 deletions lib/codegen/fromcto/rust/rustvisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ class RustVisitor {
* @private
*/
visitClassDeclaration(classDeclaration, parameters) {
this.writeDescription(classDeclaration, parameters, 0);
parameters.fileWriter.writeLine(
0,
'#[derive(Debug, Clone, Serialize, Deserialize)]'
Expand Down Expand Up @@ -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(');
Expand Down Expand Up @@ -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?.()) {
Expand Down Expand Up @@ -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(');
Expand Down Expand Up @@ -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;
23 changes: 15 additions & 8 deletions lib/codegen/fromcto/typescript/typescriptvisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}

Expand Down Expand Up @@ -400,4 +407,4 @@ class TypescriptVisitor {
}
}

module.exports = TypescriptVisitor;
module.exports = TypescriptVisitor;
18 changes: 10 additions & 8 deletions test/codegen/fromcto/typescript/typescriptvisitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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;
});
});

Expand Down
Loading