-
-
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 #44 from AArnott/fix38
Add `MessagePackConverterAttribute` to prescribe default custom converters for custom types
- Loading branch information
Showing
14 changed files
with
384 additions
and
9 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,37 @@ | ||
# NBMsgPack020: `[MessagePackConverter]` type must be compatible converter | ||
|
||
@Nerdbank.MessagePack.MessagePackConverterAttribute should specify a type that derives from @Nerdbank.MessagePack.MessagePackConverter`1 where the type argument is the type the attribute is applied to. | ||
|
||
Learn more about [custom converters](../docs/custom-converters.md). | ||
|
||
## Example violations | ||
|
||
```cs | ||
[MessagePackConverter(typeof(MyTypeConverter))] // MyTypeConverter does not derive from the correct base type | ||
public class MyType | ||
{ | ||
} | ||
|
||
public class MyTypeConverter | ||
{ | ||
public MyType Deserialize(ref MessagePackReader reader, SerializationContext context) => throw new System.NotImplementedException(); | ||
public void Serialize(ref MessagePackWriter writer, ref MyType value, SerializationContext context) => throw new System.NotImplementedException(); | ||
} | ||
``` | ||
|
||
## Resolution | ||
|
||
Fix the converter type to derive from @Nerdbank.MessagePack.MessagePackConverter`1. | ||
|
||
```cs | ||
[MessagePackConverter(typeof(MyTypeConverter)] | ||
class MyType | ||
{ | ||
} | ||
|
||
public class MyTypeConverter : MessagePackConverter<MyType> | ||
{ | ||
public override MyType Deserialize(ref MessagePackReader reader, SerializationContext context) => throw new System.NotImplementedException(); | ||
public override void Serialize(ref MessagePackWriter writer, ref MyType value, SerializationContext context) => throw new System.NotImplementedException(); | ||
} | ||
``` |
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,40 @@ | ||
# NBMsgPack021: `[MessagePackConverter]` type missing default constructor | ||
|
||
@Nerdbank.MessagePack.MessagePackConverterAttribute should specify a type that declares a public default constructor. | ||
|
||
Learn more about [custom converters](../docs/custom-converters.md). | ||
|
||
## Example violations | ||
|
||
In the below example, `MyTypeConverter` declares an explicit, *non-public* constructor. | ||
|
||
```cs | ||
[MessagePackConverter(typeof(MyTypeConverter))] | ||
public class MyType | ||
{ | ||
} | ||
|
||
public class MyTypeConverter : MessagePackConverter<MyType> | ||
{ | ||
private MyTypeConverter() { } | ||
public override MyType Deserialize(ref MessagePackReader reader, SerializationContext context) => throw new System.NotImplementedException(); | ||
public override void Serialize(ref MessagePackWriter writer, ref MyType value, SerializationContext context) => throw new System.NotImplementedException(); | ||
} | ||
``` | ||
|
||
## Resolution | ||
|
||
Fix the converter type to declare a public default constructor, or remove the explicit constructor so the C# language provides a default constructor. | ||
|
||
```cs | ||
[MessagePackConverter(typeof(MyTypeConverter))] | ||
public class MyType | ||
{ | ||
} | ||
|
||
public class MyTypeConverter : MessagePackConverter<MyType> | ||
{ | ||
public override MyType Deserialize(ref MessagePackReader reader, SerializationContext context) => throw new System.NotImplementedException(); | ||
public override void Serialize(ref MessagePackWriter writer, ref MyType value, SerializationContext context) => throw new System.NotImplementedException(); | ||
} | ||
``` |
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
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
79 changes: 79 additions & 0 deletions
79
src/Nerdbank.MessagePack.Analyzers/MessagePackConverterAttributeAnalyzer.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,79 @@ | ||
// 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.Analyzers; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public class MessagePackConverterAttributeAnalyzer : DiagnosticAnalyzer | ||
{ | ||
public const string InvalidConverterTypeDiagnosticId = "NBMsgPack020"; | ||
public const string ConverterMissingDefaultCtorDiagnosticId = "NBMsgPack021"; | ||
|
||
public static readonly DiagnosticDescriptor InvalidConverterTypeDescriptor = new( | ||
id: InvalidConverterTypeDiagnosticId, | ||
title: Strings.NBMsgPack020_Title, | ||
messageFormat: Strings.NBMsgPack020_MessageFormat, | ||
category: "Usage", | ||
defaultSeverity: DiagnosticSeverity.Error, | ||
isEnabledByDefault: true, | ||
helpLinkUri: AnalyzerUtilities.GetHelpLink(InvalidConverterTypeDiagnosticId)); | ||
|
||
public static readonly DiagnosticDescriptor ConverterMissingDefaultCtorDescriptor = new( | ||
id: ConverterMissingDefaultCtorDiagnosticId, | ||
title: Strings.NBMsgPack021_Title, | ||
messageFormat: Strings.NBMsgPack021_MessageFormat, | ||
category: "Usage", | ||
defaultSeverity: DiagnosticSeverity.Error, | ||
isEnabledByDefault: true, | ||
helpLinkUri: AnalyzerUtilities.GetHelpLink(ConverterMissingDefaultCtorDiagnosticId)); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [ | ||
InvalidConverterTypeDescriptor, | ||
ConverterMissingDefaultCtorDescriptor, | ||
]; | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); | ||
|
||
context.RegisterCompilationStartAction(context => | ||
{ | ||
if (!ReferenceSymbols.TryCreate(context.Compilation, out ReferenceSymbols? referenceSymbols)) | ||
{ | ||
return; | ||
} | ||
|
||
context.RegisterSymbolAction( | ||
context => | ||
{ | ||
var appliedType = (INamedTypeSymbol)context.Symbol; | ||
if (appliedType.FindAttributes(referenceSymbols.MessagePackConverterAttribute).FirstOrDefault() is not { } att) | ||
{ | ||
return; | ||
} | ||
|
||
if (att.ConstructorArguments is not [{ Value: INamedTypeSymbol converterType }]) | ||
{ | ||
return; | ||
} | ||
|
||
if (!converterType.IsDerivedFrom(referenceSymbols.MessagePackConverter.Construct(appliedType))) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create(InvalidConverterTypeDescriptor, GetArgumentLocation(0), appliedType.Name)); | ||
} | ||
else if (!converterType.InstanceConstructors.Any(c => c.Parameters.IsEmpty && c.DeclaredAccessibility == Accessibility.Public)) | ||
{ | ||
context.ReportDiagnostic(Diagnostic.Create(ConverterMissingDefaultCtorDescriptor, GetArgumentLocation(0), appliedType.Name)); | ||
} | ||
|
||
Location? GetArgumentLocation(int argumentIndex) | ||
{ | ||
return AnalyzerUtilities.GetArgumentLocation(att, argumentIndex, context.CancellationToken) | ||
?? appliedType.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax(context.CancellationToken).GetLocation(); | ||
} | ||
}, | ||
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
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 @@ | ||
// 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.Diagnostics.CodeAnalysis; | ||
|
||
namespace Nerdbank.MessagePack; | ||
|
||
/// <summary> | ||
/// A class applied to a custom data type to prescribe a custom <see cref="MessagePackConverter{T}"/> | ||
/// implementation to use for serialization. | ||
/// </summary> | ||
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] | ||
public class MessagePackConverterAttribute : Attribute | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="MessagePackConverterAttribute"/> class. | ||
/// </summary> | ||
/// <param name="converterType"> | ||
/// A type that implements <see cref="MessagePackConverter{T}"/> | ||
/// where <c>T</c> is a type argument matching the type to which this attribute is applied. | ||
/// </param> | ||
public MessagePackConverterAttribute(Type converterType) | ||
{ | ||
this.ConverterType = converterType; | ||
} | ||
|
||
/// <summary> | ||
/// Gets the type that implements <see cref="MessagePackConverter{T}"/>. | ||
/// </summary> | ||
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] | ||
public Type ConverterType { get; } | ||
} |
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
Oops, something went wrong.