-
-
Notifications
You must be signed in to change notification settings - Fork 4
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 #227 from AArnott/fix185
Add NBMsgPack051 analyzer
- Loading branch information
Showing
12 changed files
with
282 additions
and
10 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
# NBMsgPack051: Prefer modern .NET APIs | ||
|
||
Some APIs exist to support targeting .NET Standard or .NET Framework, while other APIs are available when targeting .NET that are far superior. | ||
This diagnostic is reported when code uses the lesser API while the preferred API is available. | ||
|
||
The diagnostic message will direct you to the preferred API. | ||
|
||
In multi-targeting projects where switching to the preferred API is inadvisable because it would break the build for older target frameworks or require the use of `#if` sections, you may suppress this warning. | ||
|
||
## Example violation | ||
|
||
The following type is declared using the non-generic @Nerdbank.MessagePack.KnownSubTypeAttribute. | ||
This is fine for projects that target .NET Standard or .NET Framework, but if the project targets .NET a warning will be emitted. | ||
|
||
[!code-csharp[](../../samples/AnalyzerDocs/NBMsgPack051.cs#Defective)] | ||
|
||
## Resolution | ||
|
||
Per the message in the warning, switch to the generic attribute: | ||
|
||
[!code-csharp[](../../samples/AnalyzerDocs/NBMsgPack051.cs#SwitchFix)] | ||
|
||
Or in a multitargeting project, use the preferred API only where it's available: | ||
|
||
[!code-csharp[](../../samples/AnalyzerDocs/NBMsgPack051.cs#MultiTargetingFix)] | ||
|
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
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,58 @@ | ||
// Copyright (c) Andrew Arnott. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
#pragma warning disable NBMsgPackAsync | ||
|
||
#if NET | ||
|
||
namespace Samples.AnalyzerDocs.NBMsgPack051 | ||
{ | ||
namespace Defective | ||
{ | ||
#pragma warning disable NBMsgPack051 | ||
#region Defective | ||
[KnownSubType(typeof(MyDerived))] // NBMsgPack051: Use the generic version of this attribute instead. | ||
class MyType { } | ||
|
||
[GenerateShape] | ||
partial class MyDerived : MyType | ||
{ | ||
} | ||
#endregion | ||
#pragma warning restore NBMsgPack051 | ||
} | ||
|
||
namespace SwitchFix | ||
{ | ||
#region SwitchFix | ||
[KnownSubType<MyDerived>] | ||
class MyType { } | ||
|
||
[GenerateShape] | ||
partial class MyDerived : MyType | ||
{ | ||
} | ||
#endregion | ||
} | ||
|
||
namespace MultiTargetingFix | ||
{ | ||
#region MultiTargetingFix | ||
#if NET | ||
[KnownSubType<MyDerived>] | ||
#else | ||
[KnownSubType(typeof(MyDerived))] | ||
#endif | ||
class MyType { } | ||
|
||
#if NET | ||
[GenerateShape] | ||
#endif | ||
partial class MyDerived : MyType | ||
{ | ||
} | ||
#endregion | ||
} | ||
} | ||
|
||
#endif |
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
107 changes: 107 additions & 0 deletions
107
src/Nerdbank.MessagePack.Analyzers/DotNetApiUsageAnalyzer.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,107 @@ | ||
// Copyright (c) Andrew Arnott. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using System.Collections.Concurrent; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Operations; | ||
|
||
namespace Nerdbank.MessagePack.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class DotNetApiUsageAnalyzer : DiagnosticAnalyzer | ||
{ | ||
public const string UseDotNetApiDiagnosticId = "NBMsgPack051"; | ||
|
||
public static readonly DiagnosticDescriptor UseDotNetApiDescriptor = new( | ||
id: UseDotNetApiDiagnosticId, | ||
title: Strings.NBMsgPack051_Title, | ||
messageFormat: Strings.NBMsgPack051_MessageFormat, | ||
category: "Usage", | ||
defaultSeverity: DiagnosticSeverity.Warning, | ||
isEnabledByDefault: true, | ||
helpLinkUri: AnalyzerUtilities.GetHelpLink(UseDotNetApiDiagnosticId)); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [UseDotNetApiDescriptor]; | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); | ||
|
||
context.RegisterCompilationStartAction(context => | ||
{ | ||
if (!ReferenceSymbols.TryCreate(context.Compilation, out ReferenceSymbols? referenceSymbols)) | ||
{ | ||
return; | ||
} | ||
|
||
ConcurrentDictionary<ISymbol, string?> obsoleteMessage = new(SymbolEqualityComparer.Default); | ||
context.RegisterOperationAction( | ||
context => | ||
{ | ||
Location? location = null; | ||
ISymbol? symbol = context.Operation switch | ||
{ | ||
IInvocationOperation invocation => invocation.TargetMethod, | ||
IAttributeOperation attribute => attribute.Type, | ||
_ => null, | ||
}; | ||
|
||
if (symbol is null) | ||
{ | ||
return; | ||
} | ||
|
||
if (!obsoleteMessage.TryGetValue(symbol, out string? message)) | ||
{ | ||
message = symbol.FindAttributes(Constants.PreferDotNetAlternativeApiAttribute.TypeName, Constants.PreferDotNetAlternativeApiAttribute.Namespace) | ||
.FirstOrDefault() | ||
?.ConstructorArguments.FirstOrDefault().Value as string; | ||
obsoleteMessage.TryAdd(symbol, message); | ||
} | ||
|
||
if (message is not null) | ||
{ | ||
location ??= context.Operation.Syntax.GetLocation(); | ||
context.ReportDiagnostic(Diagnostic.Create(UseDotNetApiDescriptor, location, message)); | ||
} | ||
}, | ||
OperationKind.Invocation | OperationKind.Attribute); | ||
|
||
context.RegisterSymbolAction( | ||
context => | ||
{ | ||
if (context.Symbol is not INamedTypeSymbol declaredType) | ||
{ | ||
return; | ||
} | ||
|
||
foreach (AttributeData att in declaredType.GetAttributes()) | ||
{ | ||
if (att.AttributeClass is null) | ||
{ | ||
continue; | ||
} | ||
|
||
if (!obsoleteMessage.TryGetValue(att.AttributeClass, out string? message)) | ||
{ | ||
message = att.AttributeClass.FindAttributes(Constants.PreferDotNetAlternativeApiAttribute.TypeName, Constants.PreferDotNetAlternativeApiAttribute.Namespace) | ||
.FirstOrDefault() | ||
?.ConstructorArguments.FirstOrDefault().Value as string; | ||
obsoleteMessage.TryAdd(att.AttributeClass, message); | ||
} | ||
|
||
if (message is not null) | ||
{ | ||
Location? location = att.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken).GetLocation(); | ||
if (location is not null) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create(UseDotNetApiDescriptor, location, message)); | ||
} | ||
} | ||
} | ||
}, | ||
SymbolKind.NamedType); | ||
}); | ||
} | ||
} |
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
13 changes: 13 additions & 0 deletions
13
src/Nerdbank.MessagePack/PreferDotNetAlternativeApiAttribute.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,13 @@ | ||
// Copyright (c) Andrew Arnott. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
namespace Nerdbank.MessagePack; | ||
|
||
/// <summary> | ||
/// Indicates that the decorated type or member has a .NET alternative that is preferred over the decorated API. | ||
/// </summary> | ||
/// <param name="advice">The message for the diagnostic to be created.</param> | ||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property)] | ||
#pragma warning disable CS9113 // Parameter is unread. | ||
internal class PreferDotNetAlternativeApiAttribute(string advice) : Attribute; | ||
#pragma warning restore CS9113 // Parameter is unread. |
58 changes: 58 additions & 0 deletions
58
test/Nerdbank.MessagePack.Analyzers.Tests/DotNetApiUsageAnalyzerTests.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,58 @@ | ||
// Copyright (c) Andrew Arnott. All rights reserved. | ||
// Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
||
using VerifyCS = CodeFixVerifier<Nerdbank.MessagePack.Analyzers.DotNetApiUsageAnalyzer, Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; | ||
|
||
public class DotNetApiUsageAnalyzerTests | ||
{ | ||
[Fact] | ||
public async Task KnownSubTypeAttribute() | ||
{ | ||
#if NET | ||
string source = /* lang=c#-test */ """ | ||
using PolyType; | ||
using PolyType.Abstractions; | ||
using Nerdbank.MessagePack; | ||
[{|NBMsgPack051:KnownSubTypeAttribute(typeof(MyDerived))|}] | ||
class MyType { } | ||
partial class MyDerived : MyType, IShapeable<MyDerived> | ||
{ | ||
public static ITypeShape<MyDerived> GetShape() => throw new System.NotImplementedException(); | ||
} | ||
"""; | ||
#else | ||
string source = /* lang=c#-test */ """ | ||
using Nerdbank.MessagePack; | ||
[KnownSubTypeAttribute(typeof(MyDerived))] | ||
class MyType { } | ||
class MyDerived : MyType { } | ||
"""; | ||
#endif | ||
await VerifyCS.VerifyAnalyzerAsync(source); | ||
} | ||
|
||
#if NET | ||
[Fact] | ||
public async Task KnownSubTypeGenericAttribute() | ||
{ | ||
string source = /* lang=c#-test */ """ | ||
using PolyType; | ||
using PolyType.Abstractions; | ||
using Nerdbank.MessagePack; | ||
[KnownSubTypeAttribute<MyDerived>] | ||
class MyType { } | ||
partial class MyDerived : MyType, IShapeable<MyDerived> | ||
{ | ||
public static ITypeShape<MyDerived> GetShape() => throw new System.NotImplementedException(); | ||
} | ||
"""; | ||
await VerifyCS.VerifyAnalyzerAsync(source); | ||
} | ||
#endif | ||
} |