File: AddImport\References\Reference.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeCleanup;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.AddImport;
 
internal abstract partial class AbstractAddImportFeatureService<TSimpleNameSyntax>
{
    private abstract class Reference(
        AbstractAddImportFeatureService<TSimpleNameSyntax> provider,
        SearchResult searchResult,
        bool isWithinImport) : IEquatable<Reference>
    {
        protected readonly AbstractAddImportFeatureService<TSimpleNameSyntax> provider = provider;
        public readonly SearchResult SearchResult = searchResult;
 
        private readonly bool _isWithinImport = isWithinImport;
 
        public int CompareTo(Document document, Reference other)
        {
            var diff = ComparerWithState.CompareTo(this, other, document, s_comparers);
            if (diff != 0)
            {
                return diff;
            }
 
            // Both our names need to change.  Sort by the name we're 
            // changing to.
            diff = StringComparer.OrdinalIgnoreCase.Compare(
                SearchResult.DesiredName, other.SearchResult.DesiredName);
            if (diff != 0)
            {
                return diff;
            }
 
            // If the weights are the same and no names changed, just order 
            // them based on the namespace we're adding an import for.
            return INamespaceOrTypeSymbolExtensions.CompareNameParts(
                    SearchResult.NameParts, other.SearchResult.NameParts,
                    placeSystemNamespaceFirst: true);
        }
 
        private static readonly ImmutableArray<Func<Reference, Document, IComparable>> s_comparers
            =
            [
                (r, d) => r.SearchResult.Weight,
                (r, d) => !r.SearchResult.DesiredNameMatchesSourceName(d),
            ];
 
        public override bool Equals(object obj)
            => Equals(obj as Reference);
 
        public bool Equals(Reference other)
        {
            return other != null &&
                other.SearchResult.NameParts != null &&
                SearchResult.NameParts.SequenceEqual(other.SearchResult.NameParts);
        }
 
        public override int GetHashCode()
            => Hash.CombineValues(SearchResult.NameParts);
 
        protected async Task<(SyntaxNode, Document)> ReplaceNameNodeAsync(
            SyntaxNode contextNode, Document document, CancellationToken cancellationToken)
        {
            if (!SearchResult.DesiredNameDiffersFromSourceName())
            {
                return (contextNode, document);
            }
 
            var identifier = SearchResult.NameNode.GetFirstToken();
            var generator = SyntaxGenerator.GetGenerator(document);
            var newIdentifier = generator.IdentifierName(SearchResult.DesiredName).GetFirstToken().WithTriviaFrom(identifier);
            var annotation = new SyntaxAnnotation();
 
            var root = contextNode.SyntaxTree.GetRoot(cancellationToken);
            root = root.ReplaceToken(identifier, newIdentifier.WithAdditionalAnnotations(annotation));
 
            var newDocument = document.WithSyntaxRoot(root);
            var newRoot = await newDocument.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
            var newContextNode = newRoot.GetAnnotatedTokens(annotation).First().Parent;
 
            return (newContextNode, newDocument);
        }
 
        public abstract Task<AddImportFixData> TryGetFixDataAsync(
            Document document, SyntaxNode node, CodeCleanupOptions options, CancellationToken cancellationToken);
 
        protected async Task<ImmutableArray<TextChange>> GetTextChangesAsync(
            Document document, SyntaxNode node, CodeCleanupOptions options, CancellationToken cancellationToken)
        {
            // Within an import, we're only adding a package/nuget reference (and we're not going to add another
            // using/import as we're already in one).  So no need for text changes.
            if (_isWithinImport)
                return [];
 
            var originalDocument = document;
 
            (node, document) = await ReplaceNameNodeAsync(
                node, document, cancellationToken).ConfigureAwait(false);
 
            var newDocument = await provider.AddImportAsync(
                node, SearchResult.NameParts, document, options.AddImportOptions, cancellationToken).ConfigureAwait(false);
 
            var cleanedDocument = await CodeAction.CleanupDocumentAsync(
                newDocument, options, cancellationToken).ConfigureAwait(false);
 
            var textChanges = await cleanedDocument.GetTextChangesAsync(
                originalDocument, cancellationToken).ConfigureAwait(false);
 
            return textChanges.ToImmutableArray();
        }
    }
}