From 1cd6f2ea178d590db3a7a3d9122cf7a463412698 Mon Sep 17 00:00:00 2001 From: Steve Dunn Date: Sun, 10 May 2026 18:26:49 +0100 Subject: [PATCH] fix: handle semicolon-bodied types when adding methods - Add regression test for semicolon-bodied types. - Adjust `AddMethodCodeFixProvider` to convert semicolon-bodied types to braces before adding members. Fixes #742 --- .../AddMethodCodeFixProvider.cs | 12 +++- .../AddMethodCodeFixProvider.cs | 12 +++- .../ValidationMethodAnalyzerTests.cs | 55 +++++++++++++++++++ 3 files changed, 77 insertions(+), 2 deletions(-) diff --git a/src/Vogen.CodeFixers/Rules/NormalizeInputMethodFixers/AddMethodCodeFixProvider.cs b/src/Vogen.CodeFixers/Rules/NormalizeInputMethodFixers/AddMethodCodeFixProvider.cs index 3d8996e0311..0b2ade1d582 100644 --- a/src/Vogen.CodeFixers/Rules/NormalizeInputMethodFixers/AddMethodCodeFixProvider.cs +++ b/src/Vogen.CodeFixers/Rules/NormalizeInputMethodFixers/AddMethodCodeFixProvider.cs @@ -60,7 +60,17 @@ private static async Task GenerateMethodAsync(Document document, var newMember = GenerateMethod(returnType); - var newTypeDecl = typeDecl.AddMembers(newMember); + // If the type has no body (ends with ';'), convert it to have braces before adding members, + // otherwise AddMembers produces malformed output. + TypeDeclarationSyntax typeDeclWithBody = typeDecl.OpenBraceToken.IsKind(SyntaxKind.None) + ? typeDecl + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken)) + .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken)) + .WithAdditionalAnnotations(Formatter.Annotation) + : typeDecl; + + var newTypeDecl = typeDeclWithBody.AddMembers(newMember); var newRoot = root.ReplaceNode(typeDecl, newTypeDecl); diff --git a/src/Vogen.CodeFixers/Rules/ValidateMethodFixers/AddMethodCodeFixProvider.cs b/src/Vogen.CodeFixers/Rules/ValidateMethodFixers/AddMethodCodeFixProvider.cs index 5ca15d3ae04..01a7e40383f 100644 --- a/src/Vogen.CodeFixers/Rules/ValidateMethodFixers/AddMethodCodeFixProvider.cs +++ b/src/Vogen.CodeFixers/Rules/ValidateMethodFixers/AddMethodCodeFixProvider.cs @@ -60,7 +60,17 @@ private static async Task GenerateValidationMethodAsync(Document docum var newMember = GenerateMethod(returnType); - var newTypeDecl = typeDecl.AddMembers(newMember); + // If the type has no body (ends with ';'), convert it to have braces before adding members, + // otherwise AddMembers produces malformed output. + TypeDeclarationSyntax typeDeclWithBody = typeDecl.OpenBraceToken.IsKind(SyntaxKind.None) + ? typeDecl + .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.None)) + .WithOpenBraceToken(SyntaxFactory.Token(SyntaxKind.OpenBraceToken)) + .WithCloseBraceToken(SyntaxFactory.Token(SyntaxKind.CloseBraceToken)) + .WithAdditionalAnnotations(Formatter.Annotation) + : typeDecl; + + var newTypeDecl = typeDeclWithBody.AddMembers(newMember); var newRoot = root.ReplaceNode(typeDecl, newTypeDecl); diff --git a/tests/AnalyzerTests/ValidationMethodAnalysis/ValidationMethodAnalyzerTests.cs b/tests/AnalyzerTests/ValidationMethodAnalysis/ValidationMethodAnalyzerTests.cs index 54265135559..2abcf9529c0 100644 --- a/tests/AnalyzerTests/ValidationMethodAnalysis/ValidationMethodAnalyzerTests.cs +++ b/tests/AnalyzerTests/ValidationMethodAnalysis/ValidationMethodAnalyzerTests.cs @@ -301,6 +301,61 @@ private static Validation Validate(int input) await test.RunAsync(); } + // Regression test for https://github.com/SteveDunn/Vogen/issues/742 + [Fact] + public async Task Triggered_when_method_is_missing_and_type_has_semicolon_body() + { + var input = LineEndingsHelper.Normalize( + """ + using System; + using Vogen; + + namespace ConsoleApplication1; + + [ValueObject] + public readonly partial struct {|#0:RedemptionId|}; + """); + + var expectedOutput = LineEndingsHelper.Normalize( + """ + using System; + using Vogen; + + namespace ConsoleApplication1; + + [ValueObject] + public readonly partial struct RedemptionId + { + private static Validation Validate(string input) + { + bool isValid = true; // todo: your validation + return isValid ? Validation.Ok : Validation.Invalid("[todo: describe the validation]"); + } + } + """); + + var expectedDiagnostic = + VerifyCS.Diagnostic("AddValidationMethod").WithSeverity(DiagnosticSeverity.Info).WithLocation(0).WithArguments("RedemptionId"); + + var test = new VerifyCS.Test + { + TestState = + { + Sources = { input }, + ReferenceAssemblies = References.Net90AndOurs.Value + }, + + CompilerDiagnostics = CompilerDiagnostics.Suggestions, + ReferenceAssemblies = References.Net90AndOurs.Value, + FixedCode = expectedOutput, + ExpectedDiagnostics = { expectedDiagnostic }, + }; + + test.DisabledDiagnostics.Add("CS1591"); + + await test.RunAsync(); + } + //Diagnostic and CodeFix both triggered and checked for [Fact] public async Task Trigged_for_missing_method_and_vo_is_decorated_with_generic_attribute()