forked from dotnet/roslyn
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDiagnosticsPullCache.cs
146 lines (121 loc) · 7.12 KB
/
DiagnosticsPullCache.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
135
136
137
138
139
140
141
142
143
144
145
146
// 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.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Text;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics;
internal abstract partial class AbstractPullDiagnosticHandler<TDiagnosticsParams, TReport, TReturn>
where TDiagnosticsParams : IPartialResultParams<TReport>
{
internal readonly record struct DiagnosticsRequestState(Project Project, int GlobalStateVersion, RequestContext Context, IDiagnosticSource DiagnosticSource);
/// <summary>
/// Cache where we store the data produced by prior requests so that they can be returned if nothing of significance
/// changed. The <see cref="VersionStamp"/> is produced by <see
/// cref="Project.GetDependentVersionAsync(CancellationToken)"/> while the <see cref="Checksum"/> is produced by
/// <see cref="CodeAnalysis.Diagnostics.Extensions.GetDiagnosticChecksumAsync"/>. The former is faster and works
/// well for us in the normal case. The latter still allows us to reuse diagnostics when changes happen that update
/// the version stamp but not the content (for example, forking LSP text).
/// </summary>
private sealed class DiagnosticsPullCache(IGlobalOptionService globalOptions, string uniqueKey)
: VersionedPullCache<(int globalStateVersion, VersionStamp? dependentVersion), (int globalStateVersion, Checksum dependentChecksum), DiagnosticsRequestState, ImmutableArray<DiagnosticData>>(uniqueKey)
{
private readonly IGlobalOptionService _globalOptions = globalOptions;
public override async Task<(int globalStateVersion, VersionStamp? dependentVersion)> ComputeCheapVersionAsync(DiagnosticsRequestState state, CancellationToken cancellationToken)
{
return (state.GlobalStateVersion, await state.Project.GetDependentVersionAsync(cancellationToken).ConfigureAwait(false));
}
public override async Task<(int globalStateVersion, Checksum dependentChecksum)> ComputeExpensiveVersionAsync(DiagnosticsRequestState state, CancellationToken cancellationToken)
{
return (state.GlobalStateVersion, await state.Project.GetDiagnosticChecksumAsync(cancellationToken).ConfigureAwait(false));
}
/// <inheritdoc cref="VersionedPullCache{TCheapVersion, TExpensiveVersion, TState, TComputedData}.ComputeDataAsync(TState, CancellationToken)"/>
public override async Task<ImmutableArray<DiagnosticData>> ComputeDataAsync(DiagnosticsRequestState state, CancellationToken cancellationToken)
{
var diagnostics = await state.DiagnosticSource.GetDiagnosticsAsync(state.Context, cancellationToken).ConfigureAwait(false);
state.Context.TraceInformation($"Found {diagnostics.Length} diagnostics for {state.DiagnosticSource.ToDisplayString()}");
return diagnostics;
}
public override Checksum ComputeChecksum(ImmutableArray<DiagnosticData> data, string language)
{
// Create checksums of diagnostic data and sort to ensure stable ordering for comparison.
using var _ = ArrayBuilder<Checksum>.GetInstance(out var builder);
foreach (var datum in data)
builder.Add(Checksum.Create(datum, SerializeDiagnosticData));
// Ensure that if fading options change that we recompute the checksum as it will produce different data
// that we would report to the client.
var option1 = _globalOptions.GetOption(FadingOptions.FadeOutUnreachableCode, language);
var option2 = _globalOptions.GetOption(FadingOptions.FadeOutUnusedImports, language);
var option3 = _globalOptions.GetOption(FadingOptions.FadeOutUnusedMembers, language);
var value =
(option1 ? (1 << 2) : 0) |
(option2 ? (1 << 1) : 0) |
(option3 ? (1 << 0) : 0);
builder.Add(new Checksum(0, value));
builder.Sort();
return Checksum.Create(builder);
}
private static void SerializeDiagnosticData(DiagnosticData diagnosticData, ObjectWriter writer)
{
writer.WriteString(diagnosticData.Id);
writer.WriteString(diagnosticData.Category);
writer.WriteString(diagnosticData.Message);
writer.WriteInt32((int)diagnosticData.Severity);
writer.WriteInt32((int)diagnosticData.DefaultSeverity);
writer.WriteBoolean(diagnosticData.IsEnabledByDefault);
writer.WriteInt32(diagnosticData.WarningLevel);
// Ensure the tag order is stable before we write it.
foreach (var tag in diagnosticData.CustomTags.Sort())
{
writer.WriteString(tag);
}
foreach (var key in diagnosticData.Properties.Keys.ToImmutableArray().Sort())
{
writer.WriteString(key);
writer.WriteString(diagnosticData.Properties[key]);
}
if (diagnosticData.ProjectId != null)
writer.WriteGuid(diagnosticData.ProjectId.Id);
WriteDiagnosticDataLocation(diagnosticData.DataLocation, writer);
foreach (var additionalLocation in diagnosticData.AdditionalLocations)
{
WriteDiagnosticDataLocation(additionalLocation, writer);
}
writer.WriteString(diagnosticData.Language);
writer.WriteString(diagnosticData.Title);
writer.WriteString(diagnosticData.Description);
writer.WriteString(diagnosticData.HelpLink);
writer.WriteBoolean(diagnosticData.IsSuppressed);
static void WriteDiagnosticDataLocation(DiagnosticDataLocation location, ObjectWriter writer)
{
WriteFileLinePositionSpan(location.UnmappedFileSpan, writer);
if (location.DocumentId != null)
writer.WriteGuid(location.DocumentId.Id);
WriteFileLinePositionSpan(location.MappedFileSpan, writer);
}
static void WriteFileLinePositionSpan(FileLinePositionSpan fileSpan, ObjectWriter writer)
{
writer.WriteString(fileSpan.Path);
WriteLinePositionSpan(fileSpan.Span, writer);
writer.WriteBoolean(fileSpan.HasMappedPath);
}
static void WriteLinePositionSpan(LinePositionSpan span, ObjectWriter writer)
{
WriteLinePosition(span.Start, writer);
WriteLinePosition(span.End, writer);
}
static void WriteLinePosition(LinePosition position, ObjectWriter writer)
{
writer.WriteInt32(position.Line);
writer.WriteInt32(position.Character);
}
}
}
}