File: Differencing\TreeComparer.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;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.Differencing;
 
// Based on general algorithm described in  
// "Change Detection in Hierarchically Structured Information"
// by Sudarshan S. Chawathe, Anand Rajaraman, Hector Garcia-Molina, and Jennifer Widom.
 
/// <summary>
/// Implements a tree differencing algorithm.
/// </summary>
/// <remarks>
/// Subclasses define relationships among tree nodes, and parameters to the differencing algorithm.
/// </remarks>
/// <typeparam name="TNode">Tree node.</typeparam>
public abstract class TreeComparer<TNode>
{
    protected TreeComparer()
    {
    }
 
    /// <summary>
    /// Returns an edit script that transforms <paramref name="oldRoot"/> to <paramref name="newRoot"/>.
    /// </summary>
    public EditScript<TNode> ComputeEditScript(TNode oldRoot, TNode newRoot)
        => new Match<TNode>(oldRoot, newRoot, this, knownMatches: null).GetTreeEdits();
 
    /// <summary>
    /// Returns a match map of <paramref name="oldRoot"/> descendants to <paramref name="newRoot"/> descendants.
    /// </summary>
    public Match<TNode> ComputeMatch(TNode oldRoot, TNode newRoot, IEnumerable<KeyValuePair<TNode, TNode>>? knownMatches = null)
        => new(oldRoot, newRoot, this, knownMatches);
 
    /// <summary>
    /// Calculates the distance [0..1] of two nodes.
    /// </summary>
    /// <remarks>
    /// The more similar the nodes the smaller the distance.
    /// 
    /// Used to determine whether two nodes of the same label match.
    /// Even if 0 is returned the nodes might be slightly different.
    /// </remarks>
    public abstract double GetDistance(TNode oldNode, TNode newNode);
 
    /// <summary>
    /// Returns true if the specified nodes have equal values.
    /// </summary>
    /// <remarks>
    /// Called with matching nodes (<paramref name="oldNode"/>, <paramref name="newNode"/>).
    /// Return true if the values of the nodes are the same, or their difference is not important.
    /// </remarks>
    public abstract bool ValuesEqual(TNode oldNode, TNode newNode);
 
    /// <summary>
    /// The number of distinct labels used in the tree.
    /// </summary>
    protected internal abstract int LabelCount { get; }
 
    /// <summary>
    /// Returns an integer label corresponding to the given node.
    /// </summary>
    /// <remarks>Returned value must be within [0, LabelCount).</remarks>
    protected internal abstract int GetLabel(TNode node);
 
    /// <summary>
    /// Returns N > 0 if the node with specified label can't change its N-th ancestor node, zero otherwise.
    /// </summary>
    /// <remarks>
    /// 1st ancestor is the node's parent node.
    /// 2nd ancestor is the node's grandparent node.
    /// etc.
    /// </remarks>
    protected internal abstract int TiedToAncestor(int label);
 
    /// <summary>
    /// May return null if the <paramref name="node"/> is a leaf.
    /// </summary>
    protected internal abstract IEnumerable<TNode>? GetChildren(TNode node);
 
    /// <summary>
    /// Enumerates all descendant nodes of the given node in depth-first prefix order.
    /// </summary>
    protected internal abstract IEnumerable<TNode> GetDescendants(TNode node);
 
    /// <summary>
    /// Returns a parent for the specified node.
    /// </summary>
    protected internal abstract bool TryGetParent(TNode node, [MaybeNullWhen(false)] out TNode parent);
 
    internal TNode GetParent(TNode node)
    {
        var hasParent = TryGetParent(node, out var parent);
        Debug.Assert(hasParent);
        return parent!;
    }
 
    internal bool TryGetAncestor(TNode node, int level, [MaybeNullWhen(false)] out TNode ancestor)
    {
        while (level > 0)
        {
            if (TryGetParent(node, out var parent))
            {
                node = parent;
            }
            else
            {
                ancestor = default;
                return false;
            }
 
            level--;
        }
 
        ancestor = node;
        return true;
    }
 
    /// <summary>
    /// Return true if specified nodes belong to the same tree.
    /// </summary>
    protected internal abstract bool TreesEqual(TNode oldNode, TNode newNode);
 
    /// <summary>
    /// Returns the position of the node.
    /// </summary>
    protected internal abstract TextSpan GetSpan(TNode node);
}