Skip to content

Commit a2ac073

Browse files
committed
Added custom SyntaxReceiver to TransitiveMembersGenerator
1 parent 4de5fd4 commit a2ac073

File tree

3 files changed

+81
-36
lines changed

3 files changed

+81
-36
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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.Collections.Generic;
6+
using System.Linq;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CSharp;
9+
using Microsoft.CodeAnalysis.CSharp.Syntax;
10+
11+
namespace Microsoft.Toolkit.Mvvm.SourceGenerators
12+
{
13+
/// <inheritdoc cref="TransitiveMembersGenerator{TAttribute}"/>
14+
public abstract partial class TransitiveMembersGenerator<TAttribute>
15+
{
16+
/// <summary>
17+
/// An <see cref="ISyntaxContextReceiver"/> that selects candidate nodes to process.
18+
/// </summary>
19+
private sealed class SyntaxReceiver : ISyntaxContextReceiver
20+
{
21+
/// <summary>
22+
/// The list of info gathered during exploration.
23+
/// </summary>
24+
private readonly List<(ClassDeclarationSyntax Class, AttributeSyntax Attribute, AttributeData Data)> gatheredInfo = new();
25+
26+
/// <summary>
27+
/// Gets the collection of gathered info to process.
28+
/// </summary>
29+
public IReadOnlyCollection<(ClassDeclarationSyntax Class, AttributeSyntax Attribute, AttributeData Data)> GatheredInfo => this.gatheredInfo;
30+
31+
/// <inheritdoc/>
32+
public void OnVisitSyntaxNode(GeneratorSyntaxContext context)
33+
{
34+
if (context.Node is ClassDeclarationSyntax classDeclaration &&
35+
classDeclaration.AttributeLists.Count > 0 &&
36+
context.SemanticModel.GetDeclaredSymbol(classDeclaration) is INamedTypeSymbol classSymbol &&
37+
classSymbol.GetAttributes().FirstOrDefault(static a => a.AttributeClass?.ToDisplayString() == typeof(TAttribute).FullName) is AttributeData attributeData &&
38+
attributeData.ApplicationSyntaxReference is SyntaxReference syntaxReference &&
39+
syntaxReference.GetSyntax() is AttributeSyntax attributeSyntax)
40+
{
41+
this.gatheredInfo.Add((classDeclaration, attributeSyntax, attributeData));
42+
}
43+
}
44+
}
45+
}
46+
}

Microsoft.Toolkit.Mvvm.SourceGenerators/ComponentModel/TransitiveMembersGenerator.cs

Lines changed: 35 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Generic;
77
using System.Diagnostics.CodeAnalysis;
8+
using System.Diagnostics.Contracts;
89
using System.IO;
910
using System.Linq;
1011
using System.Reflection;
@@ -25,7 +26,7 @@ namespace Microsoft.Toolkit.Mvvm.SourceGenerators
2526
/// A source generator for the <see cref="ObservableObjectAttribute"/> type.
2627
/// </summary>
2728
/// <typeparam name="TAttribute">The type of the source attribute to look for.</typeparam>
28-
public abstract class TransitiveMembersGenerator<TAttribute> : ISourceGenerator
29+
public abstract partial class TransitiveMembersGenerator<TAttribute> : ISourceGenerator
2930
where TAttribute : Attribute
3031
{
3132
/// <summary>
@@ -36,63 +37,62 @@ public abstract class TransitiveMembersGenerator<TAttribute> : ISourceGenerator
3637
/// <inheritdoc/>
3738
public void Initialize(GeneratorInitializationContext context)
3839
{
40+
context.RegisterForSyntaxNotifications(static () => new SyntaxReceiver());
3941
}
4042

4143
/// <inheritdoc/>
4244
public void Execute(GeneratorExecutionContext context)
4345
{
44-
// Get the symbol for the current target attribute
45-
INamedTypeSymbol attributeSymbol = context.Compilation.GetTypeByMetadataName(typeof(TAttribute).FullName)!;
46-
47-
// Find all the target attribute usages
48-
IEnumerable<AttributeSyntax> attributes =
49-
from syntaxTree in context.Compilation.SyntaxTrees
50-
let semanticModel = context.Compilation.GetSemanticModel(syntaxTree)
51-
from attribute in syntaxTree.GetRoot().DescendantNodes().OfType<AttributeSyntax>()
52-
let typeInfo = semanticModel.GetTypeInfo(attribute)
53-
where SymbolEqualityComparer.Default.Equals(typeInfo.Type, attributeSymbol)
54-
select attribute;
55-
56-
SyntaxTree? sourceSyntaxTree = null;
57-
58-
foreach (AttributeSyntax attribute in attributes)
46+
// Get the syntax receiver with the candidate nodes
47+
if (context.SyntaxContextReceiver is not SyntaxReceiver syntaxReceiver ||
48+
syntaxReceiver.GatheredInfo.Count == 0)
5949
{
60-
// Load the source syntax tree if needed
61-
if (sourceSyntaxTree is null)
62-
{
63-
string filename = $"Microsoft.Toolkit.Mvvm.SourceGenerators.EmbeddedResources.{typeof(TAttribute).Name.Replace("Attribute", string.Empty)}.cs";
64-
65-
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename);
66-
StreamReader reader = new(stream);
67-
68-
string observableObjectSource = reader.ReadToEnd();
50+
return;
51+
}
6952

70-
sourceSyntaxTree = CSharpSyntaxTree.ParseText(observableObjectSource);
71-
}
53+
// Load the syntax tree with the members to generate
54+
SyntaxTree sourceSyntaxTree = LoadSourceSyntaxTree();
7255

73-
ClassDeclarationSyntax classDeclaration = attribute.FirstAncestorOrSelf<ClassDeclarationSyntax>()!;
74-
SemanticModel semanticModel = context.Compilation.GetSemanticModel(classDeclaration.SyntaxTree);
75-
INamedTypeSymbol classDeclarationSymbol = semanticModel.GetDeclaredSymbol(classDeclaration)!;
76-
AttributeData attributeData = classDeclarationSymbol.GetAttributes().First(a => a.ApplicationSyntaxReference?.GetSyntax() == attribute);
56+
foreach (var info in syntaxReceiver.GatheredInfo)
57+
{
58+
SemanticModel semanticModel = context.Compilation.GetSemanticModel(info.Class.SyntaxTree);
59+
INamedTypeSymbol classDeclarationSymbol = semanticModel.GetDeclaredSymbol(info.Class)!;
7760

78-
if (!ValidateTargetType(context, attributeData, classDeclaration, classDeclarationSymbol, out var descriptor))
61+
if (!ValidateTargetType(context, info.Data, info.Class, classDeclarationSymbol, out var descriptor))
7962
{
80-
context.ReportDiagnostic(descriptor, attribute, classDeclarationSymbol);
63+
context.ReportDiagnostic(descriptor, info.Attribute, classDeclarationSymbol);
8164

8265
continue;
8366
}
8467

8568
try
8669
{
87-
OnExecute(context, attributeData, classDeclaration, classDeclarationSymbol, sourceSyntaxTree);
70+
OnExecute(context, info.Data, info.Class, classDeclarationSymbol, sourceSyntaxTree);
8871
}
8972
catch
9073
{
91-
context.ReportDiagnostic(TargetTypeErrorDescriptor, attribute, classDeclarationSymbol);
74+
context.ReportDiagnostic(TargetTypeErrorDescriptor, info.Attribute, classDeclarationSymbol);
9275
}
9376
}
9477
}
9578

79+
/// <summary>
80+
/// Loads the source syntax tree for the current generator.
81+
/// </summary>
82+
/// <returns>The syntax tree with the elements to emit in the generated code.</returns>
83+
[Pure]
84+
private static SyntaxTree LoadSourceSyntaxTree()
85+
{
86+
string filename = $"Microsoft.Toolkit.Mvvm.SourceGenerators.EmbeddedResources.{typeof(TAttribute).Name.Replace("Attribute", string.Empty)}.cs";
87+
88+
Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(filename);
89+
StreamReader reader = new(stream);
90+
91+
string observableObjectSource = reader.ReadToEnd();
92+
93+
return CSharpSyntaxTree.ParseText(observableObjectSource);
94+
}
95+
9696
/// <summary>
9797
/// Processes a given target type.
9898
/// </summary>

Microsoft.Toolkit.Mvvm.SourceGenerators/Messaging/IMessengerRegisterAllGenerator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
using System;
66
using System.Collections.Generic;
7-
using System.Diagnostics;
87
using System.Diagnostics.Contracts;
98
using System.Linq;
109
using System.Text;

0 commit comments

Comments
 (0)