Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -2298,26 +2298,22 @@ 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
{
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
{
Expand All @@ -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
{
Expand All @@ -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
{
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// 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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -198,17 +208,17 @@ private void ProduceTags(
// 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)
{
var tagSpan = this.CreateTagSpan(isLiveUpdate, diagnosticSpan, diagnosticData);
if (tagSpan != null)
if (diagnosticSpan.IntersectsWith(requestedSpan) && !IsSuppressed(suppressedDiagnosticsSpans, diagnosticSpan))
{
context.AddTag(tagSpan);
var tagSpan = this.CreateTagSpan(isLiveUpdate, diagnosticSpan, diagnosticData);
if (tagSpan != null)
{
context.AddTag(tagSpan);
}
}
}
}
Expand All @@ -221,6 +231,14 @@ private void ProduceTags(
// 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -57,5 +63,41 @@ protected internal override bool IncludeDiagnostic(DiagnosticData data) =>

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;
}
}
}
}
}
2 changes: 1 addition & 1 deletion src/EditorFeatures/Test/Diagnostics/DiagnosticDataTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
Loading