diff --git a/lib/codegen/fromcto/typescript/typescriptvisitor.js b/lib/codegen/fromcto/typescript/typescriptvisitor.js index 5ffd94d4..d6a34c3f 100644 --- a/lib/codegen/fromcto/typescript/typescriptvisitor.js +++ b/lib/codegen/fromcto/typescript/typescriptvisitor.js @@ -47,7 +47,10 @@ class TypescriptVisitor { } else if (thing.isMapDeclaration?.()) { return this.visitMapDeclaration(thing, parameters); } else if (thing.isTypeScalar?.()) { - return this.visitField(thing.getScalarField(), parameters); + // Propagate optional modifier from the field to visitField via parameters + // Avoid mutating the shared scalar descriptor + const scalarField = thing.getScalarField(); + return this.visitField(scalarField, { ...parameters, forceOptional: thing.isOptional() }); } else if (thing.isField?.()) { return this.visitField(thing, parameters); } else if (thing.isRelationship?.()) { @@ -247,7 +250,7 @@ class TypescriptVisitor { array = '[]'; } - if (field.isOptional()) { + if (field.isOptional() || parameters.forceOptional) { optional = '?'; } const isEnumRef = field.isPrimitive() ? false diff --git a/test/codegen/fromcto/typescript/typescriptvisitor.js b/test/codegen/fromcto/typescript/typescriptvisitor.js index 39fe80b9..e42a27d0 100644 --- a/test/codegen/fromcto/typescript/typescriptvisitor.js +++ b/test/codegen/fromcto/typescript/typescriptvisitor.js @@ -135,6 +135,39 @@ describe('TypescriptVisitor', function () { mockSpecialVisit.calledWith(thing, param).should.be.ok; }); + it('should propagate optional modifier for scalar fields via parameters', () => { + // Create a mock scalar field + let mockScalarField = sinon.createStubInstance(Field); + mockScalarField.isOptional.returns(false); // Scalar field itself is not optional + + // Create a thing that is a scalar type field + let thing = { + isModelManager: () => false, + isModelFile: () => false, + isEnum: () => false, + isClassDeclaration: () => false, + isMapDeclaration: () => false, + isTypeScalar: () => true, + isField: () => true, + isRelationship: () => false, + isEnumValue: () => false, + isOptional: () => true, // The parent field is optional + getScalarField: () => mockScalarField + }; + + let mockSpecialVisit = sinon.stub(typescriptVisitor, 'visitField'); + mockSpecialVisit.returns('Duck'); + + typescriptVisitor.visit(thing, param); + + // Verify that visitField was called with the scalar field + mockSpecialVisit.calledOnce.should.be.ok; + // Verify that forceOptional was passed via parameters (not by mutating the scalar field) + const callArgs = mockSpecialVisit.getCall(0).args; + callArgs[0].should.equal(mockScalarField); + callArgs[1].forceOptional.should.equal(true); + }); + it('should throw an error when an unrecognised type is supplied', () => { let thing = 'Something of unrecognised type';