Skip to content

Commit 9474591

Browse files
committed
Centralize handling of sealed target types
1 parent 164a8d9 commit 9474591

9 files changed

+215
-80
lines changed

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ public INotifyPropertyChangedGenerator()
3333
protected override INotifyPropertyChangedInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
3434
{
3535
bool includeAdditionalHelperMethods = attributeData.GetNamedArgument<bool>("IncludeAdditionalHelperMethods", true);
36-
bool isSealed = typeSymbol.IsSealed;
3736

38-
return new(includeAdditionalHelperMethods, isSealed);
37+
return new(includeAdditionalHelperMethods);
3938
}
4039

4140
/// <inheritdoc/>
@@ -59,28 +58,16 @@ protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, INotifyP
5958
}
6059

6160
/// <inheritdoc/>
62-
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(INotifyPropertyChangedInfo info, ClassDeclarationSyntax classDeclaration)
61+
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(INotifyPropertyChangedInfo info, ImmutableArray<MemberDeclarationSyntax> memberDeclarations)
6362
{
64-
IEnumerable<MemberDeclarationSyntax> memberDeclarations;
65-
6663
// If requested, only include the event and the basic methods to raise it, but not the additional helpers
67-
if (info.IncludeAdditionalHelperMethods)
68-
{
69-
memberDeclarations = classDeclaration.Members;
70-
}
71-
else
64+
if (!info.IncludeAdditionalHelperMethods)
7265
{
73-
memberDeclarations = classDeclaration.Members.Where(static member => member
66+
return memberDeclarations.Where(static member => member
7467
is EventFieldDeclarationSyntax
75-
or MethodDeclarationSyntax { Identifier.ValueText: "OnPropertyChanged" });
68+
or MethodDeclarationSyntax { Identifier.ValueText: "OnPropertyChanged" }).ToImmutableArray();
7669
}
7770

78-
// If the target class is sealed, make protected members private and remove the virtual modifier
79-
return
80-
memberDeclarations
81-
.Select(static member => member
82-
.ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword)
83-
.RemoveModifier(SyntaxKind.VirtualKeyword))
84-
.ToImmutableArray();
71+
return memberDeclarations;
8572
}
8673
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/INotifyPropertyChangedInfo.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.Input.Models;
88
/// A model with gathered info on a given <c>INotifyPropertyChangedAttribute</c> instance.
99
/// </summary>
1010
/// <param name="IncludeAdditionalHelperMethods">Whether to also generate helper methods in the target type.</param>
11-
/// <param name="IsSealed">Whether the target type is sealed.</param>
12-
public sealed record INotifyPropertyChangedInfo(bool IncludeAdditionalHelperMethods, bool IsSealed);
11+
public sealed record INotifyPropertyChangedInfo(bool IncludeAdditionalHelperMethods);

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/Models/ObservableRecipientInfo.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,9 @@ namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
1010
/// <param name="TypeName">The type name of the type being annotated.</param>
1111
/// <param name="HasExplicitConstructors">Whether or not the target type has explicit constructors.</param>
1212
/// <param name="IsAbstract">Whether or not the target type is abstract.</param>
13-
/// <param name="IsSealed">Whether the target type is sealed.</param>
1413
/// <param name="IsObservableValidator">Whether or not the target type inherits from <c>ObservableValidator</c>.</param>
1514
public sealed record ObservableRecipientInfo(
1615
string TypeName,
1716
bool HasExplicitConstructors,
1817
bool IsAbstract,
19-
bool IsSealed,
2018
bool IsObservableValidator);

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace CommunityToolkit.Mvvm.SourceGenerators;
1717
/// A source generator for the <c>ObservableObjectAttribute</c> type.
1818
/// </summary>
1919
[Generator(LanguageNames.CSharp)]
20-
public sealed class ObservableObjectGenerator : TransitiveMembersGenerator<bool>
20+
public sealed class ObservableObjectGenerator : TransitiveMembersGenerator<object?>
2121
{
2222
/// <summary>
2323
/// Initializes a new instance of the <see cref="ObservableObjectGenerator"/> class.
@@ -28,13 +28,13 @@ public ObservableObjectGenerator()
2828
}
2929

3030
/// <inheritdoc/>
31-
protected override bool GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
31+
protected override object? GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
3232
{
33-
return typeSymbol.IsSealed;
33+
return null;
3434
}
3535

3636
/// <inheritdoc/>
37-
protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, bool info, out ImmutableArray<Diagnostic> diagnostics)
37+
protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, object? info, out ImmutableArray<Diagnostic> diagnostics)
3838
{
3939
ImmutableArray<Diagnostic>.Builder builder = ImmutableArray.CreateBuilder<Diagnostic>();
4040

@@ -64,19 +64,8 @@ protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, bool inf
6464
}
6565

6666
/// <inheritdoc/>
67-
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(bool info, ClassDeclarationSyntax classDeclaration)
67+
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(object? info, ImmutableArray<MemberDeclarationSyntax> memberDeclarations)
6868
{
69-
// If the target class is sealed, make protected members private and remove the virtual modifier
70-
if (info)
71-
{
72-
return
73-
classDeclaration.Members
74-
.Select(static member => member
75-
.ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword)
76-
.RemoveModifier(SyntaxKind.VirtualKeyword))
77-
.ToImmutableArray();
78-
}
79-
80-
return classDeclaration.Members.ToImmutableArray();
69+
return memberDeclarations;
8170
}
8271
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -35,14 +35,12 @@ protected override ObservableRecipientInfo GetInfo(INamedTypeSymbol typeSymbol,
3535
string typeName = typeSymbol.Name;
3636
bool hasExplicitConstructors = !(typeSymbol.InstanceConstructors.Length == 1 && typeSymbol.InstanceConstructors[0] is { Parameters.IsEmpty: true, IsImplicitlyDeclared: true });
3737
bool isAbstract = typeSymbol.IsAbstract;
38-
bool isSealed = typeSymbol.IsSealed;
3938
bool isObservableValidator = typeSymbol.InheritsFrom("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator");
4039

4140
return new(
4241
typeName,
4342
hasExplicitConstructors,
4443
isAbstract,
45-
isSealed,
4644
isObservableValidator);
4745
}
4846

@@ -82,14 +80,14 @@ protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, Observab
8280
}
8381

8482
/// <inheritdoc/>
85-
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(ObservableRecipientInfo info, ClassDeclarationSyntax classDeclaration)
83+
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(ObservableRecipientInfo info, ImmutableArray<MemberDeclarationSyntax> memberDeclarations)
8684
{
8785
ImmutableArray<MemberDeclarationSyntax>.Builder builder = ImmutableArray.CreateBuilder<MemberDeclarationSyntax>();
8886

8987
// If the target type has no constructors, generate constructors as well
9088
if (!info.HasExplicitConstructors)
9189
{
92-
foreach (ConstructorDeclarationSyntax ctor in classDeclaration.Members.OfType<ConstructorDeclarationSyntax>())
90+
foreach (ConstructorDeclarationSyntax ctor in memberDeclarations.OfType<ConstructorDeclarationSyntax>())
9391
{
9492
string text = ctor.NormalizeWhitespace().ToFullString();
9593
string replaced = text.Replace("ObservableRecipient", info.TypeName);
@@ -108,7 +106,7 @@ protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers
108106
// Skip the SetProperty overloads if the target type inherits from ObservableValidator, to avoid conflicts
109107
if (info.IsObservableValidator)
110108
{
111-
foreach (MemberDeclarationSyntax member in classDeclaration.Members.Where(static member => member is not ConstructorDeclarationSyntax))
109+
foreach (MemberDeclarationSyntax member in memberDeclarations.Where(static member => member is not ConstructorDeclarationSyntax))
112110
{
113111
if (member is not MethodDeclarationSyntax { Identifier.ValueText: "SetProperty" })
114112
{
@@ -120,22 +118,11 @@ protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers
120118
}
121119

122120
// If the target type has at least one custom constructor, only generate methods
123-
foreach (MemberDeclarationSyntax member in classDeclaration.Members.Where(static member => member is not ConstructorDeclarationSyntax))
121+
foreach (MemberDeclarationSyntax member in memberDeclarations.Where(static member => member is not ConstructorDeclarationSyntax))
124122
{
125123
builder.Add(member);
126124
}
127125

128-
// If the target class is sealed, make protected members private and remove the virtual modifier
129-
if (info.IsSealed)
130-
{
131-
return
132-
builder
133-
.Select(static member => member
134-
.ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword)
135-
.RemoveModifier(SyntaxKind.VirtualKeyword))
136-
.ToImmutableArray();
137-
}
138-
139126
return builder.ToImmutable();
140127
}
141128
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System;
6+
using System.Collections.Immutable;
7+
using System.IO;
8+
using System.Linq;
9+
using System.Reflection;
10+
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
11+
using Microsoft.CodeAnalysis;
12+
using Microsoft.CodeAnalysis.CSharp;
13+
using Microsoft.CodeAnalysis.CSharp.Syntax;
14+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
15+
16+
namespace CommunityToolkit.Mvvm.SourceGenerators;
17+
18+
/// <inheritdoc/>
19+
partial class TransitiveMembersGenerator<TInfo>
20+
{
21+
/// <summary>
22+
/// A container for all the logic for <see cref="TransitiveMembersGenerator{TInfo}"/>.
23+
/// </summary>
24+
internal static class Execute
25+
{
26+
/// <summary>
27+
/// Loads the source <see cref="ClassDeclarationSyntax"/> instance to get member declarations from.
28+
/// </summary>
29+
/// <param name="attributeType">The fully qualified name of the attribute type to look for.</param>
30+
/// <returns>The source <see cref="ClassDeclarationSyntax"/> instance to get member declarations from.</returns>
31+
public static ClassDeclarationSyntax LoadClassDeclaration(string attributeType)
32+
{
33+
string attributeTypeName = attributeType.Split('.').Last();
34+
string filename = $"CommunityToolkit.Mvvm.SourceGenerators.EmbeddedResources.{attributeTypeName.Replace("Attribute", string.Empty)}.cs";
35+
36+
using Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename);
37+
using StreamReader reader = new(stream);
38+
39+
string observableObjectSource = reader.ReadToEnd();
40+
SyntaxTree syntaxTree = CSharpSyntaxTree.ParseText(observableObjectSource);
41+
42+
return syntaxTree.GetRoot().DescendantNodes().OfType<ClassDeclarationSyntax>().First();
43+
}
44+
45+
/// <summary>
46+
/// Processes the sequence of member declarations to generate.
47+
/// </summary>
48+
/// <param name="generatorType">The type of generator being used.</param>
49+
/// <param name="memberDeclarations">The input sequence of member declarations to generate.</param>
50+
/// <param name="sealedMemberDeclarations">The resulting sequence of member declarations for sealed types.</param>
51+
/// <param name="nonSealedMemberDeclarations">The resulting sequence of member declarations for non sealed types.</param>
52+
public static void ProcessMemberDeclarations(
53+
Type generatorType,
54+
ImmutableArray<MemberDeclarationSyntax> memberDeclarations,
55+
out ImmutableArray<MemberDeclarationSyntax> sealedMemberDeclarations,
56+
out ImmutableArray<MemberDeclarationSyntax> nonSealedMemberDeclarations)
57+
{
58+
ImmutableArray<MemberDeclarationSyntax> annotatedMemberDeclarations = memberDeclarations.Select(member =>
59+
{
60+
// [GeneratedCode] is always present
61+
member =
62+
member
63+
.WithoutLeadingTrivia()
64+
.AddAttributeLists(AttributeList(SingletonSeparatedList(
65+
Attribute(IdentifierName($"global::System.CodeDom.Compiler.GeneratedCode"))
66+
.AddArgumentListArguments(
67+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(generatorType.FullName))),
68+
AttributeArgument(LiteralExpression(SyntaxKind.StringLiteralExpression, Literal(generatorType.Assembly.GetName().Version.ToString())))))))
69+
.WithLeadingTrivia(member.GetLeadingTrivia());
70+
71+
// [DebuggerNonUserCode] is not supported over interfaces, events or fields
72+
if (member.Kind() is not SyntaxKind.InterfaceDeclaration and not SyntaxKind.EventFieldDeclaration and not SyntaxKind.FieldDeclaration)
73+
{
74+
member = member.AddAttributeLists(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.DebuggerNonUserCode")))));
75+
}
76+
77+
// [ExcludeFromCodeCoverage] is not supported on interfaces and fields
78+
if (member.Kind() is not SyntaxKind.InterfaceDeclaration and not SyntaxKind.FieldDeclaration)
79+
{
80+
member = member.AddAttributeLists(AttributeList(SingletonSeparatedList(Attribute(IdentifierName("global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage")))));
81+
}
82+
83+
return member;
84+
}).ToImmutableArray();
85+
86+
// If the target class is sealed, make protected members private and remove the virtual modifier
87+
sealedMemberDeclarations = annotatedMemberDeclarations.Select(static member =>
88+
{
89+
return
90+
member
91+
.ReplaceModifier(SyntaxKind.ProtectedKeyword, SyntaxKind.PrivateKeyword)
92+
.RemoveModifier(SyntaxKind.VirtualKeyword);
93+
}).ToImmutableArray();
94+
95+
nonSealedMemberDeclarations = annotatedMemberDeclarations;
96+
}
97+
}
98+
}

0 commit comments

Comments
 (0)