未验证 提交 6ea5c7e6 编写于 作者: D David 提交者: GitHub

Merge pull request #39649 from dibarbet/dupe_error_list

Add support for custom unnecessary locations on a diagnostic.
......@@ -39,10 +39,10 @@ internal override bool ShouldSkipMessageDescriptionVerification(DiagnosticDescri
}
private DiagnosticDescription GetRemoveUnnecessaryParenthesesDiagnostic(string text, int line, int column)
{
var diagnosticId = IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId;
return TestHelpers.Diagnostic(diagnosticId, text, startLocation: new LinePosition(line, column));
}
=> TestHelpers.Diagnostic(IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId, text, startLocation: new LinePosition(line, column));
private DiagnosticDescription GetRemoveUnnecessaryParenthesesDiagnostic(string text, int line, int column, DiagnosticSeverity severity)
=> GetRemoveUnnecessaryParenthesesDiagnostic(text, line, column).WithEffectiveSeverity(severity);
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)]
public async Task TestVariableInitializer_TestWithAllOptionsSetToIgnore()
......@@ -2298,9 +2298,7 @@ void Test(bool a)
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)]
public async Task TestUnnecessaryParenthesisDiagnosticSingleLineExpression()
{
var openParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 4, 16);
var parentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(1 + 2)", 4, 16);
var closeParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 4, 22);
await TestDiagnosticsAsync(
@"class C
{
......@@ -2308,16 +2306,14 @@ void M()
{
int x = [|(1 + 2)|];
}
}", new TestParameters(options: RemoveAllUnnecessaryParentheses), parentheticalExpressionDiagnostic, openParenthesesDiagnostic, closeParenthesesDiagnostic);
}", new TestParameters(options: RemoveAllUnnecessaryParentheses), parentheticalExpressionDiagnostic);
}
[WorkItem(27925, "https://github.com/dotnet/roslyn/issues/27925")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)]
public async Task TestUnnecessaryParenthesisDiagnosticInMultiLineExpression()
{
var openParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 4, 16);
var firstLineParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(1 +", 4, 16);
var closeParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 5, 13);
await TestDiagnosticsAsync(
@"class C
{
......@@ -2326,21 +2322,16 @@ void M()
int x = [|(1 +
2)|];
}
}", new TestParameters(options: RemoveAllUnnecessaryParentheses), firstLineParentheticalExpressionDiagnostic, openParenthesesDiagnostic, closeParenthesesDiagnostic);
}", new TestParameters(options: RemoveAllUnnecessaryParentheses), firstLineParentheticalExpressionDiagnostic);
}
[WorkItem(27925, "https://github.com/dotnet/roslyn/issues/27925")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)]
public async Task TestUnnecessaryParenthesisDiagnosticInNestedExpression()
{
var outerOpenParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 4, 16);
var outerParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(1 + (2 + 3) + 4)", 4, 16);
var outerCloseParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 4, 32);
var innerOpenParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 4, 21);
var innerParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(2 + 3)", 4, 21);
var innerCloseParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 4, 27);
var expectedDiagnostics = new DiagnosticDescription[] { outerParentheticalExpressionDiagnostic, outerOpenParenthesesDiagnostic,
outerCloseParenthesesDiagnostic, innerParentheticalExpressionDiagnostic, innerOpenParenthesesDiagnostic, innerCloseParenthesesDiagnostic };
var expectedDiagnostics = new DiagnosticDescription[] { outerParentheticalExpressionDiagnostic, innerParentheticalExpressionDiagnostic };
await TestDiagnosticsAsync(
@"class C
{
......@@ -2355,14 +2346,9 @@ void M()
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)]
public async Task TestUnnecessaryParenthesisDiagnosticInNestedMultiLineExpression()
{
var outerOpenParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 4, 16);
var outerFirstLineParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(1 + 2 +", 4, 16);
var outerCloseParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 6, 17);
var innerOpenParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 5, 12);
var innerParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(3 + 4)", 5, 12);
var innerCloseParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 5, 18);
var expectedDiagnostics = new DiagnosticDescription[] { outerFirstLineParentheticalExpressionDiagnostic, outerOpenParenthesesDiagnostic,
outerCloseParenthesesDiagnostic, innerParentheticalExpressionDiagnostic, innerOpenParenthesesDiagnostic, innerCloseParenthesesDiagnostic };
var expectedDiagnostics = new DiagnosticDescription[] { outerFirstLineParentheticalExpressionDiagnostic, innerParentheticalExpressionDiagnostic };
await TestDiagnosticsAsync(
@"class C
{
......@@ -2375,6 +2361,33 @@ void M()
}", new TestParameters(options: RemoveAllUnnecessaryParentheses), expectedDiagnostics);
}
[WorkItem(39529, "https://github.com/dotnet/roslyn/issues/39529")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)]
public async Task TestUnnecessaryParenthesisIncludesFadeLocations()
{
var input = @"class C
{
void M()
{
int x = [|{|expression:{|fade:(|}1 + 2{|fade:)|}|}|];
}
}";
var parameters = new TestParameters(options: RemoveAllUnnecessaryParentheses);
using var workspace = CreateWorkspaceFromOptions(input, parameters);
var expectedSpans = workspace.Documents.First().AnnotatedSpans;
var diagnostics = await GetDiagnosticsAsync(workspace, parameters).ConfigureAwait(false);
var diagnostic = diagnostics.Single();
Assert.Equal(3, diagnostic.AdditionalLocations.Count);
Assert.Equal(expectedSpans["expression"].Single(), diagnostic.AdditionalLocations[0].SourceSpan);
Assert.Equal(expectedSpans["fade"][0], diagnostic.AdditionalLocations[1].SourceSpan);
Assert.Equal(expectedSpans["fade"][1], diagnostic.AdditionalLocations[2].SourceSpan);
Assert.Equal("[1,2]", diagnostic.Properties[WellKnownDiagnosticTags.Unnecessary]);
}
[WorkItem(27925, "https://github.com/dotnet/roslyn/issues/39363")]
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)]
public async Task TestUnnecessaryParenthesesInSwitchExpression()
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
......@@ -106,6 +108,14 @@ protected override ITaggerEventSource CreateEventSource(ITextView textViewOpt, I
protected internal abstract bool IncludeDiagnostic(DiagnosticData data);
protected internal abstract ITagSpan<TTag> CreateTagSpan(bool isLiveUpdate, SnapshotSpan span, DiagnosticData data);
/// <summary>
/// Get the <see cref="DiagnosticDataLocation"/> that should have the tag applied to it.
/// In most cases, this is the <see cref="DiagnosticData.DataLocation"/> but overrides can change it (e.g. unnecessary classifications).
/// </summary>
/// <param name="diagnosticData">the diagnostic containing the location(s).</param>
/// <returns>an array of locations that should have the tag applied.</returns>
protected internal virtual ImmutableArray<DiagnosticDataLocation> GetLocationsToTag(DiagnosticData diagnosticData) => ImmutableArray.Create(diagnosticData.DataLocation);
protected override Task ProduceTagsAsync(TaggerContext<TTag> context, DocumentSnapshotSpan spanToTag, int? caretPosition)
{
ProduceTags(context, spanToTag);
......@@ -198,12 +208,11 @@ private void ProduceTags(TaggerContext<TTag> context, DocumentSnapshotSpan spanT
// So we'll eventually reach a point where the diagnostics exactly match the
// editorSnapshot.
var diagnosticSpan = diagnosticData.GetExistingOrCalculatedTextSpan(sourceText)
.ToSnapshotSpan(diagnosticSnapshot)
.TranslateTo(editorSnapshot, SpanTrackingMode.EdgeExclusive);
if (diagnosticSpan.IntersectsWith(requestedSpan) &&
!IsSuppressed(suppressedDiagnosticsSpans, diagnosticSpan))
var diagnosticSpans = this.GetLocationsToTag(diagnosticData)
.Select(location => GetDiagnosticSnapshotSpan(location, diagnosticSnapshot, editorSnapshot, sourceText));
foreach (var diagnosticSpan in diagnosticSpans)
{
if (diagnosticSpan.IntersectsWith(requestedSpan) && !IsSuppressed(suppressedDiagnosticsSpans, diagnosticSpan))
{
var tagSpan = this.CreateTagSpan(isLiveUpdate, diagnosticSpan, diagnosticData);
if (tagSpan != null)
......@@ -214,6 +223,7 @@ private void ProduceTags(TaggerContext<TTag> context, DocumentSnapshotSpan spanT
}
}
}
}
catch (ArgumentOutOfRangeException ex) when (FatalError.ReportWithoutCrash(ex))
{
// https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems?id=428328&_a=edit&triage=false
......@@ -221,6 +231,14 @@ private void ProduceTags(TaggerContext<TTag> context, DocumentSnapshotSpan spanT
// stop crashing on such occations
return;
}
static SnapshotSpan GetDiagnosticSnapshotSpan(DiagnosticDataLocation diagnosticDataLocation, ITextSnapshot diagnosticSnapshot,
ITextSnapshot editorSnapshot, SourceText sourceText)
{
return DiagnosticData.GetExistingOrCalculatedTextSpan(diagnosticDataLocation, sourceText)
.ToSnapshotSpan(diagnosticSnapshot)
.TranslateTo(editorSnapshot, SpanTrackingMode.EdgeExclusive);
}
}
private bool IsSuppressed(NormalizedSnapshotSpanCollection suppressedSpans, SnapshotSpan span)
......
......@@ -2,10 +2,16 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.Shared.Options;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Options;
......@@ -57,5 +63,41 @@ internal partial class DiagnosticsClassificationTaggerProvider : AbstractDiagnos
protected internal override ITagSpan<ClassificationTag> CreateTagSpan(bool isLiveUpdate, SnapshotSpan span, DiagnosticData data) =>
new TagSpan<ClassificationTag>(span, _classificationTag);
protected internal override ImmutableArray<DiagnosticDataLocation> GetLocationsToTag(DiagnosticData diagnosticData)
{
using var locationsToTagDisposer = PooledObjects.ArrayBuilder<DiagnosticDataLocation>.GetInstance(out var locationsToTag);
// If there are 'unnecessary' locations specified in the property bag, use those instead of the main diagnostic location.
if (diagnosticData.AdditionalLocations?.Count > 0
&& diagnosticData.Properties != null
&& diagnosticData.Properties.TryGetValue(WellKnownDiagnosticTags.Unnecessary, out var unnecessaryIndices))
{
var additionalLocations = diagnosticData.AdditionalLocations.ToImmutableArray();
var indices = GetLocationIndices(unnecessaryIndices);
locationsToTag.AddRange(indices.Select(i => additionalLocations[i]).ToImmutableArray());
}
else
{
locationsToTag.Add(diagnosticData.DataLocation);
}
return locationsToTag.ToImmutable();
static IEnumerable<int> GetLocationIndices(string indicesProperty)
{
try
{
using var stream = new MemoryStream(Encoding.UTF8.GetBytes(indicesProperty));
var serializer = new DataContractJsonSerializer(typeof(IEnumerable<int>));
var result = serializer.ReadObject(stream) as IEnumerable<int>;
return result;
}
catch (Exception e) when (FatalError.ReportWithoutCrash(e))
{
return ImmutableArray<int>.Empty;
}
}
}
}
}
......@@ -119,7 +119,7 @@ private static async Task VerifyTextSpanAsync(string code, int startLine, int st
language: document.Project.Language);
var text = await document.GetTextAsync();
var actual = data.GetExistingOrCalculatedTextSpan(text);
var actual = DiagnosticData.GetExistingOrCalculatedTextSpan(data.DataLocation, text);
Assert.Equal(span, actual);
}
......
' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
Imports System.Linq
Imports Microsoft.CodeAnalysis.CodeFixes
Imports Microsoft.CodeAnalysis.Diagnostics
Imports Microsoft.CodeAnalysis.Editor.UnitTests.Workspaces
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessaryParentheses
......@@ -560,43 +562,34 @@ end class", parameters:=New TestParameters(options:=RemoveAllUnnecessaryParenthe
<WorkItem(27925, "https://github.com/dotnet/roslyn/issues/27925")>
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)>
Public Async Function TestUnnecessaryParenthesisDiagnosticSingleLineExpression() As Task
Dim openParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 2, 16)
Dim parentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(1 + 2)", 2, 16)
Dim closeParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 2, 22)
Await TestDiagnosticsAsync(
"class C
sub M()
dim x = [|(1 + 2)|]
end sub
end class", New TestParameters(options:=RemoveAllUnnecessaryParentheses), parentheticalExpressionDiagnostic, openParenthesesDiagnostic, closeParenthesesDiagnostic)
end class", New TestParameters(options:=RemoveAllUnnecessaryParentheses), parentheticalExpressionDiagnostic)
End Function
<WorkItem(27925, "https://github.com/dotnet/roslyn/issues/27925")>
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)>
Public Async Function TestUnnecessaryParenthesisDiagnosticInMultiLineExpression() As Task
Dim openParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 2, 16)
Dim firstLineParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(1 +", 2, 16)
Dim closeParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 3, 13)
Await TestDiagnosticsAsync(
"class C
sub M()
dim x = [|(1 +
2)|]
end sub
end class", New TestParameters(options:=RemoveAllUnnecessaryParentheses), firstLineParentheticalExpressionDiagnostic, openParenthesesDiagnostic, closeParenthesesDiagnostic)
end class", New TestParameters(options:=RemoveAllUnnecessaryParentheses), firstLineParentheticalExpressionDiagnostic)
End Function
<WorkItem(27925, "https://github.com/dotnet/roslyn/issues/27925")>
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)>
Public Async Function TestUnnecessaryParenthesisDiagnosticInNestedExpression() As Task
Dim outerOpenParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 2, 16)
Dim outerParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(1 + (2 + 3) + 4)", 2, 16)
Dim outerCloseParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 2, 32)
Dim innerOpenParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 2, 21)
Dim innerParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(2 + 3)", 2, 21)
Dim innerCloseParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 2, 27)
Dim expectedDiagnostics = New DiagnosticDescription() {outerParentheticalExpressionDiagnostic, outerOpenParenthesesDiagnostic,
outerCloseParenthesesDiagnostic, innerParentheticalExpressionDiagnostic, innerOpenParenthesesDiagnostic, innerCloseParenthesesDiagnostic}
Dim expectedDiagnostics = New DiagnosticDescription() {outerParentheticalExpressionDiagnostic, innerParentheticalExpressionDiagnostic}
Await TestDiagnosticsAsync(
"class C
sub M()
......@@ -608,14 +601,9 @@ end class", New TestParameters(options:=RemoveAllUnnecessaryParentheses), expect
<WorkItem(27925, "https://github.com/dotnet/roslyn/issues/27925")>
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)>
Public Async Function TestUnnecessaryParenthesisDiagnosticInNestedMultiLineExpression() As Task
Dim outerOpenParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 2, 16)
Dim outerFirstLineParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(1 + 2 +", 2, 16)
Dim outerCloseParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 4, 17)
Dim innerOpenParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(", 3, 12)
Dim innerParentheticalExpressionDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic("(3 + 4)", 3, 12)
Dim innerCloseParenthesesDiagnostic = GetRemoveUnnecessaryParenthesesDiagnostic(")", 3, 18)
Dim expectedDiagnostics = New DiagnosticDescription() {outerFirstLineParentheticalExpressionDiagnostic, outerOpenParenthesesDiagnostic,
outerCloseParenthesesDiagnostic, innerParentheticalExpressionDiagnostic, innerOpenParenthesesDiagnostic, innerCloseParenthesesDiagnostic}
Dim expectedDiagnostics = New DiagnosticDescription() {outerFirstLineParentheticalExpressionDiagnostic, innerParentheticalExpressionDiagnostic}
Await TestDiagnosticsAsync(
"class C
sub M()
......@@ -625,5 +613,32 @@ end class", New TestParameters(options:=RemoveAllUnnecessaryParentheses), expect
end sub
end class", New TestParameters(options:=RemoveAllUnnecessaryParentheses), expectedDiagnostics)
End Function
<WorkItem(27925, "https://github.com/dotnet/roslyn/issues/39529")>
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)>
Public Async Function TestUnnecessaryParenthesisIncludesFadeLocations() As Task
Dim input =
"class C
sub M()
dim x = [|{|expression:{|fade:(|}1 + 2{|fade:)|}|}|]
end sub
end class"
Dim parameters = New TestParameters(options:=RemoveAllUnnecessaryParentheses)
Using workspace As TestWorkspace = CreateWorkspaceFromOptions(input, parameters)
Dim expectedSpans = workspace.Documents.First().AnnotatedSpans
Dim diagnostics = Await GetDiagnosticsAsync(workspace, parameters).ConfigureAwait(False)
Dim diagnostic = diagnostics.Single()
Assert.Equal(3, diagnostic.AdditionalLocations.Count)
Assert.Equal(expectedSpans.Item("expression").Item(0), diagnostic.AdditionalLocations.Item(0).SourceSpan)
Assert.Equal(expectedSpans.Item("fade").Item(0), diagnostic.AdditionalLocations.Item(1).SourceSpan)
Assert.Equal(expectedSpans.Item("fade").Item(1), diagnostic.AdditionalLocations.Item(2).SourceSpan)
Assert.Equal("[1,2]", diagnostic.Properties.Item(WellKnownDiagnosticTags.Unnecessary))
End Using
End Function
End Class
End Namespace
......@@ -3,8 +3,12 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.RemoveUnnecessaryParentheses;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
......@@ -55,6 +59,53 @@ internal static class DiagnosticHelper
return CreateWithMessage(descriptor, location, effectiveSeverity, additionalLocations, properties, message);
}
/// <summary>
/// Create a diagnostic that adds properties specifying a tag for a set of locations.
/// </summary>
/// <param name="descriptor">A <see cref="DiagnosticDescriptor"/> describing the diagnostic.</param>
/// <param name="location">An optional primary location of the diagnostic. If null, <see cref="Location"/> will return <see cref="Location.None"/>.</param>
/// <param name="effectiveSeverity">Effective severity of the diagnostic.</param>
/// <param name="additionalLocations">
/// An optional set of additional locations related to the diagnostic.
/// Typically, these are locations of other items referenced in the message.
/// If null, <see cref="Diagnostic.AdditionalLocations"/> will return an empty list.
/// </param>
/// <param name="tagIndices">
/// a map of location tag to index in additional locations.
/// <see cref="AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer{TLanguageKindEnum, TParenthesizedExpressionSyntax}"/> for an example of usage.
/// </param>
/// <param name="messageArgs">Arguments to the message of the diagnostic.</param>
/// <returns>The <see cref="Diagnostic"/> instance.</returns>
public static Diagnostic CreateWithLocationTags(
DiagnosticDescriptor descriptor,
Location location,
ReportDiagnostic effectiveSeverity,
IEnumerable<Location> additionalLocations,
IDictionary<string, IEnumerable<int>> tagIndices,
params object[] messageArgs)
{
Contract.ThrowIfTrue(additionalLocations.IsEmpty());
Contract.ThrowIfTrue(tagIndices.IsEmpty());
var properties = tagIndices.Select(kvp => new KeyValuePair<string, string>(kvp.Key, EncodeIndices(kvp.Value, additionalLocations.Count()))).ToImmutableDictionary();
return Create(descriptor, location, effectiveSeverity, additionalLocations, properties, messageArgs);
static string EncodeIndices(IEnumerable<int> indices, int additionalLocationsLength)
{
// Ensure that the provided tag index is a valid index into additional locations.
Contract.ThrowIfFalse(indices.All(idx => idx >= 0 && idx < additionalLocationsLength));
using var stream = new MemoryStream();
var serializer = new DataContractJsonSerializer(typeof(IEnumerable<int>));
serializer.WriteObject(stream, indices);
var jsonBytes = stream.ToArray();
stream.Close();
return Encoding.UTF8.GetString(jsonBytes, 0, jsonBytes.Length);
}
}
/// <summary>
/// Creates a <see cref="Diagnostic"/> instance.
/// </summary>
......
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
......@@ -20,25 +21,24 @@ internal abstract class AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer<
{
/// <summary>
/// A diagnostic descriptor that will fade the span (but not put a message or squiggle).
/// A diagnostic descriptor used to squiggle and message the span.
/// </summary>
private static readonly DiagnosticDescriptor s_diagnosticWithFade = CreateDescriptorWithId(
private static readonly DiagnosticDescriptor s_diagnosticDescriptor = CreateDescriptorWithId(
IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId,
new LocalizableResourceString(nameof(FeaturesResources.Remove_unnecessary_parentheses), FeaturesResources.ResourceManager, typeof(FeaturesResources)),
string.Empty,
new LocalizableResourceString(nameof(FeaturesResources.Parentheses_can_be_removed), FeaturesResources.ResourceManager, typeof(FeaturesResources)),
isUnneccessary: true);
/// <summary>
/// A diagnostic descriptor used to squiggle and message the span, but will not fade.
/// This analyzer inserts the fade locations into indices 1 and 2 inside additional locations.
/// </summary>
private static readonly DiagnosticDescriptor s_diagnosticWithoutFade = CreateDescriptorWithId(
IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId,
new LocalizableResourceString(nameof(FeaturesResources.Remove_unnecessary_parentheses), FeaturesResources.ResourceManager, typeof(FeaturesResources)),
new LocalizableResourceString(nameof(FeaturesResources.Parentheses_can_be_removed), FeaturesResources.ResourceManager, typeof(FeaturesResources)),
isUnneccessary: false);
private static readonly ImmutableDictionary<string, IEnumerable<int>> s_fadeLocations = new Dictionary<string, IEnumerable<int>>
{
{ nameof(WellKnownDiagnosticTags.Unnecessary), new int[] { 1, 2 } },
}.ToImmutableDictionary();
protected AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer()
: base(ImmutableArray.Create(s_diagnosticWithFade, s_diagnosticWithoutFade))
: base(ImmutableArray.Create(s_diagnosticDescriptor))
{
}
......@@ -124,21 +124,15 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
var severity = preference.Notification.Severity;
var additionalLocations = ImmutableArray.Create(parenthesizedExpression.GetLocation());
// Fades the open parentheses character and reports the suggestion.
context.ReportDiagnostic(Diagnostic.Create(s_diagnosticWithFade, parenthesizedExpression.GetFirstToken().GetLocation(), additionalLocations));
var additionalLocations = ImmutableArray.Create(parenthesizedExpression.GetLocation(),
parenthesizedExpression.GetFirstToken().GetLocation(), parenthesizedExpression.GetLastToken().GetLocation());
// Generates diagnostic used to squiggle the parenthetical expression.
context.ReportDiagnostic(DiagnosticHelper.Create(
s_diagnosticWithoutFade,
context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags(
s_diagnosticDescriptor,
GetDiagnosticSquiggleLocation(parenthesizedExpression, cancellationToken),
severity,
additionalLocations,
properties: null));
// Fades the close parentheses character.
context.ReportDiagnostic(Diagnostic.Create(s_diagnosticWithFade, parenthesizedExpression.GetLastToken().GetLocation(), additionalLocations));
s_fadeLocations));
}
/// <summary>
......
......@@ -94,7 +94,7 @@ private async Task<LSP.Diagnostic[]> GetDiagnosticsAsync(Solution solution, Docu
Code = diag.Id,
Message = diag.Message,
Severity = ProtocolConversions.DiagnosticSeverityToLspDiagnositcSeverity(diag.Severity),
Range = ProtocolConversions.TextSpanToRange(diag.GetExistingOrCalculatedTextSpan(text), text),
Range = ProtocolConversions.TextSpanToRange(DiagnosticData.GetExistingOrCalculatedTextSpan(diag.DataLocation, text), text),
Tags = diag.CustomTags.Where(s => s == "Unnecessary").ToArray()
}).ToArray();
}
......
......@@ -9,7 +9,6 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Resources;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Options;
......@@ -165,8 +164,17 @@ public override string ToString()
DataLocation?.OriginalStartColumn);
}
public TextSpan GetExistingOrCalculatedTextSpan(SourceText text)
=> HasTextSpan ? EnsureInBounds(GetTextSpan(), text) : GetTextSpan(DataLocation, text);
public static TextSpan GetExistingOrCalculatedTextSpan(DiagnosticDataLocation? diagnosticLocation, SourceText text)
{
if (diagnosticLocation?.SourceSpan != null)
{
return EnsureInBounds(diagnosticLocation.SourceSpan.Value, text);
}
else
{
return GetTextSpan(diagnosticLocation, text);
}
}
private static TextSpan EnsureInBounds(TextSpan textSpan, SourceText text)
=> TextSpan.FromBounds(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册