File: InlineDiagnostics\AbstractDiagnosticsTaggerProvider.cs
Web Access
Project: src\src\EditorFeatures\Core.Wpf\Microsoft.CodeAnalysis.EditorFeatures.Wpf_tpal30ww_wpftmp.csproj (Microsoft.CodeAnalysis.EditorFeatures.Wpf)
// 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.Collections.Immutable;
using Microsoft.CodeAnalysis.Editor.Tagging;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Tagging;
 
namespace Microsoft.CodeAnalysis.Diagnostics;
 
/// <summary>
/// Base type of all diagnostic taggers (classification, squiggles, suggestions, inline-diags).  Subclasses can control
/// things by overriding functionality in this type.  Internally, this will switch to either a pull or push cased
/// approach at instantiation time depending on our internal feature flag.
/// </summary>
internal abstract partial class AbstractDiagnosticsTaggerProvider<TTag> : ITaggerProvider
    where TTag : ITag
{
    private readonly TaggerHost _taggerHost;
    protected IGlobalOptionService GlobalOptions => _taggerHost.GlobalOptions;
 
    /// <summary>
    /// Underlying diagnostic tagger responsible for the syntax/semantic and compiler/analyzer split.  The ordering of
    /// these taggers is not relevant.  They are not executed serially.  Rather, they all run concurrently, notifying us
    /// (potentially concurrently as well) when change occur.
    /// </summary>
    private readonly ImmutableArray<SingleDiagnosticKindPullTaggerProvider> _diagnosticsTaggerProviders;
 
    public AbstractDiagnosticsTaggerProvider(
        IDiagnosticAnalyzerService analyzerService,
        TaggerHost taggerHost,
        string featureName)
    {
        _taggerHost = taggerHost;
 
        _diagnosticsTaggerProviders =
        [
            CreateDiagnosticsTaggerProvider(DiagnosticKind.CompilerSyntax),
            CreateDiagnosticsTaggerProvider(DiagnosticKind.CompilerSemantic),
            CreateDiagnosticsTaggerProvider(DiagnosticKind.AnalyzerSyntax),
            CreateDiagnosticsTaggerProvider(DiagnosticKind.AnalyzerSemantic),
        ];
 
        return;
 
        SingleDiagnosticKindPullTaggerProvider CreateDiagnosticsTaggerProvider(DiagnosticKind diagnosticKind)
            => new(this, analyzerService, diagnosticKind, taggerHost, featureName);
    }
 
    // Functionality for subclasses to control how this diagnostic tagging operates.  All the individual
    // SingleDiagnosticKindTaggerProvider will defer to these to do the work so that they otherwise operate
    // identically.
 
    protected abstract ImmutableArray<IOption2> Options { get; }
    protected virtual ImmutableArray<IOption2> FeatureOptions { get; } = [];
 
    protected abstract bool IncludeDiagnostic(DiagnosticData data);
 
    protected abstract bool TagEquals(TTag tag1, TTag tag2);
 
    protected abstract TTag? CreateTag(Workspace workspace, DiagnosticData diagnostic);
 
    /// <summary>
    /// Get the <see cref="DiagnosticDataLocation"/> that should have the tag applied to it.
    /// In most cases, this is the <see cref="DiagnosticData.DataLocation"/> but overrides can change it (e.g. unnecessary classifications).
    /// </summary>
    /// <param name="diagnosticData">the diagnostic containing the location(s).</param>
    /// <returns>an array of locations that should have the tag applied.</returns>
    protected virtual ImmutableArray<DiagnosticDataLocation> GetLocationsToTag(DiagnosticData diagnosticData)
        => diagnosticData.DataLocation is not null ? [diagnosticData.DataLocation] : [];
 
    ITagger<T>? ITaggerProvider.CreateTagger<T>(ITextBuffer buffer)
    {
        var tagger = CreateTagger<T>(buffer);
 
        if (tagger is not ITagger<T> genericTagger)
        {
            tagger.Dispose();
            return null;
        }
 
        return genericTagger;
    }
 
    public SimpleAggregateTagger<TTag> CreateTagger<T>(ITextBuffer buffer) where T : ITag
    {
        using var taggers = TemporaryArray<EfficientTagger<TTag>>.Empty;
        foreach (var taggerProvider in _diagnosticsTaggerProviders)
        {
            var innerTagger = taggerProvider.CreateTagger(buffer);
            if (innerTagger != null)
                taggers.Add(innerTagger);
        }
 
        return new SimpleAggregateTagger<TTag>(taggers.ToImmutableAndClear());
    }
 
    protected TagSpan<TTag>? CreateTagSpan(Workspace workspace, SnapshotSpan span, DiagnosticData data)
    {
        var errorTag = CreateTag(workspace, data);
        if (errorTag == null)
            return null;
 
        // Ensure the diagnostic has at least length 1.  Tags must have a non-empty length in order to actually show
        // up in the editor.
        var adjustedSpan = AdjustSnapshotSpan(span, minimumLength: 1);
        if (adjustedSpan.Length == 0)
            return null;
 
        return new TagSpan<TTag>(adjustedSpan, errorTag);
    }
 
    protected virtual SnapshotSpan AdjustSnapshotSpan(SnapshotSpan span, int minimumLength)
        => AdjustSnapshotSpan(span, minimumLength, maximumLength: int.MaxValue);
 
    protected static SnapshotSpan AdjustSnapshotSpan(SnapshotSpan span, int minimumLength, int maximumLength)
    {
        var snapshot = span.Snapshot;
 
        // new length
        var length = Math.Min(Math.Max(span.Length, minimumLength), maximumLength);
 
        // make sure start + length is smaller than snapshot.Length and start is >= 0
        var start = Math.Max(0, Math.Min(span.Start, snapshot.Length - length));
 
        // make sure length is smaller than snapshot.Length which can happen if start == 0
        return new SnapshotSpan(snapshot, start, Math.Min(start + length, snapshot.Length) - start);
    }
}