-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from ByronMayne/feature/GeneratorDiagnosticAna…
…lyzer Added Code Analyzer for common mistakes
- Loading branch information
Showing
7 changed files
with
346 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
src/SourceGenerator.Foundations/Analyzer/Rules/AnalyzerRule.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
{ | ||
/// <summary> | ||
/// Gets the descritor that this rule creates | ||
/// </summary> | ||
public DiagnosticDescriptor Descriptor { get; } | ||
|
||
/// <summary> | ||
/// Gets the current context | ||
/// </summary> | ||
protected SyntaxNodeAnalysisContext Context { get; private set; } | ||
|
||
public AnalyzerRule(DiagnosticDescriptor descriptor) | ||
{ | ||
Descriptor = descriptor; | ||
} | ||
|
||
/// <summary> | ||
/// Invokes the rule | ||
/// </summary> | ||
public void Invoke(SyntaxNodeAnalysisContext context, ClassDeclarationSyntax classDeclaration) | ||
{ | ||
Context = context; | ||
try | ||
{ | ||
Analyze(classDeclaration); | ||
} | ||
finally | ||
{ | ||
Context = default; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Tells the rule to analyze and report and errors that it sees | ||
/// </summary> | ||
protected abstract void Analyze(ClassDeclarationSyntax classDeclaration); | ||
|
||
/// <summary> | ||
/// Creates new <see cref="Diagnostic"/> using the <see cref="Descriptor"/> and reports | ||
/// it to the current context | ||
/// </summary> | ||
/// <param name="location">The location to put the diagnostic</param> | ||
/// <param name="messageArgs">Arguments that are used for it</param> | ||
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; | ||
} | ||
|
||
/// <summary> | ||
/// Returns back if the attribute with the given name is applied to the type | ||
/// </summary> | ||
protected bool HasAttribute(ClassDeclarationSyntax classDeclarationSyntax, string name, StringComparison stringComparison = StringComparison.Ordinal) | ||
=> GetAttribute(classDeclarationSyntax, name, stringComparison) != null; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/SourceGenerator.Foundations/Analyzer/Rules/ProhibitGeneratorAttributeRule.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
||
namespace SGF.Analyzer.Rules | ||
{ | ||
/// <summary> | ||
/// Ensures tha the <see cref="GeneratorAttribute"/> is not applied to | ||
/// <see cref="IncrementalGenerator"/> as these types are not really <see cref="IIncrementalGenerator"/> | ||
/// and won't be pickedup by Roslyn. | ||
/// </summary> | ||
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"); | ||
} | ||
} |
52 changes: 52 additions & 0 deletions
52
src/SourceGenerator.Foundations/Analyzer/Rules/RequireDefaultConstructorRule.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
using System.Linq; | ||
|
||
namespace SGF.Analyzer.Rules | ||
{ | ||
/// <summary> | ||
/// Ensures that <see cref="IncrementalGenerator"/> types have a default | ||
/// constructor defined. | ||
/// </summary> | ||
internal class RequireDefaultConstructorRule : AnalyzerRule | ||
{ | ||
public RequireDefaultConstructorRule() : base(CreateDescriptor()) | ||
{ | ||
} | ||
|
||
protected override void Analyze(ClassDeclarationSyntax classDeclaration) | ||
{ | ||
ConstructorDeclarationSyntax[] constructors = classDeclaration.Members | ||
.OfType<ConstructorDeclarationSyntax>() | ||
.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"); | ||
} | ||
} | ||
} |
32 changes: 32 additions & 0 deletions
32
src/SourceGenerator.Foundations/Analyzer/Rules/RequireSfgGeneratorAttributeRule.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"); | ||
} | ||
} |
Oops, something went wrong.