|
// 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.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
{
internal partial class DiagnosticIncrementalAnalyzer
{
private partial class StateManager
{
private readonly struct ProjectAnalyzerStateSets
{
public static readonly ProjectAnalyzerStateSets Default = new(
ImmutableArray<AnalyzerReference>.Empty,
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>>.Empty,
ImmutableDictionary<DiagnosticAnalyzer, StateSet>.Empty,
SkippedHostAnalyzersInfo.Empty);
public readonly IReadOnlyList<AnalyzerReference> AnalyzerReferences;
// maps analyzer reference id to list of analyzers loaded from the reference
public readonly ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> MapPerReferences;
public readonly ImmutableDictionary<DiagnosticAnalyzer, StateSet> StateSetMap;
public readonly SkippedHostAnalyzersInfo SkippedAnalyzersInfo;
internal ProjectAnalyzerStateSets(
IReadOnlyList<AnalyzerReference> analyzerReferences,
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> mapPerReferences,
ImmutableDictionary<DiagnosticAnalyzer, StateSet> stateSetMap,
SkippedHostAnalyzersInfo skippedAnalyzersInfo)
{
AnalyzerReferences = analyzerReferences;
MapPerReferences = mapPerReferences;
StateSetMap = stateSetMap;
SkippedAnalyzersInfo = skippedAnalyzersInfo;
}
}
public IEnumerable<StateSet> GetAllProjectStateSets()
{
// return existing state sets
return _projectAnalyzerStateMap.Values.SelectMany(e => e.StateSetMap.Values).ToImmutableArray();
}
private ProjectAnalyzerStateSets? TryGetProjectStateSets(Project project)
{
// check if the analyzer references have changed since the last time we updated the map:
if (_projectAnalyzerStateMap.TryGetValue(project.Id, out var entry) &&
entry.AnalyzerReferences.Equals(project.AnalyzerReferences))
{
return entry;
}
return null;
}
private ProjectAnalyzerStateSets GetOrCreateProjectStateSets(Project project)
{
// if we can't use cached one, we will create a new analyzer map. which is a bit of waste since
// we will create new StateSet for all analyzers. but since this only happens when project analyzer references
// are changed, I believe it is acceptable to have a bit of waste for simplicity.
return TryGetProjectStateSets(project) ?? CreateProjectStateSets(project);
}
private ProjectAnalyzerStateSets GetOrUpdateProjectStateSets(Project project)
=> TryGetProjectStateSets(project) ?? UpdateProjectStateSets(project);
/// <summary>
/// Creates a new project state sets.
/// </summary>
private ProjectAnalyzerStateSets CreateProjectStateSets(Project project)
{
if (project.AnalyzerReferences.Count == 0)
{
return ProjectAnalyzerStateSets.Default;
}
var hostAnalyzers = project.Solution.State.Analyzers;
var analyzersPerReference = hostAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project);
if (analyzersPerReference.Count == 0)
{
return ProjectAnalyzerStateSets.Default;
}
var newMap = CreateStateSetMap(project.Language, analyzersPerReference.Values, includeWorkspacePlaceholderAnalyzers: false);
var skippedAnalyzersInfo = project.GetSkippedAnalyzersInfo(_analyzerInfoCache);
return new ProjectAnalyzerStateSets(project.AnalyzerReferences, analyzersPerReference, newMap, skippedAnalyzersInfo);
}
/// <summary>
/// Updates the map to the given project snapshot.
/// </summary>
private ProjectAnalyzerStateSets UpdateProjectStateSets(Project project)
{
var projectStateSets = CreateProjectStateSets(project);
RaiseProjectAnalyzerReferenceChangedIfNeeded(project, projectStateSets.MapPerReferences, projectStateSets.StateSetMap);
// update cache.
_projectAnalyzerStateMap[project.Id] = projectStateSets;
VerifyProjectDiagnosticStates(projectStateSets.StateSetMap.Values);
return projectStateSets;
}
private void RaiseProjectAnalyzerReferenceChangedIfNeeded(
Project project,
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> newMapPerReference,
ImmutableDictionary<DiagnosticAnalyzer, StateSet> newMap)
{
if (!_projectAnalyzerStateMap.TryGetValue(project.Id, out var entry))
{
// no previous references and we still don't have any references
if (newMap.Count == 0)
{
return;
}
// new reference added
RaiseProjectAnalyzerReferenceChanged(
new ProjectAnalyzerReferenceChangedEventArgs(project, newMap.Values.ToImmutableArrayOrEmpty(), ImmutableArray<StateSet>.Empty));
return;
}
Debug.Assert(!entry.AnalyzerReferences.Equals(project.AnalyzerReferences));
// there has been change. find out what has changed
var addedStates = DiffStateSets(project.AnalyzerReferences.Except(entry.AnalyzerReferences), newMapPerReference, newMap);
var removedStates = DiffStateSets(entry.AnalyzerReferences.Except(project.AnalyzerReferences), entry.MapPerReferences, entry.StateSetMap);
// nothing has changed
if (addedStates.Length == 0 && removedStates.Length == 0)
{
return;
}
RaiseProjectAnalyzerReferenceChanged(
new ProjectAnalyzerReferenceChangedEventArgs(project, addedStates, removedStates));
}
private static ImmutableArray<StateSet> DiffStateSets(
IEnumerable<AnalyzerReference> references,
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> mapPerReference,
ImmutableDictionary<DiagnosticAnalyzer, StateSet> map)
{
if (mapPerReference.Count == 0 || map.Count == 0)
{
// nothing to diff
return ImmutableArray<StateSet>.Empty;
}
var builder = ImmutableArray.CreateBuilder<StateSet>();
foreach (var reference in references)
{
// check duplication
if (!mapPerReference.TryGetValue(reference.Id, out var analyzers))
{
continue;
}
// okay, this is real reference. get stateset
foreach (var analyzer in analyzers)
{
if (!map.TryGetValue(analyzer, out var set))
{
continue;
}
builder.Add(set);
}
}
return builder.ToImmutable();
}
}
}
}
|