FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Offer code fix for MsgPack011: `partial` modifier required by AArnott · Pull Request #1893 · MessagePack-CSharp/MessagePack-CSharp · GitHub

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .cs  (10) .md  (1) All 2 file types selected
Deleted files Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
8 changes: 4 additions & 4 deletions sandbox/SharedData/Class1.cs
This file contains hidden or 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
Expand Up @@ -100,9 +100,9 @@ public void Serialize(ref MessagePackWriter writer, int value, MessagePackSerial
}
}

#pragma warning disable MsgPack010 // no default constructor
#pragma warning disable MsgPack013 // no default constructor
public class OreOreFormatter2 : IMessagePackFormatter<ulong>
#pragma warning restore MsgPack010 // no default constructor
#pragma warning restore MsgPack013 // no default constructor
{
public OreOreFormatter2(int x, string y)
{
Expand Down Expand Up @@ -844,9 +844,9 @@ public DynamicArgumentTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6
}
}

#pragma warning disable MsgPack010 // no default constructor
#pragma warning disable MsgPack013 // no default constructor
public class DynamicArgumentTupleFormatter<T1, T2, T3, T4, T5, T6, T7, T8, T9> : IMessagePackFormatter<DynamicArgumentTuple<T1, T2, T3, T4, T5, T6, T7, T8, T9>>
#pragma warning restore MsgPack010 // no default constructor
#pragma warning restore MsgPack013 // no default constructor
{
private readonly T1 default1;
private readonly T2 default2;
Expand Down
This file contains hidden or 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,148 @@
// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace MessagePack.Analyzers.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SimpleBaseTypeSyntax)), Shared]
public class FormatterCodeFixProvider : CodeFixProvider
{
private static readonly ImmutableArray<string> FixableIds = ImmutableArray.Create(
MsgPack00xMessagePackAnalyzer.InaccessibleFormatterTypeId,
MsgPack00xMessagePackAnalyzer.InaccessibleFormatterInstanceId,
MsgPack00xMessagePackAnalyzer.PartialTypeRequired.Id);

public sealed override ImmutableArray<string> FixableDiagnosticIds => FixableIds;

public sealed override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (Diagnostic diagnostic in context.Diagnostics)
{
SyntaxNode? syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken);
BaseTypeDeclarationSyntax? typeDecl = syntaxRoot?.FindNode(diagnostic.Location.SourceSpan) as BaseTypeDeclarationSyntax;
if (syntaxRoot is not null && typeDecl is not null)
{
switch (diagnostic.Id)
{
case MsgPack00xMessagePackAnalyzer.PartialTypeRequiredId:
context.RegisterCodeFix(
CodeAction.Create(
"Add partial modifier",
ct => AddPartialModifierAsync(context.Document, syntaxRoot, typeDecl, diagnostic, ct),
"AddPartialModifier"),
diagnostic);

break;
case MsgPack00xMessagePackAnalyzer.InaccessibleFormatterTypeId:
context.RegisterCodeFix(
CodeAction.Create(
"Make type internal",
ct => ExposeMemberInternally(context.Document, syntaxRoot, typeDecl, diagnostic, ct),
"MakeTypeInternal"),
diagnostic);

break;
case MsgPack00xMessagePackAnalyzer.InaccessibleFormatterInstanceId:
SemanticModel? semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken);
if (semanticModel?.GetDeclaredSymbol(typeDecl, context.CancellationToken) is INamedTypeSymbol typeSymbol)
{
IMethodSymbol? defaultCtor = typeSymbol.InstanceConstructors.FirstOrDefault(c => c.Parameters.Length == 0);
if (defaultCtor is { DeclaringSyntaxReferences: { Length: > 0 } })
{
MemberDeclarationSyntax? ctorSyntax = defaultCtor.DeclaringSyntaxReferences[0].GetSyntax(context.CancellationToken) as MemberDeclarationSyntax;
if (ctorSyntax is not null)
{
syntaxRoot = await defaultCtor.DeclaringSyntaxReferences[0].SyntaxTree.GetRootAsync(context.CancellationToken);
context.RegisterCodeFix(
CodeAction.Create(
"Make constructor internal",
ct => ExposeMemberInternally(context.Document, syntaxRoot, ctorSyntax, diagnostic, context.CancellationToken),
"MakeCtorInternal"),
diagnostic);
}
}
}

break;
}
}
}
}

private static Task<Document> AddPartialModifierAsync(Document document, SyntaxNode syntaxRoot, BaseTypeDeclarationSyntax typeDecl, Diagnostic diagnostic, CancellationToken cancellationToken)
{
SyntaxNode? modifiedSyntax = syntaxRoot.ReplaceNode(
typeDecl,
typeDecl.AddModifiers(SyntaxFactory.Token(SyntaxKind.PartialKeyword)));
document = document.WithSyntaxRoot(modifiedSyntax);
return Task.FromResult(document);
}

private static Task<Document> ExposeMemberInternally(Document document, SyntaxNode syntaxRoot, MemberDeclarationSyntax memberDecl, Diagnostic diagnostic, CancellationToken ct)
{
SyntaxNode? modifiedSyntax = syntaxRoot.ReplaceNode(memberDecl, memberDecl.WithModifiers(AddInternalVisibility(memberDecl.Modifiers)));
document = document.WithSyntaxRoot(modifiedSyntax);
return Task.FromResult(document);
}

private static SyntaxTokenList AddInternalVisibility(SyntaxTokenList modifiers)
{
SyntaxToken internalKeywordToken = SyntaxFactory.Token(SyntaxKind.InternalKeyword);

int privateIndex = -1, protectedIndex = -1, internalIndex = -1, publicIndex = -1;
for (int i = 0; i < modifiers.Count; i++)
{
switch (modifiers[i].Kind())
{
case SyntaxKind.PrivateKeyword:
privateIndex = i;
break;
case SyntaxKind.ProtectedKeyword:
protectedIndex = i;
break;
case SyntaxKind.InternalKeyword:
internalIndex = i;
break;
case SyntaxKind.PublicKeyword:
publicIndex = i;
break;
}
}

if (internalIndex != -1)
{
// Nothing to do.
return modifiers;
}

if (privateIndex != -1 && protectedIndex != -1)
{
// Upgrade private protected to internal.
SyntaxTokenList newModifiers = privateIndex < protectedIndex
? modifiers.RemoveAt(protectedIndex).RemoveAt(privateIndex)
: modifiers.RemoveAt(privateIndex).RemoveAt(protectedIndex);
return newModifiers.Insert(0, internalKeywordToken);
}

if (protectedIndex != -1)
{
// upgrade to protected internal
return modifiers.Insert(protectedIndex + 1, internalKeywordToken);
}

if (privateIndex != -1)
{
return ReplaceModifierInList(privateIndex);
}

// No visibility keywords exist. Add "internal".
return modifiers.Insert(0, internalKeywordToken);

SyntaxToken ReplaceModifier(SyntaxToken original) => internalKeywordToken.WithLeadingTrivia(original.LeadingTrivia).WithTrailingTrivia(original.TrailingTrivia);
SyntaxTokenList ReplaceModifierInList(int index) => modifiers.Replace(modifiers[index], ReplaceModifier(modifiers[index]));
}
}
This file contains hidden or 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
@@ -1,17 +1,6 @@
// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Immutable;
using System.Composition;
using MessagePack.SourceGenerator;
using MessagePack.SourceGenerator.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;

namespace MessagePack.Analyzers.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(MessagePackCodeFixProvider)), Shared]
Expand Down
13 changes: 13 additions & 0 deletions src/MessagePack.Analyzers.CodeFixes/Usings.cs
This file contains hidden or 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) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

global using System.Collections.Immutable;
global using System.Composition;
global using MessagePack.SourceGenerator;
global using MessagePack.SourceGenerator.Analyzers;
global using Microsoft.CodeAnalysis;
global using Microsoft.CodeAnalysis.CodeActions;
global using Microsoft.CodeAnalysis.CodeFixes;
global using Microsoft.CodeAnalysis.CSharp;
global using Microsoft.CodeAnalysis.CSharp.Syntax;
global using Microsoft.CodeAnalysis.Editing;
This file contains hidden or 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
Expand Up @@ -14,6 +14,7 @@ MsgPack006 | Usage | Error | MsgPack00xMessagePackAnalyzer
MsgPack007 | Usage | Error | MsgPack00xMessagePackAnalyzer
MsgPack008 | Usage | Error | MsgPack00xMessagePackAnalyzer
MsgPack009 | Usage | Error | MsgPack00xMessagePackAnalyzer
MsgPack010 | Usage | Warning | MsgPack00xMessagePackAnalyzer
MsgPack010 | Usage | Warning | Formatter is not accessible to the source generated resolver
MsgPack011 | Usage | Error | MsgPack00xMessagePackAnalyzer
MsgPack012 | Usage | Error | MsgPack00xMessagePackAnalyzer
MsgPack012 | Usage | Error | MsgPack00xMessagePackAnalyzer
MsgPack013 | Usage | Warning | Formatter has no accessible instance for the source generated resolver
This file contains hidden or 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
Expand Up @@ -18,9 +18,10 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
public const string DeserializingConstructorId = "MsgPack007";
public const string AOTLimitationsId = "MsgPack008";
public const string CollidingFormattersId = "MsgPack009";
public const string InaccessibleFormatterId = "MsgPack010";
public const string InaccessibleFormatterTypeId = "MsgPack010";
public const string PartialTypeRequiredId = "MsgPack011";
public const string InaccessibleDataTypeId = "MsgPack012";
public const string InaccessibleFormatterInstanceId = "MsgPack013";

internal const string Category = "Usage";

Expand Down Expand Up @@ -215,24 +216,24 @@ public class MsgPack00xMessagePackAnalyzer : DiagnosticAnalyzer
helpLinkUri: AnalyzerUtilities.GetHelpLink(CollidingFormattersId));

public static readonly DiagnosticDescriptor InaccessibleFormatterInstance = new(
id: InaccessibleFormatterId,
id: InaccessibleFormatterInstanceId,
title: "Inaccessible formatter",
category: Category,
messageFormat: "Formatter should declare a default constructor with at least internal visibility or a public static readonly field named Instance that returns the singleton",
description: "The auto-generated resolver cannot construct this formatter without a constructor. It will be omitted from the resolver.",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: AnalyzerUtilities.GetHelpLink(InaccessibleFormatterId));
helpLinkUri: AnalyzerUtilities.GetHelpLink(InaccessibleFormatterInstanceId));

public static readonly DiagnosticDescriptor InaccessibleFormatterType = new(
id: InaccessibleFormatterId,
id: InaccessibleFormatterTypeId,
title: "Inaccessible formatter",
category: Category,
messageFormat: "Formatter should be declared with at least internal visibility",
description: "The auto-generated resolver cannot access this formatter. It will be omitted from the resolver.",
defaultSeverity: DiagnosticSeverity.Warning,
isEnabledByDefault: true,
helpLinkUri: AnalyzerUtilities.GetHelpLink(InaccessibleFormatterId));
helpLinkUri: AnalyzerUtilities.GetHelpLink(InaccessibleFormatterTypeId));

public static readonly DiagnosticDescriptor PartialTypeRequired = new(
id: PartialTypeRequiredId,
Expand Down
This file contains hidden or 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
Expand Up @@ -42,7 +42,7 @@ public static string GetSanitizedFileName(string fileName)
internal static IEnumerable<BaseTypeDeclarationSyntax> FindInaccessibleTypes(ITypeSymbol target)
{
return from x in EnumerateTypeAndContainingTypes(target)
where x.Symbol.DeclaredAccessibility is not (Accessibility.Public or Accessibility.Internal)
where x.Symbol.DeclaredAccessibility is not (Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrFriend or Accessibility.ProtectedOrInternal)
select x.FirstDeclaration;
}

Expand Down
This file contains hidden or 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,101 @@
// Copyright (c) All contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using MessagePack.SourceGenerator.Analyzers;
using Microsoft.CodeAnalysis.Testing;
using VerifyCS = CSharpCodeFixVerifier<MessagePack.SourceGenerator.Analyzers.MsgPack00xMessagePackAnalyzer, MessagePack.Analyzers.CodeFixes.FormatterCodeFixProvider>;

public class FormatterCodeFixProviderTests
{
[Theory]
[InlineData("", "internal ")]
[InlineData("private ", "internal ")]
[InlineData("protected ", "protected internal ")]
[InlineData("private protected ", "internal ")]
public async Task InaccessibleCustomFormatterType(string initialModifiers, string expectedModifiers)
{
string testSource = $$"""
using MessagePack;
using MessagePack.Formatters;
class A {
{{initialModifiers}}class {|#0:F|} : IMessagePackFormatter<A> {
public void Serialize(ref MessagePackWriter writer, A value, MessagePackSerializerOptions options) {}
public A Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) => default;
}
}
""";
string fixedSource = $$"""
using MessagePack;
using MessagePack.Formatters;
class A {
{{expectedModifiers}}class F : IMessagePackFormatter<A> {
public void Serialize(ref MessagePackWriter writer, A value, MessagePackSerializerOptions options) {}
public A Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) => default;
}
}
""";
await new VerifyCS.Test()
{
TestState =
{
Sources = { testSource },
ExpectedDiagnostics =
{
new DiagnosticResult(MsgPack00xMessagePackAnalyzer.InaccessibleFormatterType).WithLocation(0),
},
},
FixedState =
{
Sources = { fixedSource },
InheritanceMode = StateInheritanceMode.Explicit,
},
}.RunAsync();
}

[Theory]
[InlineData("", "internal ")]
[InlineData("private ", "internal ")]
[InlineData("protected ", "protected internal ")]
[InlineData("private protected ", "internal ")]
public async Task InaccessibleCustomFormatterConstructor(string initialModifiers, string expectedModifiers)
{
string testSource = $$"""
using MessagePack;
using MessagePack.Formatters;
class A {
internal class {|#0:F|} : IMessagePackFormatter<A> {
{{initialModifiers}}F() {}
public void Serialize(ref MessagePackWriter writer, A value, MessagePackSerializerOptions options) {}
public A Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) => default;
}
}
""";
string fixedSource = $$"""
using MessagePack;
using MessagePack.Formatters;
class A {
internal class F : IMessagePackFormatter<A> {
{{expectedModifiers}}F() {}
public void Serialize(ref MessagePackWriter writer, A value, MessagePackSerializerOptions options) {}
public A Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) => default;
}
}
""";
await new VerifyCS.Test()
{
TestState =
{
Sources = { testSource },
ExpectedDiagnostics =
{
new DiagnosticResult(MsgPack00xMessagePackAnalyzer.InaccessibleFormatterInstance).WithLocation(0),
},
},
FixedState =
{
Sources = { fixedSource },
InheritanceMode = StateInheritanceMode.Explicit,
},
}.RunAsync();
}
}
Loading

Back | FazBrowse Home | New Git URL