-
Notifications
You must be signed in to change notification settings - Fork 4.1k
/
Copy pathDiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs
134 lines (113 loc) · 6.56 KB
/
DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// 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 System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
{
internal partial class DiagnosticIncrementalAnalyzer
{
public async Task<ImmutableArray<DiagnosticData>> ForceAnalyzeProjectAsync(Project project, CancellationToken cancellationToken)
{
try
{
var stateSetsForProject = await _stateManager.GetOrCreateStateSetsAsync(project, cancellationToken).ConfigureAwait(false);
var stateSets = GetStateSetsForFullSolutionAnalysis(stateSetsForProject, project);
// PERF: get analyzers that are not suppressed and marked as open file only
// this is perf optimization. we cache these result since we know the result. (no diagnostics)
var activeProjectAnalyzers = stateSets.SelectAsArray(s => !s.IsHostAnalyzer, s => s.Analyzer);
var activeHostAnalyzers = stateSets.SelectAsArray(s => s.IsHostAnalyzer, s => s.Analyzer);
CompilationWithAnalyzersPair? compilationWithAnalyzers = null;
compilationWithAnalyzers = await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync(
project, activeProjectAnalyzers, activeHostAnalyzers, AnalyzerService.CrashOnAnalyzerException, cancellationToken).ConfigureAwait(false);
var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, stateSets, cancellationToken).ConfigureAwait(false);
using var _ = ArrayBuilder<DiagnosticData>.GetInstance(out var diagnostics);
// no cancellation after this point.
foreach (var stateSet in stateSets)
{
var state = stateSet.GetOrCreateProjectState(project.Id);
if (result.TryGetResult(stateSet.Analyzer, out var analyzerResult))
{
diagnostics.AddRange(analyzerResult.GetAllDiagnostics());
await state.SaveToInMemoryStorageAsync(project, analyzerResult).ConfigureAwait(false);
}
}
return diagnostics.ToImmutableAndClear();
}
catch (Exception e) when (FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
{
throw ExceptionUtilities.Unreachable();
}
}
private async Task TextDocumentOpenAsync(TextDocument document, CancellationToken cancellationToken)
{
using (Logger.LogBlock(FunctionId.Diagnostics_DocumentOpen, GetOpenLogMessage, document, cancellationToken))
{
var stateSets = _stateManager.GetStateSets(document.Project);
// can not be canceled
foreach (var stateSet in stateSets)
await stateSet.OnDocumentOpenedAsync(document).ConfigureAwait(false);
}
}
/// <summary>
/// Return list of <see cref="StateSet"/> to be used for full solution analysis.
/// </summary>
private ImmutableArray<StateSet> GetStateSetsForFullSolutionAnalysis(ImmutableArray<StateSet> stateSets, Project project)
{
// Include only analyzers we want to run for full solution analysis.
// Analyzers not included here will never be saved because result is unknown.
return stateSets.WhereAsArray(static (s, arg) => arg.self.IsCandidateForFullSolutionAnalysis(s.Analyzer, s.IsHostAnalyzer, arg.project), (self: this, project));
}
private bool IsCandidateForFullSolutionAnalysis(DiagnosticAnalyzer analyzer, bool isHostAnalyzer, Project project)
{
// PERF: Don't query descriptors for compiler analyzer or workspace load analyzer, always execute them.
if (analyzer == FileContentLoadAnalyzer.Instance ||
analyzer == GeneratorDiagnosticsPlaceholderAnalyzer.Instance ||
analyzer.IsCompilerAnalyzer())
{
return true;
}
if (analyzer.IsBuiltInAnalyzer())
{
// always return true for builtin analyzer. we can't use
// descriptor check since many builtin analyzer always return
// hidden descriptor regardless what descriptor it actually
// return on runtime. they do this so that they can control
// severity through option page rather than rule set editor.
// this is special behavior only ide analyzer can do. we hope
// once we support editorconfig fully, third party can use this
// ability as well and we can remove this kind special treatment on builtin
// analyzer.
return true;
}
if (analyzer is DiagnosticSuppressor)
{
// Always execute diagnostic suppressors.
return true;
}
if (project.CompilationOptions is null)
{
// Skip compilation options based checks for non-C#/VB projects.
return true;
}
// For most of analyzers, the number of diagnostic descriptors is small, so this should be cheap.
var descriptors = DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(analyzer);
var analyzerConfigOptions = project.GetAnalyzerConfigOptions();
return descriptors.Any(static (d, arg) => d.GetEffectiveSeverity(arg.CompilationOptions, arg.isHostAnalyzer ? arg.analyzerConfigOptions?.ConfigOptionsWithFallback : arg.analyzerConfigOptions?.ConfigOptionsWithoutFallback, arg.analyzerConfigOptions?.TreeOptions) != ReportDiagnostic.Hidden, (project.CompilationOptions, isHostAnalyzer, analyzerConfigOptions));
}
public TestAccessor GetTestAccessor()
=> new(this);
public readonly struct TestAccessor(DiagnosticIncrementalAnalyzer diagnosticIncrementalAnalyzer)
{
public Task TextDocumentOpenAsync(TextDocument document)
=> diagnosticIncrementalAnalyzer.TextDocumentOpenAsync(document, CancellationToken.None);
}
}
}