Skip to content

Commit df714ea

Browse files
committed
Add support for 'get;' accessors too
1 parent da89014 commit df714ea

File tree

2 files changed

+83
-0
lines changed

2 files changed

+83
-0
lines changed

src/CommunityToolkit.Mvvm.SourceGenerators/Diagnostics/Analyzers/UseObservablePropertyOnSemiAutoPropertyAnalyzer.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.Linq;
1111
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
1212
using Microsoft.CodeAnalysis;
13+
using Microsoft.CodeAnalysis.CSharp;
1314
using Microsoft.CodeAnalysis.CSharp.Syntax;
1415
using Microsoft.CodeAnalysis.Diagnostics;
1516
using Microsoft.CodeAnalysis.Operations;
@@ -201,6 +202,35 @@ public override void Initialize(AnalysisContext context)
201202
}
202203
});
203204

205+
// We also need to track getters which have no body, and we need syntax for that
206+
context.RegisterSyntaxNodeAction(context =>
207+
{
208+
// Let's just make sure we do have a property symbol
209+
if (context.ContainingSymbol is not IPropertySymbol { GetMethod: not null } propertySymbol)
210+
{
211+
return;
212+
}
213+
214+
// Lookup the property to get its flags
215+
if (!propertyMap.TryGetValue(propertySymbol, out bool[]? validFlags))
216+
{
217+
return;
218+
}
219+
220+
// We expect two accessors, skip if otherwise (the setter will be validated by the other callback)
221+
if (context.Node is not PropertyDeclarationSyntax { AccessorList.Accessors: [{ } firstAccessor, { } secondAccessor] })
222+
{
223+
return;
224+
}
225+
226+
// Check that either of them is a semicolon token 'get;' accessor (it can be in either position)
227+
if (firstAccessor.IsKind(SyntaxKind.GetAccessorDeclaration) && firstAccessor.SemicolonToken.IsKind(SyntaxKind.SemicolonToken) ||
228+
secondAccessor.IsKind(SyntaxKind.GetAccessorDeclaration) && secondAccessor.SemicolonToken.IsKind(SyntaxKind.SemicolonToken))
229+
{
230+
validFlags[0] = true;
231+
}
232+
}, SyntaxKind.PropertyDeclaration);
233+
204234
// Finally, we can consume this information when we finish processing the symbol
205235
context.RegisterSymbolEndAction(context =>
206236
{

tests/CommunityToolkit.Mvvm.SourceGenerators.Roslyn4120.UnitTests/Test_UseObservablePropertyOnSemiAutoPropertyCodeFixer.cs

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,59 @@ public partial class SampleViewModel : ObservableObject
7474
await test.RunAsync();
7575
}
7676

77+
[TestMethod]
78+
public async Task SimpleProperty_WithSemicolonTokenGetAccessor()
79+
{
80+
string original = """
81+
using CommunityToolkit.Mvvm.ComponentModel;
82+
83+
namespace MyApp;
84+
85+
public class SampleViewModel : ObservableObject
86+
{
87+
public string Name
88+
{
89+
get;
90+
set => SetProperty(ref field, value);
91+
}
92+
}
93+
""";
94+
95+
string @fixed = """
96+
using CommunityToolkit.Mvvm.ComponentModel;
97+
98+
namespace MyApp;
99+
100+
public partial class SampleViewModel : ObservableObject
101+
{
102+
[ObservableProperty]
103+
public partial string Name { get; set; }
104+
}
105+
""";
106+
107+
CSharpCodeFixTest test = new(LanguageVersion.Preview)
108+
{
109+
TestCode = original,
110+
FixedCode = @fixed,
111+
ReferenceAssemblies = ReferenceAssemblies.Net.Net80,
112+
};
113+
114+
test.TestState.AdditionalReferences.Add(typeof(ObservableObject).Assembly);
115+
test.ExpectedDiagnostics.AddRange(new[]
116+
{
117+
// /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)
118+
CSharpCodeFixVerifier.Diagnostic().WithSpan(7, 19, 7, 23).WithArguments("MyApp.SampleViewModel", "Name"),
119+
});
120+
121+
test.FixedState.ExpectedDiagnostics.AddRange(new[]
122+
{
123+
// /0/Test0.cs(8,27): error CS9248: Partial property 'SampleViewModel.Name' must have an implementation part.
124+
DiagnosticResult.CompilerError("CS9248").WithSpan(8, 27, 8, 31).WithArguments("MyApp.SampleViewModel.Name"),
125+
});
126+
127+
await test.RunAsync();
128+
}
129+
77130
[TestMethod]
78131
public async Task SimpleProperty_WithLeadingTrivia()
79132
{

0 commit comments

Comments
 (0)