-
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.
- Loading branch information
Showing
11 changed files
with
244 additions
and
0 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
namespace NUnit.Analyzers.Constants | ||
{ | ||
internal static class AnalyzerIdentifiers | ||
{ | ||
internal const string TestFieldIsNotReadonly = "NU0001"; | ||
} | ||
} |
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,7 @@ | ||
namespace NUnit.Analyzers.Constants | ||
{ | ||
internal static class Categories | ||
{ | ||
internal const string ParallelExecution = nameof(ParallelExecution); | ||
} | ||
} |
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,13 @@ | ||
namespace NUnit.Analyzers.Constants | ||
{ | ||
public static class NUnitFrameworkConstants | ||
{ | ||
public const string FullNameOfTypeITestBuilder = "NUnit.Framework.Interfaces.ITestBuilder"; | ||
public const string FullNameOfTypeISimpleTestBuilder = "NUnit.Framework.Interfaces.ISimpleTestBuilder"; | ||
|
||
public const string FullNameOfTypeOneTimeSetUpAttribute = "NUnit.Framework.OneTimeSetUpAttribute"; | ||
public const string FullNameOfTypeOneTimeTearDownAttribute = "NUnit.Framework.OneTimeTearDownAttribute"; | ||
public const string FullNameOfTypeSetUpAttribute = "NUnit.Framework.SetUpAttribute"; | ||
public const string FullNameOfTypeTearDownAttribute = "NUnit.Framework.TearDownAttribute"; | ||
} | ||
} |
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,51 @@ | ||
using Microsoft.CodeAnalysis; | ||
|
||
using NUnit.Analyzers.Constants; | ||
|
||
namespace NUnit.Analyzers.Extensions | ||
{ | ||
public static class AttributeDataExtensions | ||
{ | ||
public static bool DerivesFromISimpleTestBuilder(this AttributeData @this, Compilation compilation) | ||
{ | ||
return DerivesFromInterface(compilation, @this, NUnitFrameworkConstants.FullNameOfTypeISimpleTestBuilder); | ||
} | ||
|
||
public static bool DerivesFromITestBuilder(this AttributeData @this, Compilation compilation) | ||
{ | ||
return DerivesFromInterface(compilation, @this, NUnitFrameworkConstants.FullNameOfTypeITestBuilder); | ||
} | ||
|
||
public static bool IsTestMethodAttribute(this AttributeData @this, Compilation compilation) | ||
{ | ||
return @this.DerivesFromITestBuilder(compilation) || | ||
@this.DerivesFromISimpleTestBuilder(compilation); | ||
} | ||
|
||
public static bool IsSetUpOrTearDownMethodAttribute(this AttributeData @this, Compilation compilation) | ||
{ | ||
var attributeType = @this.AttributeClass; | ||
|
||
if (attributeType is null) | ||
return false; | ||
|
||
return attributeType.IsType(NUnitFrameworkConstants.FullNameOfTypeOneTimeSetUpAttribute, compilation) | ||
|| attributeType.IsType(NUnitFrameworkConstants.FullNameOfTypeOneTimeTearDownAttribute, compilation) | ||
|| attributeType.IsType(NUnitFrameworkConstants.FullNameOfTypeSetUpAttribute, compilation) | ||
|| attributeType.IsType(NUnitFrameworkConstants.FullNameOfTypeTearDownAttribute, compilation); | ||
} | ||
|
||
private static bool DerivesFromInterface(Compilation compilation, AttributeData attributeData, string interfaceTypeFullName) | ||
{ | ||
if (attributeData.AttributeClass is null) | ||
return false; | ||
|
||
var interfaceType = compilation.GetTypeByMetadataName(interfaceTypeFullName); | ||
|
||
if (interfaceType is null) | ||
return false; | ||
|
||
return attributeData.AttributeClass.AllInterfaces.Any(i => SymbolEqualityComparer.Default.Equals(i, interfaceType)); | ||
} | ||
} | ||
} |
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,19 @@ | ||
using Microsoft.CodeAnalysis; | ||
|
||
namespace NUnit.Analyzers.Extensions | ||
{ | ||
internal static class MethodSymbolExtensions | ||
{ | ||
internal static bool IsTestRelatedMethod(this IMethodSymbol methodSymbol, Compilation compilation) | ||
{ | ||
return methodSymbol.HasTestRelatedAttributes(compilation) || | ||
(methodSymbol.OverriddenMethod is not null && methodSymbol.OverriddenMethod.IsTestRelatedMethod(compilation)); | ||
} | ||
|
||
internal static bool HasTestRelatedAttributes(this IMethodSymbol methodSymbol, Compilation compilation) | ||
{ | ||
return methodSymbol.GetAttributes().Any( | ||
a => a.IsTestMethodAttribute(compilation) || a.IsSetUpOrTearDownMethodAttribute(compilation)); | ||
} | ||
} | ||
} |
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,16 @@ | ||
using System.Diagnostics.CodeAnalysis; | ||
|
||
using Microsoft.CodeAnalysis; | ||
|
||
namespace NUnit.Analyzers.Extensions | ||
{ | ||
public static class TypeSymbolExtensions | ||
{ | ||
internal static bool IsType([NotNullWhen(true)] this ITypeSymbol? @this, string fullMetadataName, Compilation compilation) | ||
{ | ||
var typeSymbol = compilation.GetTypeByMetadataName(fullMetadataName); | ||
|
||
return SymbolEqualityComparer.Default.Equals(typeSymbol, @this); | ||
} | ||
} | ||
} |
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,13 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<TargetFramework>net8.0</TargetFramework> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<Nullable>enable</Nullable> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.10.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
55 changes: 55 additions & 0 deletions
55
NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyAnalyzer.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,55 @@ | ||
using System.Collections.Immutable; | ||
|
||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
|
||
using NUnit.Analyzers.Constants; | ||
using NUnit.Analyzers.Extensions; | ||
|
||
namespace NUnit.Analyzers.TestFieldIsNotReadonly | ||
{ | ||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class TestFieldIsNotReadonlyAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private static readonly DiagnosticDescriptor testFieldIsNotReadonly = new DiagnosticDescriptor( | ||
AnalyzerIdentifiers.TestFieldIsNotReadonly, | ||
TestFieldIsNotReadonlyConstants.TestFieldIsNotReadonlyTitle, | ||
TestFieldIsNotReadonlyConstants.TestFieldIsNotReadonlyMessage, | ||
Categories.ParallelExecution, | ||
DiagnosticSeverity.Warning, | ||
true, | ||
TestFieldIsNotReadonlyConstants.TestFieldIsNotReadonlyDescription | ||
); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
context.EnableConcurrentExecution(); | ||
context.RegisterSymbolAction(AnalyzeType, SymbolKind.NamedType); | ||
} | ||
|
||
private static void AnalyzeType(SymbolAnalysisContext context) | ||
{ | ||
var typeSymbol = (INamedTypeSymbol)context.Symbol; | ||
|
||
var hasTests = typeSymbol | ||
.GetMembers() | ||
.OfType<IMethodSymbol>() | ||
.Any(x => x.MethodKind == MethodKind.Ordinary && x.IsTestRelatedMethod(context.Compilation)); | ||
|
||
if (!hasTests) | ||
return; | ||
|
||
var fields = typeSymbol | ||
.GetMembers() | ||
.OfType<IFieldSymbol>() | ||
.Where(x => !x.IsRequired && !x.IsConst); | ||
|
||
foreach (var field in fields) | ||
context.ReportDiagnostic(Diagnostic.Create(testFieldIsNotReadonly, field.Locations[0])); | ||
} | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => | ||
ImmutableArray.Create(testFieldIsNotReadonly); | ||
} | ||
} |
48 changes: 48 additions & 0 deletions
48
NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyCodeFix.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,48 @@ | ||
using System.Collections.Immutable; | ||
|
||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.CSharp; | ||
using Microsoft.CodeAnalysis.CSharp.Syntax; | ||
|
||
using NUnit.Analyzers.Constants; | ||
|
||
namespace NUnit.Analyzers.TestFieldIsNotReadonly | ||
{ | ||
[ExportCodeFixProvider(LanguageNames.CSharp)] | ||
public class TestFieldIsNotReadonlyCodeFix : CodeFixProvider | ||
{ | ||
public override sealed FixAllProvider GetFixAllProvider() | ||
{ | ||
return WellKnownFixAllProviders.BatchFixer; | ||
} | ||
|
||
public override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
|
||
if (root is null) | ||
{ | ||
return; | ||
} | ||
|
||
context.CancellationToken.ThrowIfCancellationRequested(); | ||
|
||
var node = root.FindNode(context.Span); | ||
if (node is not FieldDeclarationSyntax originalExpression) | ||
return; | ||
|
||
if (originalExpression.Modifiers.Any(IsReadonlyModifier)) | ||
return; | ||
|
||
// var newExpression = originalExpression.WithModifiers(originalExpression.Modifiers.Add(Synra)) | ||
} | ||
|
||
private static bool IsReadonlyModifier(SyntaxToken syntaxToken) => | ||
syntaxToken.IsKind(SyntaxKind.ConstKeyword) || | ||
syntaxToken.IsKind(SyntaxKind.ReadOnlyKeyword); | ||
|
||
public override ImmutableArray<string> FixableDiagnosticIds | ||
=> ImmutableArray.Create(AnalyzerIdentifiers.TestFieldIsNotReadonly); | ||
} | ||
} |
9 changes: 9 additions & 0 deletions
9
NUnit.Analyzers/TestFieldIsNotReadonly/TestFieldIsNotReadonlyConstants.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,9 @@ | ||
namespace NUnit.Analyzers.TestFieldIsNotReadonly | ||
{ | ||
internal class TestFieldIsNotReadonlyConstants | ||
{ | ||
internal const string TestFieldIsNotReadonlyTitle = "The field in test class is not readonly"; | ||
internal const string TestFieldIsNotReadonlyMessage = "Fields in test classes should be readonly"; | ||
internal const string TestFieldIsNotReadonlyDescription = "A test fixture should not contain any modifiable shared state to simplify tests parallel execution."; | ||
} | ||
} |
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