File: FindSymbols\ReferenceLocation.cs
Web Access
Project: src\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 System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.FindSymbols;
 
/// <summary>
/// Information about a reference to a symbol.
/// </summary>
[DebuggerDisplay("{GetDebuggerDisplay(),nq}")]
public readonly record struct ReferenceLocation : IComparable<ReferenceLocation>
{
    /// <summary>
    /// The document that the reference was found in.
    /// </summary>
    public Document Document { get; }
 
    /// <summary>
    /// If the symbol was bound through an alias, then this is the alias that was used.
    /// </summary>
    public IAliasSymbol? Alias { get; }
 
    /// <summary>
    /// The actual source location for a given symbol.
    /// </summary>
    public Location Location { get; }
 
    /// <summary>
    /// Indicates if this is an implicit reference to the definition.  i.e. the definition wasn't
    /// explicitly stated in the source code at this position, but it was still referenced. For
    /// example, this can happen with special methods like GetEnumerator that are used
    /// implicitly by a 'for each' statement.
    /// </summary>
    public bool IsImplicit { get; }
 
    /// <summary>
    /// Indicates if this is a location where the reference is written to.
    /// </summary>
    internal bool IsWrittenTo => SymbolUsageInfo.IsWrittenTo();
 
    /// <summary>
    /// Symbol usage info for this reference.
    /// </summary>
    internal SymbolUsageInfo SymbolUsageInfo { get; }
 
    /// <summary>
    /// Additional properties for this reference
    /// </summary>
    internal ImmutableArray<(string key, string value)> AdditionalProperties { get; }
 
    /// <summary>
    /// If this reference location is within a string literal, then this property
    /// indicates the location of the containing string literal token.
    /// Otherwise, <see cref="Location.None"/>.
    /// </summary>
    internal Location ContainingStringLocation { get; }
 
    public CandidateReason CandidateReason { get; }
 
    private ReferenceLocation(
        Document document,
        IAliasSymbol? alias,
        Location location,
        bool isImplicit,
        SymbolUsageInfo symbolUsageInfo,
        ImmutableArray<(string key, string value)> additionalProperties,
        CandidateReason candidateReason,
        Location containingStringLocation)
    {
        this.Document = document;
        this.Alias = alias;
        this.Location = location;
        this.IsImplicit = isImplicit;
        this.SymbolUsageInfo = symbolUsageInfo;
        this.AdditionalProperties = additionalProperties.NullToEmpty();
        this.CandidateReason = candidateReason;
        this.ContainingStringLocation = containingStringLocation;
    }
 
    /// <summary>
    /// Creates a reference location with the given properties.
    /// </summary>
    internal ReferenceLocation(Document document, IAliasSymbol? alias, Location location, bool isImplicit, SymbolUsageInfo symbolUsageInfo, ImmutableArray<(string key, string value)> additionalProperties, CandidateReason candidateReason)
        : this(document, alias, location, isImplicit, symbolUsageInfo, additionalProperties, candidateReason, containingStringLocation: Location.None)
    {
    }
 
    /// <summary>
    /// Creates a reference location within a string literal.
    /// For example, location inside the target string of a global SuppressMessageAttribute.
    /// </summary>
    internal ReferenceLocation(Document document, Location location, Location containingStringLocation)
        : this(document, alias: null, location, isImplicit: false,
               SymbolUsageInfo.None, additionalProperties: [],
               CandidateReason.None, containingStringLocation)
    {
    }
 
    /// <summary>
    /// Indicates if this was not an exact reference to a location, but was instead a possible
    /// location that was found through error tolerance.  For example, a call to a method like
    /// "Goo()" could show up as an error tolerance location to a method "Goo(int i)" if no
    /// actual "Goo()" method existed.
    /// </summary>
    public bool IsCandidateLocation => this.CandidateReason != CandidateReason.None;
 
    public bool Equals(ReferenceLocation other)
    {
        return
            EqualityComparer<IAliasSymbol>.Default.Equals(this.Alias, other.Alias) &&
            EqualityComparer<Location>.Default.Equals(this.Location, other.Location) &&
            EqualityComparer<DocumentId>.Default.Equals(this.Document.Id, other.Document.Id) &&
            this.CandidateReason == other.CandidateReason &&
            this.IsImplicit == other.IsImplicit;
    }
 
    public override int GetHashCode()
    {
        return
            Hash.Combine(this.IsImplicit,
            Hash.Combine((int)this.CandidateReason,
            Hash.Combine(this.Alias,
            Hash.Combine(this.Location, this.Document.Id.GetHashCode()))));
    }
 
    public int CompareTo(ReferenceLocation other)
    {
        int compare;
 
        var thisPath = this.Location.SourceTree?.FilePath;
        var otherPath = other.Location.SourceTree?.FilePath;
 
        if ((compare = StringComparer.OrdinalIgnoreCase.Compare(thisPath, otherPath)) != 0 ||
            (compare = this.Location.SourceSpan.CompareTo(other.Location.SourceSpan)) != 0)
        {
            return compare;
        }
 
        return 0;
    }
 
    private string GetDebuggerDisplay()
        => string.Format("{0}: {1}", this.Document.Name, this.Location);
}