提交 42e39570 编写于 作者: C Cyrus Najmabadi

Provide VB implementation of 'use auto property'

上级 91554c36
using System.Collections.Concurrent;
using System;
using System.Collections.Concurrent;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
......@@ -64,8 +65,8 @@ private bool CheckExpressionSyntactically(ExpressionSyntax expression)
protected override ExpressionSyntax GetGetterExpression(IMethodSymbol getMethod, CancellationToken cancellationToken)
{
var getAccessor = (AccessorDeclarationSyntax)getMethod.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
var firstStatement = getAccessor.Body?.Statements.SingleOrDefault();
var getAccessor = getMethod.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken) as AccessorDeclarationSyntax;
var firstStatement = getAccessor?.Body.Statements.SingleOrDefault();
if (firstStatement?.Kind() == SyntaxKind.ReturnStatement)
{
var expr = ((ReturnStatementSyntax)firstStatement).Expression;
......@@ -75,15 +76,15 @@ protected override ExpressionSyntax GetGetterExpression(IMethodSymbol getMethod,
return null;
}
protected override ExpressionSyntax GetSetterExpression(IMethodSymbol setMethod, CancellationToken cancellationToken)
protected override ExpressionSyntax GetSetterExpression(IMethodSymbol setMethod, SemanticModel semanticModel, CancellationToken cancellationToken)
{
var setAccessor = (AccessorDeclarationSyntax)setMethod.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken);
var setAccessor = setMethod.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken) as AccessorDeclarationSyntax;
// Setter has to be of the form:
//
// set { field = value; } or
// set { this.field = value; }
var firstStatement = setAccessor.Body?.Statements.SingleOrDefault();
var firstStatement = setAccessor?.Body.Statements.SingleOrDefault();
if (firstStatement?.Kind() == SyntaxKind.ExpressionStatement)
{
var expressionStatement = (ExpressionStatementSyntax)firstStatement;
......
using System;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.UseAutoProperty;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UseAutoProperty
{
[Shared]
[ExportCodeFixProvider(LanguageNames.CSharp, Name = "UseAutoProperty")]
internal class UseAutoPropertyCodeFixProvider : CodeFixProvider
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseAutoPropertyCodeFixProvider))]
internal class UseAutoPropertyCodeFixProvider : AbstractUseAutoPropertyCodeFixProvider<PropertyDeclarationSyntax, FieldDeclarationSyntax, VariableDeclaratorSyntax, ConstructorDeclarationSyntax, ExpressionSyntax>
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(UseAutoPropertyAnalyzer.UseAutoProperty);
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
var equivalenceKey = diagnostic.Properties["SymbolEquivalenceKey"];
context.RegisterCodeFix(
new UseAutoPropertyCodeAction(
FeaturesResources.UseAutoProperty,
c => ProcessResult(context, diagnostic, c),
equivalenceKey),
diagnostic);
}
return Task.FromResult(false);
}
private async Task<Solution> ProcessResult(CodeFixContext context, Diagnostic diagnostic, CancellationToken cancellationToken)
protected override SyntaxNode GetNodeToRemove(VariableDeclaratorSyntax declarator)
{
var locations = diagnostic.AdditionalLocations;
var propertyLocation = locations[0];
var declaratorLocation = locations[1];
var declarator = declaratorLocation.FindToken(cancellationToken).Parent.FirstAncestorOrSelf<VariableDeclaratorSyntax>();
var fieldDocument = context.Document.Project.GetDocument(declarator.SyntaxTree);
var fieldSemanticModel = await fieldDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var fieldSymbol = (IFieldSymbol)fieldSemanticModel.GetDeclaredSymbol(declarator);
var property = propertyLocation.FindToken(cancellationToken).Parent.FirstAncestorOrSelf<PropertyDeclarationSyntax>();
var propertyDocument = context.Document.Project.GetDocument(property.SyntaxTree);
var propertySemanticModel = await propertyDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var propertySymbol = (IPropertySymbol)propertySemanticModel.GetDeclaredSymbol(property);
Debug.Assert(fieldDocument.Project == propertyDocument.Project);
var project = fieldDocument.Project;
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var solution = context.Document.Project.Solution;
var fieldLocations = await Renamer.GetRenameLocationsAsync(solution, fieldSymbol, solution.Workspace.Options, cancellationToken).ConfigureAwait(false);
// First, create the updated property we want to replace the old property with
var updatedProperty = UpdateProperty(project, fieldSymbol, propertySymbol, property, fieldLocations, cancellationToken);
// Now, rename all usages of the field to point at the property. Except don't actually
// rename the field itself. We want to be able to find it again post rename.
var updatedSolution = await Renamer.RenameAsync(fieldLocations, propertySymbol.Name,
location => !location.SourceSpan.IntersectsWith(declaratorLocation.SourceSpan),
symbols => HasConflict(symbols, propertySymbol, compilation, cancellationToken),
cancellationToken).ConfigureAwait(false);
solution = updatedSolution;
// Now find the field and property again post rename.
fieldDocument = solution.GetDocument(fieldDocument.Id);
propertyDocument = solution.GetDocument(propertyDocument.Id);
Debug.Assert(fieldDocument.Project == propertyDocument.Project);
compilation = await fieldDocument.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
fieldSymbol = (IFieldSymbol)fieldSymbol.GetSymbolKey().Resolve(compilation, cancellationToken: cancellationToken).Symbol;
propertySymbol = (IPropertySymbol)propertySymbol.GetSymbolKey().Resolve(compilation, cancellationToken: cancellationToken).Symbol;
Debug.Assert(fieldSymbol != null && propertySymbol != null);
declarator = (VariableDeclaratorSyntax)await fieldSymbol.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
property = (PropertyDeclarationSyntax)await propertySymbol.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
var fieldDeclaration = (FieldDeclarationSyntax)declarator.Parent.Parent;
var nodeToRemove = fieldDeclaration.Declaration.Variables.Count > 1 ? declarator : (SyntaxNode)fieldDeclaration;
const SyntaxRemoveOptions options = SyntaxRemoveOptions.KeepUnbalancedDirectives | SyntaxRemoveOptions.AddElasticMarker;
if (fieldDocument == propertyDocument)
{
// Same file. Have to do this in a slightly complicated fashion.
var declaratorTreeRoot = await fieldDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var editor = new SyntaxEditor(declaratorTreeRoot, fieldDocument.Project.Solution.Workspace);
editor.RemoveNode(nodeToRemove, options);
editor.ReplaceNode(property, updatedProperty);
return solution.WithDocumentSyntaxRoot(
fieldDocument.Id, editor.GetChangedRoot());
}
else
{
// In different files. Just update both files.
var fieldTreeRoot = await fieldDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var propertyTreeRoot = await propertyDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newFieldTreeRoot = fieldTreeRoot.RemoveNode(nodeToRemove, options);
var newPropertyTreeRoot = propertyTreeRoot.ReplaceNode(property, updatedProperty);
updatedSolution = solution.WithDocumentSyntaxRoot(fieldDocument.Id, newFieldTreeRoot);
updatedSolution = updatedSolution.WithDocumentSyntaxRoot(propertyDocument.Id, newPropertyTreeRoot);
return updatedSolution;
}
}
private bool? HasConflict(IEnumerable<ISymbol> symbols, IPropertySymbol property, Compilation compilation, CancellationToken cancellationToken)
{
// We're asking the rename API to update a bunch of references to an existing field to
// the same name as an existing property. Rename will often flag this situation as
// an unresolvable conflict because the new name won't bind to the field anymore.
//
// To address this, we let rename know that there is no conflict if the new symbol it
// resolves to is the same as the property we're trying to get the references pointing
// to.
foreach (var symbol in symbols)
{
var otherProperty = symbol as IPropertySymbol;
if (otherProperty != null)
{
var mappedProperty = otherProperty.GetSymbolKey().Resolve(compilation, cancellationToken: cancellationToken).Symbol as IPropertySymbol;
if (property.Equals(mappedProperty))
{
// No conflict.
return false;
}
}
}
// Just do the default check.
return null;
return nodeToRemove;
}
private PropertyDeclarationSyntax UpdateProperty(
protected override SyntaxNode UpdateProperty(
Project project, IFieldSymbol fieldSymbol, IPropertySymbol propertySymbol, PropertyDeclarationSyntax propertyDeclaration,
RenameLocations fieldRenameLocations, CancellationToken cancellationToken)
bool isWrittenOutsideOfConstructor, CancellationToken cancellationToken)
{
var updatedProperty = propertyDeclaration.WithAccessorList(UpdateAccessorList(propertyDeclaration.AccessorList));
// We may need to add a setter if the field is written to outside of the constructor
// of it's class.
if (AddSetterIfNecessary(fieldSymbol, propertyDeclaration, fieldRenameLocations, cancellationToken))
if (NeedsSetter(propertyDeclaration, isWrittenOutsideOfConstructor))
{
var accessor = SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
......@@ -177,61 +49,9 @@ private async Task<Solution> ProcessResult(CodeFixContext context, Diagnostic di
return updatedProperty;
}
private bool AddSetterIfNecessary(
IFieldSymbol fieldSymbol,
PropertyDeclarationSyntax propertyDeclaration,
RenameLocations fieldRenameLocations,
CancellationToken cancellationToken)
private bool NeedsSetter(PropertyDeclarationSyntax propertyDeclaration, bool isWrittenOutsideOfConstructor)
{
if (propertyDeclaration.AccessorList.Accessors.Any(SyntaxKind.SetAccessorDeclaration))
{
// No need to add an setter if we already have one.
return false;
}
// If the original field was written to outside of a constructor (or the property
// we're converting), then we'll need to add a setter to the property we're creating.
var containingTypeNodes = fieldSymbol.ContainingType.DeclaringSyntaxReferences.Select(s => s.GetSyntax(cancellationToken)).ToImmutableArray();
return fieldRenameLocations.Locations.Any(loc => NeedsSetter(loc, containingTypeNodes, propertyDeclaration, cancellationToken));
}
private bool NeedsSetter(
RenameLocation location,
ImmutableArray<SyntaxNode> containingTypeNodes,
PropertyDeclarationSyntax propertyDeclaration,
CancellationToken cancellationToken)
{
if (!location.IsWrittenTo)
{
// We don't need a setter if we're not writing to this field.
return false;
}
var node = location.Location.FindToken(cancellationToken).Parent;
while (node != null)
{
if (node == propertyDeclaration)
{
// We don't need a setter if we're a reference in the property we're replacing.
return false;
}
if (node.IsKind(SyntaxKind.ConstructorDeclaration))
{
// If we're written to in a constructor in the field's class, we don't need
// a setter.
if (containingTypeNodes.Contains(node.Parent))
{
return false;
}
}
node = node.Parent;
}
// We do need a setter
return true;
return isWrittenOutsideOfConstructor && !propertyDeclaration.AccessorList.Accessors.Any(SyntaxKind.SetAccessorDeclaration);
}
private AccessorListSyntax UpdateAccessorList(AccessorListSyntax accessorList)
......@@ -246,13 +66,5 @@ private IEnumerable<AccessorDeclarationSyntax> GetAccessors(SyntaxList<AccessorD
yield return accessor.WithBody(null).WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken));
}
}
private class UseAutoPropertyCodeAction : CodeAction.SolutionChangeAction
{
public UseAutoPropertyCodeAction(string title, Func<CancellationToken, Task<Solution>> createChangedSolution, string equivalenceKey)
: base(title, createChangedSolution, equivalenceKey)
{
}
}
}
}
\ No newline at end of file
......@@ -101,14 +101,14 @@ public void TestFieldAndPropertyHaveDifferentStaticInstance()
public void TestFieldUseInRefArgument1()
{
TestMissing(
@"class Class { [|int i|]; int P { get { return i; } } void M(ref x) { M(ref i); } }");
@"class Class { [|int i|]; int P { get { return i; } } void M(ref int x) { M(ref i); } }");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)]
public void TestFieldUseInRefArgument2()
{
TestMissing(
@"class Class { [|int i|]; int P { get { return i; } } void M(ref x) { M(ref this.i); } }");
@"class Class { [|int i|]; int P { get { return i; } } void M(ref int x) { M(ref this.i); } }");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)]
......
......@@ -241,6 +241,9 @@
<Compile Include="SignatureHelp\SignatureHelpUtilities.vb" />
<Compile Include="TextStructureNavigation\TextStructureNavigatorProvider.vb" />
<Compile Include="TodoComment\BasicTodoCommentIncrementalAnalyzerProvider.vb" />
<Compile Include="UseAutoProperty\UseAutoPropertyAnalyzer.vb" />
<Compile Include="UseAutoProperty\UseAutoPropertyCodeFixProvider.vb" />
<Compile Include="UseAutoProperty\Utilities.vb" />
<Compile Include="Utilities\LineAdjustmentFormattingRule.vb" />
<Compile Include="Utilities\CommandHandlers\AbstractImplementAbstractClassOrInterfaceCommandHandler.vb" />
<Compile Include="Utilities\NamedTypeSymbolExtensions.vb" />
......@@ -318,4 +321,4 @@
<Import Project="..\..\..\build\Targets\VSL.Imports.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file
Imports System.Collections.Concurrent
Imports System.ComponentModel.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.UseAutoProperty
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty
<Export>
<DiagnosticAnalyzer(LanguageNames.VisualBasic)>
Friend Class UseAutoPropertyAnalyzer
Inherits AbstractUseAutoPropertyAnalyzer(Of PropertyBlockSyntax, FieldDeclarationSyntax, ModifiedIdentifierSyntax, ExpressionSyntax)
Private ReadOnly semanticFacts As New VisualBasicSemanticFactsService()
Protected Overrides Sub RegisterIneligibleFieldsAction(context As CompilationStartAnalysisContext, ineligibleFields As ConcurrentBag(Of IFieldSymbol))
End Sub
Private Function CheckExpressionSyntactically(expression As ExpressionSyntax) As Boolean
If expression?.Kind() = SyntaxKind.SimpleMemberAccessExpression Then
Dim memberAccessExpression = DirectCast(expression, MemberAccessExpressionSyntax)
Return memberAccessExpression.Expression.Kind() = SyntaxKind.MeExpression AndAlso
memberAccessExpression.Name.Kind() = SyntaxKind.IdentifierName
ElseIf expression.Kind() = SyntaxKind.IdentifierName
Return True
End If
Return False
End Function
Protected Overrides Function GetGetterExpression(getMethod As IMethodSymbol, cancellationToken As CancellationToken) As ExpressionSyntax
Dim accessor = TryCast(TryCast(getMethod.DeclaringSyntaxReferences(0).GetSyntax(cancellationToken), AccessorStatementSyntax)?.Parent, AccessorBlockSyntax)
Dim firstStatement = accessor?.Statements.SingleOrDefault()
If firstStatement?.Kind() = SyntaxKind.ReturnStatement Then
Dim expr = DirectCast(firstStatement, ReturnStatementSyntax).Expression
Return If(CheckExpressionSyntactically(expr), expr, Nothing)
End If
Return Nothing
End Function
Protected Overrides Function GetSetterExpression(setMethod As IMethodSymbol, semanticModel As SemanticModel, cancellationToken As CancellationToken) As ExpressionSyntax
Dim setAccessor = TryCast(TryCast(setMethod.DeclaringSyntaxReferences(0).GetSyntax(cancellationToken), AccessorStatementSyntax)?.Parent, AccessorBlockSyntax)
Dim firstStatement = setAccessor?.Statements.SingleOrDefault()
If firstStatement?.Kind() = SyntaxKind.SimpleAssignmentStatement Then
Dim assignmentStatement = DirectCast(firstStatement, AssignmentStatementSyntax)
If assignmentStatement.Right.Kind() = SyntaxKind.IdentifierName Then
Dim identifier = DirectCast(assignmentStatement.Right, IdentifierNameSyntax)
Dim symbol = semanticModel.GetSymbolInfo(identifier).Symbol
If setMethod.Parameters.Contains(TryCast(symbol, IParameterSymbol)) Then
Return If(CheckExpressionSyntactically(assignmentStatement.Left), assignmentStatement.Left, Nothing)
End If
End If
End If
Return Nothing
End Function
Protected Overrides Function GetNodeToFade(fieldDeclaration As FieldDeclarationSyntax, identifier As ModifiedIdentifierSyntax) As SyntaxNode
Return Utilities.GetNodeToRemove(identifier)
End Function
Protected Overrides Function IsEligibleHeuristic(field As IFieldSymbol, propertyDeclaration As PropertyBlockSyntax, compilation As Compilation, cancellationToken As CancellationToken) As Boolean
If propertyDeclaration.Accessors.Any(SyntaxKind.SetAccessorBlock) Then
' If this property already has a setter, then we can definitely simplify it to an auto-prop
Return True
End If
' the property doesn't have a setter currently. check all the types the field is
' declared in. If the field is written to outside of a constructor, then this
' field Is Not elegible for replacement with an auto prop. We'd have to make
' the autoprop read/write, And that could be opening up the propert widely
' (in accessibility terms) in a way the user would not want.
Dim containingType = field.ContainingType
For Each ref In containingType.DeclaringSyntaxReferences
Dim containingNode = ref.GetSyntax(cancellationToken)
If containingNode IsNot Nothing Then
Dim semanticModel = compilation.GetSemanticModel(containingNode.SyntaxTree)
If IsWrittenOutsideOfConstructorOrProperty(field, propertyDeclaration, containingNode, semanticModel, cancellationToken) Then
Return False
End If
End If
Next
' No problem simplifying this field.
Return True
End Function
Private Function IsWrittenOutsideOfConstructorOrProperty(field As IFieldSymbol,
propertyDeclaration As PropertyBlockSyntax,
node As SyntaxNode,
semanticModel As SemanticModel,
cancellationToken As CancellationToken) As Boolean
cancellationToken.ThrowIfCancellationRequested()
If node Is propertyDeclaration Then
Return False
End If
If node.Kind() = SyntaxKind.ConstructorBlock Then
Return False
End If
If node.Kind() = SyntaxKind.IdentifierName Then
Dim symbol = semanticModel.GetSymbolInfo(node)
If field.Equals(symbol.Symbol) Then
If semanticFacts.IsWrittenTo(semanticModel, node, cancellationToken) Then
Return True
End If
End If
Else
For Each child In node.ChildNodesAndTokens
If child.IsNode Then
If IsWrittenOutsideOfConstructorOrProperty(field, propertyDeclaration, child.AsNode(), semanticModel, cancellationToken) Then
Return True
End If
End If
Next
End If
Return False
End Function
End Class
End Namespace
Imports System.Composition
Imports System.Threading
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Editing
Imports Microsoft.CodeAnalysis.UseAutoProperty
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty
<[Shared]>
<ExportCodeFixProvider(LanguageNames.VisualBasic, Name:=NameOf(UseAutoPropertyCodeFixProvider))>
Friend Class UseAutoPropertyCodeFixProvider
Inherits AbstractUseAutoPropertyCodeFixProvider(Of PropertyBlockSyntax, FieldDeclarationSyntax, ModifiedIdentifierSyntax, ConstructorBlockSyntax, ExpressionSyntax)
Protected Overrides Function GetNodeToRemove(identifier As ModifiedIdentifierSyntax) As SyntaxNode
Return Utilities.GetNodeToRemove(identifier)
End Function
Protected Overrides Function UpdateProperty(project As Project,
fieldSymbol As IFieldSymbol,
propertySymbol As IPropertySymbol,
propertyDeclaration As PropertyBlockSyntax,
isWrittenToOutsideOfConstructor As Boolean,
cancellationToken As CancellationToken) As SyntaxNode
Dim statement = propertyDeclaration.PropertyStatement
If Not isWrittenToOutsideOfConstructor AndAlso Not propertyDeclaration.Accessors.Any(SyntaxKind.SetAccessorBlock) Then
Dim generator = SyntaxGenerator.GetGenerator(project)
statement = DirectCast(generator.WithModifiers(statement, DeclarationModifiers.ReadOnly), PropertyStatementSyntax)
End If
Return statement
End Function
End Class
End Namespace
\ No newline at end of file
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty
Friend Class Utilities
Friend Shared Function GetNodeToRemove(identifier As ModifiedIdentifierSyntax) As SyntaxNode
Dim declarator = DirectCast(identifier.Parent, VariableDeclaratorSyntax)
If declarator.Names.Count > 1 Then
' more than one name in this declarator group. just remove this name.
Return identifier
End If
' only name in this declarator group. if our field has multiple declarator groups,
' just remove this one. otherwise remove the field entirely.
Dim field = DirectCast(declarator.Parent, FieldDeclarationSyntax)
If field.Declarators.Count > 1 Then
Return declarator
End If
Return field
End Function
End Class
End Namespace
\ No newline at end of file
......@@ -216,6 +216,7 @@
<Compile Include="Diagnostics\Spellcheck\SpellcheckTests.vb" />
<Compile Include="Diagnostics\Suppression\SuppressionAllCodeTests.vb" />
<Compile Include="Diagnostics\Suppression\SuppressionTests.vb" />
<Compile Include="Diagnostics\UseAutoProperty\UseAutoPropertyTests.vb" />
<Compile Include="DocumentationComments\DocumentationCommentTests.vb" />
<Compile Include="DocumentationComments\XmlTagCompletionTests.vb" />
<Compile Include="EditAndContinue\ActiveStatementTests.vb" />
......@@ -622,4 +623,4 @@
<Import Project="..\..\..\build\Targets\Roslyn.Toolsets.Xunit.targets" />
<Import Project="$(SolutionDir)\.nuget\NuGet.targets" Condition="Exists('$(SolutionDir)\.nuget\NuGet.targets')" />
</ImportGroup>
</Project>
</Project>
\ No newline at end of file

Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UseAutoProperty
Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics.UseAutoProperty
Public Class UseAutoPropertyTests
Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest
Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As Tuple(Of DiagnosticAnalyzer, CodeFixProvider)
Return Tuple.Create(Of DiagnosticAnalyzer, CodeFixProvider)(New UseAutoPropertyAnalyzer(), New UseAutoPropertyCodeFixProvider())
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestSingleGetter1()
Test(
NewLines("class Class1 \n [|dim i as integer|] \n readonly property P as integer \n get \n return i \n end get \n end property \n end class"),
NewLines("class Class1 \n readonly property P as integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestSingleGetter2()
Test(
NewLines("class Class1 \n dim i as Integer \n [|readonly property P as integer \n get \n return i \n end get \n end property|] \n end class"),
NewLines("class Class1 \n readonly property P as integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestSingleSetter()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n set \n i = value \n end set \end property \end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestGetterAndSetter()
Test(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return i \n end get \n set \n i = value \n end set \n end property \n end class"),
NewLines("class Class1 \n property P as Integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestDifferentValueName()
Test(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return i \n end get \n set(v as integer) \n i = v \n end set \n end property \n end class"),
NewLines("class Class1 \n property P as Integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestSingleGetterWithMe()
Test(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return me.i \n end get \n end property \n end class"),
NewLines("class Class1 \n ReadOnly property P as Integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestSingleSetterWithMe()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n set \n me.i = value \n end set \n end property \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestGetterAndSetterWithMe()
Test(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return me.i \n end get \n set \n me.i = value \n end property \n end class"),
NewLines("class Class1 \n property P as Integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestGetterWithMutipleStatements()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n Foo() \n return i \n end get \n end property \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestSetterWithMutipleStatements()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n set \n Foo() \n i = value \n end set \n end property \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestGetterAndSetterUseDifferentFields()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n dim j as Integer \n property P as Integer \n get \n return i \n end get \n set \n j = value \n end set \n end property \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestFieldAndPropertyHaveDifferentStaticInstance()
TestMissing(
NewLines("class Class1 \n [|shared i a integer|] \n property P as Integer \n get \n return i \n end property \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestFieldUseInRefArgument1()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return i \n end property \n sub M(byref x as integer) \n M(i) \n end sub \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestFieldUseInRefArgument2()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return i \n end property \n sub M(byref x as integer) \n M(me.i) \end sub \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestNotWithVirtualProperty()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n public virtual property P as Integer \n get \n return i \n end property \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestNotWithConstField()
TestMissing(
NewLines("class Class1 \n [|const int i|] \n property P as Integer \n get \n return i \n end property \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestFieldWithMultipleDeclarators1()
Test(
NewLines("class Class1 \n dim [|i|] as integer, j, k \n property P as Integer \n get \n return i \n end property \n end class"),
NewLines("class Class1 \n dim j, k \n ReadOnly property P as Integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestFieldWithMultipleDeclarators2()
Test(
NewLines("class Class1 \n dim i, [|j|] as integer, k \n property P as Integer \n get \n return j \n end get \n end property \n end class"),
NewLines("class Class1 \n dim i as integer, k \n ReadOnly property P as Integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestFieldWithMultipleDeclarators3()
Test(
NewLines("class Class1 \n dim i, j, [|k|] as integer \n property P as Integer \n get \n return k \n end get \n end property \n end class"),
NewLines("class Class1 \n dim i, j as integer \n ReadOnly property P as Integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestFieldWithMultipleDeclarators4()
Test(
NewLines("class Class1 \n dim i as integer, [|k|] as integer \n property P as Integer \n get \n return k \n end get \n end property \n end class"),
NewLines("class Class1 \n dim i as integer \n ReadOnly property P as Integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestFieldAndPropertyInDifferentParts()
Test(
NewLines("partial class Class1 \n [|dim i as integer|] \n end class \n partial class Class1 \n property P as Integer \n get \n return i \n end property \n end class"),
NewLines("partial class Class1 \n end class \n partial class Class1 \n ReadOnly property P as Integer \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestNotWithFieldWithAttirbute()
TestMissing(
NewLines("class Class1 \n [|<A>dim i as integer|] \n property P as Integer \n get \n return i \n end property \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestUpdateReferences()
Test(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return i \n end get \n end property \n public sub new() \n i = 1 \n end sub \n end class"),
NewLines("class Class1 \n ReadOnly property P as Integer \n public sub new() \n P = 1 \n end sub \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestUpdateReferencesConflictResolution()
Test(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return i \n end get \n public sub new(dim P as integer) \n i = 1 \n end sub \n end class"),
NewLines("class Class1 \n ReadOnly property P as Integer \n public sub new(dim P as integer) \n Me.P = 1 \n end sub \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestWriteInConstructor()
Test(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return i \n end get \n end property \n public sub new() \n i = 1 \n end sub \n end class"),
NewLines("class Class1 \n ReadOnly property P as Integer \n public sub new() \n P = 1 \n end sub \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestWriteInNotInConstructor1()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n property P as Integer \n get \n return i \n end property \n public sub Foo() \n i = 1 \n end sub \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestWriteInNotInConstructor2()
TestMissing(
NewLines("class Class1 \n [|dim i as integer|] \n public property P as Integer \n get \n return i \n \end property \n public sub Foo() \n i = 1 \n end sub \n end class"))
End Sub
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseAutoProperty)>
Public Sub TestWriteInNotInConstructor3()
Test(
NewLines("class Class1 \n [|dim i as integer|] \n public property P as Integer \n get \n return i \n end get \n set \n i = value \n end set \n end property \n public sub Foo() \n i = 1 \n end sub \n end class"),
NewLines("class Class1 \n public property P as Integer \n public sub Foo() P = 1 \n end sub \n end class"))
End Sub
End Class
End Namespace
......@@ -511,6 +511,7 @@
<Compile Include="SolutionCrawler\WorkCoordinator.SemanticChangeProcessor.cs" />
<Compile Include="SolutionCrawler\WorkCoordinator.WorkItem.cs" />
<Compile Include="SolutionCrawler\SolutionCrawlerRegistrationService.cs" />
<Compile Include="UseAutoProperty\AbstractUseAutoPropertyCodeFixProvider.cs" />
<Compile Include="UseAutoProperty\AbstractUseAutoPropertyAnalyzer.cs" />
<Compile Include="Workspace\BackgroundCompiler.cs" />
<Compile Include="Workspace\BackgroundParser.cs" />
......
using System.Collections.Concurrent;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
......@@ -42,7 +43,7 @@ public sealed override void Initialize(AnalysisContext context)
protected abstract void RegisterIneligibleFieldsAction(CompilationStartAnalysisContext context, ConcurrentBag<IFieldSymbol> ineligibleFields);
protected abstract TExpression GetGetterExpression(IMethodSymbol getMethod, CancellationToken cancellationToken);
protected abstract TExpression GetSetterExpression(IMethodSymbol setMethod, CancellationToken cancellationToken);
protected abstract TExpression GetSetterExpression(IMethodSymbol setMethod, SemanticModel semanticModel, CancellationToken cancellationToken);
protected abstract SyntaxNode GetNodeToFade(TFieldDeclaration fieldDeclaration, TVariableDeclarator variableDeclarator);
private void AnalyzeProperty(ConcurrentBag<AnalysisResult> analysisResults, SymbolAnalysisContext symbolContext)
......@@ -70,6 +71,7 @@ private void AnalyzeProperty(ConcurrentBag<AnalysisResult> analysisResults, Symb
return;
}
// Need at least a getter.
if (property.GetMethod == null)
{
return;
......@@ -87,24 +89,23 @@ private void AnalyzeProperty(ConcurrentBag<AnalysisResult> analysisResults, Symb
return;
}
// Need at least a getter.
if (property.GetMethod == null)
var cancellationToken = symbolContext.CancellationToken;
var propertyDeclaration = property.DeclaringSyntaxReferences[0].GetSyntax(cancellationToken).FirstAncestorOrSelf<TPropertyDeclaration>();
if (propertyDeclaration == null)
{
return;
}
var syntaxReference = declarations[0];
var propertyDeclaration = syntaxReference.GetSyntax(symbolContext.CancellationToken) as TPropertyDeclaration;
if (propertyDeclaration == null)
var semanticModel = symbolContext.Compilation.GetSemanticModel(propertyDeclaration.SyntaxTree);
var getterField = GetGetterField(semanticModel, property.GetMethod, cancellationToken);
if (getterField == null)
{
return;
}
var cancellationToken = symbolContext.CancellationToken;
var semanticModel = symbolContext.Compilation.GetSemanticModel(syntaxReference.SyntaxTree);
var getterField = GetGetterField(semanticModel, containingType, property.GetMethod, cancellationToken);
if (getterField == null)
if (!containingType.Equals(getterField.ContainingType))
{
// Field and property have to be in the same type.
return;
}
......@@ -171,15 +172,15 @@ private void AnalyzeProperty(ConcurrentBag<AnalysisResult> analysisResults, Symb
private IFieldSymbol GetSetterField(
SemanticModel semanticModel, ISymbol containingType, IMethodSymbol setMethod, CancellationToken cancellationToken)
{
return CheckFieldAccessExpression(semanticModel, containingType, GetSetterExpression(setMethod, cancellationToken));
return CheckFieldAccessExpression(semanticModel, GetSetterExpression(setMethod, semanticModel, cancellationToken));
}
private IFieldSymbol GetGetterField(SemanticModel semanticModel, ISymbol containingType, IMethodSymbol getMethod, CancellationToken cancellationToken)
private IFieldSymbol GetGetterField(SemanticModel semanticModel, IMethodSymbol getMethod, CancellationToken cancellationToken)
{
return CheckFieldAccessExpression(semanticModel, containingType, GetGetterExpression(getMethod, cancellationToken));
return CheckFieldAccessExpression(semanticModel, GetGetterExpression(getMethod, cancellationToken));
}
private IFieldSymbol CheckFieldAccessExpression(SemanticModel semanticModel, ISymbol containingType, TExpression expression)
private IFieldSymbol CheckFieldAccessExpression(SemanticModel semanticModel, TExpression expression)
{
if (expression == null)
{
......@@ -187,7 +188,7 @@ private IFieldSymbol CheckFieldAccessExpression(SemanticModel semanticModel, ISy
}
var symbolInfo = semanticModel.GetSymbolInfo(expression);
if (symbolInfo.Symbol == null || symbolInfo.Symbol.Kind != SymbolKind.Field || symbolInfo.Symbol.ContainingType != containingType)
if (symbolInfo.Symbol == null || symbolInfo.Symbol.Kind != SymbolKind.Field)
{
return null;
}
......@@ -221,6 +222,13 @@ private IFieldSymbol CheckFieldAccessExpression(SemanticModel semanticModel, ISy
private void Process(AnalysisResult result, CompilationAnalysisContext compilationContext)
{
// Check if there are additional reasons we think this field might be ineligible for
// replacing with an auto prop.
if (!IsEligibleHeuristic(result.Field, result.PropertyDeclaration, compilationContext.Compilation, compilationContext.CancellationToken))
{
return;
}
var propertyDeclaration = result.PropertyDeclaration;
var variableDeclarator = result.VariableDeclarator;
var nodeToFade = GetNodeToFade(result.FieldDeclaration, variableDeclarator);
......@@ -243,6 +251,11 @@ private void Process(AnalysisResult result, CompilationAnalysisContext compilati
compilationContext.ReportDiagnostic(diagnostic3);
}
protected virtual bool IsEligibleHeuristic(IFieldSymbol field, TPropertyDeclaration propertyDeclaration, Compilation compilation, CancellationToken cancellationToken)
{
return true;
}
internal class AnalysisResult
{
public readonly IPropertySymbol Property;
......
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Rename;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.UseAutoProperty
{
internal abstract class AbstractUseAutoPropertyCodeFixProvider<TPropertyDeclaration, TFieldDeclaration, TVariableDeclarator, TConstructorDeclaration, TExpression> : CodeFixProvider
where TPropertyDeclaration : SyntaxNode
where TFieldDeclaration : SyntaxNode
where TVariableDeclarator : SyntaxNode
where TConstructorDeclaration : SyntaxNode
where TExpression : SyntaxNode
{
public sealed override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(
AbstractUseAutoPropertyAnalyzer<TPropertyDeclaration, TFieldDeclaration, TVariableDeclarator, TExpression>.UseAutoProperty);
public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
protected abstract SyntaxNode GetNodeToRemove(TVariableDeclarator declarator);
protected abstract SyntaxNode UpdateProperty(
Project project, IFieldSymbol fieldSymbol, IPropertySymbol propertySymbol, TPropertyDeclaration propertyDeclaration,
bool isWrittenOutsideConstructor, CancellationToken cancellationToken);
public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
var equivalenceKey = diagnostic.Properties["SymbolEquivalenceKey"];
context.RegisterCodeFix(
new UseAutoPropertyCodeAction(
FeaturesResources.UseAutoProperty,
c => ProcessResult(context, diagnostic, c),
equivalenceKey),
diagnostic);
}
return Task.FromResult(false);
}
private async Task<Solution> ProcessResult(CodeFixContext context, Diagnostic diagnostic, CancellationToken cancellationToken)
{
var locations = diagnostic.AdditionalLocations;
var propertyLocation = locations[0];
var declaratorLocation = locations[1];
var declarator = declaratorLocation.FindToken(cancellationToken).Parent.FirstAncestorOrSelf<TVariableDeclarator>();
var fieldDocument = context.Document.Project.GetDocument(declarator.SyntaxTree);
var fieldSemanticModel = await fieldDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var fieldSymbol = (IFieldSymbol)fieldSemanticModel.GetDeclaredSymbol(declarator);
var property = propertyLocation.FindToken(cancellationToken).Parent.FirstAncestorOrSelf<TPropertyDeclaration>();
var propertyDocument = context.Document.Project.GetDocument(property.SyntaxTree);
var propertySemanticModel = await propertyDocument.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var propertySymbol = (IPropertySymbol)propertySemanticModel.GetDeclaredSymbol(property);
Debug.Assert(fieldDocument.Project == propertyDocument.Project);
var project = fieldDocument.Project;
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
var solution = context.Document.Project.Solution;
var fieldLocations = await Renamer.GetRenameLocationsAsync(solution, fieldSymbol, solution.Workspace.Options, cancellationToken).ConfigureAwait(false);
// First, create the updated property we want to replace the old property with
var updatedProperty = UpdateProperty(project, fieldSymbol, propertySymbol, property,
IsWrittenToOutsideOfConstructorOrProperty(fieldSymbol, fieldLocations, property, cancellationToken), cancellationToken);
// Now, rename all usages of the field to point at the property. Except don't actually
// rename the field itself. We want to be able to find it again post rename.
var updatedSolution = await Renamer.RenameAsync(fieldLocations, propertySymbol.Name,
location => !location.SourceSpan.IntersectsWith(declaratorLocation.SourceSpan),
symbols => HasConflict(symbols, propertySymbol, compilation, cancellationToken),
cancellationToken).ConfigureAwait(false);
solution = updatedSolution;
// Now find the field and property again post rename.
fieldDocument = solution.GetDocument(fieldDocument.Id);
propertyDocument = solution.GetDocument(propertyDocument.Id);
Debug.Assert(fieldDocument.Project == propertyDocument.Project);
compilation = await fieldDocument.Project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
fieldSymbol = (IFieldSymbol)fieldSymbol.GetSymbolKey().Resolve(compilation, cancellationToken: cancellationToken).Symbol;
propertySymbol = (IPropertySymbol)propertySymbol.GetSymbolKey().Resolve(compilation, cancellationToken: cancellationToken).Symbol;
Debug.Assert(fieldSymbol != null && propertySymbol != null);
declarator = (TVariableDeclarator)await fieldSymbol.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
var temp = await propertySymbol.DeclaringSyntaxReferences[0].GetSyntaxAsync(cancellationToken).ConfigureAwait(false);
property = temp.FirstAncestorOrSelf<TPropertyDeclaration>();
var nodeToRemove = GetNodeToRemove(declarator);
const SyntaxRemoveOptions options = SyntaxRemoveOptions.KeepUnbalancedDirectives | SyntaxRemoveOptions.AddElasticMarker;
if (fieldDocument == propertyDocument)
{
// Same file. Have to do this in a slightly complicated fashion.
var declaratorTreeRoot = await fieldDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var editor = new SyntaxEditor(declaratorTreeRoot, fieldDocument.Project.Solution.Workspace);
editor.RemoveNode(nodeToRemove, options);
editor.ReplaceNode(property, updatedProperty);
return solution.WithDocumentSyntaxRoot(
fieldDocument.Id, editor.GetChangedRoot());
}
else
{
// In different files. Just update both files.
var fieldTreeRoot = await fieldDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var propertyTreeRoot = await propertyDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newFieldTreeRoot = fieldTreeRoot.RemoveNode(nodeToRemove, options);
var newPropertyTreeRoot = propertyTreeRoot.ReplaceNode(property, updatedProperty);
updatedSolution = solution.WithDocumentSyntaxRoot(fieldDocument.Id, newFieldTreeRoot);
updatedSolution = updatedSolution.WithDocumentSyntaxRoot(propertyDocument.Id, newPropertyTreeRoot);
return updatedSolution;
}
}
private static bool IsWrittenToOutsideOfConstructorOrProperty(
IFieldSymbol field, RenameLocations renameLocations, TPropertyDeclaration propertyDeclaration, CancellationToken cancellationToken)
{
var constructorNodes = field.ContainingType.GetMembers()
.Where(m => m.IsConstructor())
.SelectMany(c => c.DeclaringSyntaxReferences)
.Select(s => s.GetSyntax(cancellationToken))
.Select(n => n.FirstAncestorOrSelf<TConstructorDeclaration>())
.WhereNotNull()
.ToSet();
return renameLocations.Locations.Any(
loc => IsWrittenToOutsideOfConstructorOrProperty(loc, propertyDeclaration, constructorNodes, cancellationToken));
}
private static bool IsWrittenToOutsideOfConstructorOrProperty(
RenameLocation location, TPropertyDeclaration propertyDeclaration, ISet<TConstructorDeclaration> constructorNodes, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (!location.IsWrittenTo)
{
// We don't need a setter if we're not writing to this field.
return false;
}
var node = location.Location.FindToken(cancellationToken).Parent;
while (node != null)
{
if (node == propertyDeclaration)
{
// Not a write outside the property declaration.
return false;
}
if (constructorNodes.Contains(node))
{
// Not a write outside a constructor of the field's class
return false;
}
node = node.Parent;
}
// We do need a setter
return true;
}
private bool? HasConflict(IEnumerable<ISymbol> symbols, IPropertySymbol property, Compilation compilation, CancellationToken cancellationToken)
{
// We're asking the rename API to update a bunch of references to an existing field to
// the same name as an existing property. Rename will often flag this situation as
// an unresolvable conflict because the new name won't bind to the field anymore.
//
// To address this, we let rename know that there is no conflict if the new symbol it
// resolves to is the same as the property we're trying to get the references pointing
// to.
foreach (var symbol in symbols)
{
var otherProperty = symbol as IPropertySymbol;
if (otherProperty != null)
{
var mappedProperty = otherProperty.GetSymbolKey().Resolve(compilation, cancellationToken: cancellationToken).Symbol as IPropertySymbol;
if (property.Equals(mappedProperty))
{
// No conflict.
return false;
}
}
}
// Just do the default check.
return null;
}
private class UseAutoPropertyCodeAction : CodeAction.SolutionChangeAction
{
public UseAutoPropertyCodeAction(string title, Func<CancellationToken, Task<Solution>> createChangedSolution, string equivalenceKey)
: base(title, createChangedSolution, equivalenceKey)
{
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册