From e635084535a482c414fd6af39f5962d04b403271 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 12 Dec 2024 06:07:17 +0100 Subject: [PATCH 1/2] More tests for semi auto prop analyzer --- ...ablePropertyOnSemiAutoPropertyCodeFixer.cs | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_UseObservablePropertyOnSemiAutoPropertyCodeFixer.cs b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_UseObservablePropertyOnSemiAutoPropertyCodeFixer.cs index e9cccaa91..bc2c589f8 100644 --- a/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_UseObservablePropertyOnSemiAutoPropertyCodeFixer.cs +++ b/tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_UseObservablePropertyOnSemiAutoPropertyCodeFixer.cs @@ -74,6 +74,123 @@ public partial class SampleViewModel : ObservableObject await test.RunAsync(); } + [TestMethod] + public async Task SimplePropertyWithBlockAccessorSyntax() + { + string original = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp; + + public class SampleViewModel : ObservableObject + { + public string Name + { + get + { + return field; + } + set + { + SetProperty(ref field, value); + } + } + } + """; + + string @fixed = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp; + + public partial class SampleViewModel : ObservableObject + { + [ObservableProperty] + public partial string Name { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }; + + test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); + test.ExpectedDiagnostics.AddRange(new[] + { + // /0/Test0.cs(7,19): info MVVMTK0056: The semi-auto property MyApp.SampleViewModel.Name can be converted to a partial property using [ObservableProperty], which is recommended (doing so makes the code less verbose and results in more optimized code) + CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 19, 7, 23).WithArguments("MyApp.SampleViewModel", "Name"), + }); + + test.FixedState.ExpectedDiagnostics.AddRange(new[] + { + // /0/Test0.cs(8,27): error CS9248: Partial property 'SampleViewModel.Name' must have an implementation part. + DiagnosticResult.CompilerError("CS9248").WithSpan(8, 27, 8, 31).WithArguments("MyApp.SampleViewModel.Name"), + }); + + await test.RunAsync(); + } + + [TestMethod] + public async Task SimplePropertyWithNestedBlockSyntax() + { + string original = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp; + + public class SampleViewModel : ObservableObject + { + public string Name + { + get + { + { + return field; + } + } + set => SetProperty(ref field, value); + } + } + """; + + string @fixed = """ + using CommunityToolkit.Mvvm.ComponentModel; + + namespace MyApp; + + public partial class SampleViewModel : ObservableObject + { + [ObservableProperty] + public partial string Name { get; set; } + } + """; + + CSharpCodeFixTest test = new(LanguageVersion.Preview) + { + TestCode = original, + FixedCode = @fixed, + ReferenceAssemblies = ReferenceAssemblies.Net.Net80, + }; + + test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly); + test.ExpectedDiagnostics.AddRange(new[] + { + // /0/Test0.cs(7,19): info MVVMTK0056: The semi-auto property MyApp.SampleViewModel.Name can be converted to a partial property using [ObservableProperty], which is recommended (doing so makes the code less verbose and results in more optimized code) + CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 19, 7, 23).WithArguments("MyApp.SampleViewModel", "Name"), + }); + + test.FixedState.ExpectedDiagnostics.AddRange(new[] + { + // /0/Test0.cs(8,27): error CS9248: Partial property 'SampleViewModel.Name' must have an implementation part. + DiagnosticResult.CompilerError("CS9248").WithSpan(8, 27, 8, 31).WithArguments("MyApp.SampleViewModel.Name"), + }); + + await test.RunAsync(); + } + [TestMethod] public async Task SimpleProperty_WithSemicolonTokenGetAccessor() { From b3c0266aec32de01ef95aca9b17505efb4731288 Mon Sep 17 00:00:00 2001 From: Sergio Pedri Date: Mon, 24 Nov 2025 15:00:52 -0800 Subject: [PATCH 2/2] Handle nested block operations in analyzer Adds support for analyzing nested block operations in 'get' accessors, improving compatibility with code generated by other tooling. The analyzer now correctly identifies return operations within nested blocks. --- ...UseObservablePropertyOnSemiAutoPropertyAnalyzer.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnSemiAutoPropertyAnalyzer.cs b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnSemiAutoPropertyAnalyzer.cs index 18155958a..bcdacd2a8 100644 --- a/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnSemiAutoPropertyAnalyzer.cs +++ b/src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnSemiAutoPropertyAnalyzer.cs @@ -148,7 +148,16 @@ public override void Initialize(AnalysisContext context) // We expect a top-level block operation, that immediately returns an expression if (context.OperationBlocks is not [IBlockOperation { Operations: [IReturnOperation returnOperation] }]) { - return; + // Special case for nested blocks (e.g. for code generated by some other tooling) + if (context.OperationBlocks is [IBlockOperation { Operations: [IBlockOperation { Operations: [IReturnOperation nestedReturnOperation] }] }]) + { + returnOperation = nestedReturnOperation; + } + else + { + // Invalid 'get' accessor structure, we have to stop here + return; + } } // Next, we expect the return to produce a field reference