File: Differencing\AbstractSyntaxComparer.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;
 
internal abstract class AbstractSyntaxComparer : TreeComparer<SyntaxNode>
{
    protected const double ExactMatchDist = 0.0;
    protected const double EpsilonDist = 0.00001;
 
    internal const int IgnoredNode = -1;
 
    protected readonly SyntaxNode? _oldRoot;
    protected readonly SyntaxNode? _newRoot;
    private readonly IEnumerable<SyntaxNode>? _oldRootChildren;
    private readonly IEnumerable<SyntaxNode>? _newRootChildren;
 
    // This comparer can operate in two modes:
    // * Top level syntax, which looks at member declarations, but doesn't look inside method bodies etc.
    // * Statement syntax, which looks into member bodies and descends through all statements and expressions
    // This flag is used where there needs to be a distinction made between how these are treated
    protected readonly bool _compareStatementSyntax;
 
    internal AbstractSyntaxComparer(
        SyntaxNode? oldRoot,
        SyntaxNode? newRoot,
        IEnumerable<SyntaxNode>? oldRootChildren,
        IEnumerable<SyntaxNode>? newRootChildren,
        bool compareStatementSyntax)
    {
        _compareStatementSyntax = compareStatementSyntax;
 
        _oldRoot = oldRoot;
        _newRoot = newRoot;
        _oldRootChildren = oldRootChildren;
        _newRootChildren = newRootChildren;
    }
 
    protected internal sealed override bool TreesEqual(SyntaxNode oldNode, SyntaxNode newNode)
        => oldNode.SyntaxTree == newNode.SyntaxTree;
 
    protected internal sealed override TextSpan GetSpan(SyntaxNode node)
        => node.Span;
 
    /// <summary>
    /// Calculates distance of two nodes based on their significant parts.
    /// Returns false if the nodes don't have any significant parts and should be compared as a whole.
    /// </summary>
    protected abstract bool TryComputeWeightedDistance(SyntaxNode oldNode, SyntaxNode newNode, out double distance);
 
    protected abstract bool IsLambdaBodyStatementOrExpression(SyntaxNode node);
 
    protected internal override bool TryGetParent(SyntaxNode node, [NotNullWhen(true)] out SyntaxNode? parent)
    {
        if (node == _oldRoot || node == _newRoot)
        {
            parent = null;
            return false;
        }
 
        parent = node.Parent;
        while (parent != null && !HasLabel(parent))
        {
            parent = parent.Parent;
        }
 
        return parent != null;
    }
 
    protected internal override IEnumerable<SyntaxNode>? GetChildren(SyntaxNode node)
    {
        if (node == _oldRoot)
        {
            return _oldRootChildren;
        }
 
        if (node == _newRoot)
        {
            return _newRootChildren;
        }
 
        return HasChildren(node) ? EnumerateChildren(node) : null;
    }
 
    private IEnumerable<SyntaxNode> EnumerateChildren(SyntaxNode node)
    {
        foreach (var child in node.ChildNodes())
        {
            if (IsLambdaBodyStatementOrExpression(child))
            {
                continue;
            }
 
            if (HasLabel(child))
            {
                yield return child;
            }
            else if (_compareStatementSyntax)
            {
                foreach (var descendant in child.DescendantNodes(DescendIntoChildren))
                {
                    if (HasLabel(descendant))
                    {
                        yield return descendant;
                    }
                }
            }
        }
    }
    private bool DescendIntoChildren(SyntaxNode node)
        => !IsLambdaBodyStatementOrExpression(node) && !HasLabel(node);
 
    protected internal sealed override IEnumerable<SyntaxNode> GetDescendants(SyntaxNode node)
    {
        var rootChildren = (node == _oldRoot) ? _oldRootChildren : (node == _newRoot) ? _newRootChildren : null;
        return (rootChildren != null) ? EnumerateDescendants(rootChildren) : EnumerateDescendants(node);
    }
 
    private IEnumerable<SyntaxNode> EnumerateDescendants(IEnumerable<SyntaxNode> nodes)
    {
        foreach (var node in nodes)
        {
            if (HasLabel(node))
            {
                yield return node;
            }
 
            foreach (var descendant in EnumerateDescendants(node))
            {
                if (HasLabel(descendant))
                {
                    yield return descendant;
                }
            }
        }
    }
 
    private IEnumerable<SyntaxNode> EnumerateDescendants(SyntaxNode node)
    {
        foreach (var descendant in node.DescendantNodesAndTokens(
            descendIntoChildren: ShouldEnumerateChildren,
            descendIntoTrivia: false))
        {
            var descendantNode = descendant.AsNode();
            if (descendantNode != null && HasLabel(descendantNode))
            {
                if (!IsLambdaBodyStatementOrExpression(descendantNode))
                {
                    yield return descendantNode;
                }
            }
        }
 
        bool ShouldEnumerateChildren(SyntaxNode child)
        {
            // if we don't want to consider this nodes children, then don't
            if (!HasChildren(child))
            {
                return false;
            }
 
            // Always descend into the children of the node we were asked about
            if (child == node)
            {
                return true;
            }
 
            // otherwise, as long as we don't descend into lambdas
            return !IsLambdaBodyStatementOrExpression(child);
        }
    }
 
    protected bool HasChildren(SyntaxNode node)
    {
        // Leaves are labeled statements that don't have a labeled child.
        // We also return true for non-labeled statements.
        var label = Classify(node.RawKind, node, out var isLeaf);
 
        // ignored should always be reported as leaves for top syntax, but for statements
        // we want to look at all child nodes, because almost anything could have a lambda
        if (!_compareStatementSyntax)
        {
            Debug.Assert(label != IgnoredNode || isLeaf);
        }
 
        return !isLeaf;
    }
 
    internal bool HasLabel(SyntaxNode node)
        => Classify(node.RawKind, node, out _) != IgnoredNode;
 
    internal abstract int Classify(int kind, SyntaxNode? node, out bool isLeaf);
 
    protected internal override int GetLabel(SyntaxNode node)
        => Classify(node.RawKind, node, out _);
 
}