From 7650489c7e4af26cd2a203b431d2eef0b23b978c Mon Sep 17 00:00:00 2001 From: David Date: Thu, 24 Jan 2019 16:55:44 -0800 Subject: [PATCH] Issue 27925 - Squiggle first line / entire parenthetical expression in remove unnecessary parentheses diagnostic (#32500) * Update unnecessary parentheses analyzer to squiggle the entire expression / first line. Resolves #27925 --- .../CSharpTest/AddUsing/AddUsingTests.cs | 2 +- .../GenerateType/GenerateTypeTests.cs | 2 +- .../FullyQualify/FullyQualifyTests.cs | 2 +- .../RemoveUnnecessaryParenthesesTests.cs | 106 +++++++++++++++++- ...agnosticProviderBasedUserDiagnosticTest.cs | 21 +++- .../Diagnostics/AbstractUserDiagnosticTest.cs | 15 +-- .../RemoveUnnecessaryParenthesesTests.vb | 88 ++++++++++++++- .../AbstractParenthesesDiagnosticAnalyzer.cs | 6 + ...nnecessaryParenthesesDiagnosticAnalyzer.cs | 60 ++++++++-- 9 files changed, 262 insertions(+), 40 deletions(-) diff --git a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs index b63f4860b80..b38a2206c3c 100644 --- a/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs +++ b/src/EditorFeatures/CSharpTest/AddUsing/AddUsingTests.cs @@ -2135,7 +2135,7 @@ void Goo() public async Task TestAttribute() { var input = @"[ assembly : [|Guid|] ( ""9ed54f84-a89d-4fcd-a854-44251e925f09"" ) ] "; - await TestActionCountAsync(input, 1); + await TestActionCountAsync(input, 2); await TestAsync( input, diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateType/GenerateTypeTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateType/GenerateTypeTests.cs index 42e267d0f35..63cfbbd0d07 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateType/GenerateTypeTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateType/GenerateTypeTests.cs @@ -4394,7 +4394,7 @@ void Goo() { } }", -count: 3); +count: 6); } [WorkItem(543061, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/543061")] diff --git a/src/EditorFeatures/CSharpTest/FullyQualify/FullyQualifyTests.cs b/src/EditorFeatures/CSharpTest/FullyQualify/FullyQualifyTests.cs index 84244687515..2484d42f9ff 100644 --- a/src/EditorFeatures/CSharpTest/FullyQualify/FullyQualifyTests.cs +++ b/src/EditorFeatures/CSharpTest/FullyQualify/FullyQualifyTests.cs @@ -1052,7 +1052,7 @@ static void Main(string[] args) public async Task TestAttribute() { var input = @"[ assembly : [|Guid|] ( ""9ed54f84-a89d-4fcd-a854-44251e925f09"" ) ] "; - await TestActionCountAsync(input, 1); + await TestActionCountAsync(input, 2); await TestInRegularAndScriptAsync( input, diff --git a/src/EditorFeatures/CSharpTest/RemoveUnnecessaryParentheses/RemoveUnnecessaryParenthesesTests.cs b/src/EditorFeatures/CSharpTest/RemoveUnnecessaryParentheses/RemoveUnnecessaryParenthesesTests.cs index 1997a3d84d1..63028e48ae3 100644 --- a/src/EditorFeatures/CSharpTest/RemoveUnnecessaryParentheses/RemoveUnnecessaryParenthesesTests.cs +++ b/src/EditorFeatures/CSharpTest/RemoveUnnecessaryParentheses/RemoveUnnecessaryParenthesesTests.cs @@ -1,12 +1,14 @@ // 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.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp.RemoveUnnecessaryParentheses; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; using Microsoft.CodeAnalysis.Test.Utilities; +using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; @@ -17,13 +19,13 @@ public partial class RemoveUnnecessaryParenthesesTests : AbstractCSharpDiagnosti internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) => (new CSharpRemoveUnnecessaryParenthesesDiagnosticAnalyzer(), new CSharpRemoveUnnecessaryParenthesesCodeFixProvider()); - private async Task TestAsync(string initial, string expected, bool offeredWhenRequireForClarityIsEnabled) + private async Task TestAsync(string initial, string expected, bool offeredWhenRequireForClarityIsEnabled, int index = 0) { - await TestInRegularAndScriptAsync(initial, expected, options: RemoveAllUnnecessaryParentheses); + await TestInRegularAndScriptAsync(initial, expected, options: RemoveAllUnnecessaryParentheses, index: index); if (offeredWhenRequireForClarityIsEnabled) { - await TestInRegularAndScriptAsync(initial, expected, options: RequireAllParenthesesForClarity); + await TestInRegularAndScriptAsync(initial, expected, options: RequireAllParenthesesForClarity, index: index); } else { @@ -31,6 +33,17 @@ private async Task TestAsync(string initial, string expected, bool offeredWhenRe } } + internal override bool ShouldSkipMessageDescriptionVerification(DiagnosticDescriptor descriptor) + { + return descriptor.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary) && descriptor.DefaultSeverity == DiagnosticSeverity.Hidden; + } + + private DiagnosticDescription GetRemoveUnnecessaryParenthesesDiagnostic(string text, int line, int column) + { + var diagnosticId = IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId; + return TestHelpers.Diagnostic(diagnosticId, text, startLocation: new LinePosition(line, column)); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)] public async Task TestVariableInitializer_TestWithAllOptionsSetToIgnore() { @@ -388,7 +401,7 @@ void M() { int i = ( 1 + 2 ); } -}", offeredWhenRequireForClarityIsEnabled: true); +}", offeredWhenRequireForClarityIsEnabled: true, index: 1); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessaryParentheses)] @@ -2064,7 +2077,7 @@ void M() #endif } }", -offeredWhenRequireForClarityIsEnabled: true); +offeredWhenRequireForClarityIsEnabled: true, index: 1); } [WorkItem(29454, "https://github.com/dotnet/roslyn/issues/29454")] @@ -2276,9 +2289,90 @@ public async Task TestMissingForNestedConditionalExpressionInLambda() void Test(bool a) { Func lambda = - number => (number + $""{ ($$a ? ""foo"" : ""bar"") }""); + number => number + $""{ ($$a ? ""foo"" : ""bar"") }""; } }", new TestParameters(options: RemoveAllUnnecessaryParentheses)); } + + [WorkItem(27925, "https://github.com/dotnet/roslyn/issues/27925")] + [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); + } + + [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 +{ + void M() + { + int x = [|(1 + + 2)|]; + } +}", new TestParameters(options: RemoveAllUnnecessaryParentheses), firstLineParentheticalExpressionDiagnostic, openParenthesesDiagnostic, closeParenthesesDiagnostic); + } + + [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 }; + await TestDiagnosticsAsync( +@"class C +{ + void M() + { + int x = [|(1 + (2 + 3) + 4)|]; + } +}", new TestParameters(options: RemoveAllUnnecessaryParentheses), expectedDiagnostics); + } + + [WorkItem(27925, "https://github.com/dotnet/roslyn/issues/27925")] + [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 }; + await TestDiagnosticsAsync( +@"class C +{ + void M() + { + int x = [|(1 + 2 + + (3 + 4) + + 5 + 6)|]; + } +}", new TestParameters(options: RemoveAllUnnecessaryParentheses), expectedDiagnostics); + } } } diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs b/src/EditorFeatures/TestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs index cd218e8f27b..fb2f4b4da9f 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/AbstractDiagnosticProviderBasedUserDiagnosticTest.cs @@ -38,6 +38,19 @@ internal virtual (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderA : CreateDiagnosticProviderAndFixer(workspace, parameters); } + internal virtual bool ShouldSkipMessageDescriptionVerification(DiagnosticDescriptor descriptor) + { + if (descriptor.CustomTags.Contains(WellKnownDiagnosticTags.NotConfigurable)) + { + if (!descriptor.IsEnabledByDefault || descriptor.DefaultSeverity == DiagnosticSeverity.Hidden) + { + // The message only displayed if either enabled and not hidden, or configurable + return true; + } + } + return false; + } + [Fact] public void TestSupportedDiagnosticsMessageTitle() { @@ -75,13 +88,9 @@ public void TestSupportedDiagnosticsMessageDescription() foreach (var descriptor in diagnosticAnalyzer.SupportedDiagnostics) { - if (descriptor.CustomTags.Contains(WellKnownDiagnosticTags.NotConfigurable)) + if (ShouldSkipMessageDescriptionVerification(descriptor)) { - if (!descriptor.IsEnabledByDefault || descriptor.DefaultSeverity == DiagnosticSeverity.Hidden) - { - // The message only displayed if either enabled and not hidden, or configurable - continue; - } + continue; } Assert.NotEqual("", descriptor.MessageFormat?.ToString() ?? ""); diff --git a/src/EditorFeatures/TestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs b/src/EditorFeatures/TestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs index ef20a84b411..d12133b8884 100644 --- a/src/EditorFeatures/TestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs +++ b/src/EditorFeatures/TestUtilities/Diagnostics/AbstractUserDiagnosticTest.cs @@ -193,20 +193,13 @@ protected Document GetDocumentAndAnnotatedSpan(TestWorkspace workspace, out stri CancellationToken.None); await fixer.RegisterCodeFixesAsync(context); - if (fixes.Count > 0) - { - break; - } } var actions = fixes.SelectAsArray(f => f.Action); - if (actions.Length == 1) - { - if (actions[0] is TopLevelSuppressionCodeAction suppressionAction) - { - actions = suppressionAction.NestedCodeActions; - } - } + + actions = actions.SelectMany(a => a is TopLevelSuppressionCodeAction + ? a.NestedCodeActions + : ImmutableArray.Create(a)).ToImmutableArray(); actions = MassageActions(actions); diff --git a/src/EditorFeatures/VisualBasicTest/RemoveUnnecessaryParentheses/RemoveUnnecessaryParenthesesTests.vb b/src/EditorFeatures/VisualBasicTest/RemoveUnnecessaryParentheses/RemoveUnnecessaryParenthesesTests.vb index 2fbedeb8c29..c4a320093eb 100644 --- a/src/EditorFeatures/VisualBasicTest/RemoveUnnecessaryParentheses/RemoveUnnecessaryParenthesesTests.vb +++ b/src/EditorFeatures/VisualBasicTest/RemoveUnnecessaryParentheses/RemoveUnnecessaryParenthesesTests.vb @@ -3,6 +3,7 @@ Imports Microsoft.CodeAnalysis.CodeFixes Imports Microsoft.CodeAnalysis.Diagnostics Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics +Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.RemoveUnnecessaryParentheses Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.RemoveUnnecessaryParentheses @@ -18,11 +19,21 @@ Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.RemoveUnnecessaryP Return (New VisualBasicRemoveUnnecessaryParenthesesDiagnosticAnalyzer(), New VisualBasicRemoveUnnecessaryParenthesesCodeFixProvider()) End Function - Private Shadows Async Function TestAsync(initial As String, expected As String, offeredWhenRequireAllParenthesesForClarityIsEnabled As Boolean) As Task - Await TestInRegularAndScriptAsync(initial, expected, options:=RemoveAllUnnecessaryParentheses) + Friend Function GetRemoveUnnecessaryParenthesesDiagnostic(text As String, line As Integer, column As Integer) As DiagnosticDescription + Return TestHelpers.Diagnostic(IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId, text, startLocation:=New LinePosition(line, column)) + End Function + + Friend Overrides Function ShouldSkipMessageDescriptionVerification(descriptor As DiagnosticDescriptor) As Boolean + Return descriptor.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary) And descriptor.DefaultSeverity = DiagnosticSeverity.Hidden + End Function + + Private Shadows Async Function TestAsync(initial As String, expected As String, + offeredWhenRequireAllParenthesesForClarityIsEnabled As Boolean, + Optional ByVal index As Integer = 0) As Task + Await TestInRegularAndScriptAsync(initial, expected, options:=RemoveAllUnnecessaryParentheses, index:=index) If (offeredWhenRequireAllParenthesesForClarityIsEnabled) Then - Await TestInRegularAndScriptAsync(initial, expected, options:=MyBase.RequireAllParenthesesForClarity) + Await TestInRegularAndScriptAsync(initial, expected, options:=MyBase.RequireAllParenthesesForClarity, index:=index) Else Await TestMissingAsync(initial, parameters:=New TestParameters(options:=MyBase.RequireAllParenthesesForClarity)) End If @@ -297,7 +308,7 @@ end class", sub M() dim i = ( 1 + 2 ) end sub -end class", offeredWhenRequireAllParenthesesForClarityIsEnabled:=True) +end class", offeredWhenRequireAllParenthesesForClarityIsEnabled:=True, index:=1) End Function @@ -545,5 +556,74 @@ end class", end sub end class", parameters:=New TestParameters(options:=RemoveAllUnnecessaryParentheses)) End Function + + + + 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 Function + + + + 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 Function + + + + 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} + Await TestDiagnosticsAsync( +"class C + sub M() + dim x = [|(1 + (2 + 3) + 4)|] + end sub +end class", New TestParameters(options:=RemoveAllUnnecessaryParentheses), expectedDiagnostics) + End Function + + + + 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} + Await TestDiagnosticsAsync( +"class C + sub M() + dim x = [|(1 + 2 + + (3 + 4) + + 5 + 6)|] + end sub +end class", New TestParameters(options:=RemoveAllUnnecessaryParentheses), expectedDiagnostics) + End Function End Class End Namespace diff --git a/src/Features/Core/Portable/AbstractParenthesesDiagnosticAnalyzer.cs b/src/Features/Core/Portable/AbstractParenthesesDiagnosticAnalyzer.cs index e27c67a0ecb..3a6940a79fc 100644 --- a/src/Features/Core/Portable/AbstractParenthesesDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/AbstractParenthesesDiagnosticAnalyzer.cs @@ -4,6 +4,7 @@ using Microsoft.CodeAnalysis.LanguageServices; using Microsoft.CodeAnalysis.Options; using Roslyn.Utilities; +using System.Collections.Immutable; namespace Microsoft.CodeAnalysis.RemoveUnnecessaryParentheses { @@ -15,6 +16,11 @@ internal abstract class AbstractParenthesesDiagnosticAnalyzer : AbstractBuiltInC { } + protected AbstractParenthesesDiagnosticAnalyzer(ImmutableArray diagnosticDescriptors) + : base(diagnosticDescriptors) + { + } + protected PerLanguageOption> GetLanguageOption(PrecedenceKind precedenceKind) { switch (precedenceKind) diff --git a/src/Features/Core/Portable/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.cs b/src/Features/Core/Portable/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.cs index dc2046fc6d4..bf1443a8060 100644 --- a/src/Features/Core/Portable/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.cs +++ b/src/Features/Core/Portable/RemoveUnnecessaryParentheses/AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer.cs @@ -1,10 +1,14 @@ // 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.Diagnostics; +using System.Threading; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Text; +using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.RemoveUnnecessaryParentheses { @@ -15,10 +19,27 @@ internal abstract class AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer< where TLanguageKindEnum : struct where TParenthesizedExpressionSyntax : SyntaxNode { + + /// + /// A diagnostic descriptor that will fade the span (but not put a message or squiggle). + /// + private static readonly DiagnosticDescriptor s_diagnosticWithFade = CreateDescriptorWithId( + IDEDiagnosticIds.RemoveUnnecessaryParenthesesDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Remove_unnecessary_parentheses), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + string.Empty, + isUnneccessary: true); + + /// + /// A diagnostic descriptor used to squiggle and message the span, but will not fade. + /// + 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); + protected AbstractRemoveUnnecessaryParenthesesDiagnosticAnalyzer() - : base(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))) + : base(ImmutableArray.Create(s_diagnosticWithFade, s_diagnosticWithoutFade)) { } @@ -41,8 +62,8 @@ protected sealed override void InitializeWorker(AnalysisContext context) private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) { var syntaxTree = context.SemanticModel.SyntaxTree; - var cancellationTokan = context.CancellationToken; - var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationTokan).GetAwaiter().GetResult(); + var cancellationToken = context.CancellationToken; + var optionSet = context.Options.GetDocumentOptionSetAsync(syntaxTree, cancellationToken).GetAwaiter().GetResult(); if (optionSet == null) { return; @@ -109,16 +130,35 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) 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)); + + // Generates diagnostic used to squiggle the parenthetical expression. context.ReportDiagnostic(DiagnosticHelper.Create( - UnnecessaryWithSuggestionDescriptor, - parenthesizedExpression.GetFirstToken().GetLocation(), + s_diagnosticWithoutFade, + GetDiagnosticSquiggleLocation(parenthesizedExpression, cancellationToken), severity, additionalLocations, properties: null)); - context.ReportDiagnostic(Diagnostic.Create( - UnnecessaryWithoutSuggestionDescriptor, - parenthesizedExpression.GetLastToken().GetLocation(), additionalLocations)); + // Fades the close parentheses character. + context.ReportDiagnostic(Diagnostic.Create(s_diagnosticWithFade, parenthesizedExpression.GetLastToken().GetLocation(), additionalLocations)); + } + + /// + /// Gets the span of text to squiggle underline. + /// If the expression is contained within a single line, the entire expression span is returned. + /// Otherwise it will return the span from the expression start to the end of the same line. + /// + private Location GetDiagnosticSquiggleLocation(TParenthesizedExpressionSyntax parenthesizedExpression, CancellationToken cancellationToken) + { + var parenthesizedExpressionLocation = parenthesizedExpression.GetLocation(); + + var lines = parenthesizedExpression.SyntaxTree.GetText(cancellationToken).Lines; + var expressionFirstLine = lines.GetLineFromPosition(parenthesizedExpressionLocation.SourceSpan.Start); + + var textSpanEndPosition = Math.Min(parenthesizedExpressionLocation.SourceSpan.End, expressionFirstLine.Span.End); + return Location.Create(parenthesizedExpression.SyntaxTree, TextSpan.FromBounds(parenthesizedExpressionLocation.SourceSpan.Start, textSpanEndPosition)); } } } -- GitLab