Added handling for modifiers in sealed classes

This commit is contained in:
Sergio Pedri
2021-07-21 02:32:09 +02:00
parent b744017678
commit b1fb60f331
3 changed files with 119 additions and 1 deletions
@@ -108,6 +108,16 @@ namespace Microsoft.Toolkit.Mvvm.SourceGenerators
ClassDeclarationSyntax sourceDeclaration = sourceSyntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().First();
UsingDirectiveSyntax[] usingDirectives = sourceSyntaxTree.GetRoot().DescendantNodes().OfType<UsingDirectiveSyntax>().ToArray();
IEnumerable<MemberDeclarationSyntax> generatedMembers = FilterDeclaredMembers(context, attributeData, classDeclaration, classDeclarationSymbol, sourceDeclaration);
// If the target class is sealed, make protected members private and remove the virtual modifier
if (classDeclarationSymbol.IsSealed)
{
generatedMembers = generatedMembers.Select(static member => member
.ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword)
.RemoveModifier(SyntaxKind.VirtualKeyword));
}
// Create the class declaration for the user type. This will produce a tree as follows:
//
// <MODIFIERS> <CLASS_NAME> : <BASE_TYPES>
@@ -118,7 +128,7 @@ namespace Microsoft.Toolkit.Mvvm.SourceGenerators
ClassDeclaration(classDeclaration.Identifier.Text)
.WithModifiers(classDeclaration.Modifiers)
.WithBaseList(sourceDeclaration.BaseList)
.AddMembers(FilterDeclaredMembers(context, attributeData, classDeclaration, classDeclarationSymbol, sourceDeclaration).ToArray());
.AddMembers(generatedMembers.ToArray());
TypeDeclarationSyntax typeDeclarationSyntax = classDeclarationSyntax;
@@ -0,0 +1,57 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics.Contracts;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace Microsoft.Toolkit.Mvvm.SourceGenerators.Extensions
{
/// <summary>
/// Extension methods for the <see cref="MemberDeclarationSyntax"/> type.
/// </summary>
internal static class MemberDeclarationSyntaxExtensions
{
/// <summary>
/// Replaces a specific modifier.
/// </summary>
/// <param name="memberDeclaration">The input <see cref="MemberDeclarationSyntax"/> instance.</param>
/// <param name="oldKind">The target modifier kind to replace.</param>
/// <param name="newKind">The new modifier kind to add or replace.</param>
/// <returns>A <see cref="MemberDeclarationSyntax"/> instance with the target modifier.</returns>
[Pure]
public static MemberDeclarationSyntax ReplaceModifier(this MemberDeclarationSyntax memberDeclaration, SyntaxKind oldKind, SyntaxKind newKind)
{
int index = memberDeclaration.Modifiers.IndexOf(oldKind);
if (index != -1)
{
return memberDeclaration.WithModifiers(memberDeclaration.Modifiers.Replace(memberDeclaration.Modifiers[index], Token(newKind)));
}
return memberDeclaration;
}
/// <summary>
/// Removes a specific modifier.
/// </summary>
/// <param name="memberDeclaration">The input <see cref="MemberDeclarationSyntax"/> instance.</param>
/// <param name="kind">The modifier kind to remove.</param>
/// <returns>A <see cref="MemberDeclarationSyntax"/> instance without the specified modifier.</returns>
[Pure]
public static MemberDeclarationSyntax RemoveModifier(this MemberDeclarationSyntax memberDeclaration, SyntaxKind kind)
{
int index = memberDeclaration.Modifiers.IndexOf(kind);
if (index != -1)
{
return memberDeclaration.WithModifiers(memberDeclaration.Modifiers.RemoveAt(index));
}
return memberDeclaration;
}
}
}
@@ -50,6 +50,45 @@ namespace UnitTests.Mvvm
Assert.AreEqual(changed.Item2, 42);
}
[TestCategory("Mvvm")]
[TestMethod]
public void Test_ObservableObjectAttribute_OnSealedClass_Events()
{
var model = new SampleModelSealed();
(PropertyChangingEventArgs, int) changing = default;
(PropertyChangedEventArgs, int) changed = default;
model.PropertyChanging += (s, e) =>
{
Assert.IsNull(changing.Item1);
Assert.IsNull(changed.Item1);
Assert.AreSame(model, s);
Assert.IsNotNull(s);
Assert.IsNotNull(e);
changing = (e, model.Data);
};
model.PropertyChanged += (s, e) =>
{
Assert.IsNotNull(changing.Item1);
Assert.IsNull(changed.Item1);
Assert.AreSame(model, s);
Assert.IsNotNull(s);
Assert.IsNotNull(e);
changed = (e, model.Data);
};
model.Data = 42;
Assert.AreEqual(changing.Item1?.PropertyName, nameof(SampleModelSealed.Data));
Assert.AreEqual(changing.Item2, 0);
Assert.AreEqual(changed.Item1?.PropertyName, nameof(SampleModelSealed.Data));
Assert.AreEqual(changed.Item2, 42);
}
[ObservableObject]
public partial class SampleModel
{
@@ -61,5 +100,17 @@ namespace UnitTests.Mvvm
set => SetProperty(ref data, value);
}
}
[ObservableObject]
public sealed partial class SampleModelSealed
{
private int data;
public int Data
{
get => data;
set => SetProperty(ref data, value);
}
}
}
}