Skip to content

Commit e7fadbf

Browse files
Move the StateManager type up to the DiagnosticService from the DiagnosticIncrementalANalyzer (#79984)
2 parents 2d98651 + 8fc7874 commit e7fadbf

8 files changed

+195
-205
lines changed

src/Features/Core/Portable/Diagnostics/Service/DiagnosticAnalyzerService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ internal sealed partial class DiagnosticAnalyzerService : IDiagnosticAnalyzerSer
5858
private readonly IDiagnosticsRefresher _diagnosticsRefresher;
5959
private readonly DiagnosticIncrementalAnalyzer _incrementalAnalyzer;
6060
private readonly DiagnosticAnalyzerInfoCache _analyzerInfoCache;
61+
private readonly StateManager _stateManager;
6162

6263
public DiagnosticAnalyzerService(
6364
IGlobalOptionService globalOptions,
@@ -71,6 +72,7 @@ public DiagnosticAnalyzerService(
7172
GlobalOptions = globalOptions;
7273
_diagnosticsRefresher = diagnosticsRefresher;
7374
_incrementalAnalyzer = new DiagnosticIncrementalAnalyzer(this, _analyzerInfoCache, this.GlobalOptions);
75+
_stateManager = new StateManager(_analyzerInfoCache);
7476

7577
globalOptions.AddOptionChangedHandler(this, (_, _, e) =>
7678
{

src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.HostAnalyzerInfo.cs

Lines changed: 55 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,95 +2,90 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5-
using System;
65
using System.Collections.Generic;
76
using System.Collections.Immutable;
87
using System.Linq;
98
using Microsoft.CodeAnalysis.PooledObjects;
10-
using Roslyn.Utilities;
119

1210
namespace Microsoft.CodeAnalysis.Diagnostics;
1311

1412
internal sealed partial class DiagnosticAnalyzerService
1513
{
16-
private sealed partial class DiagnosticIncrementalAnalyzer
14+
private sealed partial class StateManager
1715
{
18-
private sealed partial class StateManager
16+
private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo(
17+
SolutionState solution, ProjectState project, ProjectAnalyzerInfo projectAnalyzerInfo)
1918
{
20-
private HostAnalyzerInfo GetOrCreateHostAnalyzerInfo(
21-
SolutionState solution, ProjectState project, ProjectAnalyzerInfo projectAnalyzerInfo)
19+
var key = new HostAnalyzerInfoKey(project.Language, project.HasSdkCodeStyleAnalyzers, solution.Analyzers.HostAnalyzerReferences);
20+
// Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the
21+
// Host fallback options. These ids will be used when building up the Host and Project analyzer collections.
22+
var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(solution, project);
23+
var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (solution.Analyzers, referenceIdsToRedirect));
24+
return hostAnalyzerInfo.WithExcludedAnalyzers(projectAnalyzerInfo.SkippedAnalyzersInfo.SkippedAnalyzers);
25+
26+
static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet<object> ReferenceIdsToRedirect) state)
2227
{
23-
var key = new HostAnalyzerInfoKey(project.Language, project.HasSdkCodeStyleAnalyzers, solution.Analyzers.HostAnalyzerReferences);
24-
// Some Host Analyzers may need to be treated as Project Analyzers so that they do not have access to the
25-
// Host fallback options. These ids will be used when building up the Host and Project analyzer collections.
26-
var referenceIdsToRedirect = GetReferenceIdsToRedirectAsProjectAnalyzers(solution, project);
27-
var hostAnalyzerInfo = ImmutableInterlocked.GetOrAdd(ref _hostAnalyzerStateMap, key, CreateLanguageSpecificAnalyzerMap, (solution.Analyzers, referenceIdsToRedirect));
28-
return hostAnalyzerInfo.WithExcludedAnalyzers(projectAnalyzerInfo.SkippedAnalyzersInfo.SkippedAnalyzers);
29-
30-
static HostAnalyzerInfo CreateLanguageSpecificAnalyzerMap(HostAnalyzerInfoKey arg, (HostDiagnosticAnalyzers HostAnalyzers, ImmutableHashSet<object> ReferenceIdsToRedirect) state)
31-
{
32-
var language = arg.Language;
33-
var analyzersPerReference = state.HostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language);
28+
var language = arg.Language;
29+
var analyzersPerReference = state.HostAnalyzers.GetOrCreateHostDiagnosticAnalyzersPerReference(language);
30+
31+
var (hostAnalyzerCollection, projectAnalyzerCollection) = GetAnalyzerCollections(analyzersPerReference, state.ReferenceIdsToRedirect);
32+
var (hostAnalyzers, allAnalyzers) = PartitionAnalyzers(projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true);
3433

35-
var (hostAnalyzerCollection, projectAnalyzerCollection) = GetAnalyzerCollections(analyzersPerReference, state.ReferenceIdsToRedirect);
36-
var (hostAnalyzers, allAnalyzers) = PartitionAnalyzers(projectAnalyzerCollection, hostAnalyzerCollection, includeWorkspacePlaceholderAnalyzers: true);
34+
return new HostAnalyzerInfo(hostAnalyzers, allAnalyzers);
35+
}
3736

38-
return new HostAnalyzerInfo(hostAnalyzers, allAnalyzers);
37+
static (IEnumerable<ImmutableArray<DiagnosticAnalyzer>> HostAnalyzerCollection, IEnumerable<ImmutableArray<DiagnosticAnalyzer>> ProjectAnalyzerCollection) GetAnalyzerCollections(
38+
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> analyzersPerReference,
39+
ImmutableHashSet<object> referenceIdsToRedirectAsProjectAnalyzers)
40+
{
41+
if (referenceIdsToRedirectAsProjectAnalyzers.IsEmpty)
42+
{
43+
return (analyzersPerReference.Values, []);
3944
}
4045

41-
static (IEnumerable<ImmutableArray<DiagnosticAnalyzer>> HostAnalyzerCollection, IEnumerable<ImmutableArray<DiagnosticAnalyzer>> ProjectAnalyzerCollection) GetAnalyzerCollections(
42-
ImmutableDictionary<object, ImmutableArray<DiagnosticAnalyzer>> analyzersPerReference,
43-
ImmutableHashSet<object> referenceIdsToRedirectAsProjectAnalyzers)
46+
var hostAnalyzerCollection = ArrayBuilder<ImmutableArray<DiagnosticAnalyzer>>.GetInstance();
47+
var projectAnalyzerCollection = ArrayBuilder<ImmutableArray<DiagnosticAnalyzer>>.GetInstance();
48+
49+
foreach (var (referenceId, analyzers) in analyzersPerReference)
4450
{
45-
if (referenceIdsToRedirectAsProjectAnalyzers.IsEmpty)
51+
if (referenceIdsToRedirectAsProjectAnalyzers.Contains(referenceId))
4652
{
47-
return (analyzersPerReference.Values, []);
53+
projectAnalyzerCollection.Add(analyzers);
4854
}
49-
50-
var hostAnalyzerCollection = ArrayBuilder<ImmutableArray<DiagnosticAnalyzer>>.GetInstance();
51-
var projectAnalyzerCollection = ArrayBuilder<ImmutableArray<DiagnosticAnalyzer>>.GetInstance();
52-
53-
foreach (var (referenceId, analyzers) in analyzersPerReference)
55+
else
5456
{
55-
if (referenceIdsToRedirectAsProjectAnalyzers.Contains(referenceId))
56-
{
57-
projectAnalyzerCollection.Add(analyzers);
58-
}
59-
else
60-
{
61-
hostAnalyzerCollection.Add(analyzers);
62-
}
57+
hostAnalyzerCollection.Add(analyzers);
6358
}
64-
65-
return (hostAnalyzerCollection.ToImmutableAndFree(), projectAnalyzerCollection.ToImmutableAndFree());
6659
}
60+
61+
return (hostAnalyzerCollection.ToImmutableAndFree(), projectAnalyzerCollection.ToImmutableAndFree());
6762
}
63+
}
6864

69-
private static ImmutableHashSet<object> GetReferenceIdsToRedirectAsProjectAnalyzers(
70-
SolutionState solution, ProjectState project)
65+
private static ImmutableHashSet<object> GetReferenceIdsToRedirectAsProjectAnalyzers(
66+
SolutionState solution, ProjectState project)
67+
{
68+
if (project.HasSdkCodeStyleAnalyzers)
7169
{
72-
if (project.HasSdkCodeStyleAnalyzers)
73-
{
74-
// When a project uses CodeStyle analyzers added by the SDK, we remove them in favor of the
75-
// Features analyzers. We need to then treat the Features analyzers as Project analyzers so
76-
// they do not get access to the Host fallback options.
77-
return GetFeaturesAnalyzerReferenceIds(solution.Analyzers);
78-
}
79-
80-
return [];
70+
// When a project uses CodeStyle analyzers added by the SDK, we remove them in favor of the
71+
// Features analyzers. We need to then treat the Features analyzers as Project analyzers so
72+
// they do not get access to the Host fallback options.
73+
return GetFeaturesAnalyzerReferenceIds(solution.Analyzers);
74+
}
8175

82-
static ImmutableHashSet<object> GetFeaturesAnalyzerReferenceIds(HostDiagnosticAnalyzers hostAnalyzers)
83-
{
84-
var builder = ImmutableHashSet.CreateBuilder<object>();
76+
return [];
8577

86-
foreach (var analyzerReference in hostAnalyzers.HostAnalyzerReferences)
87-
{
88-
if (analyzerReference.IsFeaturesAnalyzer())
89-
builder.Add(analyzerReference.Id);
90-
}
78+
static ImmutableHashSet<object> GetFeaturesAnalyzerReferenceIds(HostDiagnosticAnalyzers hostAnalyzers)
79+
{
80+
var builder = ImmutableHashSet.CreateBuilder<object>();
9181

92-
return builder.ToImmutable();
82+
foreach (var analyzerReference in hostAnalyzers.HostAnalyzerReferences)
83+
{
84+
if (analyzerReference.IsFeaturesAnalyzer())
85+
builder.Add(analyzerReference.Id);
9386
}
87+
88+
return builder.ToImmutable();
9489
}
9590
}
9691
}

src/Features/Core/Portable/Diagnostics/Service/EngineV2/DiagnosticIncrementalAnalyzer.StateManager.ProjectStates.cs

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -13,96 +13,93 @@ namespace Microsoft.CodeAnalysis.Diagnostics;
1313

1414
internal sealed partial class DiagnosticAnalyzerService
1515
{
16-
private sealed partial class DiagnosticIncrementalAnalyzer
16+
private sealed partial class StateManager
1717
{
18-
private sealed partial class StateManager
18+
private readonly struct ProjectAnalyzerInfo
1919
{
20-
private readonly struct ProjectAnalyzerInfo
21-
{
22-
public static readonly ProjectAnalyzerInfo Default = new(
23-
analyzerReferences: [],
24-
analyzers: [],
25-
SkippedHostAnalyzersInfo.Empty);
20+
public static readonly ProjectAnalyzerInfo Default = new(
21+
analyzerReferences: [],
22+
analyzers: [],
23+
SkippedHostAnalyzersInfo.Empty);
2624

27-
public readonly IReadOnlyList<AnalyzerReference> AnalyzerReferences;
25+
public readonly IReadOnlyList<AnalyzerReference> AnalyzerReferences;
2826

29-
public readonly ImmutableHashSet<DiagnosticAnalyzer> Analyzers;
27+
public readonly ImmutableHashSet<DiagnosticAnalyzer> Analyzers;
3028

31-
public readonly SkippedHostAnalyzersInfo SkippedAnalyzersInfo;
29+
public readonly SkippedHostAnalyzersInfo SkippedAnalyzersInfo;
3230

33-
internal ProjectAnalyzerInfo(
34-
IReadOnlyList<AnalyzerReference> analyzerReferences,
35-
ImmutableHashSet<DiagnosticAnalyzer> analyzers,
36-
SkippedHostAnalyzersInfo skippedAnalyzersInfo)
37-
{
38-
AnalyzerReferences = analyzerReferences;
39-
Analyzers = analyzers;
40-
SkippedAnalyzersInfo = skippedAnalyzersInfo;
41-
}
31+
internal ProjectAnalyzerInfo(
32+
IReadOnlyList<AnalyzerReference> analyzerReferences,
33+
ImmutableHashSet<DiagnosticAnalyzer> analyzers,
34+
SkippedHostAnalyzersInfo skippedAnalyzersInfo)
35+
{
36+
AnalyzerReferences = analyzerReferences;
37+
Analyzers = analyzers;
38+
SkippedAnalyzersInfo = skippedAnalyzersInfo;
4239
}
40+
}
4341

44-
private ProjectAnalyzerInfo? TryGetProjectAnalyzerInfo(ProjectState project)
42+
private ProjectAnalyzerInfo? TryGetProjectAnalyzerInfo(ProjectState project)
43+
{
44+
// check if the analyzer references have changed since the last time we updated the map:
45+
// No need to use _projectAnalyzerStateMapGuard during reads of _projectAnalyzerStateMap
46+
if (_projectAnalyzerStateMap.TryGetValue(project.Id, out var entry) &&
47+
entry.AnalyzerReferences.SequenceEqual(project.AnalyzerReferences))
4548
{
46-
// check if the analyzer references have changed since the last time we updated the map:
47-
// No need to use _projectAnalyzerStateMapGuard during reads of _projectAnalyzerStateMap
48-
if (_projectAnalyzerStateMap.TryGetValue(project.Id, out var entry) &&
49-
entry.AnalyzerReferences.SequenceEqual(project.AnalyzerReferences))
50-
{
51-
return entry;
52-
}
53-
54-
return null;
49+
return entry;
5550
}
5651

57-
private async Task<ProjectAnalyzerInfo> GetOrCreateProjectAnalyzerInfoAsync(SolutionState solution, ProjectState project, CancellationToken cancellationToken)
58-
=> TryGetProjectAnalyzerInfo(project) ?? await UpdateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false);
52+
return null;
53+
}
54+
55+
private async Task<ProjectAnalyzerInfo> GetOrCreateProjectAnalyzerInfoAsync(SolutionState solution, ProjectState project, CancellationToken cancellationToken)
56+
=> TryGetProjectAnalyzerInfo(project) ?? await UpdateProjectAnalyzerInfoAsync(solution, project, cancellationToken).ConfigureAwait(false);
5957

60-
private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(SolutionState solution, ProjectState project)
58+
private ProjectAnalyzerInfo CreateProjectAnalyzerInfo(SolutionState solution, ProjectState project)
59+
{
60+
if (project.AnalyzerReferences.Count == 0)
6161
{
62-
if (project.AnalyzerReferences.Count == 0)
63-
{
64-
return ProjectAnalyzerInfo.Default;
65-
}
62+
return ProjectAnalyzerInfo.Default;
63+
}
6664

67-
var solutionAnalyzers = solution.Analyzers;
68-
var analyzersPerReference = solutionAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project);
69-
if (analyzersPerReference.Count == 0)
70-
{
71-
return ProjectAnalyzerInfo.Default;
72-
}
65+
var solutionAnalyzers = solution.Analyzers;
66+
var analyzersPerReference = solutionAnalyzers.CreateProjectDiagnosticAnalyzersPerReference(project);
67+
if (analyzersPerReference.Count == 0)
68+
{
69+
return ProjectAnalyzerInfo.Default;
70+
}
7371

74-
var (newHostAnalyzers, newAllAnalyzers) = PartitionAnalyzers(
75-
analyzersPerReference.Values, hostAnalyzerCollection: [], includeWorkspacePlaceholderAnalyzers: false);
72+
var (newHostAnalyzers, newAllAnalyzers) = PartitionAnalyzers(
73+
analyzersPerReference.Values, hostAnalyzerCollection: [], includeWorkspacePlaceholderAnalyzers: false);
7674

77-
// We passed an empty array for 'hostAnalyzeCollection' above, and we specifically asked to not include
78-
// workspace placeholder analyzers. So we should never get host analyzers back here.
79-
Contract.ThrowIfTrue(newHostAnalyzers.Count > 0);
75+
// We passed an empty array for 'hostAnalyzeCollection' above, and we specifically asked to not include
76+
// workspace placeholder analyzers. So we should never get host analyzers back here.
77+
Contract.ThrowIfTrue(newHostAnalyzers.Count > 0);
8078

81-
var skippedAnalyzersInfo = solutionAnalyzers.GetSkippedAnalyzersInfo(project, _analyzerInfoCache);
82-
return new ProjectAnalyzerInfo(project.AnalyzerReferences, newAllAnalyzers, skippedAnalyzersInfo);
83-
}
79+
var skippedAnalyzersInfo = solutionAnalyzers.GetSkippedAnalyzersInfo(project, _analyzerInfoCache);
80+
return new ProjectAnalyzerInfo(project.AnalyzerReferences, newAllAnalyzers, skippedAnalyzersInfo);
81+
}
8482

85-
/// <summary>
86-
/// Updates the map to the given project snapshot.
87-
/// </summary>
88-
private async Task<ProjectAnalyzerInfo> UpdateProjectAnalyzerInfoAsync(
89-
SolutionState solution, ProjectState project, CancellationToken cancellationToken)
83+
/// <summary>
84+
/// Updates the map to the given project snapshot.
85+
/// </summary>
86+
private async Task<ProjectAnalyzerInfo> UpdateProjectAnalyzerInfoAsync(
87+
SolutionState solution, ProjectState project, CancellationToken cancellationToken)
88+
{
89+
// This code is called concurrently for a project, so the guard prevents duplicated effort calculating StateSets.
90+
using (await _projectAnalyzerStateMapGuard.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
9091
{
91-
// This code is called concurrently for a project, so the guard prevents duplicated effort calculating StateSets.
92-
using (await _projectAnalyzerStateMapGuard.DisposableWaitAsync(cancellationToken).ConfigureAwait(false))
93-
{
94-
var projectAnalyzerInfo = TryGetProjectAnalyzerInfo(project);
92+
var projectAnalyzerInfo = TryGetProjectAnalyzerInfo(project);
9593

96-
if (projectAnalyzerInfo == null)
97-
{
98-
projectAnalyzerInfo = CreateProjectAnalyzerInfo(solution, project);
99-
100-
// update cache.
101-
_projectAnalyzerStateMap = _projectAnalyzerStateMap.SetItem(project.Id, projectAnalyzerInfo.Value);
102-
}
94+
if (projectAnalyzerInfo == null)
95+
{
96+
projectAnalyzerInfo = CreateProjectAnalyzerInfo(solution, project);
10397

104-
return projectAnalyzerInfo.Value;
98+
// update cache.
99+
_projectAnalyzerStateMap = _projectAnalyzerStateMap.SetItem(project.Id, projectAnalyzerInfo.Value);
105100
}
101+
102+
return projectAnalyzerInfo.Value;
106103
}
107104
}
108105
}

0 commit comments

Comments
 (0)