|
// 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;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Shell;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer
{
internal abstract partial class BaseDiagnosticAndGeneratorItemSource : IAttachedCollectionSource
{
private static readonly DiagnosticDescriptorComparer s_comparer = new DiagnosticDescriptorComparer();
private readonly IDiagnosticAnalyzerService _diagnosticAnalyzerService;
private BulkObservableCollection<BaseItem>? _items;
private ReportDiagnostic _generalDiagnosticOption;
private ImmutableDictionary<string, ReportDiagnostic>? _specificDiagnosticOptions;
private AnalyzerConfigData? _analyzerConfigOptions;
public BaseDiagnosticAndGeneratorItemSource(Workspace workspace, ProjectId projectId, IAnalyzersCommandHandler commandHandler, IDiagnosticAnalyzerService diagnosticAnalyzerService)
{
Workspace = workspace;
ProjectId = projectId;
CommandHandler = commandHandler;
_diagnosticAnalyzerService = diagnosticAnalyzerService;
}
public Workspace Workspace { get; }
public ProjectId ProjectId { get; }
protected IAnalyzersCommandHandler CommandHandler { get; }
public abstract AnalyzerReference? AnalyzerReference { get; }
public abstract object SourceItem { get; }
[MemberNotNullWhen(true, nameof(AnalyzerReference))]
public bool HasItems
{
get
{
if (_items != null)
{
return _items.Count > 0;
}
if (AnalyzerReference == null)
{
return false;
}
var project = Workspace.CurrentSolution.GetProject(ProjectId);
if (project == null)
{
return false;
}
return AnalyzerReference.GetAnalyzers(project.Language).Any() ||
AnalyzerReference.GetGenerators(project.Language).Any();
}
}
public IEnumerable Items
{
get
{
if (_items == null)
{
var project = Workspace.CurrentSolution.GetRequiredProject(ProjectId);
_generalDiagnosticOption = project.CompilationOptions!.GeneralDiagnosticOption;
_specificDiagnosticOptions = project.CompilationOptions!.SpecificDiagnosticOptions;
_analyzerConfigOptions = project.GetAnalyzerConfigOptions();
_items = CreateDiagnosticAndGeneratorItems(project.Id, project.Language, project.CompilationOptions, _analyzerConfigOptions);
Workspace.WorkspaceChanged += OnWorkspaceChangedLookForOptionsChanges;
}
Logger.Log(
FunctionId.SolutionExplorer_DiagnosticItemSource_GetItems,
KeyValueLogMessage.Create(m => m["Count"] = _items.Count));
return _items;
}
}
private BulkObservableCollection<BaseItem> CreateDiagnosticAndGeneratorItems(ProjectId projectId, string language, CompilationOptions options, AnalyzerConfigData? analyzerConfigOptions)
{
// Within an analyzer assembly, an individual analyzer may report multiple different diagnostics
// with the same ID. Or, multiple analyzers may report diagnostics with the same ID. Or a
// combination of the two may occur.
// We only want to show one node in Solution Explorer for a given ID. So we pick one, but we need
// to be consistent in which one we pick. Diagnostics with the same ID may have different
// descriptions or messages, and it would be strange if the node's name changed from one run of
// VS to another. So we group the diagnostics by ID, sort them within a group, and take the first
// one.
Contract.ThrowIfFalse(HasItems);
var collection = new BulkObservableCollection<BaseItem>();
collection.AddRange(
AnalyzerReference.GetAnalyzers(language)
.SelectMany(a => _diagnosticAnalyzerService.AnalyzerInfoCache.GetDiagnosticDescriptors(a))
.GroupBy(d => d.Id)
.OrderBy(g => g.Key, StringComparer.CurrentCulture)
.Select(g =>
{
var selectedDiagnostic = g.OrderBy(d => d, s_comparer).First();
var effectiveSeverity = selectedDiagnostic.GetEffectiveSeverity(options, analyzerConfigOptions?.AnalyzerOptions, analyzerConfigOptions?.TreeOptions);
return new DiagnosticItem(projectId, AnalyzerReference, selectedDiagnostic, effectiveSeverity, CommandHandler);
}));
collection.AddRange(
AnalyzerReference.GetGenerators(language)
.Select(g => new SourceGeneratorItem(projectId, g, AnalyzerReference)));
return collection;
}
private void OnWorkspaceChangedLookForOptionsChanges(object sender, WorkspaceChangeEventArgs e)
{
if (e.Kind is WorkspaceChangeKind.SolutionCleared or
WorkspaceChangeKind.SolutionReloaded or
WorkspaceChangeKind.SolutionRemoved)
{
Workspace.WorkspaceChanged -= OnWorkspaceChangedLookForOptionsChanges;
}
else if (e.ProjectId == ProjectId)
{
if (e.Kind == WorkspaceChangeKind.ProjectRemoved)
{
Workspace.WorkspaceChanged -= OnWorkspaceChangedLookForOptionsChanges;
}
else if (e.Kind == WorkspaceChangeKind.ProjectChanged)
{
OnProjectConfigurationChanged();
}
else if (e.DocumentId != null)
{
switch (e.Kind)
{
case WorkspaceChangeKind.AnalyzerConfigDocumentAdded:
case WorkspaceChangeKind.AnalyzerConfigDocumentChanged:
case WorkspaceChangeKind.AnalyzerConfigDocumentReloaded:
case WorkspaceChangeKind.AnalyzerConfigDocumentRemoved:
OnProjectConfigurationChanged();
break;
}
}
}
return;
// Local functions.
void OnProjectConfigurationChanged()
{
var project = e.NewSolution.GetRequiredProject(ProjectId);
var newGeneralDiagnosticOption = project.CompilationOptions!.GeneralDiagnosticOption;
var newSpecificDiagnosticOptions = project.CompilationOptions!.SpecificDiagnosticOptions;
var newAnalyzerConfigOptions = project.GetAnalyzerConfigOptions();
if (newGeneralDiagnosticOption != _generalDiagnosticOption ||
!object.ReferenceEquals(newSpecificDiagnosticOptions, _specificDiagnosticOptions) ||
!object.ReferenceEquals(newAnalyzerConfigOptions?.TreeOptions, _analyzerConfigOptions?.TreeOptions) ||
!object.ReferenceEquals(newAnalyzerConfigOptions?.AnalyzerOptions, _analyzerConfigOptions?.AnalyzerOptions))
{
_generalDiagnosticOption = newGeneralDiagnosticOption;
_specificDiagnosticOptions = newSpecificDiagnosticOptions;
_analyzerConfigOptions = newAnalyzerConfigOptions;
Contract.ThrowIfNull(_items, "We only subscribe to events after we create the items, so this should not be null.");
foreach (var item in _items.OfType<DiagnosticItem>())
{
var effectiveSeverity = item.Descriptor.GetEffectiveSeverity(project.CompilationOptions, newAnalyzerConfigOptions?.AnalyzerOptions, newAnalyzerConfigOptions?.TreeOptions);
item.UpdateEffectiveSeverity(effectiveSeverity);
}
}
}
}
}
}
|