File: DifferenceVisitor.cs
Web Access
Project: Microsoft.DotNet.ApiCompatibility.csproj (Microsoft.DotNet.ApiCompatibility)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.CodeAnalysis;
using Microsoft.DotNet.ApiCompatibility.Mapping;

namespace Microsoft.DotNet.ApiCompatibility
{
    /// <summary>
    /// A visitor that traverses the mapping tree and stores found differences as <see cref="CompatDifference" /> items.
    /// </summary>
    public class DifferenceVisitor : IDifferenceVisitor
    {
        private readonly HashSet<CompatDifference> _compatDifferences = [];

        /// <inheritdoc />
        public IEnumerable<CompatDifference> CompatDifferences => _compatDifferences;

        /// <inheritdoc />
        public void Visit<T>(IElementMapper<T> mapper)
        {
            if (mapper is IAssemblySetMapper assemblySetMapper)
            {
                Visit(assemblySetMapper);
            }
            else if (mapper is IAssemblyMapper assemblyMapper)
            {
                Visit(assemblyMapper);
            }
            else if (mapper is INamespaceMapper nsMapper)
            {
                Visit(nsMapper);
            }
            else if (mapper is ITypeMapper typeMapper)
            {
                Visit(typeMapper);
            }
            else if (mapper is IMemberMapper memberMapper)
            {
                Visit(memberMapper);
            }
        }

        /// <inheritdoc />
        public void Visit(IAssemblySetMapper mapper)
        {
            foreach (IAssemblyMapper assembly in mapper.GetAssemblies())
            {
                Visit(assembly);
            }
        }

        /// <inheritdoc />
        public void Visit(IAssemblyMapper assembly)
        {
            AddSymbolDifferences(assembly.GetDifferences());

            foreach (INamespaceMapper @namespace in assembly.GetNamespaces())
            {
                Visit(@namespace);
            }
        }

        /// <inheritdoc />
        public void Visit(INamespaceMapper @namespace)
        {
            foreach (ITypeMapper type in @namespace.GetTypes())
            {
                Visit(type);
            }
        }

        /// <inheritdoc />
        public void Visit(ITypeMapper type)
        {
            AddSymbolDifferences(type);

            if (type.ShouldDiffMembers)
            {
                foreach (ITypeMapper nestedType in type.GetNestedTypes())
                {
                    Visit(nestedType);
                }

                foreach (IMemberMapper member in type.GetMembers())
                {
                    Visit(member);
                }
            }
        }

        /// <inheritdoc />
        public void Visit(IMemberMapper member)
        {
            AddSymbolDifferences(member);
        }

        private void AddSymbolDifferences<T>(IElementMapper<T> mapper)
            where T : ISymbol
            => AddSymbolDifferences(mapper.GetDifferences());

        private void AddSymbolDifferences(IEnumerable<CompatDifference> differences)
        {
            foreach (CompatDifference item in differences)
            {
                bool isExperimentalDifference =
                    (item.LeftStability is null && item.RightStability == ApiStability.Experimental) ||
                    (item.RightStability is null && item.LeftStability == ApiStability.Experimental) ||
                    (item.LeftStability == ApiStability.Experimental && item.RightStability == ApiStability.Experimental);

                CompatDifference difference = isExperimentalDifference
                    ? item.WithSeverity(DifferenceSeverity.Informational)
                    : item;

                if (_compatDifferences.TryGetValue(difference, out CompatDifference existingDifference))
                {
                    if (existingDifference.Severity == DifferenceSeverity.Informational &&
                        difference.Severity == DifferenceSeverity.Error)
                    {
                        _compatDifferences.Remove(existingDifference);
                        _compatDifferences.Add(difference);
                    }
                }
                else
                {
                    _compatDifferences.Add(difference);
                }
            }
        }
    }
}