Skip to content

Commit 1f90e52

Browse files
committed
Switch ObservableRecipientGenerator to incremental
1 parent b3eb5e5 commit 1f90e52

File tree

5 files changed

+75
-56
lines changed

5 files changed

+75
-56
lines changed

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/INotifyPropertyChangedGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public INotifyPropertyChangedGenerator()
2828
}
2929

3030
/// <inheritdoc/>
31-
protected override INotifyPropertyChangedInfo GetInfo(AttributeData attributeData)
31+
protected override INotifyPropertyChangedInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
3232
{
3333
if (attributeData.TryGetNamedArgument("IncludeAdditionalHelperMethods", out bool includeAdditionalHelperMethods))
3434
{
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
namespace CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
6+
7+
/// <summary>
8+
/// A model with gathered info on a given <c>ObservableRecipientAttribute</c> instance.
9+
/// </summary>
10+
/// <param name="TypeName">The type name of the type being annotated.</param>
11+
/// <param name="HasExplicitConstructors">Whether or not the target type has explicit constructors.</param>
12+
/// <param name="IsAbstract">Whether or not the target type is abstract.</param>
13+
/// <param name="IsObservableValidator">Whether or not the target type inherits from <c>ObservableValidator</c>.</param>
14+
public sealed record ObservableRecipientInfo(
15+
string TypeName,
16+
bool HasExplicitConstructors,
17+
bool IsAbstract,
18+
bool IsObservableValidator);

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableObjectGenerator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public ObservableObjectGenerator()
2727
}
2828

2929
/// <inheritdoc/>
30-
protected override object? GetInfo(AttributeData attributeData)
30+
protected override object? GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
3131
{
3232
return null;
3333
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/ObservableRecipientGenerator.cs

Lines changed: 52 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,127 +2,127 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System.Collections.Generic;
6-
using System.Diagnostics.CodeAnalysis;
5+
using System.Collections.Immutable;
76
using System.Linq;
7+
using CommunityToolkit.Mvvm.SourceGenerators.ComponentModel.Models;
8+
using CommunityToolkit.Mvvm.SourceGenerators.Diagnostics;
9+
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
810
using Microsoft.CodeAnalysis;
911
using Microsoft.CodeAnalysis.CSharp;
1012
using Microsoft.CodeAnalysis.CSharp.Syntax;
11-
using CommunityToolkit.Mvvm.SourceGenerators.Extensions;
12-
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
1313
using static CommunityToolkit.Mvvm.SourceGenerators.Diagnostics.DiagnosticDescriptors;
14+
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
1415

1516
namespace CommunityToolkit.Mvvm.SourceGenerators;
1617

1718
/// <summary>
1819
/// A source generator for the <c>ObservableRecipientAttribute</c> type.
1920
/// </summary>
20-
[Generator]
21-
public sealed class ObservableRecipientGenerator : TransitiveMembersGenerator
21+
[Generator(LanguageNames.CSharp)]
22+
public sealed class ObservableRecipientGenerator : TransitiveMembersGenerator2<ObservableRecipientInfo>
2223
{
2324
/// <summary>
2425
/// Initializes a new instance of the <see cref="ObservableRecipientGenerator"/> class.
2526
/// </summary>
2627
public ObservableRecipientGenerator()
27-
: base("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")
28+
: base("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipientAttribute")
2829
{
2930
}
3031

3132
/// <inheritdoc/>
32-
protected override DiagnosticDescriptor TargetTypeErrorDescriptor => ObservableRecipientGeneratorError;
33+
protected override ObservableRecipientInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData)
34+
{
35+
string typeName = typeSymbol.Name;
36+
bool hasExplicitConstructors = !(typeSymbol.InstanceConstructors.Length == 1 && typeSymbol.InstanceConstructors[0] is { Parameters.IsEmpty: true, IsImplicitlyDeclared: true });
37+
bool isAbstract = typeSymbol.IsAbstract;
38+
bool isObservableValidator = typeSymbol.InheritsFrom("global::CommunityToolkit.Mvvm.ComponentModel.ObservableValidator");
39+
40+
return new(
41+
typeName,
42+
hasExplicitConstructors,
43+
isAbstract,
44+
isObservableValidator);
45+
}
3346

3447
/// <inheritdoc/>
35-
protected override bool ValidateTargetType(
36-
GeneratorExecutionContext context,
37-
AttributeData attributeData,
38-
ClassDeclarationSyntax classDeclaration,
39-
INamedTypeSymbol classDeclarationSymbol,
40-
[NotNullWhen(false)] out DiagnosticDescriptor? descriptor)
48+
protected override bool ValidateTargetType(INamedTypeSymbol typeSymbol, ObservableRecipientInfo info, out ImmutableArray<Diagnostic> diagnostics)
4149
{
42-
INamedTypeSymbol observableRecipientSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient")!;
43-
INamedTypeSymbol observableObjectSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObject")!;
44-
INamedTypeSymbol observableObjectAttributeSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute")!;
45-
INamedTypeSymbol iNotifyPropertyChangedSymbol = context.Compilation.GetTypeByMetadataName("System.ComponentModel.INotifyPropertyChanged")!;
50+
ImmutableArray<Diagnostic>.Builder builder = ImmutableArray.CreateBuilder<Diagnostic>();
4651

4752
// Check if the type already inherits from ObservableRecipient
48-
if (classDeclarationSymbol.InheritsFrom(observableRecipientSymbol))
53+
if (typeSymbol.InheritsFrom("global::CommunityToolkit.Mvvm.ComponentModel.ObservableRecipient"))
4954
{
50-
descriptor = DuplicateObservableRecipientError;
55+
builder.Add(DuplicateObservableRecipientError, typeSymbol, typeSymbol);
56+
57+
diagnostics = builder.ToImmutable();
5158

5259
return false;
5360
}
5461

5562
// In order to use [ObservableRecipient], the target type needs to inherit from ObservableObject,
5663
// or be annotated with [ObservableObject] or [INotifyPropertyChanged] (with additional helpers).
57-
if (!classDeclarationSymbol.InheritsFrom(observableObjectSymbol) &&
58-
!classDeclarationSymbol.GetAttributes().Any(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, observableObjectAttributeSymbol)) &&
59-
!classDeclarationSymbol.GetAttributes().Any(a =>
60-
SymbolEqualityComparer.Default.Equals(a.AttributeClass, iNotifyPropertyChangedSymbol) &&
64+
if (!typeSymbol.InheritsFrom("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObject") &&
65+
!typeSymbol.GetAttributes().Any(static a => a.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.ObservableObjectAttribute") == true) &&
66+
!typeSymbol.GetAttributes().Any(static a =>
67+
a.AttributeClass?.HasFullyQualifiedName("global::CommunityToolkit.Mvvm.ComponentModel.INotifyPropertyChangedAttribute") == true &&
6168
!a.HasNamedArgument("IncludeAdditionalHelperMethods", false)))
6269
{
63-
descriptor = MissingBaseObservableObjectFunctionalityError;
70+
builder.Add(MissingBaseObservableObjectFunctionalityError, typeSymbol, typeSymbol);
71+
72+
diagnostics = builder.ToImmutable();
6473

6574
return false;
6675
}
6776

68-
descriptor = null;
77+
diagnostics = builder.ToImmutable();
6978

7079
return true;
7180
}
7281

7382
/// <inheritdoc/>
74-
protected override IEnumerable<MemberDeclarationSyntax> FilterDeclaredMembers(
75-
GeneratorExecutionContext context,
76-
AttributeData attributeData,
77-
ClassDeclarationSyntax classDeclaration,
78-
INamedTypeSymbol classDeclarationSymbol,
79-
ClassDeclarationSyntax sourceDeclaration)
83+
protected override ImmutableArray<MemberDeclarationSyntax> FilterDeclaredMembers(ObservableRecipientInfo info, ClassDeclarationSyntax classDeclaration)
8084
{
85+
ImmutableArray<MemberDeclarationSyntax>.Builder builder = ImmutableArray.CreateBuilder<MemberDeclarationSyntax>();
86+
8187
// If the target type has no constructors, generate constructors as well
82-
if (classDeclarationSymbol.InstanceConstructors.Length == 1 &&
83-
classDeclarationSymbol.InstanceConstructors[0] is
84-
{
85-
Parameters: { IsEmpty: true },
86-
DeclaringSyntaxReferences: { IsEmpty: true },
87-
IsImplicitlyDeclared: true
88-
})
88+
if (!info.HasExplicitConstructors)
8989
{
90-
foreach (ConstructorDeclarationSyntax ctor in sourceDeclaration.Members.OfType<ConstructorDeclarationSyntax>())
90+
foreach (ConstructorDeclarationSyntax ctor in classDeclaration.Members.OfType<ConstructorDeclarationSyntax>())
9191
{
9292
string text = ctor.NormalizeWhitespace().ToFullString();
93-
string replaced = text.Replace("ObservableRecipient", classDeclarationSymbol.Name);
93+
string replaced = text.Replace("ObservableRecipient", info.TypeName);
9494

9595
// Adjust the visibility of the constructors based on whether the target type is abstract.
9696
// If that is not the case, the constructors have to be declared as public and not protected.
97-
if (!classDeclarationSymbol.IsAbstract)
97+
if (!info.IsAbstract)
9898
{
9999
replaced = replaced.Replace("protected", "public");
100100
}
101101

102-
yield return (ConstructorDeclarationSyntax)ParseMemberDeclaration(replaced)!;
102+
builder.Add((ConstructorDeclarationSyntax)ParseMemberDeclaration(replaced)!);
103103
}
104104
}
105105

106-
INamedTypeSymbol observableValidatorSymbol = context.Compilation.GetTypeByMetadataName("CommunityToolkit.Mvvm.ComponentModel.ObservableValidator")!;
107-
108106
// Skip the SetProperty overloads if the target type inherits from ObservableValidator, to avoid conflicts
109-
if (classDeclarationSymbol.InheritsFrom(observableValidatorSymbol))
107+
if (info.IsObservableValidator)
110108
{
111-
foreach (MemberDeclarationSyntax member in sourceDeclaration.Members.Where(static member => member is not ConstructorDeclarationSyntax))
109+
foreach (MemberDeclarationSyntax member in classDeclaration.Members.Where(static member => member is not ConstructorDeclarationSyntax))
112110
{
113-
if (member is not MethodDeclarationSyntax { Identifier: { ValueText: "SetProperty" } })
111+
if (member is not MethodDeclarationSyntax { Identifier.ValueText: "SetProperty" })
114112
{
115-
yield return member;
113+
builder.Add(member);
116114
}
117115
}
118116

119-
yield break;
117+
return builder.ToImmutable();
120118
}
121119

122120
// If the target type has at least one custom constructor, only generate methods
123-
foreach (MemberDeclarationSyntax member in sourceDeclaration.Members.Where(static member => member is not ConstructorDeclarationSyntax))
121+
foreach (MemberDeclarationSyntax member in classDeclaration.Members.Where(static member => member is not ConstructorDeclarationSyntax))
124122
{
125-
yield return member;
123+
builder.Add(member);
126124
}
125+
126+
return builder.ToImmutable();
127127
}
128128
}

CommunityToolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.Incremental.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
9090
Symbol: item,
9191
Attribute: item.GetAttributes().FirstOrDefault(a => a.AttributeClass?.HasFullyQualifiedName(this.attributeType) == true)))
9292
.Where(static item => item.Attribute is not null)!
93-
.Select((item, _) => (item.Symbol, GetInfo(item.Attribute!)));
93+
.Select((item, _) => (item.Symbol, GetInfo(item.Symbol, item.Attribute!)));
9494

9595
// Gather all generation info, and any diagnostics
9696
IncrementalValuesProvider<Result<(HierarchyInfo Hierarchy, TInfo Info)>> generationInfoWithErrors =
@@ -131,9 +131,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
131131
/// <summary>
132132
/// Gets an info model from a retrieved <see cref="AttributeData"/> instance.
133133
/// </summary>
134+
/// <param name="typeSymbol">The <see cref="INamedTypeSymbol"/> instance for the target type.</param>
134135
/// <param name="attributeData">The input <see cref="AttributeData"/> to get info from.</param>
135136
/// <returns>A <typeparamref name="TInfo"/> instance with data extracted from <paramref name="attributeData"/>.</returns>
136-
protected abstract TInfo GetInfo(AttributeData attributeData);
137+
protected abstract TInfo GetInfo(INamedTypeSymbol typeSymbol, AttributeData attributeData);
137138

138139
/// <summary>
139140
/// Validates a target type being processed.

0 commit comments

Comments
 (0)