Skip to content

Commit

Permalink
Add support for 'get;' accessors too
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergio0694 committed Dec 11, 2024
1 parent da89014 commit df714ea
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Linq;
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
Expand Down Expand Up @@ -201,6 +202,35 @@ public override void Initialize(AnalysisContext context)
}
});

// We also need to track getters which have no body, and we need syntax for that
context.RegisterSyntaxNodeAction(context =>
{
// Let's just make sure we do have a property symbol
if (context.ContainingSymbol is not IPropertySymbol { GetMethod: not null } propertySymbol)
{
return;
}

// Lookup the property to get its flags
if (!propertyMap.TryGetValue(propertySymbol, out bool[]? validFlags))
{
return;
}

// We expect two accessors, skip if otherwise (the setter will be validated by the other callback)
if (context.Node is not PropertyDeclarationSyntax { AccessorList.Accessors: [{ } firstAccessor, { } secondAccessor] })
{
return;
}

// Check that either of them is a semicolon token 'get;' accessor (it can be in either position)
if (firstAccessor.IsKind(SyntaxKind.GetAccessorDeclaration) && firstAccessor.SemicolonToken.IsKind(SyntaxKind.SemicolonToken) ||
secondAccessor.IsKind(SyntaxKind.GetAccessorDeclaration) && secondAccessor.SemicolonToken.IsKind(SyntaxKind.SemicolonToken))
{
validFlags[0] = true;
}
}, SyntaxKind.PropertyDeclaration);

// Finally, we can consume this information when we finish processing the symbol
context.RegisterSymbolEndAction(context =>
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,59 @@ public partial class SampleViewModel : ObservableObject
await test.RunAsync();
}

[TestMethod]
public async Task SimpleProperty_WithSemicolonTokenGetAccessor()
{
string original = """
using CommunityToolkit.Mvvm.ComponentModel;
namespace MyApp;
public class SampleViewModel : ObservableObject
{
public string Name
{
get;
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_WithLeadingTrivia()
{
Expand Down

0 comments on commit df714ea

Please sign in to comment.