From 85eb35facc2e9d2bf0516f4dcbc4b300c03ee752 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 04:29:02 +0000 Subject: [PATCH 1/2] Brace a member body that holds only comments or directives [patch] WriteBody chose a body's shape by counting non-blank lines, so a body of one line comment became an expression body ("void M() // note") and two or more comment or #pragma lines were spliced below the declaration with no braces. Either way the member had no body (CS1002). A body with no line of code is now wrapped in a braced block; one-line bodies with code still stay on the declaration line. Fixes ktsu-dev/CodeBlocker#157 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW3C2EQ5hMiLw63t4Aa --- CodeBlocker.Test/TemplateTests.cs | 65 ++++++++++++++++++++++ CodeBlocker/Templates/TemplateRendering.cs | 37 +++++++++++- 2 files changed, 101 insertions(+), 1 deletion(-) diff --git a/CodeBlocker.Test/TemplateTests.cs b/CodeBlocker.Test/TemplateTests.cs index cbe4b4a..fe47d2e 100644 --- a/CodeBlocker.Test/TemplateTests.cs +++ b/CodeBlocker.Test/TemplateTests.cs @@ -648,6 +648,71 @@ public void ABodyThatWritesOnlyBlankLinesRendersAsAnEmptyBlock(int newLines) Assert.AreEqual("public void M() { }\n", Render(method)); } + [TestMethod] + public void ABodyThatWritesOnlyALineCommentIsBraced() => + // "void M() // nothing to reset" has no body at all (CS1002). + Assert.AreEqual( + "void M()\n{\n\t// nothing to reset\n}\n", + Render(new MethodTemplate + { + Type = "void", + Name = "M", + BodyFactory = codeBlocker => codeBlocker.WriteLine("// nothing to reset"), + })); + + [TestMethod] + public void ABodyThatWritesOnlyLineCommentsIsBraced() => + Assert.AreEqual( + "void M()\n{\n\t// a\n\t// b\n}\n", + Render(new MethodTemplate + { + Type = "void", + Name = "M", + BodyFactory = codeBlocker => + { + codeBlocker.WriteLine("// a"); + codeBlocker.WriteLine("// b"); + }, + })); + + [TestMethod] + public void AConstructorBodyThatWritesOnlyALineCommentIsBraced() => + Assert.AreEqual( + "C()\n{\n\t// no state\n}\n", + Render(new ConstructorTemplate + { + Name = "C", + BodyFactory = codeBlocker => codeBlocker.WriteLine("// no state"), + })); + + [TestMethod] + public void ABodyThatWritesOnlyAnEmptyPragmaScopeIsBraced() => + Assert.AreEqual( + "void M()\n{\n\t#pragma warning disable CS0168\n\t#pragma warning restore CS0168\n}\n", + Render(new MethodTemplate + { + Type = "void", + Name = "M", + BodyFactory = codeBlocker => + { + using PragmaScope pragma = new(codeBlocker, "CS0168"); + }, + })); + + [TestMethod] + [DataRow("=> x;")] + [DataRow("{ }")] + [DataRow("=> x; // trailing")] + public void AOneLineBodyWithCodeStaysOnTheDeclarationLine(string body) => + Assert.AreEqual( + $"int M() {body}\n", + Render(new MethodTemplate + { + Type = "int", + Name = "M", + BodyFactory = codeBlocker => codeBlocker.WriteLine(body), + })); + [TestMethod] public void AnExpressionBodyBetweenBlankLinesStaysOnTheDeclarationLine() => Assert.AreEqual( diff --git a/CodeBlocker/Templates/TemplateRendering.cs b/CodeBlocker/Templates/TemplateRendering.cs index a2625bd..f1c329c 100644 --- a/CodeBlocker/Templates/TemplateRendering.cs +++ b/CodeBlocker/Templates/TemplateRendering.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2026 ktsu-dev contributors +// Copyright (c) 2023-2026 ktsu-dev contributors namespace ktsu.CodeBlocker.Templates; @@ -294,6 +294,20 @@ internal static void WriteBody(CodeBlocker codeBlocker, Action? bod return; } + // A comment or a directive is not a body either: written after the declaration it leaves the + // member with none, so a body made only of them is wrapped in braces instead. A generator + // writing "// no fields to reset" in place of an empty loop would otherwise emit CS1002. + if (!HasCode(lines)) + { + codeBlocker.WriteLine(); + using (new Scope(codeBlocker)) + { + SpliceFragment(codeBlocker, body); + } + + return; + } + if (contentLines.Length == 1) { // An expression body stays on the declaration line. @@ -372,6 +386,27 @@ internal static void WriteExpressionBody(CodeBlocker codeBlocker, Action + /// Reports whether any line of a fragment holds code: something other than a line comment or a + /// preprocessor directive. + /// + /// The lines of a fragment, as returns them. + /// if at least one line has code on it. + private static bool HasCode(string[] lines) + { + int[] codeEnds = FindCodeEnds(lines); + for (int i = 0; i < lines.Length; i++) + { + string code = lines[i][..codeEnds[i]].Trim(); + if (code.Length != 0 && code[0] != '#') + { + return true; + } + } + + return false; + } + /// /// Finds where the code on each line ends: at the start of the line comment the line ends in, /// or at the end of the line when it has none. From bfd5a9c1b67e06c33d3abcc631b0bb6245096d52 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 2 Nov 2026 04:30:23 +0000 Subject: [PATCH 2/2] Keep TemplateRendering.cs without a byte order mark Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW3C2EQ5hMiLw63t4Aa --- CodeBlocker/Templates/TemplateRendering.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeBlocker/Templates/TemplateRendering.cs b/CodeBlocker/Templates/TemplateRendering.cs index f1c329c..0405eb0 100644 --- a/CodeBlocker/Templates/TemplateRendering.cs +++ b/CodeBlocker/Templates/TemplateRendering.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2023-2026 ktsu-dev contributors +// Copyright (c) 2023-2026 ktsu-dev contributors namespace ktsu.CodeBlocker.Templates;