File: DiagnosticAnalyzer\AnalyzerExecutor.AnalyzerDiagnosticReporter.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;
using System.Diagnostics;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Diagnostics
{
    internal partial class AnalyzerExecutor
    {
        /// <summary>
        /// Pooled object that carries the info needed to process
        /// a reported diagnostic from a syntax node action.
        /// </summary>
        private sealed class AnalyzerDiagnosticReporter
        {
            public readonly Action<Diagnostic> AddDiagnosticAction;
 
            private static readonly ObjectPool<AnalyzerDiagnosticReporter> s_objectPool =
                new ObjectPool<AnalyzerDiagnosticReporter>(() => new AnalyzerDiagnosticReporter(), 10);
 
            public static AnalyzerDiagnosticReporter GetInstance(
                SourceOrAdditionalFile contextFile,
                TextSpan? span,
                Compilation compilation,
                DiagnosticAnalyzer analyzer,
                bool isSyntaxDiagnostic,
                Action<Diagnostic, CancellationToken>? addNonCategorizedDiagnostic,
                Action<Diagnostic, DiagnosticAnalyzer, bool, CancellationToken>? addCategorizedLocalDiagnostic,
                Action<Diagnostic, DiagnosticAnalyzer, CancellationToken>? addCategorizedNonLocalDiagnostic,
                Func<Diagnostic, DiagnosticAnalyzer, Compilation, CancellationToken, bool> shouldSuppressGeneratedCodeDiagnostic,
                CancellationToken cancellationToken)
            {
                var item = s_objectPool.Allocate();
                item._contextFile = contextFile;
                item.FilterSpanForLocalDiagnostics = span;
                item._compilation = compilation;
                item._analyzer = analyzer;
                item._isSyntaxDiagnostic = isSyntaxDiagnostic;
                item._addNonCategorizedDiagnostic = addNonCategorizedDiagnostic;
                item._addCategorizedLocalDiagnostic = addCategorizedLocalDiagnostic;
                item._addCategorizedNonLocalDiagnostic = addCategorizedNonLocalDiagnostic;
                item._shouldSuppressGeneratedCodeDiagnostic = shouldSuppressGeneratedCodeDiagnostic;
                item._cancellationToken = cancellationToken;
                return item;
            }
 
            public void Free()
            {
                _contextFile = null!;
                FilterSpanForLocalDiagnostics = null;
                _compilation = null!;
                _analyzer = null!;
                _isSyntaxDiagnostic = default;
                _addNonCategorizedDiagnostic = null!;
                _addCategorizedLocalDiagnostic = null!;
                _addCategorizedNonLocalDiagnostic = null!;
                _shouldSuppressGeneratedCodeDiagnostic = null!;
                _cancellationToken = default;
                s_objectPool.Free(this);
            }
 
            private SourceOrAdditionalFile? _contextFile;
            private Compilation _compilation;
            private DiagnosticAnalyzer _analyzer;
            private bool _isSyntaxDiagnostic;
            private Action<Diagnostic, CancellationToken>? _addNonCategorizedDiagnostic;
            private Action<Diagnostic, DiagnosticAnalyzer, bool, CancellationToken>? _addCategorizedLocalDiagnostic;
            private Action<Diagnostic, DiagnosticAnalyzer, CancellationToken>? _addCategorizedNonLocalDiagnostic;
            private Func<Diagnostic, DiagnosticAnalyzer, Compilation, CancellationToken, bool> _shouldSuppressGeneratedCodeDiagnostic;
            private CancellationToken _cancellationToken;
 
            /// <summary>
            /// An optional filter span, which if non-null, indicates that diagnostics reported within this span
            /// are considered local diagnostics, and those reported outside this span are considered non-local.
            /// 
            /// NOTE: <see cref="AnalyzerDiagnosticReporter"/> is a pooled type that is always used from a single
            /// thread, hence it is safe to expose a public mutable field.
            /// </summary>
            public TextSpan? FilterSpanForLocalDiagnostics;
 
            // Pooled objects are initialized in their GetInstance method
#pragma warning disable 8618
            private AnalyzerDiagnosticReporter()
            {
                AddDiagnosticAction = AddDiagnostic;
            }
#pragma warning restore 8618
 
            private void AddDiagnostic(Diagnostic diagnostic)
            {
                if (_shouldSuppressGeneratedCodeDiagnostic(diagnostic, _analyzer, _compilation, _cancellationToken))
                {
                    return;
                }
 
                if (_addCategorizedLocalDiagnostic == null)
                {
                    Debug.Assert(_addNonCategorizedDiagnostic != null);
                    _addNonCategorizedDiagnostic(diagnostic, _cancellationToken);
                    return;
                }
 
                Debug.Assert(_addNonCategorizedDiagnostic == null);
                Debug.Assert(_addCategorizedNonLocalDiagnostic != null);
 
                if (isLocalDiagnostic(diagnostic) &&
                    (!FilterSpanForLocalDiagnostics.HasValue || FilterSpanForLocalDiagnostics.Value.IntersectsWith(diagnostic.Location.SourceSpan)))
                {
                    _addCategorizedLocalDiagnostic(diagnostic, _analyzer, _isSyntaxDiagnostic, _cancellationToken);
                }
                else
                {
                    _addCategorizedNonLocalDiagnostic(diagnostic, _analyzer, _cancellationToken);
                }
 
                return;
 
                bool isLocalDiagnostic(Diagnostic diagnostic)
                {
                    if (diagnostic.Location.IsInSource)
                    {
                        return _contextFile?.SourceTree != null &&
                            _contextFile.Value.SourceTree == diagnostic.Location.SourceTree;
                    }
 
                    if (_contextFile?.AdditionalFile != null &&
                        diagnostic.Location is ExternalFileLocation externalFileLocation)
                    {
                        return PathUtilities.Comparer.Equals(_contextFile.Value.AdditionalFile.Path, externalFileLocation.GetLineSpan().Path);
                    }
 
                    return false;
                }
            }
        }
    }
}