Skip to content

Commit

Permalink
Add Analyzer and codefix for static property
Browse files Browse the repository at this point in the history
  • Loading branch information
Dylan Snel committed Oct 23, 2023
1 parent c749879 commit 91240c7
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 10 deletions.
3 changes: 3 additions & 0 deletions Source/EpicEnums/EpicEnums/AnalyzerReleases.Shipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
; Shipped analyzer releases
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

8 changes: 8 additions & 0 deletions Source/EpicEnums/EpicEnums/AnalyzerReleases.Unshipped.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
; Unshipped analyzer release
; https://github.com/dotnet/roslyn-analyzers/blob/main/src/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md

### New Rules

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
EE0002 | EpicEnums | Error | StaticPropertiesAnalyzer
9 changes: 7 additions & 2 deletions Source/EpicEnums/EpicEnums/Analyzers/PartialRecordAnalyzer.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
using EpicEnums.SourceGeneration.Extensions;
using EpicEnums.CodeFix;
using EpicEnums.SourceGeneration.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Composition;

namespace EpicEnums.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PartialRecordCodeFixProvider)), Shared]
public class PartialRecordAnalyzer : DiagnosticAnalyzer
{
internal const string ErrorId = "EE0001";
readonly DiagnosticDescriptor _recordShouldBePartialDescriptor = new(
id: "EE0001",
id: ErrorId,

Check warning on line 19 in Source/EpicEnums/EpicEnums/Analyzers/PartialRecordAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

title: "EpicEnums",
messageFormat: "EpicEnums: Record {0} inherits {1} should be marked partial",
category: "EpicEnums",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
using EpicEnums.CodeFix;
using EpicEnums.SourceGeneration.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using System.Collections.Immutable;
using System.Composition;
using System.Data;

namespace EpicEnums.Analyzers;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StaticEnumPropertiesCodeFixProvider)), Shared]
public class StaticEnumPropertiesAnalyzer : DiagnosticAnalyzer
{
internal const string ErrorId = "EE0002";
readonly DiagnosticDescriptor _enumPropertiesShouldBeStaticDescriptor = new(
id: ErrorId,
title: "EpicEnums",
messageFormat: "EpicEnums: Property '{0}' of type '{1}' should be marked as static",
category: "EpicEnums",
DiagnosticSeverity.Error,
isEnabledByDefault: true);

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(_enumPropertiesShouldBeStaticDescriptor);

public override void Initialize(AnalysisContext context)

Check warning on line 29 in Source/EpicEnums/EpicEnums/Analyzers/StaticEnumPropertiesAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Configure generated code analysis

Check warning on line 29 in Source/EpicEnums/EpicEnums/Analyzers/StaticEnumPropertiesAnalyzer.cs

View workflow job for this annotation

GitHub Actions / build

Enable concurrent execution
{
context.RegisterSyntaxNodeAction(AnalyzePropertyDeclaration, SyntaxKind.PropertyDeclaration);
}

private void AnalyzePropertyDeclaration(SyntaxNodeAnalysisContext context)
{
var propertyDeclaration = (PropertyDeclarationSyntax)context.Node;
//#if DEBUG
// if (!System.Diagnostics.Debugger.IsAttached)
// {
// System.Diagnostics.Debugger.Launch();
// }
//#endif
// Check if the enclosing type inherits from EpicEnum<T>
if (propertyDeclaration.Parent is RecordDeclarationSyntax recordDeclaration &&
recordDeclaration.BaseList is not null)
{
foreach (var baseType in recordDeclaration.BaseList.Types)
{
var typeSymbol = context.SemanticModel.GetTypeInfo(baseType.Type).Type as INamedTypeSymbol;

if (typeSymbol?.ConstructedFrom.Name == "EpicEnum" &&
typeSymbol.TypeArguments.Length == 1 &&
typeSymbol.TypeArguments[0].Name == propertyDeclaration.Type.ToString())
{
// Check if the property is not static
if (!propertyDeclaration.Modifiers.Any(SyntaxKind.StaticKeyword))
{
var diagnostic = Diagnostic.Create(_enumPropertiesShouldBeStaticDescriptor, propertyDeclaration.GetLocation(), propertyDeclaration.Identifier.Text, propertyDeclaration.Type.ToString());
context.ReportDiagnostic(diagnostic);
}
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,27 +1,31 @@
using Microsoft.CodeAnalysis;
using EpicEnums.Analyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Immutable;
using System.Composition;

namespace EpicEnums.Analyzers;
namespace EpicEnums.CodeFix;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(PartialRecordCodeFixProvider))]
[Shared]
public class PartialRecordCodeFixProvider : CodeFixProvider
{

public override ImmutableArray<string> FixableDiagnosticIds =>
ImmutableArray.Create("EE0001");
ImmutableArray.Create(PartialRecordAnalyzer.ErrorId);

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<RecordDeclarationSyntax>().First();
var declaration = root.FindToken(diagnosticSpan.Start).Parent

Check warning on line 25 in Source/EpicEnums/EpicEnums/CodeFix/PartialRecordCodeFixProvider.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 25 in Source/EpicEnums/EpicEnums/CodeFix/PartialRecordCodeFixProvider.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.
.AncestorsAndSelf()
.OfType<RecordDeclarationSyntax>()
.First();

context.RegisterCodeFix(
CodeAction.Create(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using EpicEnums.Analyzers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Text;

namespace EpicEnums.CodeFix;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(StaticEnumPropertiesCodeFixProvider)), Shared]
internal class StaticEnumPropertiesCodeFixProvider : CodeFixProvider
{
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(StaticEnumPropertiesAnalyzer.ErrorId);

public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var diagnostic = context.Diagnostics.First();
var diagnosticSpan = diagnostic.Location.SourceSpan;

// Find the property declaration identified by the diagnostic.
var declaration = root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<PropertyDeclarationSyntax>().First();

Check warning on line 30 in Source/EpicEnums/EpicEnums/CodeFix/StaticEnumPropertiesCodeFixProvider.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

Check warning on line 30 in Source/EpicEnums/EpicEnums/CodeFix/StaticEnumPropertiesCodeFixProvider.cs

View workflow job for this annotation

GitHub Actions / build

Dereference of a possibly null reference.

// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
title: "Make property static",
createChangedDocument: c => MakePropertyStaticAsync(context.Document, declaration, c),
equivalenceKey: "MakePropertyStatic"),
diagnostic);
}

private async Task<Document> MakePropertyStaticAsync(Document document, PropertyDeclarationSyntax propertyDecl, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var staticToken = SyntaxFactory.Token(SyntaxKind.StaticKeyword);

// Add the static modifier to the property
editor.SetModifiers(propertyDecl, editor.Generator.GetModifiers(propertyDecl).WithIsStatic(true));

return editor.GetChangedDocument();
}
}
7 changes: 6 additions & 1 deletion Source/EpicEnums/EpicEnums/EpicEnums.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<InternalsVisibleTo>EpicEnums.Tests</InternalsVisibleTo>
<RepositoryUrl>https://github.com/DylanSnel/EpicEnums</RepositoryUrl>

<Version>1.4.14</Version>
<Version>1.4.17</Version>
</PropertyGroup>

<ItemGroup>
Expand All @@ -28,6 +28,11 @@
<None Remove="nupkgs\**" />
</ItemGroup>

<ItemGroup>
<AdditionalFiles Include="AnalyzerReleases.Shipped.md" />
<AdditionalFiles Include="AnalyzerReleases.Unshipped.md" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.6.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="7.0.0" />
Expand Down
4 changes: 3 additions & 1 deletion Source/EpicEnums/EpicEnums/SourceGeneration/EnumGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
//#endif
#pragma warning restore S125 // Sections of code should not be commented out
#pragma warning restore IDE0079 // Remove unnecessary suppression


IncrementalValuesProvider<RecordDeclarationSyntax> epicEnumDeclarations = context.SyntaxProvider
.CreateSyntaxProvider(
predicate: static (s, _) => IsSyntaxTargetForGeneration(s), // select enums with attributes
predicate: static (s, _) => IsSyntaxTargetForGeneration(s), // select records with basetypes
transform: static (ctx, _) => GetSemanticTargetForGeneration(ctx)) // select the enum with the EpicEnum basetype
.Where(static m => m is not null)!; // filter out enums that we don't care about

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ internal static class SymbolExtensions
public static bool HasAttribute(this ISymbol symbol, string atrributeName)
{
return symbol.GetAttributes()
.Any(_ => _.AttributeClass?.ToDisplayString() == atrributeName);
.Any(_ => _.AttributeClass?.Name == atrributeName);
}

public static AttributeData? FindAttribute(this ISymbol symbol, string atrributeName)
{
return symbol.GetAttributes()
.FirstOrDefault(_ => _.AttributeClass?.ToDisplayString() == atrributeName);
.FirstOrDefault(_ => _.AttributeClass?.Name == atrributeName);
}

public static bool IsDirectlyDerivedDrom(this INamedTypeSymbol symbol, string typeName)
Expand Down

0 comments on commit 91240c7

Please sign in to comment.