diff --git a/README.md b/README.md
index 66da656..e8599e2 100644
--- a/README.md
+++ b/README.md
@@ -15,12 +15,8 @@ namespace Example
[SgfGenerator]
public class ExampleSourceGenerator : IncrementalGenerator
{
- // Constructor can only take two arguments in this order
- public ExampleSourceGenerator(
- IGeneratorEnvironment generatorEnvironment,
- ILogger logger) : base("ExampleSourceGenerator",
- generatorPlatform, logger)
- {$$
+ public ExampleSourceGenerator() : base("ExampleSourceGenerator")
+ {
}
@@ -129,6 +125,88 @@ When your source generator runs it needs to find it's dependencies and this is o
You can embed any assemblies you want by adding them to ``
+## Diagnostic Analyzer
+
+Included with this package is a code analyzer that will be used to catch common mistakes when working with this library.
+
+
+### `SGF1001`
+**Has SgfGenerator Attribute**
+
+Any class that inherits from `IncrementalGenerator` is required to have the `SgfGenerator` attribute applied to it.
+
+```cs
+// Error
+public class MyGenerator : IncrementalGenerator
+{
+ public MyGenerator() : base("MyGenerator")
+ {}
+}
+```
+
+To fix the error just apply the attribute.
+
+```cs
+// Fixed
+[SgfGeneratorAttribute]
+public class MyGenerator : IncrementalGenerator
+{
+ public MyGenerator() : base("MyGenerator")
+ {}
+}
+```
+
+### `SGF1002`
+
+**Prohibit Generator Attribute**
+
+If an `IncrementalGenerator` has the `Generator` attribute applied it will cause a compiler error. The reason being that the `Generator` attribute is used on classes that implement `IIncrementalGenerator` which `IncrementalGenerator` does not. SGF has it's own attribute to not confuse roslyn. SGFs `IncrementalGenerator` is run from within a wrapper to help capture exceptions and handle runtime type resolving.
+
+```cs
+// Error
+[Generator]
+public class MyGenerator : IncrementalGenerator
+{
+ public MyGenerator() : base("MyGenerator")
+ {}
+}
+```
+
+To fix the error just remove the attribute.
+
+```cs
+// Fixed
+public class MyGenerator : IncrementalGenerator
+{
+ public MyGenerator() : base("MyGenerator")
+ {}
+}
+```
+
+### `SGF1003`
+**Has Default Constructor**
+
+`IncrementalGenerator` require a default constructor so they can be instantiated at runtime. If no constructor is defined the generator will never be run.
+
+```cs
+// Error
+public class MyGenerator : IncrementalGenerator
+{
+ public MyGenerator(string name) : base(name)
+ {}
+}
+```
+
+Add a default constructor that takes no arguments.
+
+```cs
+// Fixed
+public class MyGenerator : IncrementalGenerator
+{
+ public MyGenerator() : base("MyGenerator")
+ {}
+}
+```
## Project Layout
diff --git a/src/Sandbox/ConsoleApp.SourceGenerator/ConsoleAppSourceGenerator.cs b/src/Sandbox/ConsoleApp.SourceGenerator/ConsoleAppSourceGenerator.cs
index 7e573ec..89e56b2 100644
--- a/src/Sandbox/ConsoleApp.SourceGenerator/ConsoleAppSourceGenerator.cs
+++ b/src/Sandbox/ConsoleApp.SourceGenerator/ConsoleAppSourceGenerator.cs
@@ -2,7 +2,6 @@
using Microsoft.CodeAnalysis.Text;
using Newtonsoft.Json;
using SGF;
-using SGF.Diagnostics;
using System;
using System.Text;
diff --git a/src/SourceGenerator.Foundations/Analyzer/Rules/AnalyzerRule.cs b/src/SourceGenerator.Foundations/Analyzer/Rules/AnalyzerRule.cs
new file mode 100644
index 0000000..e6197f7
--- /dev/null
+++ b/src/SourceGenerator.Foundations/Analyzer/Rules/AnalyzerRule.cs
@@ -0,0 +1,98 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using System;
+
+namespace SGF.Analyzer.Rules
+{
+ internal abstract class AnalyzerRule
+ {
+ ///
+ /// Gets the descritor that this rule creates
+ ///
+ public DiagnosticDescriptor Descriptor { get; }
+
+ ///
+ /// Gets the current context
+ ///
+ protected SyntaxNodeAnalysisContext Context { get; private set; }
+
+ public AnalyzerRule(DiagnosticDescriptor descriptor)
+ {
+ Descriptor = descriptor;
+ }
+
+ ///
+ /// Invokes the rule
+ ///
+ public void Invoke(SyntaxNodeAnalysisContext context, ClassDeclarationSyntax classDeclaration)
+ {
+ Context = context;
+ try
+ {
+ Analyze(classDeclaration);
+ }
+ finally
+ {
+ Context = default;
+ }
+ }
+
+ ///
+ /// Tells the rule to analyze and report and errors that it sees
+ ///
+ protected abstract void Analyze(ClassDeclarationSyntax classDeclaration);
+
+ ///
+ /// Creates new using the and reports
+ /// it to the current context
+ ///
+ /// The location to put the diagnostic
+ /// Arguments that are used for it
+ protected void ReportDiagnostic(Location location, params object[] messageArgs)
+ {
+ Diagnostic diagnostic = Diagnostic.Create(Descriptor, location, messageArgs); ;
+ Context.ReportDiagnostic(diagnostic);
+ }
+
+ protected bool TryGetAttribute(ClassDeclarationSyntax classDeclaration, string name, out AttributeSyntax? attribute)
+ => TryGetAttribute(classDeclaration, name, StringComparison.Ordinal, out attribute);
+
+ protected bool TryGetAttribute(ClassDeclarationSyntax classDeclaration, string name, StringComparison stringComparison, out AttributeSyntax? attribute)
+ {
+ attribute = GetAttribute(classDeclaration, name);
+ return attribute != null;
+ }
+
+ protected AttributeSyntax? GetAttribute(ClassDeclarationSyntax classDeclarationSyntax, string name, StringComparison stringComparison = StringComparison.Ordinal)
+ {
+ const string POSTFIX = "Attribute";
+
+ string alterntiveName = name.EndsWith(POSTFIX, StringComparison.Ordinal)
+ ? name.Substring(0, name.Length - POSTFIX.Length)
+ : $"{name}{POSTFIX}";
+
+ foreach (AttributeListSyntax attributeList in classDeclarationSyntax.AttributeLists)
+ {
+ foreach (AttributeSyntax attribute in attributeList.Attributes)
+ {
+ string attributeName = attribute.Name.ToString();
+
+ if (string.Equals(attributeName, name, stringComparison) ||
+ string.Equals(attributeName, alterntiveName, stringComparison))
+ {
+ return attribute;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Returns back if the attribute with the given name is applied to the type
+ ///
+ protected bool HasAttribute(ClassDeclarationSyntax classDeclarationSyntax, string name, StringComparison stringComparison = StringComparison.Ordinal)
+ => GetAttribute(classDeclarationSyntax, name, stringComparison) != null;
+ }
+}
diff --git a/src/SourceGenerator.Foundations/Analyzer/Rules/ProhibitGeneratorAttributeRule.cs b/src/SourceGenerator.Foundations/Analyzer/Rules/ProhibitGeneratorAttributeRule.cs
new file mode 100644
index 0000000..349a841
--- /dev/null
+++ b/src/SourceGenerator.Foundations/Analyzer/Rules/ProhibitGeneratorAttributeRule.cs
@@ -0,0 +1,38 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace SGF.Analyzer.Rules
+{
+ ///
+ /// Ensures tha the is not applied to
+ /// as these types are not really
+ /// and won't be pickedup by Roslyn.
+ ///
+ internal class ProhibitGeneratorAttributeRule : AnalyzerRule
+ {
+ public ProhibitGeneratorAttributeRule() : base(CreateDescriptor())
+ {
+
+ }
+
+ protected override void Analyze(ClassDeclarationSyntax classDeclaration)
+ {
+
+ if (TryGetAttribute(classDeclaration, nameof(GeneratorAttribute), out AttributeSyntax? attributeSyntax))
+ {
+ Location location = attributeSyntax!.GetLocation();
+ ReportDiagnostic(location, classDeclaration.Identifier.Text);
+ }
+ }
+
+ private static DiagnosticDescriptor CreateDescriptor()
+ => new DiagnosticDescriptor("SGF1002",
+ "Prohibit GeneratorAttribute",
+ $"{{0}} has the {nameof(GeneratorAttribute)} which can't be applied to classes which are inheirting from the Generator Foundations type {nameof(IncrementalGenerator)}.",
+ "SourceGeneration",
+ DiagnosticSeverity.Error,
+ true,
+ $"Incremental Generators should not have the {nameof(GeneratorAttribute)} applied to them.",
+ "https://github.com/ByronMayne/SourceGenerator.Foundations?tab=readme-ov-file#sgf1002");
+ }
+}
diff --git a/src/SourceGenerator.Foundations/Analyzer/Rules/RequireDefaultConstructorRule.cs b/src/SourceGenerator.Foundations/Analyzer/Rules/RequireDefaultConstructorRule.cs
new file mode 100644
index 0000000..3ac74c3
--- /dev/null
+++ b/src/SourceGenerator.Foundations/Analyzer/Rules/RequireDefaultConstructorRule.cs
@@ -0,0 +1,52 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using System.Linq;
+
+namespace SGF.Analyzer.Rules
+{
+ ///
+ /// Ensures that types have a default
+ /// constructor defined.
+ ///
+ internal class RequireDefaultConstructorRule : AnalyzerRule
+ {
+ public RequireDefaultConstructorRule() : base(CreateDescriptor())
+ {
+ }
+
+ protected override void Analyze(ClassDeclarationSyntax classDeclaration)
+ {
+ ConstructorDeclarationSyntax[] constructors = classDeclaration.Members
+ .OfType()
+ .ToArray();
+
+ if(constructors.Length == 0)
+ {
+ // Already a compiler error since you need to call the base class constructor
+ return;
+ }
+
+ if(constructors.Any(c => c.ParameterList.Parameters.Count == 0))
+ {
+ // We have a default constructor
+ return;
+ }
+
+
+ Location location = classDeclaration.Identifier.GetLocation();
+ ReportDiagnostic(location, classDeclaration.Identifier.Text);
+ }
+
+ private static DiagnosticDescriptor CreateDescriptor()
+ {
+ return new DiagnosticDescriptor("SGF1003",
+ "HasDefaultConstructor",
+ $"{{0}} is missing a default constructor",
+ "SourceGeneration",
+ DiagnosticSeverity.Error,
+ true,
+ "SGF Incremental Generators must have a default constructor otherwise they will not be run",
+ "https://github.com/ByronMayne/SourceGenerator.Foundations?tab=readme-ov-file#sgf1003");
+ }
+ }
+}
diff --git a/src/SourceGenerator.Foundations/Analyzer/Rules/RequireSfgGeneratorAttributeRule.cs b/src/SourceGenerator.Foundations/Analyzer/Rules/RequireSfgGeneratorAttributeRule.cs
new file mode 100644
index 0000000..b73daa7
--- /dev/null
+++ b/src/SourceGenerator.Foundations/Analyzer/Rules/RequireSfgGeneratorAttributeRule.cs
@@ -0,0 +1,32 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+namespace SGF.Analyzer.Rules
+{
+ internal class RequireSfgGeneratorAttributeRule : AnalyzerRule
+ {
+
+ public RequireSfgGeneratorAttributeRule() : base(CreateDescriptor())
+ {
+ }
+
+ protected override void Analyze(ClassDeclarationSyntax classDeclaration)
+ {
+ if (!HasAttribute(classDeclaration, nameof(SgfGeneratorAttribute)))
+ {
+ Location location = classDeclaration.Identifier.GetLocation();
+ ReportDiagnostic(location, classDeclaration.Identifier.Text);
+ }
+ }
+
+ private static DiagnosticDescriptor CreateDescriptor()
+ => new DiagnosticDescriptor("SGF1001",
+ "SGFGeneratorAttributeApplied",
+ $"{{0}} is missing the {nameof(SgfGeneratorAttribute)}",
+ "SourceGeneration",
+ DiagnosticSeverity.Error,
+ true,
+ $"Source generators are required to have the attribute {nameof(SgfGeneratorAttribute)} applied to them otherwise the compiler won't invoke them",
+ "https://github.com/ByronMayne/SourceGenerator.Foundations?tab=readme-ov-file#sgf1001");
+ }
+}
diff --git a/src/SourceGenerator.Foundations/Analyzer/SourceGeneratorAnalyzer.cs b/src/SourceGenerator.Foundations/Analyzer/SourceGeneratorAnalyzer.cs
index 9118f4b..21d4c04 100644
--- a/src/SourceGenerator.Foundations/Analyzer/SourceGeneratorAnalyzer.cs
+++ b/src/SourceGenerator.Foundations/Analyzer/SourceGeneratorAnalyzer.cs
@@ -2,31 +2,29 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
-using System;
+using SGF.Analyzer.Rules;
using System.Collections.Immutable;
+using System.Linq;
namespace SGF.Analyzer
{
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class SourceGeneratorAnalyzer : DiagnosticAnalyzer
{
- public DiagnosticDescriptor GeneratorAttributeDescriptor { get; }
-
///
public override ImmutableArray SupportedDiagnostics { get; }
+ internal ImmutableArray Rules { get; }
+
public SourceGeneratorAnalyzer()
{
- GeneratorAttributeDescriptor = new DiagnosticDescriptor("sgf-generator-attribute-is-applied",
- "SourceGeneratorAttributeApplied",
- $"The class is missing the {nameof(GeneratorAttribute)} which is required for them to work.",
- "SourceGeneration",
- DiagnosticSeverity.Error,
- true,
- $"Source generators are required to have the attribute {nameof(GeneratorAttribute)} applied to them otherwise the compiler won't invoke them",
- "https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.generatorattribute?view=roslyn-dotnet-4.3.0");
-
- SupportedDiagnostics = new[] { GeneratorAttributeDescriptor }.ToImmutableArray();
+ Rules = new AnalyzerRule[]
+ {
+ new RequireSfgGeneratorAttributeRule(),
+ new ProhibitGeneratorAttributeRule(),
+ new RequireDefaultConstructorRule()
+ }.ToImmutableArray();
+ SupportedDiagnostics = Rules.Select(r => r.Descriptor).ToImmutableArray();
}
public override void Initialize(AnalysisContext context)
@@ -38,8 +36,37 @@ public override void Initialize(AnalysisContext context)
private void CheckForAttribute(SyntaxNodeAnalysisContext context)
{
-
-
+ SemanticModel semanticModel = context.SemanticModel;
+ ClassDeclarationSyntax classDeclaration = (ClassDeclarationSyntax)context.Node;
+ INamedTypeSymbol? symbolInfo = semanticModel.GetDeclaredSymbol(classDeclaration);
+
+ if (classDeclaration.BaseList == null || symbolInfo == null) return;
+ if (!IsIncrementalGenerator(symbolInfo)) return;
+
+ foreach(AnalyzerRule rule in Rules)
+ {
+ rule.Invoke(context, classDeclaration);
+ }
+ }
+
+ ///
+ /// Returns back if the type inheirts from or not
+ ///
+ /// The type to check
+ /// True if it does and false if it does not
+ private static bool IsIncrementalGenerator(INamedTypeSymbol? typeSymbol)
+ {
+ while (typeSymbol != null)
+ {
+ if (string.Equals(typeSymbol.ToDisplayString(), "SGF.IncrementalGenerator"))
+ {
+ return true;
+ }
+
+ typeSymbol = typeSymbol.BaseType;
+ }
+
+ return false;
}
}
}