diff --git a/CodeBlocker.Test/TemplateTests.cs b/CodeBlocker.Test/TemplateTests.cs index 3fd8237..384105a 100644 --- a/CodeBlocker.Test/TemplateTests.cs +++ b/CodeBlocker.Test/TemplateTests.cs @@ -530,6 +530,48 @@ public void AnExpressionBodyOfOnlyBlankLinesIsRejectedBeforeAnythingIsWritten() Assert.AreEqual("int a;\n", codeBlocker.ToString()); } + [TestMethod] + public void AnExpressionBodyWithADefaultValueIsRejectedBeforeAnythingIsWritten() + { + // It used to render "int P => x;" and drop the "= 5" without a word. + PropertyTemplate property = new() + { + Type = "int", + Name = "P", + DefaultValue = "5", + ExpressionBodyFactory = codeBlocker => codeBlocker.Write("x"), + }; + + using CodeBlocker codeBlocker = CodeBlocker.Create(CodeBlocker.DefaultIndentString, NewLines.Lf); + codeBlocker.WriteLine("int a;"); + + InvalidOperationException exception = + Assert.ThrowsExactly(() => property.WriteTo(codeBlocker)); + Assert.Contains("'P'", exception.Message); + Assert.AreEqual("int a;\n", codeBlocker.ToString()); + } + + [TestMethod] + public void AClassWithAnExpressionBodiedPropertyThatHasADefaultValueWritesNothing() + { + ClassTemplate type = new() { Name = "C" }; + type.Members.Add(new FieldTemplate { Name = "a", Type = "int" }); + type.Members.Add(new PropertyTemplate + { + Type = "int", + Name = "P", + DefaultValue = "5", + ExpressionBodyFactory = codeBlocker => codeBlocker.Write("x"), + }); + + using CodeBlocker codeBlocker = CodeBlocker.Create(CodeBlocker.DefaultIndentString, NewLines.Lf); + + InvalidOperationException exception = + Assert.ThrowsExactly(() => codeBlocker.AddClass(type)); + Assert.Contains("'P'", exception.Message); + Assert.AreEqual(string.Empty, codeBlocker.ToString()); + } + private static string RenderExpressionBody(Action expressionFactory) => Render(new PropertyTemplate { Type = "int", Name = "X", Keywords = { "public" }, ExpressionBodyFactory = expressionFactory }); diff --git a/CodeBlocker/Templates/PropertyTemplate.cs b/CodeBlocker/Templates/PropertyTemplate.cs index 471ec0a..cca793d 100644 --- a/CodeBlocker/Templates/PropertyTemplate.cs +++ b/CodeBlocker/Templates/PropertyTemplate.cs @@ -48,7 +48,8 @@ public class PropertyTemplate : MemberTemplate /// is . /// /// , and are all - /// ; or, with no expression body, an accessor's kind is unknown, or is + /// ; or is set together with a + /// ; or, with no expression body, an accessor's kind is unknown, or is /// with no ; or /// the expression body, or an expression accessor's body, writes no expression. Nothing is /// written in any of these cases. @@ -98,6 +99,16 @@ internal override void Validate() "Setter, or ExpressionBodyFactory."); } + if (ExpressionBodyFactory is not null && HasDefaultValue) + { + // C# has no initialiser on an expression-bodied property, so writing either one would + // silently drop the other. + throw new InvalidOperationException( + $"Property '{Name}' has both an expression body and a default value. An " + + "expression-bodied property cannot have an initialiser; clear ExpressionBodyFactory " + + "or DefaultValue."); + } + if (ExpressionBodyFactory is null) { // Likewise checked up front: the accessor list is only reached after "Type Name {".