-
Notifications
You must be signed in to change notification settings - Fork 410
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add PreferNonGenericVariantFor attribute and analyzer (#5190)
Co-authored-by: Pieter-Jan Briers <[email protected]>
- Loading branch information
Showing
7 changed files
with
162 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,71 @@ | ||
using System.Threading.Tasks; | ||
using Microsoft.CodeAnalysis.CSharp.Testing; | ||
using Microsoft.CodeAnalysis.Testing; | ||
using Microsoft.CodeAnalysis.Testing.Verifiers; | ||
using NUnit.Framework; | ||
using VerifyCS = | ||
Microsoft.CodeAnalysis.CSharp.Testing.NUnit.AnalyzerVerifier<Robust.Analyzers.PreferNonGenericVariantForAnalyzer>; | ||
|
||
namespace Robust.Analyzers.Tests; | ||
|
||
[Parallelizable(ParallelScope.All | ParallelScope.Fixtures)] | ||
[TestFixture] | ||
public sealed class PreferNonGenericVariantForTest | ||
{ | ||
private static Task Verifier(string code, params DiagnosticResult[] expected) | ||
{ | ||
var test = new CSharpAnalyzerTest<PreferNonGenericVariantForAnalyzer, NUnitVerifier>() | ||
{ | ||
TestState = | ||
{ | ||
Sources = { code }, | ||
}, | ||
}; | ||
|
||
TestHelper.AddEmbeddedSources( | ||
test.TestState, | ||
"Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute.cs" | ||
); | ||
|
||
// ExpectedDiagnostics cannot be set, so we need to AddRange here... | ||
test.TestState.ExpectedDiagnostics.AddRange(expected); | ||
|
||
return test.RunAsync(); | ||
} | ||
|
||
[Test] | ||
public async Task Test() | ||
{ | ||
const string code = """ | ||
using Robust.Shared.Analyzers; | ||
|
||
public class Bar { }; | ||
public class Baz { }; | ||
public class Okay { }; | ||
|
||
public static class Foo | ||
{ | ||
[PreferNonGenericVariantFor(typeof(Bar), typeof(Baz))] | ||
public static void DoFoo<T>() { } | ||
} | ||
|
||
public class Test | ||
{ | ||
public void DoBad() | ||
{ | ||
Foo.DoFoo<Bar>(); | ||
} | ||
|
||
public void DoGood() | ||
{ | ||
Foo.DoFoo<Okay>(); | ||
} | ||
} | ||
"""; | ||
|
||
await Verifier(code, | ||
// /0/Test0.cs(17,9): warning RA0029: Use the non-generic variant of this method for type Bar | ||
VerifyCS.Diagnostic().WithSpan(17, 9, 17, 25).WithArguments("Bar") | ||
); | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
using System.Collections.Immutable; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
using Robust.Roslyn.Shared; | ||
|
||
namespace Robust.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class PreferNonGenericVariantForAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private const string AttributeType = "Robust.Shared.Analyzers.PreferNonGenericVariantForAttribute"; | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create( | ||
UseNonGenericVariantDescriptor | ||
); | ||
|
||
private static readonly DiagnosticDescriptor UseNonGenericVariantDescriptor = new( | ||
Diagnostics.IdUseNonGenericVariant, | ||
"Consider using the non-generic variant of this method", | ||
"Use the non-generic variant of this method for type {0}", | ||
"Usage", | ||
DiagnosticSeverity.Warning, | ||
true, | ||
"Use the generic variant of this method."); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics | GeneratedCodeAnalysisFlags.Analyze); | ||
context.EnableConcurrentExecution(); | ||
context.RegisterOperationAction(CheckForNonGenericVariant, OperationKind.Invocation); | ||
} | ||
|
||
private void CheckForNonGenericVariant(OperationAnalysisContext obj) | ||
{ | ||
if (obj.Operation is not IInvocationOperation invocationOperation) return; | ||
|
||
var preferNonGenericAttribute = obj.Compilation.GetTypeByMetadataName(AttributeType); | ||
|
||
HashSet<ITypeSymbol> forTypes = []; | ||
foreach (var attribute in invocationOperation.TargetMethod.GetAttributes()) | ||
{ | ||
if (!SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, preferNonGenericAttribute)) | ||
continue; | ||
|
||
foreach (var type in attribute.ConstructorArguments[0].Values) | ||
forTypes.Add((ITypeSymbol)type.Value); | ||
|
||
break; | ||
} | ||
|
||
if (forTypes == null) | ||
return; | ||
|
||
foreach (var typeArg in invocationOperation.TargetMethod.TypeArguments) | ||
{ | ||
if (forTypes.Contains(typeArg)) | ||
{ | ||
obj.ReportDiagnostic( | ||
Diagnostic.Create(UseNonGenericVariantDescriptor, | ||
invocationOperation.Syntax.GetLocation(), typeArg.Name)); | ||
} | ||
} | ||
} | ||
} |
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
18 changes: 18 additions & 0 deletions
18
Robust.Shared/Analyzers/PreferNonGenericVariantForAttribute.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,18 @@ | ||
using System; | ||
|
||
#if ROBUST_ANALYZERS_IMPL | ||
namespace Robust.Shared.Analyzers.Implementation; | ||
#else | ||
namespace Robust.Shared.Analyzers; | ||
#endif | ||
|
||
[AttributeUsage(AttributeTargets.Method)] | ||
public sealed class PreferNonGenericVariantForAttribute : Attribute | ||
{ | ||
public readonly Type[] ForTypes; | ||
|
||
public PreferNonGenericVariantForAttribute(params Type[] forTypes) | ||
{ | ||
ForTypes = forTypes; | ||
} | ||
} |
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