File: DiffRecorder.cs
Web Access
Project: src\src\Microsoft.DotNet.AsmDiff\Microsoft.DotNet.AsmDiff.csproj (Microsoft.DotNet.AsmDiff)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Microsoft.Cci.Writers.Syntax;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
 
namespace Microsoft.DotNet.AsmDiff
{
    internal sealed class DiffRecorder : IStyleSyntaxWriter
    {
        private Stack<DiffStyle> _styleStack = new Stack<DiffStyle>();
        private bool _needIndent = true;
        private bool _skipNextWithspace;
        private bool _lastTokenWasLineBreak;
 
        public CancellationToken CancellationToken { get; }
        public List<DiffToken> Tokens { get; } = new List<DiffToken>();
        public int Line { get; private set; }
 
        public DiffRecorder(CancellationToken cancellationToken)
        {
            CancellationToken = cancellationToken;
        }
 
        public void Dispose()
        {
        }
 
        private void WriteIndentIfNeeded()
        {
            if (!_needIndent)
                return;
 
            WriteToken(DiffTokenKind.Indent, new string(' ', IndentLevel * 4));
            _needIndent = false;
        }
 
        private void WriteToken(DiffTokenKind kind, string text)
        {
            CancellationToken.ThrowIfCancellationRequested();
 
            var tokenIsLineBreak = kind == DiffTokenKind.LineBreak;
            if (tokenIsLineBreak && _lastTokenWasLineBreak)
                return;
 
            if (tokenIsLineBreak)
                Line++;
 
            _lastTokenWasLineBreak = tokenIsLineBreak;
 
            var diffStyle = GetCurrentDiffStyle();
            var token = new DiffToken(diffStyle, kind, text);
            Tokens.Add(token);
        }
 
        private DiffStyle GetCurrentDiffStyle()
        {
            return _styleStack.Aggregate(DiffStyle.None, (current, diffStyle) => current | diffStyle);
        }
 
        public void Write(string str)
        {
            // Work around issue where member attributes are emitted using a textual linebreak.
            if (str == "\r\n" || str == "\r" || str == "\n")
            {
                // We should ignore the next whitespace text to avoid double indenting.
                // However, setting _needIndent to false is not a good solution either
                // as it assumes that the writer uses the same level of indentation as
                // we are (which is not the case).
                WriteLine();
                _skipNextWithspace = true;
                return;
            }
 
            // If the text is whitespace only, recored it as such.
            if (str.Trim().Length == 0)
            {
                WriteWhitespace(str);
                return;
            }
 
            // Work around issue where member attributes are emitted using a textual indent.
            if (_lastTokenWasLineBreak && str.Trim().Length == 0)
            {
                return;
            }
 
            WriteIndentIfNeeded();
            WriteToken(DiffTokenKind.Text, str);
        }
 
        private void WriteWhitespace(string str)
        {
            if (_skipNextWithspace)
            {
                _skipNextWithspace = false;
                return;
            }
 
            WriteIndentIfNeeded();
            WriteToken(DiffTokenKind.Whitespace, str);
        }
 
        public void WriteSymbol(string symbol)
        {
            WriteIndentIfNeeded();
            WriteToken(DiffTokenKind.Symbol, symbol);
        }
 
        public void WriteIdentifier(string id)
        {
            WriteIndentIfNeeded();
            WriteToken(DiffTokenKind.Identifier, id);
        }
 
        public void WriteKeyword(string keyword)
        {
            WriteIndentIfNeeded();
            WriteToken(DiffTokenKind.Keyword, keyword);
        }
 
        public void WriteTypeName(string typeName)
        {
            WriteIndentIfNeeded();
            WriteToken(DiffTokenKind.TypeName, typeName);
        }
 
        public void WriteLine()
        {
            WriteToken(DiffTokenKind.LineBreak, Environment.NewLine);
            _needIndent = true;
        }
 
        public int IndentLevel { get; set; }
 
        public IDisposable StartStyle(SyntaxStyle style, object context)
        {
            var convertedStyle = ConvertStyle(style);
            _styleStack.Push(convertedStyle);
            return new DisposeAction(() => _styleStack.Pop());
        }
 
        private static DiffStyle ConvertStyle(SyntaxStyle style)
        {
            switch (style)
            {
                case SyntaxStyle.Added:
                    return DiffStyle.Added;
                case SyntaxStyle.Removed:
                    return DiffStyle.Removed;
                case SyntaxStyle.InterfaceMember:
                    return DiffStyle.InterfaceMember;
                case SyntaxStyle.InheritedMember:
                    return DiffStyle.InheritedMember;
                case SyntaxStyle.NotCompatible:
                    return DiffStyle.NotCompatible;
                default:
                    throw new ArgumentOutOfRangeException("style");
            }
        }
    }
}