提交 7b1e026f 编写于 作者: S Srivatsn Narayanan

Adding a code fixer for CA1019. Using the new property bag on diagnostics to...

Adding a code fixer for CA1019. Using the new property bag on diagnostics to communicate from the diagnostic about which case needs fixing. Also using SyntaxGenerator to produce fixes in the base layer. However there are a couple of bugs there and so I've disabled the tests with those bugs #662 and #679.
上级 a9051d3d
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
namespace System.Runtime.Analyzers
{
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
public class DefineAccessorsForAttributeArgumentsFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(DefineAccessorsForAttributeArgumentsAnalyzer.RuleId);
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var generator = SyntaxGenerator.GetGenerator(context.Document);
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var node = root.FindNode(context.Span);
// We cannot have multiple overlapping diagnostics of this id.
var diagnostic = context.Diagnostics.Single();
string fixCase;
if (diagnostic.Properties.TryGetValue("case", out fixCase))
{
switch (fixCase)
{
case DefineAccessorsForAttributeArgumentsAnalyzer.AddAccessorCase:
var parameter = generator.GetDeclaration(node, DeclarationKind.Parameter);
if (parameter != null)
{
context.RegisterCodeFix(new MyCodeAction(SystemRuntimeAnalyzersResources.DefineAccessorsForAttributeArguments,
async ct => await AddAccessor(context.Document, parameter, ct).ConfigureAwait(false)),
diagnostic);
}
return;
case DefineAccessorsForAttributeArgumentsAnalyzer.MakePublicCase:
context.RegisterCodeFix(new MyCodeAction(SystemRuntimeAnalyzersResources.DefineAccessorsForAttributeArguments,
async ct => await MakePublic(context.Document, node, ct).ConfigureAwait(false)),
diagnostic);
return;
case DefineAccessorsForAttributeArgumentsAnalyzer.RemoveSetterCase:
context.RegisterCodeFix(new MyCodeAction(SystemRuntimeAnalyzersResources.DefineAccessorsForAttributeArguments,
async ct => await RemoveSetter(context.Document, node, ct).ConfigureAwait(false)),
diagnostic);
return;
default:
return;
}
}
}
private async Task<Document> AddAccessor(Document document, SyntaxNode parameter, CancellationToken cancellationToken)
{
SymbolEditor symbolEditor = SymbolEditor.Create(document);
var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var parameterSymbol = model.GetDeclaredSymbol(parameter, cancellationToken) as IParameterSymbol;
if (parameterSymbol == null)
{
return document;
}
// Make the first character uppercase since we are generating a property.
var propName = char.ToUpper(parameterSymbol.Name[0]).ToString() + parameterSymbol.Name.Substring(1);
var typeSymbol = parameterSymbol.ContainingType;
var propertySymbol = typeSymbol.GetMembers(propName).Where(m => m.Kind == SymbolKind.Property).FirstOrDefault();
// Add a new property
if (propertySymbol == null)
{
await symbolEditor.EditOneDeclarationAsync(typeSymbol,
parameter.GetLocation(), // edit the partial declaration that has this parameter symbol.
(editor, typeDeclaration) =>
{
var newProperty = editor.Generator.PropertyDeclaration(propName,
editor.Generator.TypeExpression(parameterSymbol.Type),
Accessibility.Public,
DeclarationModifiers.ReadOnly);
newProperty = editor.Generator.WithGetAccessorStatements(newProperty, null);
editor.AddMember(typeDeclaration, newProperty);
},
cancellationToken).ConfigureAwait(false);
}
else
{
await symbolEditor.EditOneDeclarationAsync(propertySymbol,
(editor, propertyDeclaration) =>
{
editor.SetGetAccessorStatements(propertyDeclaration, null);
},
cancellationToken).ConfigureAwait(false);
}
return symbolEditor.GetChangedDocuments().First();
}
private async Task<Document> MakePublic(Document document, SyntaxNode getMethod, CancellationToken cancellationToken)
{
DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
editor.SetAccessibility(getMethod, Accessibility.Public);
return editor.GetChangedDocument();
}
private async Task<Document> RemoveSetter(Document document, SyntaxNode setMethod, CancellationToken cancellationToken)
{
DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
editor.SetAccessibility(setMethod, Accessibility.Internal);
return editor.GetChangedDocument();
}
private class MyCodeAction : DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
}
}
}
}
......@@ -19,6 +19,9 @@ namespace System.Runtime.Analyzers
public abstract class DefineAccessorsForAttributeArgumentsAnalyzer : DiagnosticAnalyzer
{
internal const string RuleId = "CA1019";
internal const string AddAccessorCase = "AddAccessor";
internal const string MakePublicCase = "MakePublic";
internal const string RemoveSetterCase = "RemoveSetter";
private static LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(SystemRuntimeAnalyzersResources.DefineAccessorsForAttributeArguments), SystemRuntimeAnalyzersResources.ResourceManager, typeof(SystemRuntimeAnalyzersResources));
internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor(RuleId,
......@@ -158,21 +161,21 @@ private static Diagnostic GetDefaultDiagnostic(IParameterSymbol parameter, IName
{
// Add a public read-only property accessor for positional argument '{0}' of attribute '{1}'.
var message = string.Format(SystemRuntimeAnalyzersResources.DefineAccessorsForAttributeArgumentsDefault, parameter.Name, attributeType.Name);
return parameter.CreateDiagnostic(Rule, message);
return parameter.Locations.CreateDiagnostic(Rule, new Dictionary<string, string> { { "case", AddAccessorCase } }.ToImmutableDictionary(), message);
}
private static Diagnostic GetIncreaseVisibilityDiagnostic(IParameterSymbol parameter, IPropertySymbol property)
{
// If '{0}' is the property accessor for positional argument '{1}', make it public.
var message = string.Format(SystemRuntimeAnalyzersResources.DefineAccessorsForAttributeArgumentsIncreaseVisibility, property.Name, parameter.Name);
return property.GetMethod.CreateDiagnostic(Rule, message);
return property.GetMethod.Locations.CreateDiagnostic(Rule, new Dictionary<string, string> { { "case", MakePublicCase } }.ToImmutableDictionary(), message);
}
private static Diagnostic GetRemoveSetterDiagnostic(IParameterSymbol parameter, IPropertySymbol property)
{
// Remove the property setter from '{0}' or reduce its accessibility because it corresponds to positional argument '{1}'.
var message = string.Format(SystemRuntimeAnalyzersResources.DefineAccessorsForAttributeArgumentsRemoveSetter, property.Name, parameter.Name);
return property.SetMethod.CreateDiagnostic(Rule, message);
return property.SetMethod.Locations.CreateDiagnostic(Rule, new Dictionary<string, string> { { "case", RemoveSetterCase } }.ToImmutableDictionary(), message);
}
}
}
......@@ -21,15 +21,7 @@ public class TypesThatOwnDisposableFieldsShouldBeDisposableFixer : CodeFixProvid
protected const string NotImplementedExceptionName = "System.NotImplementedException";
protected const string IDisposableName = "System.IDisposable";
public sealed override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(TypesThatOwnDisposableFieldsShouldBeDisposableAnalyzer.RuleId); }
}
protected string GetCodeFixDescription(Diagnostic diagnostic)
{
return SystemRuntimeAnalyzersResources.ImplementIDisposableInterface;
}
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(TypesThatOwnDisposableFieldsShouldBeDisposableAnalyzer.RuleId);
public async override Task RegisterCodeFixesAsync(CodeFixContext context)
{
......@@ -47,7 +39,7 @@ public async override Task RegisterCodeFixesAsync(CodeFixContext context)
// We cannot have multiple overlapping diagnostics of this id.
var diagnostic = context.Diagnostics.Single();
context.RegisterCodeFix(new DocumentChangeAction(SystemRuntimeAnalyzersResources.ImplementIDisposableInterface,
context.RegisterCodeFix(new MyCodeAction(SystemRuntimeAnalyzersResources.ImplementIDisposableInterface,
async ct => await ImplementIDisposable(context.Document, declaration, ct).ConfigureAwait(false)),
diagnostic);
}
......@@ -81,5 +73,13 @@ private async Task<Document> ImplementIDisposable(Document document, SyntaxNode
return editor.GetChangedDocument();
}
private class MyCodeAction : DocumentChangeAction
{
public MyCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument)
: base(title, createChangedDocument)
{
}
}
}
}
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
......@@ -84,17 +85,6 @@ internal static class DiagnosticExtensions
return symbol.Locations.CreateDiagnostic(rule, args);
}
public static IEnumerable<Diagnostic> CreateDiagnostics(
this IEnumerable<Location> locations,
DiagnosticDescriptor rule,
params object[] args)
{
foreach (var location in locations)
{
yield return location.CreateDiagnostic(rule, args);
}
}
public static Diagnostic CreateDiagnostic(
this Location location,
DiagnosticDescriptor rule,
......@@ -131,5 +121,20 @@ internal static class DiagnosticExtensions
additionalLocations: additionalLocations,
messageArgs: args);
}
public static Diagnostic CreateDiagnostic(
this IEnumerable<Location> locations,
DiagnosticDescriptor rule,
ImmutableDictionary<string, string> properties,
params object[] args)
{
var location = locations.First(l => l.IsInSource);
var additionalLocations = locations.Where(l => l.IsInSource).Skip(1);
return Diagnostic.Create(rule,
location: location,
additionalLocations: additionalLocations,
properties: properties,
messageArgs: args);
}
}
}
......@@ -54,6 +54,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="Design\DefineAccessorsForAttributeArguments.cs" />
<Compile Include="Design\DefineAccessorsForAttributeArguments.Fixer.cs" />
<Compile Include="Design\MarkAttributesWithAttributeUsage.cs" />
<Compile Include="Design\TypesThatOwnDisposableFieldsShouldBeDisposable.Fixer.cs" />
<Compile Include="Design\TypesThatOwnDisposableFieldsShouldBeDisposable.cs" />
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.UnitTests;
using Xunit;
namespace System.Runtime.Analyzers.UnitTests
{
public partial class DefineAccessorsForAttributeArgumentsTests : CodeFixTestBase
{
protected override CodeFixProvider GetCSharpCodeFixProvider()
{
return new DefineAccessorsForAttributeArgumentsFixer();
}
protected override CodeFixProvider GetBasicCodeFixProvider()
{
return new DefineAccessorsForAttributeArgumentsFixer();
}
[Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void CSharp_CA1019_AddAccessor()
{
VerifyCSharpFix(@"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class NoAccessorTestAttribute : Attribute
{
private string m_name;
public NoAccessorTestAttribute(string name)
{
m_name = name;
}
}", @"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class NoAccessorTestAttribute : Attribute
{
private string m_name;
public NoAccessorTestAttribute(string name)
{
m_name = name;
}
public string Name { get; }
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void CSharp_CA1019_AddAccessor1()
{
VerifyCSharpFix(@"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class SetterOnlyTestAttribute : Attribute
{
private string m_name;
public SetterOnlyTestAttribute(string name)
{
m_name = name;
}
public string Name
{
set { m_name = value }
}
}", @"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class SetterOnlyTestAttribute : Attribute
{
private string m_name;
public SetterOnlyTestAttribute(string name)
{
m_name = name;
}
public string Name
{
set { m_name = value }
get;
}
}", allowNewCompilerDiagnostics: true);
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/662"), Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void CSharp_CA1019_MakeGetterPublic()
{
VerifyCSharpFix(@"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class InternalGetterTestAttribute : Attribute
{
private string m_name;
public InternalGetterTestAttribute(string name)
{
m_name = name;
}
public string Name
{
internal get { return m_name; }
set { m_name = value; }
}
}", @"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class InternalGetterTestAttribute : Attribute
{
private string m_name;
public InternalGetterTestAttribute(string name)
{
m_name = name;
}
public string Name
{
get { return m_name; }
set { m_name = value; }
}
}");
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/662"), Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void CSharp_CA1019_MakeGetterPublic2()
{
VerifyCSharpFix(@"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class InternalGetterTestAttribute : Attribute
{
private string m_name;
public InternalGetterTestAttribute(string name)
{
m_name = name;
}
internal string Name
{
get { return m_name; }
set { m_name = value; }
}
}", @"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class InternalGetterTestAttribute : Attribute
{
private string m_name;
public InternalGetterTestAttribute(string name)
{
m_name = name;
}
public string Name
{
get { return m_name; }
set { m_name = value; }
}
}");
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/662"), Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void CSharp_CA1019_MakeSetterInternal()
{
VerifyCSharpFix(@"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class PublicSetterTestAttribute : Attribute
{
private string m_name;
public PublicSetterTestAttribute(string name)
{
m_name = name;
}
public string Name
{
get { return m_name; }
set { m_name = value; }
}
}", @"
using System;
[AttributeUsage(AttributeTargets.All)]
public sealed class PublicSetterTestAttribute : Attribute
{
private string m_name;
public PublicSetterTestAttribute(string name)
{
m_name = name;
}
public string Name
{
get { return m_name; }
internal set { m_name = value; }
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void VisualBasic_CA1019_AddAccessor()
{
VerifyBasicFix(@"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class NoAccessorTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
End Class", @"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class NoAccessorTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
Public ReadOnly Property Name As String
Get
End Get
End Property
End Class", allowNewCompilerDiagnostics: true);
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/679"), Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void VisualBasic_CA1019_AddAccessor2()
{
VerifyBasicFix(@"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class SetterOnlyTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
Public WriteOnly Property Name() As String
Set
m_name = value
End Set
End Property
End Class", @"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class SetterOnlyTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
Public Property Name() As String
Set
m_name = value
End Set
Get
End Get
End Property
End Class", allowNewCompilerDiagnostics: true);
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/662"), Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void VisualBasic_CA1019_MakeGetterPublic()
{
VerifyBasicFix(@"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class InternalGetterTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
Public Property Name() As String
Friend Get
Return m_name
End Get
Set
m_name = value
End Set
End Property
End Class", @"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class InternalGetterTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
Public Property Name() As String
Get
Return m_name
End Get
Set
m_name = value
End Set
End Property
End Class", allowNewCompilerDiagnostics: true);
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/662"), Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void VisualBasic_CA1019_MakeGetterPublic2()
{
VerifyBasicFix(@"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class InternalGetterTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
Friend Property Name() As String
Get
Return m_name
End Get
Set
m_name = value
End Set
End Property
End Class", @"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class InternalGetterTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
Public Property Name() As String
Get
Return m_name
End Get
Set
m_name = value
End Set
End Property
End Class", allowNewCompilerDiagnostics: true);
}
[Fact(Skip = "https://github.com/dotnet/roslyn/issues/662"), Trait(Traits.Feature, Traits.Features.Diagnostics)]
public void VisualBasic_CA1019_MakeSetterInternal()
{
VerifyBasicFix(@"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class PublicSetterTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
Property Name() As String
Get
Return m_name
End Get
Set
m_name = value
End Set
End Property
End Class", @"
Imports System
<AttributeUsage(AttributeTargets.All)> _
Public NotInheritable Class PublicSetterTestAttribute
Inherits Attribute
Private m_name As String
Public Sub New(name As String)
m_name = name
End Sub
Public Property Name() As String
Get
Return m_name
End Get
Friend Set
m_name = value
End Set
End Property
End Class", allowNewCompilerDiagnostics: true);
}
}
}
......@@ -7,7 +7,7 @@
namespace System.Runtime.Analyzers.UnitTests
{
public partial class DefineAccessorsForAttributeArgumentsTests : DiagnosticAnalyzerTestBase
public partial class DefineAccessorsForAttributeArgumentsTests : CodeFixTestBase
{
protected override DiagnosticAnalyzer GetBasicDiagnosticAnalyzer()
{
......
......@@ -4,7 +4,6 @@
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Test.Utilities;
using Xunit;
namespace System.Runtime.Analyzers.UnitTests
......
......@@ -97,6 +97,7 @@
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<Compile Include="Design\DefineAccessorsForAttributeArgumentsTests.Fixer.cs" />
<Compile Include="Design\DefineAccessorsForAttributeArgumentsTests.cs" />
<Compile Include="Design\MarkAttributesWithAttributeUsageTests.cs" />
<Compile Include="Design\TypesThatOwnDisposableFieldsShouldBeDisposableTests.Fixer.cs" />
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册