File: Rename\ConflictEngine\ConflictingIdentifierTracker.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.Collections.Generic;
 
namespace Microsoft.CodeAnalysis.Rename.ConflictEngine;
 
internal sealed class ConflictingIdentifierTracker(SyntaxToken tokenBeingRenamed, IEqualityComparer<string> identifierComparer)
{
    /// <summary>
    /// The core data structure of the tracker. This is a dictionary of variable name to the
    /// current identifier tokens that are declaring variables. This should only ever be updated
    /// via the AddIdentifier and RemoveIdentifier helpers.
    /// </summary>
    private readonly Dictionary<string, List<SyntaxToken>> _currentIdentifiersInScope = new Dictionary<string, List<SyntaxToken>>(identifierComparer);
    private readonly HashSet<SyntaxToken> _conflictingTokensToReport = [];
 
    public IEnumerable<SyntaxToken> ConflictingTokens => _conflictingTokensToReport;
 
    public void AddIdentifier(SyntaxToken token)
    {
        if (token.IsMissing || token.ValueText == null)
        {
            return;
        }
 
        var name = token.ValueText;
 
        if (_currentIdentifiersInScope.TryGetValue(name, out var conflictingTokens))
        {
            conflictingTokens.Add(token);
 
            // If at least one of the identifiers is the one we're renaming,
            // track it. This means that conflicts unrelated to our rename (that
            // were there when we started) we won't flag.
            if (conflictingTokens.Contains(tokenBeingRenamed))
            {
                foreach (var conflictingToken in conflictingTokens)
                {
                    if (conflictingToken != tokenBeingRenamed)
                    {
                        // conflictingTokensToReport is a set, so we won't get duplicates
                        _conflictingTokensToReport.Add(conflictingToken);
                    }
                }
            }
        }
        else
        {
            // No identifiers yet, so record the first one
            _currentIdentifiersInScope.Add(name, [token]);
        }
    }
 
    public void AddIdentifiers(IEnumerable<SyntaxToken> tokens)
    {
        foreach (var token in tokens)
        {
            AddIdentifier(token);
        }
    }
 
    public void RemoveIdentifier(SyntaxToken token)
    {
        if (token.IsMissing || token.ValueText == null)
        {
            return;
        }
 
        var name = token.ValueText;
 
        var currentIdentifiers = _currentIdentifiersInScope[name];
        currentIdentifiers.Remove(token);
 
        if (currentIdentifiers.Count == 0)
        {
            _currentIdentifiersInScope.Remove(name);
        }
    }
 
    public void RemoveIdentifiers(IEnumerable<SyntaxToken> tokens)
    {
        foreach (var token in tokens)
        {
            RemoveIdentifier(token);
        }
    }
}