From 9e49adb3dcd97f67632ac2df155d3509ab148737 Mon Sep 17 00:00:00 2001 From: pearl Date: Sat, 4 May 2024 23:10:20 -0400 Subject: [PATCH 1/3] Clean up checks for if the source generators should be run on a class Extra utility methods were added here so simplify checking if a class has a given attribute on it and if any class members have a given attribute. This drastically reduced how much code we needed to have duplicated between source generators to check if it needs to run on a class. --- .../AutoInjectionSourceGenerator.cs | 38 ++--------- .../LazyDependencyInjectionSourceGenerator.cs | 37 ++--------- .../Utility/GeneratorExtensions.cs | 66 +++++++++++++++++++ 3 files changed, 75 insertions(+), 66 deletions(-) create mode 100644 PearlSourceGenerators/PearlSourceGenerators/Utility/GeneratorExtensions.cs diff --git a/PearlSourceGenerators/PearlSourceGenerators/AutoInjectionSourceGenerator.cs b/PearlSourceGenerators/PearlSourceGenerators/AutoInjectionSourceGenerator.cs index 742c500..e1d4964 100644 --- a/PearlSourceGenerators/PearlSourceGenerators/AutoInjectionSourceGenerator.cs +++ b/PearlSourceGenerators/PearlSourceGenerators/AutoInjectionSourceGenerator.cs @@ -7,6 +7,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using PearlSourceGenerators.Utility; namespace PearlSourceGenerators; @@ -52,42 +53,13 @@ public void Initialize(IncrementalGeneratorInitializationContext context) private static (ClassDeclarationSyntax, bool attributesFound) GetClassDeclarationForSourceGen( GeneratorSyntaxContext context) { - var hasClassAttr = false; - var hasPropAttr = false; - // check if the class has the attribute we expect var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; - foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists) - foreach (var attributeSyntax in attributeListSyntax.Attributes) - { - if (ModelExtensions.GetSymbolInfo(context.SemanticModel, attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) - continue; // ignore symbols we can't get - var attrName = attributeSymbol.ContainingType.ToDisplayString(); - - if (attrName != $"{Namespace}.{ClassAttribute}") - continue; - - hasClassAttr = true; - break; - } + var hasClassAttr = classDeclarationSyntax.HasAttribute(context, $"{Namespace}.{ClassAttribute}"); - // check if any fields have the attribute we expect - var fields = classDeclarationSyntax.Members - .Where(m => m is PropertyDeclarationSyntax); - foreach (var field in fields) - foreach (var attributeListSyntax in field.AttributeLists) - foreach (var attributeSyntax in attributeListSyntax.Attributes) - { - if (ModelExtensions.GetSymbolInfo(context.SemanticModel, attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) - continue; - var attrName = attributeSymbol.ContainingType.ToDisplayString(); - - if (attrName != $"{Namespace}.{PropertyAttribute}") - continue; - - hasPropAttr = true; - break; - } + // check if any properties have the attribute we expect + var hasPropAttr = classDeclarationSyntax.AnyClassMembersHave(context, + $"{Namespace}.{PropertyAttribute}"); return (classDeclarationSyntax, hasClassAttr && hasPropAttr); } diff --git a/PearlSourceGenerators/PearlSourceGenerators/LazyDependencyInjectionSourceGenerator.cs b/PearlSourceGenerators/PearlSourceGenerators/LazyDependencyInjectionSourceGenerator.cs index dc4d2ce..2209e0f 100644 --- a/PearlSourceGenerators/PearlSourceGenerators/LazyDependencyInjectionSourceGenerator.cs +++ b/PearlSourceGenerators/PearlSourceGenerators/LazyDependencyInjectionSourceGenerator.cs @@ -5,6 +5,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Text; +using PearlSourceGenerators.Utility; namespace PearlSourceGenerators; @@ -55,45 +56,15 @@ public void Initialize(IncrementalGeneratorInitializationContext context) private static (ClassDeclarationSyntax, bool attributesFound) GetClassDeclarationForSourceGen( GeneratorSyntaxContext context) { - var hasClassAttr = false; - var hasFieldAttr = false; - // check if the class has the attribute we expect var classDeclarationSyntax = (ClassDeclarationSyntax)context.Node; - foreach (var attributeListSyntax in classDeclarationSyntax.AttributeLists) - foreach (var attributeSyntax in attributeListSyntax.Attributes) - { - if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) - continue; // ignore symbols we can't get - var attrName = attributeSymbol.ContainingType.ToDisplayString(); - - if (attrName != $"{Namespace}.{ClassAttribute}") - continue; - - hasClassAttr = true; - break; - } + var hasClassAttr = classDeclarationSyntax.HasAttribute(context, $"{Namespace}.{ClassAttribute}"); // check if any fields have the attribute we expect - var fields = classDeclarationSyntax.Members - .Where(m => m is FieldDeclarationSyntax); - foreach (var field in fields) - foreach (var attributeListSyntax in field.AttributeLists) - foreach (var attributeSyntax in attributeListSyntax.Attributes) - { - if (context.SemanticModel.GetSymbolInfo(attributeSyntax).Symbol is not IMethodSymbol attributeSymbol) - continue; - var attrName = attributeSymbol.ContainingType.ToDisplayString(); - - if (attrName != $"{Namespace}.{FieldAttribute}") - continue; - - hasFieldAttr = true; - break; - } + var hasFieldAttr = classDeclarationSyntax.AnyClassMembersHave(context, + $"{Namespace}.{FieldAttribute}"); return (classDeclarationSyntax, hasClassAttr && hasFieldAttr); - } private void GenerateCode(SourceProductionContext context, Compilation compilation, diff --git a/PearlSourceGenerators/PearlSourceGenerators/Utility/GeneratorExtensions.cs b/PearlSourceGenerators/PearlSourceGenerators/Utility/GeneratorExtensions.cs new file mode 100644 index 0000000..ca11382 --- /dev/null +++ b/PearlSourceGenerators/PearlSourceGenerators/Utility/GeneratorExtensions.cs @@ -0,0 +1,66 @@ +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace PearlSourceGenerators.Utility; + +public static class GeneratorExtensions +{ + /// + /// Check if a given member (ie. class, field, method, etc) has at least one instance of an attribute present. + /// + /// The member to check + /// The syntax context of the generator + /// The fully qualified type name of the attribute + /// true if the attribute is present, false if it is not + public static bool HasAttribute(this MemberDeclarationSyntax member, GeneratorSyntaxContext context, + string attributeType) + { + var attrs = member.AttributeLists + .SelectMany(al => al.Attributes); + + foreach (var attr in attrs) + { + if (context.SemanticModel.GetSymbolInfo(attr).Symbol is not IMethodSymbol attrSym) + continue; + + if (attrSym.ContainingType.ToDisplayString() != attributeType) + continue; + + return true; + } + + return false; + } + + /// + /// Check if any class members have at least one instance of an attribute present + /// + /// Class to check + /// The syntax context of the source generator + /// The fully-qualified name of the attribute + /// true if the attribute is present, false if it is not + public static bool AnyClassMembersHave(this ClassDeclarationSyntax cls, GeneratorSyntaxContext context, + string attributeType) + { + return cls.Members + .Any(p => p.HasAttribute(context, attributeType)); + } + + /// + /// Check if any class members of the given type have at least one instance of an attribute present + /// + /// The parameter type to check + /// Class to check + /// The syntax context of the source generator + /// The fully-qualified name of the attribute + /// true if the attribute is present, false if it is not + public static bool AnyClassMembersHave(this ClassDeclarationSyntax cls, GeneratorSyntaxContext context, + string attributeType) where T : MemberDeclarationSyntax + { + return cls.Members + .OfType() + .Any(p => p.HasAttribute(context, attributeType)); + } +} \ No newline at end of file From 85a912d05c3f9ac9812264016ce5b557ae5bb6dd Mon Sep 17 00:00:00 2001 From: pearl Date: Sat, 4 May 2024 23:19:37 -0400 Subject: [PATCH 2/3] Remove diagnostic emitted during source generation I realized after I wrote the code initially that emitting diagnostics is not recommended from incremental source generators, since it breaks incremental builds. --- .../AutoInjectionSourceGenerator.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/PearlSourceGenerators/PearlSourceGenerators/AutoInjectionSourceGenerator.cs b/PearlSourceGenerators/PearlSourceGenerators/AutoInjectionSourceGenerator.cs index e1d4964..616aa0c 100644 --- a/PearlSourceGenerators/PearlSourceGenerators/AutoInjectionSourceGenerator.cs +++ b/PearlSourceGenerators/PearlSourceGenerators/AutoInjectionSourceGenerator.cs @@ -78,17 +78,7 @@ private void GenerateCode(SourceProductionContext context, Compilation compilati var namespaceName = classSymbol.ContainingNamespace.ToDisplayString(); var className = classSymbol.Name; - - if (!classDecl.Modifiers.Any(m => m.IsKind(SyntaxKind.PartialKeyword))) - { - context.ReportDiagnostic(Diagnostic.Create(new DiagnosticDescriptor( - "PSG001", - "Class must be partial", - "Class must be partial", - "PearlSourceGenerators", - DiagnosticSeverity.Warning, true), - classDecl.GetLocation())); - } + var properties = classSymbol.GetMembers() .OfType() .Where(p => From 0011f6ec05275c0a587bc73636a3604dabaf0fc4 Mon Sep 17 00:00:00 2001 From: pearl Date: Sun, 5 May 2024 19:58:01 -0400 Subject: [PATCH 3/3] Rename utility class in preparation for writing some tools for helping generate output cleanly. --- .../{GeneratorExtensions.cs => ClassValidationExtensions.cs} | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) rename PearlSourceGenerators/PearlSourceGenerators/Utility/{GeneratorExtensions.cs => ClassValidationExtensions.cs} (94%) diff --git a/PearlSourceGenerators/PearlSourceGenerators/Utility/GeneratorExtensions.cs b/PearlSourceGenerators/PearlSourceGenerators/Utility/ClassValidationExtensions.cs similarity index 94% rename from PearlSourceGenerators/PearlSourceGenerators/Utility/GeneratorExtensions.cs rename to PearlSourceGenerators/PearlSourceGenerators/Utility/ClassValidationExtensions.cs index ca11382..bb66d65 100644 --- a/PearlSourceGenerators/PearlSourceGenerators/Utility/GeneratorExtensions.cs +++ b/PearlSourceGenerators/PearlSourceGenerators/Utility/ClassValidationExtensions.cs @@ -5,7 +5,10 @@ namespace PearlSourceGenerators.Utility; -public static class GeneratorExtensions +/// +/// Utilities for validating that a class should be processed. +/// +public static class GeneratorValidationExtensions { /// /// Check if a given member (ie. class, field, method, etc) has at least one instance of an attribute present.