Merge pull request #1025 from Youssef1313/patch-1

Support nested blocks in semi-auto property analyzer, add tests
This commit is contained in:
Sergio Pedri
2025-11-24 22:10:03 -08:00
committed by GitHub
2 changed files with 127 additions and 1 deletions
@@ -148,7 +148,16 @@ public sealed class UseObservablePropertyOnSemiAutoPropertyAnalyzer : Diagnostic
// 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
@@ -74,6 +74,123 @@ public class Test_UseObservablePropertyOnSemiAutoPropertyCodeFixer
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()
{