File: DiagnosticAnalyzer\AnalysisContextInfo.cs
Web Access
Project: src\src\Compilers\Core\Portable\Microsoft.CodeAnalysis.csproj (Microsoft.CodeAnalysis)
// 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.Diagnostics;
using System.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Diagnostics
{
    /// <summary>
    /// this hold onto analyzer executor context which will be used later to put context information in analyzer exception if it occurs.
    /// </summary>
    internal readonly struct AnalysisContextInfo
    {
        private readonly Compilation? _compilation;
        private readonly IOperation? _operation;
        private readonly ISymbol? _symbol;
        private readonly SourceOrAdditionalFile? _file;
        private readonly SyntaxNode? _node;
 
        public AnalysisContextInfo(Compilation compilation) :
            this(compilation: compilation, operation: null, symbol: null, file: null, node: null)
        {
        }
 
        public AnalysisContextInfo(SemanticModel model) :
            this(model.Compilation, new SourceOrAdditionalFile(model.SyntaxTree))
        {
        }
 
        public AnalysisContextInfo(Compilation compilation, ISymbol symbol) :
            this(compilation: compilation, operation: null, symbol: symbol, file: null, node: null)
        {
        }
 
        public AnalysisContextInfo(Compilation compilation, SourceOrAdditionalFile file) :
            this(compilation: compilation, operation: null, symbol: null, file: file, node: null)
        {
        }
 
        public AnalysisContextInfo(Compilation compilation, SyntaxNode node) :
            this(compilation: compilation, operation: null, symbol: null, file: new SourceOrAdditionalFile(node.SyntaxTree), node)
        {
        }
 
        public AnalysisContextInfo(Compilation compilation, IOperation operation) :
            this(compilation: compilation, operation: operation, symbol: null, file: new SourceOrAdditionalFile(operation.Syntax.SyntaxTree), node: operation.Syntax)
        {
        }
 
        public AnalysisContextInfo(Compilation compilation, ISymbol symbol, SyntaxNode node) :
            this(compilation: compilation, operation: null, symbol: symbol, file: new SourceOrAdditionalFile(node.SyntaxTree), node)
        {
        }
 
        private AnalysisContextInfo(
            Compilation? compilation,
            IOperation? operation,
            ISymbol? symbol,
            SourceOrAdditionalFile? file,
            SyntaxNode? node)
        {
            Debug.Assert(node == null || file?.SourceTree != null);
            Debug.Assert(operation == null || file?.SourceTree != null);
 
            _compilation = compilation;
            _operation = operation;
            _symbol = symbol;
            _file = file;
            _node = node;
        }
 
        public string GetContext()
        {
            var sb = new StringBuilder();
 
            if (_compilation?.AssemblyName != null)
            {
                sb.AppendLine($"{nameof(Compilation)}: {_compilation.AssemblyName}");
            }
 
            if (_operation != null)
            {
                sb.AppendLine($"{nameof(IOperation)}: {_operation.Kind}");
            }
 
            if (_symbol?.Name != null)
            {
                sb.AppendLine($"{nameof(ISymbol)}: {_symbol.Name} ({_symbol.Kind})");
            }
 
            if (_file.HasValue)
            {
                if (_file.Value.SourceTree != null)
                {
                    sb.AppendLine($"{nameof(SyntaxTree)}: {_file.Value.SourceTree.FilePath}");
                }
                else
                {
                    RoslynDebug.Assert(_file.Value.AdditionalFile != null);
                    sb.AppendLine($"{nameof(AdditionalText)}: {_file.Value.AdditionalFile.Path}");
                }
            }
 
            if (_node != null)
            {
                RoslynDebug.Assert(_file.HasValue);
                RoslynDebug.Assert(_file.Value.SourceTree != null);
 
                var text = _file.Value.SourceTree.GetText();
                var lineSpan = text?.Lines?.GetLinePositionSpan(_node.Span);
 
                // can't use Kind since that is language specific. instead will output typename.
                sb.AppendLine($"{nameof(SyntaxNode)}: {GetFlattenedNodeText(_node)} [{_node.GetType().Name}]@{_node.Span} {(lineSpan.HasValue ? lineSpan.Value.ToString() : string.Empty)}");
            }
 
            return sb.ToString();
        }
 
        private string GetFlattenedNodeText(SyntaxNode node)
        {
            const int cutoff = 30;
 
            var lastEnd = node.Span.Start;
            var sb = new StringBuilder();
            foreach (var token in node.DescendantTokens(descendIntoTrivia: false))
            {
                if (token.Span.Start - lastEnd > 0)
                {
                    sb.Append(' ');
                }
 
                sb.Append(token.ToString());
                lastEnd = token.Span.End;
 
                if (sb.Length > cutoff)
                {
                    break;
                }
            }
 
            return sb.ToString() + (sb.Length > cutoff ? " ..." : string.Empty);
        }
    }
}