File: Rename\RenameLocation.cs
Web Access
Project: ..\..\..\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Text;
 
namespace Microsoft.CodeAnalysis.Rename
{
    internal readonly struct RenameLocation : IEquatable<RenameLocation>
    {
        public readonly Location Location;
        public readonly DocumentId DocumentId;
        public readonly CandidateReason CandidateReason;
        public readonly bool IsRenamableAliasUsage;
        public readonly bool IsRenamableAccessor;
        public readonly TextSpan ContainingLocationForStringOrComment;
        public readonly bool IsWrittenTo;
 
        public bool IsRenameInStringOrComment => ContainingLocationForStringOrComment != default;
 
        public RenameLocation(
            Location location,
            DocumentId documentId,
            CandidateReason candidateReason = CandidateReason.None,
            bool isRenamableAliasUsage = false,
            bool isRenamableAccessor = false,
            bool isWrittenTo = false,
            TextSpan containingLocationForStringOrComment = default)
        {
            Location = location;
            DocumentId = documentId;
            CandidateReason = candidateReason;
            IsRenamableAliasUsage = isRenamableAliasUsage;
            IsRenamableAccessor = isRenamableAccessor;
            IsWrittenTo = isWrittenTo;
            ContainingLocationForStringOrComment = containingLocationForStringOrComment;
        }
 
        public RenameLocation(ReferenceLocation referenceLocation, DocumentId documentId)
            : this(referenceLocation.Location, documentId,
                   candidateReason: referenceLocation.CandidateReason,
                   isWrittenTo: referenceLocation.IsWrittenTo)
        {
        }
 
        public bool Equals(RenameLocation other)
            => Location == other.Location;
 
        public override bool Equals(object? obj)
        {
            return obj is RenameLocation loc &&
                   Equals(loc);
        }
 
        public override int GetHashCode()
            => Location.GetHashCode();
 
        internal static bool ShouldRename(RenameLocation location)
            => ShouldRename(location.CandidateReason);
 
        internal static bool ShouldRename(CandidateReason candidateReason)
        {
            if (candidateReason != CandidateReason.None)
            {
                // When we have a CandidateReason that means (for most reasons) the compiler 
                // encountered some sort of issue when binding the node.  This means we're 
                // less certain about what the code meant and if the node bound to the actual
                // symbol that we're trying to rename.  However, for many of these reasons,
                // even if the code is in error, we can still be confident enough that the 
                // node bound to the symbol we care about.
 
                switch (candidateReason)
                {
                    case CandidateReason.NotATypeOrNamespace:
                        // We had a reference to the symbol in a location where we needed a 
                        // type or namespace.  This is usually a wildly broken situation.  i.e.
                        // now due to hiding, something like a field/property is being referenced
                        // in a type location.  It is highly likely that this should not be
                        // renamed.
                        return false;
 
                    case CandidateReason.NotAnEvent:
                    case CandidateReason.NotAWithEventsMember:
                        // it's unlikely that someone would be referencing a non-event in an 
                        // event context.  Likely this location should not be included.
                        return false;
 
                    case CandidateReason.NotAnAttributeType:
                        // It's feasible that someone was referencing some type in an attribute
                        // location before making that type itself descend from System.Attribute.
                        // Still allow this type to be renamed.
                        return true;
 
                    case CandidateReason.WrongArity:
                        // Someone may have provided the wrong number of type arguments to
                        // a type/method when calling it. We should still allow the reference
                        // to be updated.
                        return true;
 
                    case CandidateReason.NotCreatable:
                        // Can happen when someone tries to do something like 'new' an 
                        // abstract type.  We still want to allow renaming this location.
                        return true;
 
                    case CandidateReason.NotReferencable:
                        // Happens when the user does something like directly accessing
                        // the accessor of a normal property.  In this case, we do still
                        // want to allow the rename to happen.
                        return true;
 
                    case CandidateReason.Inaccessible:
                        // Can trivially occur in code that is in an initially broken state
                        // where inaccessible members are being referenced.  We still want
                        // to update these references.
                        return true;
 
                    case CandidateReason.NotAValue:
                    case CandidateReason.NotAVariable:
                        // Happens with code like "NS = 1".  If "NS" is binding now to a 
                        // namespace, then it's likely something has gone very wrong (similar to
                        // NotATypeOrNamespace), and we shouldn't update this reference.
                        return false;
 
                    case CandidateReason.NotInvocable:
                        // Happens when something like a variable is being invoked, but the variable
                        // isn't a delegate type.  This may be because the user intends to give 
                        // this value a delegate type, but hasn't done so yet.  We should still allow
                        // renaming this reference for now.
                        return true;
 
                    case CandidateReason.StaticInstanceMismatch:
                        // Similar to 'Inaccessible', the code is currently broken, but it's fairly
                        // clear what the user's intent was.  In this case, we want to update the
                        // references, even though the code isn't valid.
                        return true;
 
                    case CandidateReason.OverloadResolutionFailure:
                        // Here we were renaming a method, and have a reference location that didn't
                        // bind properly to any methods.  This case is simply hard to reason about.
                        // There are times where we might want to rename this, and times when we 
                        // don't.  As overloading methods is very common, we won't update this location
                        // as it might update code the user really doesn't want us to be touching.
                        return false;
 
                    case CandidateReason.LateBound:
                        // This is a late bound call, so we should not update this location. 
                        return false;
 
                    case CandidateReason.Ambiguous:
                        // We should not touch ambiguous code.  We have no way to feel confident that
                        // this really is a location that we should be updating.
                        return false;
 
                    case CandidateReason.MemberGroup:
                        // MemberGroup is not an error case.  It happens in completely legal code,
                        // like nameof(int.ToString).  Because of that, we do want to update the
                        // reference here.
                        return true;
 
                    default:
                        // For cases added in the future, conservatively presume we can't update them.
                        // If we need to we can just add a case above this.
                        return false;
                }
            }
 
            // If there is no candidate reason, we can rename this reference.
            return true;
        }
    }
}