diff --git a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj
index b361cba5f691130a0471db5dbee1aa10523bd820..28924a4bd65d30ae417d6c40c6d0db177deb4df8 100644
--- a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj
+++ b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj
@@ -93,8 +93,10 @@
Roslyn.Services.Editor.CSharp.UnitTests
UnitTest
-
-
+
+
+
+
@@ -278,6 +280,8 @@
+
+
diff --git a/src/EditorFeatures/CSharpTest/UseCoalesceExpression/UseCoalesceExpressionForNullableTests.cs b/src/EditorFeatures/CSharpTest/UseCoalesceExpression/UseCoalesceExpressionForNullableTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..1e91d9c552fbb1775192b38c17c2376662f9b39e
--- /dev/null
+++ b/src/EditorFeatures/CSharpTest/UseCoalesceExpression/UseCoalesceExpressionForNullableTests.cs
@@ -0,0 +1,201 @@
+// 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.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.UseCoalesceExpression;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
+using Microsoft.CodeAnalysis.UseCoalesceExpression;
+using Roslyn.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseCoalesceExpression
+{
+ public class UseCoalesceExpressionForNullableTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
+ {
+ internal override Tuple CreateDiagnosticProviderAndFixer(Workspace workspace)
+ {
+ return new Tuple(
+ new CSharpUseCoalesceExpressionForNullableDiagnosticAnalyzer(),
+ new UseCoalesceExpressionForNullableCodeFixProvider());
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestOnLeft_Equals()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z = [||]!x.HasValue ? y : x.Value;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestOnLeft_NotEquals()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z = [||]x.HasValue ? x.Value : y;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestComplexExpression()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z = [||]!(x + y).HasValue ? y : (x + y).Value;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z = (x + y) ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestParens1()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z = [||](x.HasValue) ? x.Value : y;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestFixAll1()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z1 = {|FixAllInDocument:x|}.HasValue ? x.Value : y;
+ var z2 = !x.HasValue ? y : x.Value;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(int? x, int? y)
+ {
+ var z1 = x ?? y;
+ var z2 = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestFixAll2()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(int? x, int? y, int? z)
+ {
+ var w = {|FixAllInDocument:x|}.HasValue ? x.Value : y.ToString(z.HasValue ? z.Value : y);
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(int? x, int? y, int? z)
+ {
+ var w = x ?? y.ToString(z ?? y);
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestFixAll3()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(int? x, int? y, int? z)
+ {
+ var w = {|FixAllInDocument:x|}.HasValue ? x.Value : y.HasValue ? y.Value : z;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(int? x, int? y, int? z)
+ {
+ var w = x ?? y ?? z;
+ }
+}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/EditorFeatures/CSharpTest/UseCoalesceExpression/UseCoalesceExpressionTests.cs b/src/EditorFeatures/CSharpTest/UseCoalesceExpression/UseCoalesceExpressionTests.cs
new file mode 100644
index 0000000000000000000000000000000000000000..64ab66eea2b73829a7ef2d6029e8d82b31b38bf3
--- /dev/null
+++ b/src/EditorFeatures/CSharpTest/UseCoalesceExpression/UseCoalesceExpressionTests.cs
@@ -0,0 +1,326 @@
+// 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.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CSharp.UseCoalesceExpression;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
+using Microsoft.CodeAnalysis.UseCoalesceExpression;
+using Roslyn.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseCoalesceExpression
+{
+ public class UseCoalesceExpressionTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
+ {
+ internal override Tuple CreateDiagnosticProviderAndFixer(Workspace workspace)
+ {
+ return new Tuple(
+ new CSharpUseCoalesceExpressionDiagnosticAnalyzer(),
+ new UseCoalesceExpressionCodeFixProvider());
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestOnLeft_Equals()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = [||]x == null ? y : x;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestOnLeft_NotEquals()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = [||]x != null ? x : y;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestOnRight_Equals()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = [||]null == x ? y : x;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestOnRight_NotEquals()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = [||]null != x ? x : y;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestComplexExpression()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = [||]x.ToString() == null ? y : x.ToString();
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = x.ToString() ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestParens1()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = [||](x == null) ? y : x;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestParens2()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = [||](x) == null ? y : x;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestParens3()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = [||]x == null ? y : (x);
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestParens4()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = [||]x == null ? (y) : x;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestFixAll1()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z1 = {|FixAllInDocument:x|} == null ? y : x;
+ var z2 = x != null ? x : y;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y)
+ {
+ var z1 = x ?? y;
+ var z2 = x ?? y;
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestFixAll2()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y, string z)
+ {
+ var w = {|FixAllInDocument:x|} != null ? x : y.ToString(z != null ? z : y);
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y, string z)
+ {
+ var w = x ?? y.ToString(z ?? y);
+ }
+}");
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCoalesceExpression)]
+ public async Task TestFixAll3()
+ {
+ await TestAsync(
+@"
+using System;
+
+class C
+{
+ void M(string x, string y, string z)
+ {
+ var w = {|FixAllInDocument:x|} != null ? x : y != null ? y : z;
+ }
+}",
+@"using System;
+
+class C
+{
+ void M(string x, string y, string z)
+ {
+ var w = x ?? y ?? z;
+ }
+}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/EditorFeatures/TestUtilities/Traits.cs b/src/EditorFeatures/TestUtilities/Traits.cs
index eb782d4f9855f5991bef685fad0040acdecf967b..473ac72329c76612a39438c2db46eec70e86be04 100644
--- a/src/EditorFeatures/TestUtilities/Traits.cs
+++ b/src/EditorFeatures/TestUtilities/Traits.cs
@@ -84,6 +84,7 @@ public static class Features
public const string CodeActionsSpellcheck = "CodeActions.Spellcheck";
public const string CodeActionsSuppression = "CodeActions.Suppression";
public const string CodeActionsUseAutoProperty = "CodeActions.UseAutoProperty";
+ public const string CodeActionsUseCoalesceExpression = "CodeActions.UseCoalesceExpression";
public const string CodeActionsUseExpressionBody = "CodeActions.UseExpressionBody";
public const string CodeActionsUseImplicitType = "CodeActions.UseImplicitType";
public const string CodeActionsUseExplicitType = "CodeActions.UseExplicitType";
diff --git a/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj b/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj
index 6188dbeac11414c50feeaccae8e24d2f5025600e..e7b3ce1c284fd1cdeab079221409f03b2dce1b33 100644
--- a/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj
+++ b/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj
@@ -504,6 +504,7 @@
+
@@ -601,6 +602,7 @@
+
PreserveNewest
Designer
diff --git a/src/EditorFeatures/VisualBasicTest/UseCoalesceExpression/UseCoalesceExpressionForNullableTests.vb b/src/EditorFeatures/VisualBasicTest/UseCoalesceExpression/UseCoalesceExpressionForNullableTests.vb
new file mode 100644
index 0000000000000000000000000000000000000000..e35a21e8a66bfcff94ee595a0d07cec409f93584
--- /dev/null
+++ b/src/EditorFeatures/VisualBasicTest/UseCoalesceExpression/UseCoalesceExpressionForNullableTests.vb
@@ -0,0 +1,141 @@
+' 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 Microsoft.CodeAnalysis.CodeFixes
+Imports Microsoft.CodeAnalysis.Diagnostics
+Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics
+Imports Microsoft.CodeAnalysis.UseCoalesceExpression
+Imports Microsoft.CodeAnalysis.VisualBasic.UseCoalesceExpression
+
+Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.UseCoalesceExpression
+ Public Class UseCoalesceExpressionForNullableTests
+ Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest
+
+ Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As Tuple(Of DiagnosticAnalyzer, CodeFixProvider)
+ Return New Tuple(Of DiagnosticAnalyzer, CodeFixProvider)(
+ New VisualBasicUseCoalesceExpressionForNullableDiagnosticAnalyzer(),
+ New UseCoalesceExpressionForNullableCodeFixProvider())
+ End Function
+
+
+ Public Async Function TestOnLeft_Equals() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z = [||]If (Not x.HasValue, y, x.Value)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestOnLeft_NotEquals() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z = [||]If(x.HasValue, x.Value, y)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestComplexExpression() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z = [||]If (Not (x + y).HasValue, y, (x + y).Value)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z = If((x + y), y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestParens1() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z = [||]If ((Not x.HasValue), y, x.Value)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestFixAll1() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z1 = {|FixAllInDocument:If|} (Not x.HasValue, y, x.Value)
+ Dim z2 = If(x.HasValue, x.Value, y)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?)
+ Dim z1 = If(x, y)
+ Dim z2 = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestFixAll2() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?, z as Integer?)
+ dim w = {|FixAllInDocument:If|} (x.HasValue, x.Value, If(y.HasValue, y.Value, z))
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as Integer?, y as Integer?, z as Integer?)
+ dim w = If(x, If(y, z))
+ End Sub
+End Class")
+ End Function
+ End Class
+End Namespace
\ No newline at end of file
diff --git a/src/EditorFeatures/VisualBasicTest/UseCoalesceExpression/UseCoalesceExpressionTests.vb b/src/EditorFeatures/VisualBasicTest/UseCoalesceExpression/UseCoalesceExpressionTests.vb
new file mode 100644
index 0000000000000000000000000000000000000000..d702742b1feb94e9e0f51fb202e960adaabe6d18
--- /dev/null
+++ b/src/EditorFeatures/VisualBasicTest/UseCoalesceExpression/UseCoalesceExpressionTests.vb
@@ -0,0 +1,241 @@
+' 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 Microsoft.CodeAnalysis.CodeFixes
+Imports Microsoft.CodeAnalysis.Diagnostics
+Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.Diagnostics
+Imports Microsoft.CodeAnalysis.UseCoalesceExpression
+Imports Microsoft.CodeAnalysis.VisualBasic.UseCoalesceExpression
+
+Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.UseCoalesceExpression
+ Public Class UseCoalesceExpressionTests
+ Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest
+
+ Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As Tuple(Of DiagnosticAnalyzer, CodeFixProvider)
+ Return New Tuple(Of DiagnosticAnalyzer, CodeFixProvider)(
+ New VisualBasicUseCoalesceExpressionDiagnosticAnalyzer(),
+ New UseCoalesceExpressionCodeFixProvider())
+ End Function
+
+
+ Public Async Function TestOnLeft_Equals() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = [||]If (x Is Nothing, y, x)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestOnLeft_NotEquals() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = [||]If(x IsNot Nothing, x, y)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestOnRight_Equals() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = [||]If(Nothing Is x, y, x)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestOnRight_NotEquals() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = [||]If(Nothing IsNot x, x, y)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestComplexExpression() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = [||]If (x.ToString() is Nothing, y, x.ToString())
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = If(x.ToString(), y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestParens1() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = [||]If ((x Is Nothing), y, x)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestParens2() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = [||]If ((x) Is Nothing, y, x)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestParens3() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = [||]If (x Is Nothing, y, (x))
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestParens4() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = [||]If (x Is Nothing, (y), x)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestFixAll1() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z1 = {|FixAllInDocument:If|} (x is Nothing, y, x)
+ Dim z2 = If(x IsNot Nothing, x, y)
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string)
+ Dim z1 = If(x, y)
+ Dim z2 = If(x, y)
+ End Sub
+End Class")
+ End Function
+
+
+ Public Async Function TestFixAll2() As Task
+ Await TestAsync(
+"
+Imports System
+
+Class C
+ Sub M(x as string, y as string, z as string)
+ dim w = {|FixAllInDocument:If|} (x isnot Nothing, x, If(y isnot Nothing, y, z))
+ End Sub
+End Class",
+"Imports System
+
+Class C
+ Sub M(x as string, y as string, z as string)
+ dim w = If(x, If(y, z))
+ End Sub
+End Class")
+ End Function
+ End Class
+End Namespace
\ No newline at end of file
diff --git a/src/Features/CSharp/Portable/CSharpFeatures.csproj b/src/Features/CSharp/Portable/CSharpFeatures.csproj
index 34cd473fbf1f5c6cd87778ed451802c6b522b95b..eb0650f5e3cbcee4312133ced08d2fa374a6ad6a 100644
--- a/src/Features/CSharp/Portable/CSharpFeatures.csproj
+++ b/src/Features/CSharp/Portable/CSharpFeatures.csproj
@@ -55,6 +55,8 @@
InternalUtilities\LambdaUtilities.cs
+
+
diff --git a/src/Features/CSharp/Portable/UseCoalesceExpression/CSharpUseCoalesceExpressionDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseCoalesceExpression/CSharpUseCoalesceExpressionDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..0001415e930da104dba5738fc053006b5033119c
--- /dev/null
+++ b/src/Features/CSharp/Portable/UseCoalesceExpression/CSharpUseCoalesceExpressionDiagnosticAnalyzer.cs
@@ -0,0 +1,30 @@
+// 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 Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.LanguageServices;
+using Microsoft.CodeAnalysis.UseCoalesceExpression;
+
+namespace Microsoft.CodeAnalysis.CSharp.UseCoalesceExpression
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ internal class CSharpUseCoalesceExpressionDiagnosticAnalyzer :
+ AbstractUseCoalesceExpressionDiagnosticAnalyzer<
+ SyntaxKind,
+ ExpressionSyntax,
+ ConditionalExpressionSyntax,
+ BinaryExpressionSyntax>
+ {
+ protected override ISyntaxFactsService GetSyntaxFactsService()
+ => CSharpSyntaxFactsService.Instance;
+
+ protected override SyntaxKind GetSyntaxKindToAnalyze()
+ => SyntaxKind.ConditionalExpression;
+
+ protected override bool IsEquals(BinaryExpressionSyntax condition)
+ => condition.Kind() == SyntaxKind.EqualsExpression;
+
+ protected override bool IsNotEquals(BinaryExpressionSyntax condition)
+ => condition.Kind() == SyntaxKind.NotEqualsExpression;
+ }
+}
\ No newline at end of file
diff --git a/src/Features/CSharp/Portable/UseCoalesceExpression/CSharpUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/UseCoalesceExpression/CSharpUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..b3a22312cd001ac2282a81da28d024f26a6ca5b8
--- /dev/null
+++ b/src/Features/CSharp/Portable/UseCoalesceExpression/CSharpUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs
@@ -0,0 +1,26 @@
+// 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 Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.LanguageServices;
+using Microsoft.CodeAnalysis.UseCoalesceExpression;
+
+namespace Microsoft.CodeAnalysis.CSharp.UseCoalesceExpression
+{
+ [DiagnosticAnalyzer(LanguageNames.CSharp)]
+ internal class CSharpUseCoalesceExpressionForNullableDiagnosticAnalyzer :
+ AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer<
+ SyntaxKind,
+ ExpressionSyntax,
+ ConditionalExpressionSyntax,
+ BinaryExpressionSyntax,
+ MemberAccessExpressionSyntax,
+ PrefixUnaryExpressionSyntax>
+ {
+ protected override ISyntaxFactsService GetSyntaxFactsService()
+ => CSharpSyntaxFactsService.Instance;
+
+ protected override SyntaxKind GetSyntaxKindToAnalyze()
+ => SyntaxKind.ConditionalExpression;
+ }
+}
\ No newline at end of file
diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs
index ddc1c436a5cda6b3e7f2af0a231d8d9eb3b54eb3..89a7877b2ba14e04a03d58c6789fcfa7bb54270c 100644
--- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs
+++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs
@@ -33,6 +33,9 @@ internal static class IDEDiagnosticIds
public const string UseExpressionBodyForIndexersDiagnosticId = "IDE0025";
public const string UseExpressionBodyForAccessorsDiagnosticId = "IDE0026";
+ public const string UseCoalesceExpressionDiagnosticId = "IDE0028";
+ public const string UseCoalesceExpressionForNullableDiagnosticId = "IDE0029";
+
// Analyzer error Ids
public const string AnalyzerChangedId = "IDE1001";
public const string AnalyzerDependencyConflictId = "IDE1002";
diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj
index 28dd45dac9751c8ec1977411af27688d990358b0..0a79e11b1152e44c06ef71f5805ab0c816281ade 100644
--- a/src/Features/Core/Portable/Features.csproj
+++ b/src/Features/Core/Portable/Features.csproj
@@ -121,6 +121,10 @@
+
+
+
+
diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs
index 77b5dfa07e6be161d3e8e3c0e131841f3443f076..3678dacbda45cb06b6d3938635cd890d3fbf2d95 100644
--- a/src/Features/Core/Portable/FeaturesResources.Designer.cs
+++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs
@@ -3037,6 +3037,15 @@ internal class FeaturesResources {
}
}
+ ///
+ /// Looks up a localized string similar to Use coalesce expression.
+ ///
+ internal static string Use_coalesce_expression {
+ get {
+ return ResourceManager.GetString("Use_coalesce_expression", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Use expression body for accessors.
///
diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx
index 83a61dac4da1ebe9184feaca4d87c65d935f922c..a1d7bc43102d73220dcc06a7dfec8c3e791bff13 100644
--- a/src/Features/Core/Portable/FeaturesResources.resx
+++ b/src/Features/Core/Portable/FeaturesResources.resx
@@ -1151,4 +1151,7 @@ This version used in: {2}
Pascal Case
+
+ Use coalesce expression
+
\ No newline at end of file
diff --git a/src/Features/Core/Portable/UseCoalesceExpression/AbstractUseCoalesceExpressionDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseCoalesceExpression/AbstractUseCoalesceExpressionDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e06cf2cca4d793a69422d9f9e6feb10b89d4f2ab
--- /dev/null
+++ b/src/Features/Core/Portable/UseCoalesceExpression/AbstractUseCoalesceExpressionDiagnosticAnalyzer.cs
@@ -0,0 +1,111 @@
+// 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.Collections.Immutable;
+using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.LanguageServices;
+
+namespace Microsoft.CodeAnalysis.UseCoalesceExpression
+{
+ internal abstract class AbstractUseCoalesceExpressionDiagnosticAnalyzer<
+ TSyntaxKind,
+ TExpressionSyntax,
+ TConditionalExpressionSyntax,
+ TBinaryExpressionSyntax> : AbstractCodeStyleDiagnosticAnalyzer
+ where TSyntaxKind : struct
+ where TExpressionSyntax : SyntaxNode
+ where TConditionalExpressionSyntax : TExpressionSyntax
+ where TBinaryExpressionSyntax : TExpressionSyntax
+ {
+ protected AbstractUseCoalesceExpressionDiagnosticAnalyzer()
+ : base(IDEDiagnosticIds.UseCoalesceExpressionDiagnosticId,
+ new LocalizableResourceString(nameof(FeaturesResources.Use_coalesce_expression), FeaturesResources.ResourceManager, typeof(FeaturesResources)))
+ {
+ }
+
+ protected abstract TSyntaxKind GetSyntaxKindToAnalyze();
+ protected abstract ISyntaxFactsService GetSyntaxFactsService();
+ protected abstract bool IsEquals(TBinaryExpressionSyntax condition);
+ protected abstract bool IsNotEquals(TBinaryExpressionSyntax condition);
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.RegisterSyntaxNodeAction(AnalyzeSyntax, GetSyntaxKindToAnalyze());
+ }
+
+ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
+ {
+ var conditionalExpression = (TConditionalExpressionSyntax)context.Node;
+
+ var optionSet = context.Options.GetOptionSet();
+ var option = optionSet.GetOption(CodeStyleOptions.PreferCoalesceExpression, conditionalExpression.Language);
+ if (!option.Value)
+ {
+ return;
+ }
+
+ var syntaxFacts = this.GetSyntaxFactsService();
+
+ SyntaxNode conditionNode, whenTrueNodeHigh, whenFalseNodeHigh;
+ syntaxFacts.GetPartsOfConditionalExpression(
+ conditionalExpression, out conditionNode, out whenTrueNodeHigh, out whenFalseNodeHigh);
+
+ conditionNode = syntaxFacts.WalkDownParentheses(conditionNode);
+ var whenTrueNodeLow = syntaxFacts.WalkDownParentheses(whenTrueNodeHigh);
+ var whenFalseNodeLow = syntaxFacts.WalkDownParentheses(whenFalseNodeHigh);
+
+ var condition = conditionNode as TBinaryExpressionSyntax;
+ if (condition == null)
+ {
+ return;
+ }
+
+ var isEquals = IsEquals(condition);
+ var isNotEquals = IsNotEquals(condition);
+ if (!isEquals && !isNotEquals)
+ {
+ return;
+ }
+
+ SyntaxNode conditionLeftHigh;
+ SyntaxNode conditionRightHigh;
+ syntaxFacts.GetPartsOfBinaryExpression(condition, out conditionLeftHigh, out conditionRightHigh);
+
+ var conditionLeftLow = syntaxFacts.WalkDownParentheses(conditionLeftHigh);
+ var conditionRightLow = syntaxFacts.WalkDownParentheses(conditionRightHigh);
+
+ var conditionLeftIsNull = syntaxFacts.IsNullLiteralExpression(conditionLeftLow);
+ var conditionRightIsNull = syntaxFacts.IsNullLiteralExpression(conditionRightLow);
+
+ if (conditionRightIsNull && conditionLeftIsNull)
+ {
+ // null == null nothing to do here.
+ return;
+ }
+
+ if (!conditionRightIsNull && !conditionLeftIsNull)
+ {
+ return;
+ }
+
+ if (!syntaxFacts.AreEquivalent(
+ conditionRightIsNull ? conditionLeftLow : conditionRightLow,
+ isEquals ? whenFalseNodeLow : whenTrueNodeLow))
+ {
+ return;
+ }
+
+ var conditionPartToCheck = conditionRightIsNull ? conditionLeftHigh : conditionRightHigh;
+ var whenPartToKeep = isEquals ? whenTrueNodeHigh : whenFalseNodeHigh;
+ var locations = ImmutableArray.Create(
+ conditionalExpression.GetLocation(),
+ conditionPartToCheck.GetLocation(),
+ whenPartToKeep.GetLocation());
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ this.CreateDescriptor(this.DescriptorId, option.Notification.Value),
+ conditionalExpression.GetLocation(),
+ locations));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/Core/Portable/UseCoalesceExpression/AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs b/src/Features/Core/Portable/UseCoalesceExpression/AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs
new file mode 100644
index 0000000000000000000000000000000000000000..e8898d53b2b9a62dc62845d26b4b5743b618c61b
--- /dev/null
+++ b/src/Features/Core/Portable/UseCoalesceExpression/AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer.cs
@@ -0,0 +1,137 @@
+// 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 Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.LanguageServices;
+
+namespace Microsoft.CodeAnalysis.UseCoalesceExpression
+{
+ internal abstract class AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer<
+ TSyntaxKind,
+ TExpressionSyntax,
+ TConditionalExpressionSyntax,
+ TBinaryExpressionSyntax,
+ TMemberAccessExpression,
+ TPrefixUnaryExpressionSyntax> : AbstractCodeStyleDiagnosticAnalyzer
+ where TSyntaxKind : struct
+ where TExpressionSyntax : SyntaxNode
+ where TConditionalExpressionSyntax : TExpressionSyntax
+ where TBinaryExpressionSyntax : TExpressionSyntax
+ where TMemberAccessExpression : TExpressionSyntax
+ where TPrefixUnaryExpressionSyntax : TExpressionSyntax
+ {
+ protected AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer()
+ : base(IDEDiagnosticIds.UseCoalesceExpressionForNullableDiagnosticId,
+ new LocalizableResourceString(nameof(FeaturesResources.Use_coalesce_expression), FeaturesResources.ResourceManager, typeof(FeaturesResources)))
+ {
+ }
+
+ protected abstract TSyntaxKind GetSyntaxKindToAnalyze();
+ protected abstract ISyntaxFactsService GetSyntaxFactsService();
+
+ public override void Initialize(AnalysisContext context)
+ {
+ context.RegisterSyntaxNodeAction(AnalyzeSyntax, GetSyntaxKindToAnalyze());
+ }
+
+ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
+ {
+ var conditionalExpression = (TConditionalExpressionSyntax)context.Node;
+
+ var optionSet = context.Options.GetOptionSet();
+ var option = optionSet.GetOption(CodeStyleOptions.PreferCoalesceExpression, conditionalExpression.Language);
+ if (!option.Value)
+ {
+ return;
+ }
+
+ var syntaxFacts = this.GetSyntaxFactsService();
+
+ SyntaxNode conditionNode, whenTrueNodeHigh, whenFalseNodeHigh;
+ syntaxFacts.GetPartsOfConditionalExpression(
+ conditionalExpression, out conditionNode, out whenTrueNodeHigh, out whenFalseNodeHigh);
+
+ conditionNode = syntaxFacts.WalkDownParentheses(conditionNode);
+ var whenTrueNodeLow = syntaxFacts.WalkDownParentheses(whenTrueNodeHigh);
+ var whenFalseNodeLow = syntaxFacts.WalkDownParentheses(whenFalseNodeHigh);
+
+ var notHasValueExpression = false;
+ if (syntaxFacts.IsLogicalNotExpression(conditionNode))
+ {
+ notHasValueExpression = true;
+ conditionNode = syntaxFacts.GetOperandOfPrefixUnaryExpression(conditionNode);
+ }
+
+ var conditionMemberAccess = conditionNode as TMemberAccessExpression;
+ if (conditionMemberAccess == null)
+ {
+ return;
+ }
+
+ SyntaxNode conditionExpression, conditionSimpleName;
+ syntaxFacts.GetPartsOfMemberAccessExpression(conditionMemberAccess, out conditionExpression, out conditionSimpleName);
+
+ string conditionName; int unused;
+ syntaxFacts.GetNameAndArityOfSimpleName(conditionSimpleName, out conditionName, out unused);
+
+ if (conditionName != nameof(Nullable.HasValue))
+ {
+ return;
+ }
+
+ var whenPartToCheck = notHasValueExpression ? whenFalseNodeLow : whenTrueNodeLow;
+ var whenPartMemberAccess = whenPartToCheck as TMemberAccessExpression;
+ if (whenPartMemberAccess == null)
+ {
+ return;
+ }
+
+ SyntaxNode whenPartExpression, whenPartSimpleName;
+ syntaxFacts.GetPartsOfMemberAccessExpression(whenPartMemberAccess, out whenPartExpression, out whenPartSimpleName);
+
+ string whenPartName;
+ syntaxFacts.GetNameAndArityOfSimpleName(whenPartSimpleName, out whenPartName, out unused);
+
+ if (whenPartName != nameof(Nullable.Value))
+ {
+ return;
+ }
+
+ if (!syntaxFacts.AreEquivalent(conditionExpression, whenPartExpression))
+ {
+ return;
+ }
+
+ // Syntactically this looks like something we can simplify. Make sure we're
+ // actually looking at something Nullable (and not some type that uses a similar
+ // syntactic pattern).
+ var semanticModel = context.SemanticModel;
+ var nullableType = semanticModel.Compilation.GetTypeByMetadataName("System.Nullable`1");
+ if (nullableType == null)
+ {
+ return;
+ }
+
+ var cancellationToken = context.CancellationToken;
+ var type = semanticModel.GetTypeInfo(conditionExpression, cancellationToken);
+
+ if (!nullableType.Equals(type.Type?.OriginalDefinition))
+ {
+ return;
+ }
+
+ var whenPartToKeep = notHasValueExpression ? whenTrueNodeHigh : whenFalseNodeHigh;
+ var locations = ImmutableArray.Create(
+ conditionalExpression.GetLocation(),
+ conditionExpression.GetLocation(),
+ whenPartToKeep.GetLocation());
+
+ context.ReportDiagnostic(Diagnostic.Create(
+ this.CreateDescriptor(this.DescriptorId, option.Notification.Value),
+ conditionalExpression.GetLocation(),
+ locations));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/Core/Portable/UseCoalesceExpression/UseCoalesceExpressionCodeFixProvider.cs b/src/Features/Core/Portable/UseCoalesceExpression/UseCoalesceExpressionCodeFixProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..6f18b3cfa1997bd56ca94bfcbc0ce0d37a6513b4
--- /dev/null
+++ b/src/Features/Core/Portable/UseCoalesceExpression/UseCoalesceExpressionCodeFixProvider.cs
@@ -0,0 +1,109 @@
+// 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.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CodeFixes.FixAllOccurrences;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.LanguageServices;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.UseCoalesceExpression
+{
+ [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
+ internal class UseCoalesceExpressionCodeFixProvider : CodeFixProvider
+ {
+ public override ImmutableArray FixableDiagnosticIds
+ => ImmutableArray.Create(IDEDiagnosticIds.UseCoalesceExpressionDiagnosticId);
+
+ public override FixAllProvider GetFixAllProvider() => new UseCoalesceExpressionFixAllProvider(this);
+
+ public override Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ context.RegisterCodeFix(new MyCodeAction(
+ c => FixAsync(context.Document, context.Diagnostics[0], c)),
+ context.Diagnostics);
+ return SpecializedTasks.EmptyTask;
+ }
+
+ private Task FixAsync(
+ Document document,
+ Diagnostic diagnostic,
+ CancellationToken cancellationToken)
+ {
+ return FixAllAsync(document, ImmutableArray.Create(diagnostic), cancellationToken);
+ }
+
+ private async Task FixAllAsync(
+ Document document,
+ ImmutableArray diagnostics,
+ CancellationToken cancellationToken)
+ {
+ var syntaxFacts = document.GetLanguageService();
+ var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+
+ var editor = new SyntaxEditor(root, document.Project.Solution.Workspace);
+ var generator = editor.Generator;
+
+ foreach (var diagnostic in diagnostics)
+ {
+ var conditionalExpression = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true);
+ var conditionalPartHigh = root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan);
+ var whenPart = root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan);
+
+ SyntaxNode condition, whenTrue, whenFalse;
+ syntaxFacts.GetPartsOfConditionalExpression(
+ conditionalExpression, out condition, out whenTrue, out whenFalse);
+
+ var conditionalPartLow = syntaxFacts.WalkDownParentheses(conditionalPartHigh);
+ editor.ReplaceNode(conditionalExpression,
+ (c, g) => {
+ SyntaxNode currentCondition, currentWhenTrue, currentWhenFalse;
+ syntaxFacts.GetPartsOfConditionalExpression(
+ c, out currentCondition, out currentWhenTrue, out currentWhenFalse);
+
+ return whenPart == whenTrue
+ ? g.CoalesceExpression(conditionalPartLow, syntaxFacts.WalkDownParentheses(currentWhenTrue))
+ : g.CoalesceExpression(conditionalPartLow, syntaxFacts.WalkDownParentheses(currentWhenFalse));
+ });
+ }
+
+ var newRoot = editor.GetChangedRoot();
+ return document.WithSyntaxRoot(newRoot);
+ }
+
+ private class UseCoalesceExpressionFixAllProvider : DocumentBasedFixAllProvider
+ {
+ private readonly UseCoalesceExpressionCodeFixProvider _provider;
+
+ public UseCoalesceExpressionFixAllProvider(UseCoalesceExpressionCodeFixProvider provider)
+ {
+ _provider = provider;
+ }
+
+ protected override Task FixDocumentAsync(
+ Document document, ImmutableArray diagnostics, CancellationToken cancellationToken)
+ {
+ var filteredDiagnostics = diagnostics.WhereAsArray(
+ d => !d.Descriptor.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary));
+ return _provider.FixAllAsync(document, filteredDiagnostics, cancellationToken);
+ }
+ }
+
+ private class MyCodeAction : CodeAction.DocumentChangeAction
+ {
+ public MyCodeAction(Func> createChangedDocument)
+ : base(FeaturesResources.Use_coalesce_expression, createChangedDocument)
+ {
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/Core/Portable/UseCoalesceExpression/UseCoalesceExpressionForNullableCodeFixProvider.cs b/src/Features/Core/Portable/UseCoalesceExpression/UseCoalesceExpressionForNullableCodeFixProvider.cs
new file mode 100644
index 0000000000000000000000000000000000000000..8dec7b90ad9e46fbd11d402c030974d328efbbfd
--- /dev/null
+++ b/src/Features/Core/Portable/UseCoalesceExpression/UseCoalesceExpressionForNullableCodeFixProvider.cs
@@ -0,0 +1,108 @@
+// 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.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeActions;
+using Microsoft.CodeAnalysis.CodeFixes;
+using Microsoft.CodeAnalysis.CodeFixes.FixAllOccurrences;
+using Microsoft.CodeAnalysis.Diagnostics;
+using Microsoft.CodeAnalysis.Editing;
+using Microsoft.CodeAnalysis.LanguageServices;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+using Roslyn.Utilities;
+
+namespace Microsoft.CodeAnalysis.UseCoalesceExpression
+{
+ [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
+ internal class UseCoalesceExpressionForNullableCodeFixProvider : CodeFixProvider
+ {
+ public override ImmutableArray FixableDiagnosticIds
+ => ImmutableArray.Create(IDEDiagnosticIds.UseCoalesceExpressionForNullableDiagnosticId);
+
+ public override FixAllProvider GetFixAllProvider() => new UseCoalesceExpressionForNullableFixAllProvider(this);
+
+ public override Task RegisterCodeFixesAsync(CodeFixContext context)
+ {
+ context.RegisterCodeFix(new MyCodeAction(
+ c => FixAsync(context.Document, context.Diagnostics[0], c)),
+ context.Diagnostics);
+ return SpecializedTasks.EmptyTask;
+ }
+
+ private Task FixAsync(
+ Document document,
+ Diagnostic diagnostic,
+ CancellationToken cancellationToken)
+ {
+ return FixAllAsync(document, ImmutableArray.Create(diagnostic), cancellationToken);
+ }
+
+ private async Task FixAllAsync(
+ Document document,
+ ImmutableArray diagnostics,
+ CancellationToken cancellationToken)
+ {
+ var syntaxFacts = document.GetLanguageService();
+ var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
+
+ var editor = new SyntaxEditor(root, document.Project.Solution.Workspace);
+ var generator = editor.Generator;
+
+ foreach (var diagnostic in diagnostics)
+ {
+ var conditionalExpression = root.FindNode(diagnostic.AdditionalLocations[0].SourceSpan, getInnermostNodeForTie: true);
+ var conditionExpression = root.FindNode(diagnostic.AdditionalLocations[1].SourceSpan);
+ var whenPart = root.FindNode(diagnostic.AdditionalLocations[2].SourceSpan);
+
+ SyntaxNode condition, whenTrue, whenFalse;
+ syntaxFacts.GetPartsOfConditionalExpression(
+ conditionalExpression, out condition, out whenTrue, out whenFalse);
+
+ editor.ReplaceNode(conditionalExpression,
+ (c, g) => {
+ SyntaxNode currentCondition, currentWhenTrue, currentWhenFalse;
+ syntaxFacts.GetPartsOfConditionalExpression(
+ c, out currentCondition, out currentWhenTrue, out currentWhenFalse);
+
+ return whenPart == whenTrue
+ ? g.CoalesceExpression(conditionExpression, syntaxFacts.WalkDownParentheses(currentWhenTrue))
+ : g.CoalesceExpression(conditionExpression, syntaxFacts.WalkDownParentheses(currentWhenFalse));
+ });
+ }
+
+ var newRoot = editor.GetChangedRoot();
+ return document.WithSyntaxRoot(newRoot);
+ }
+
+ private class UseCoalesceExpressionForNullableFixAllProvider : DocumentBasedFixAllProvider
+ {
+ private readonly UseCoalesceExpressionForNullableCodeFixProvider _provider;
+
+ public UseCoalesceExpressionForNullableFixAllProvider(UseCoalesceExpressionForNullableCodeFixProvider provider)
+ {
+ _provider = provider;
+ }
+
+ protected override Task FixDocumentAsync(
+ Document document, ImmutableArray diagnostics, CancellationToken cancellationToken)
+ {
+ var filteredDiagnostics = diagnostics.WhereAsArray(
+ d => !d.Descriptor.CustomTags.Contains(WellKnownDiagnosticTags.Unnecessary));
+ return _provider.FixAllAsync(document, filteredDiagnostics, cancellationToken);
+ }
+ }
+
+ private class MyCodeAction : CodeAction.DocumentChangeAction
+ {
+ public MyCodeAction(Func> createChangedDocument)
+ : base(FeaturesResources.Use_coalesce_expression, createChangedDocument)
+ {
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Features/VisualBasic/Portable/BasicFeatures.vbproj b/src/Features/VisualBasic/Portable/BasicFeatures.vbproj
index bea3fd1fcb933ed572b0758582a61467911715ef..113ac52862a87cc0aa5452d19b754f24d36c80e2 100644
--- a/src/Features/VisualBasic/Portable/BasicFeatures.vbproj
+++ b/src/Features/VisualBasic/Portable/BasicFeatures.vbproj
@@ -5,7 +5,8 @@
AnyCPU
{A1BCD0CE-6C2F-4F8C-9A48-D9D93928E26D}
Library
-
+
+
Microsoft.CodeAnalysis.VisualBasic.Features
.NETPortable
v5.0
@@ -429,6 +430,8 @@
+
+
diff --git a/src/Features/VisualBasic/Portable/UseCoalesceExpression/VisualBasicUseCoalesceExpressionDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/UseCoalesceExpression/VisualBasicUseCoalesceExpressionDiagnosticAnalyzer.vb
new file mode 100644
index 0000000000000000000000000000000000000000..f8f65b8d5af09e3add0b752325fd8b1c0d3a1fd5
--- /dev/null
+++ b/src/Features/VisualBasic/Portable/UseCoalesceExpression/VisualBasicUseCoalesceExpressionDiagnosticAnalyzer.vb
@@ -0,0 +1,33 @@
+' 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 Microsoft.CodeAnalysis.Diagnostics
+Imports Microsoft.CodeAnalysis.LanguageServices
+Imports Microsoft.CodeAnalysis.UseCoalesceExpression
+Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
+
+Namespace Microsoft.CodeAnalysis.VisualBasic.UseCoalesceExpression
+
+ Friend Class VisualBasicUseCoalesceExpressionDiagnosticAnalyzer
+ Inherits AbstractUseCoalesceExpressionDiagnosticAnalyzer(Of
+ SyntaxKind,
+ ExpressionSyntax,
+ TernaryConditionalExpressionSyntax,
+ BinaryExpressionSyntax)
+
+ Protected Overrides Function GetSyntaxFactsService() As ISyntaxFactsService
+ Return VisualBasicSyntaxFactsService.Instance
+ End Function
+
+ Protected Overrides Function GetSyntaxKindToAnalyze() As SyntaxKind
+ Return SyntaxKind.TernaryConditionalExpression
+ End Function
+
+ Protected Overrides Function IsEquals(condition As BinaryExpressionSyntax) As Boolean
+ Return condition.Kind() = SyntaxKind.IsExpression
+ End Function
+
+ Protected Overrides Function IsNotEquals(condition As BinaryExpressionSyntax) As Boolean
+ Return condition.Kind() = SyntaxKind.IsNotExpression
+ End Function
+ End Class
+End Namespace
\ No newline at end of file
diff --git a/src/Features/VisualBasic/Portable/UseCoalesceExpression/VisualBasicUseCoalesceExpressionForNullableDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/UseCoalesceExpression/VisualBasicUseCoalesceExpressionForNullableDiagnosticAnalyzer.vb
new file mode 100644
index 0000000000000000000000000000000000000000..16dc451b5daf41772b081774d17b574a9e3784a9
--- /dev/null
+++ b/src/Features/VisualBasic/Portable/UseCoalesceExpression/VisualBasicUseCoalesceExpressionForNullableDiagnosticAnalyzer.vb
@@ -0,0 +1,27 @@
+' 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 Microsoft.CodeAnalysis.Diagnostics
+Imports Microsoft.CodeAnalysis.LanguageServices
+Imports Microsoft.CodeAnalysis.UseCoalesceExpression
+Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
+
+Namespace Microsoft.CodeAnalysis.VisualBasic.UseCoalesceExpression
+
+ Friend Class VisualBasicUseCoalesceExpressionForNullableDiagnosticAnalyzer
+ Inherits AbstractUseCoalesceExpressionForNullableDiagnosticAnalyzer(Of
+ SyntaxKind,
+ ExpressionSyntax,
+ TernaryConditionalExpressionSyntax,
+ BinaryExpressionSyntax,
+ MemberAccessExpressionSyntax,
+ UnaryExpressionSyntax)
+
+ Protected Overrides Function GetSyntaxFactsService() As ISyntaxFactsService
+ Return VisualBasicSyntaxFactsService.Instance
+ End Function
+
+ Protected Overrides Function GetSyntaxKindToAnalyze() As SyntaxKind
+ Return SyntaxKind.TernaryConditionalExpression
+ End Function
+ End Class
+End Namespace
\ No newline at end of file
diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs
index 2e88b34b02237e3b5b5cc4ac7cbc18e36d970b56..5eb995fc877ca94c10bf375a4fdb5f992052784f 100644
--- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs
+++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs
@@ -249,6 +249,27 @@ public C(string s)
//]
}
}
+";
+
+ private static readonly string s_preferCoalesceExpression = @"
+using System;
+
+class C
+{
+ private string s;
+
+ public C(string s)
+ {
+//[
+ // Prefer:
+ var v = x ?? y;
+
+ // Over:
+ var v = x != null ? x : y; // or
+ var v = x == null ? y : x;
+//]
+ }
+}
";
private static readonly string s_preferConditionalDelegateCall = @"
@@ -598,6 +619,7 @@ internal StyleViewModel(OptionSet optionSet, IServiceProvider serviceProvider) :
// Null preferences.
CodeStyleItems.Add(new SimpleCodeStyleOptionViewModel(CodeStyleOptions.PreferThrowExpression, CSharpVSResources.Prefer_throw_expression, s_preferThrowExpression, s_preferThrowExpression, this, optionSet, nullCheckingGroupTitle));
CodeStyleItems.Add(new SimpleCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferConditionalDelegateCall, CSharpVSResources.Prefer_conditional_delegate_call, s_preferConditionalDelegateCall, s_preferConditionalDelegateCall, this, optionSet, nullCheckingGroupTitle));
+ CodeStyleItems.Add(new SimpleCodeStyleOptionViewModel(CodeStyleOptions.PreferCoalesceExpression, ServicesVSResources.Prefer_coalesce_expression, s_preferCoalesceExpression, s_preferCoalesceExpression, this, optionSet, nullCheckingGroupTitle));
}
}
}
\ No newline at end of file
diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs b/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs
index 4f4e8ff05a183ee68a8f25c19d691b3a23af0236..349b94b901ffa2ed4b0ce6cb8e46e72afaa8f812 100644
--- a/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs
+++ b/src/VisualStudio/Core/Def/ServicesVSResources.Designer.cs
@@ -1365,6 +1365,15 @@ internal class ServicesVSResources {
}
}
+ ///
+ /// Looks up a localized string similar to Prefer coalesce expression.
+ ///
+ internal static string Prefer_coalesce_expression {
+ get {
+ return ResourceManager.GetString("Prefer_coalesce_expression", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Prefer framework type.
///
diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx
index 417c32725b0106199433b1d8136f3d9c6b9e4ba5..713ce6086342a8652171407a5974fa8a435f89a3 100644
--- a/src/VisualStudio/Core/Def/ServicesVSResources.resx
+++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx
@@ -804,4 +804,7 @@ Additional information: {1}
This item cannot be deleted because it is used by an existing Naming Rule.
+
+ Prefer coalesce expression
+
\ No newline at end of file
diff --git a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.Designer.vb b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.Designer.vb
index 7d994bff1bb0f1297f2ac927e09af6b3277c71dc..38b1f6e14e58e3018a8ae6563456a548ce693f3c 100644
--- a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.Designer.vb
+++ b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.Designer.vb
@@ -325,6 +325,15 @@ Namespace Microsoft.VisualStudio.LanguageServices.VisualBasic
End Get
End Property
+ '''
+ ''' Looks up a localized string similar to 'nothing' checking:.
+ '''
+ Friend Shared ReadOnly Property nothing_checking_colon() As String
+ Get
+ Return ResourceManager.GetString("nothing_checking_colon", resourceCulture)
+ End Get
+ End Property
+
'''
''' Looks up a localized string similar to _Only add new line on enter after end of fully typed word.
'''
diff --git a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx
index bea1a34e97d96777755d747e00f8d1ddbdae1fbc..6327a685e47d6ab6e9428e032474f5d1903b9b66 100644
--- a/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx
+++ b/src/VisualStudio/VisualBasic/Impl/BasicVSResources.resx
@@ -267,4 +267,7 @@
_Show completion list after a character is typed
+
+ 'nothing' checking:
+
\ No newline at end of file
diff --git a/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb b/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb
index fbe94d07d06f63e99cdf24620f248e204cbd85a5..0f494a3566673a09b83750f1f68041589aa83a81 100644
--- a/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb
+++ b/src/VisualStudio/VisualBasic/Impl/Options/StyleViewModel.vb
@@ -165,6 +165,24 @@ Class Customer
End Sub
End Class"
+ Private Shared ReadOnly s_preferCoalesceExpression As String = "
+Imports System
+
+Class Customer
+ Private Age As Integer
+
+ Sub New()
+//[
+ ' Prefer:
+ Dim v = If(x, y)
+
+ ' Over:
+ Dim v = If(x Is Nothing, y, x) ' or
+ Dim v = If(x IsNot Nothing, x, y)
+//]
+ End Sub
+End Class"
+
#End Region
Public Sub New(optionSet As OptionSet, serviceProvider As IServiceProvider)
@@ -188,7 +206,7 @@ End Class"
}
Dim expressionPreferencesGroupTitle = ServicesVSResources.Expression_preferences_colon
-
+ Dim nothingPreferencesGroupTitle = BasicVSResources.nothing_checking_colon
' qualify with Me. group
Me.CodeStyleItems.Add(New SimpleCodeStyleOptionViewModel(CodeStyleOptions.QualifyFieldAccess, BasicVSResources.Qualify_field_access_with_Me, s_fieldDeclarationPreviewTrue, s_fieldDeclarationPreviewFalse, Me, optionSet, qualifyGroupTitle, qualifyMemberAccessPreferences))
@@ -203,6 +221,8 @@ End Class"
' expression preferences
Me.CodeStyleItems.Add(New SimpleCodeStyleOptionViewModel(CodeStyleOptions.PreferObjectInitializer, ServicesVSResources.Prefer_object_initializer, s_preferObjectInitializer, s_preferObjectInitializer, Me, optionSet, expressionPreferencesGroupTitle))
+ ' nothing preferences
+ Me.CodeStyleItems.Add(New SimpleCodeStyleOptionViewModel(CodeStyleOptions.PreferCoalesceExpression, ServicesVSResources.Prefer_coalesce_expression, s_preferCoalesceExpression, s_preferCoalesceExpression, Me, optionSet, nothingPreferencesGroupTitle))
End Sub
End Class
End Namespace
\ No newline at end of file
diff --git a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs
index e68131ea5e23e7b297275096f56e5cd88d944410..994770a89b1708d9190b9c1251be798f3946629b 100644
--- a/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs
+++ b/src/Workspaces/CSharp/Portable/LanguageServices/CSharpSyntaxFactsService.cs
@@ -1871,6 +1871,40 @@ public bool IsPossibleTupleContext(SyntaxTree syntaxTree, int position, Cancella
return syntaxTree.IsPossibleTupleContext(token, position);
}
+ public bool IsNullLiteralExpression(SyntaxNode node)
+ => node.Kind() == SyntaxKind.NullLiteralExpression;
+
+ public void GetPartsOfBinaryExpression(SyntaxNode node, out SyntaxNode left, out SyntaxNode right)
+ {
+ var binaryExpression = (BinaryExpressionSyntax)node;
+ left = binaryExpression.Left;
+ right = binaryExpression.Right;
+ }
+
+ public void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode condition, out SyntaxNode whenTrue, out SyntaxNode whenFalse)
+ {
+ var conditionalExpression = (ConditionalExpressionSyntax)node;
+ condition = conditionalExpression.Condition;
+ whenTrue = conditionalExpression.WhenTrue;
+ whenFalse = conditionalExpression.WhenFalse;
+ }
+
+ public SyntaxNode WalkDownParentheses(SyntaxNode node)
+ => (node as ExpressionSyntax)?.WalkDownParentheses() ?? node;
+
+ public bool IsLogicalNotExpression(SyntaxNode node)
+ => node.Kind() == SyntaxKind.LogicalNotExpression;
+
+ public SyntaxNode GetOperandOfPrefixUnaryExpression(SyntaxNode node)
+ => ((PrefixUnaryExpressionSyntax)node).Operand;
+
+ public void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode name)
+ {
+ var memberAccessExpression = (MemberAccessExpressionSyntax)node;
+ expression = memberAccessExpression.Expression;
+ name = memberAccessExpression.Name;
+ }
+
private class AddFirstMissingCloseBaceRewriter: CSharpSyntaxRewriter
{
private readonly SyntaxNode _contextNode;
diff --git a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs
index 9c27bb56c6d3cce8e209c87798d9fabdc304ba17..5982c0e0529bac1b2eda0d6c7db54872e616d96b 100644
--- a/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs
+++ b/src/Workspaces/Core/Portable/CodeStyle/CodeStyleOptions.cs
@@ -70,6 +70,12 @@ public class CodeStyleOptions
defaultValue: false,
storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferObjectInitializer"));
+ internal static readonly PerLanguageOption> PreferCoalesceExpression = new PerLanguageOption>(
+ nameof(CodeStyleOptions),
+ nameof(PreferCoalesceExpression),
+ defaultValue: TrueWithSuggestionEnforcement,
+ storageLocations: new RoamingProfileStorageLocation("TextEditor.%LANGUAGE%.Specific.PreferCoalesceExpression"));
+
internal static readonly PerLanguageOption> PreferInlinedVariableDeclaration = new PerLanguageOption>(
nameof(CodeStyleOptions),
nameof(PreferInlinedVariableDeclaration),
diff --git a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs
index 47eb763a4175298a913b1e526ba7c51d2c224961..92a325cd9ce5e97b695d16ecdd5e0aa9aa0be9a5 100644
--- a/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs
+++ b/src/Workspaces/Core/Portable/LanguageServices/SyntaxFactsService/ISyntaxFactsService.cs
@@ -29,7 +29,7 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsLiteral(SyntaxToken token);
bool IsStringLiteralOrInterpolatedStringLiteral(SyntaxToken token);
bool IsStringLiteral(SyntaxToken token);
- bool IsNumericLiteralExpression(SyntaxNode node);
+
bool IsTypeNamedVarInVariableOrFieldDeclaration(SyntaxToken token, SyntaxNode parent);
bool IsTypeNamedDynamic(SyntaxToken token, SyntaxNode parent);
bool IsDocumentationComment(SyntaxNode node);
@@ -37,6 +37,9 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsGlobalAttribute(SyntaxNode node);
bool IsDeclaration(SyntaxNode node);
+ bool IsNumericLiteralExpression(SyntaxNode node);
+ bool IsNullLiteralExpression(SyntaxNode node);
+
string GetText(int kind);
bool IsInInactiveRegion(SyntaxTree syntaxTree, int position, CancellationToken cancellationToken);
@@ -52,6 +55,9 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsObjectCreationExpression(SyntaxNode node);
SyntaxNode GetObjectCreationInitializer(SyntaxNode objectCreationExpression);
+ void GetPartsOfBinaryExpression(SyntaxNode node, out SyntaxNode left, out SyntaxNode right);
+ void GetPartsOfConditionalExpression(SyntaxNode node, out SyntaxNode condition, out SyntaxNode whenTrue, out SyntaxNode whenFalse);
+
bool IsInvocationExpression(SyntaxNode node);
bool IsExpressionOfInvocationExpression(SyntaxNode node);
SyntaxNode GetExpressionOfInvocationExpression(SyntaxNode node);
@@ -59,6 +65,9 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsExpressionOfAwaitExpression(SyntaxNode node);
SyntaxNode GetExpressionOfAwaitExpression(SyntaxNode node);
+ bool IsLogicalNotExpression(SyntaxNode node);
+ SyntaxNode GetOperandOfPrefixUnaryExpression(SyntaxNode node);
+
// Left side of = assignment.
bool IsLeftSideOfAssignment(SyntaxNode node);
@@ -81,9 +90,10 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsNameOfMemberAccessExpression(SyntaxNode node);
bool IsExpressionOfMemberAccessExpression(SyntaxNode node);
- SyntaxNode GetNameOfMemberAccessExpression(SyntaxNode memberAccessExpression);
- SyntaxNode GetExpressionOfMemberAccessExpression(SyntaxNode memberAccessExpression);
- SyntaxToken GetOperatorTokenOfMemberAccessExpression(SyntaxNode memberAccessExpression);
+ SyntaxNode GetNameOfMemberAccessExpression(SyntaxNode node);
+ SyntaxNode GetExpressionOfMemberAccessExpression(SyntaxNode node);
+ SyntaxToken GetOperatorTokenOfMemberAccessExpression(SyntaxNode node);
+ void GetPartsOfMemberAccessExpression(SyntaxNode node, out SyntaxNode expression, out SyntaxNode name);
bool IsSimpleMemberAccessExpression(SyntaxNode node);
bool IsPointerMemberAccessExpression(SyntaxNode node);
@@ -176,6 +186,7 @@ internal interface ISyntaxFactsService : ILanguageService
SyntaxToken FindTokenOnRightOfPosition(SyntaxNode node, int position, bool includeSkipped = true, bool includeDirectives = false, bool includeDocumentationComments = false);
SyntaxNode Parenthesize(SyntaxNode expression, bool includeElasticTrivia = true);
+ SyntaxNode WalkDownParentheses(SyntaxNode node);
SyntaxNode ConvertToSingleLine(SyntaxNode node, bool useElasticTrivia = false);
diff --git a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb
index d8978c90cbb60d8e1a5477ef4bbec47fe14943ba..82478ba05df4eb1cc4ab3063631cebf2ceaf550e 100644
--- a/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb
+++ b/src/Workspaces/VisualBasic/Portable/LanguageServices/VisualBasicSyntaxFactsService.vb
@@ -1546,5 +1546,41 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Dim token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken)
Return syntaxTree.IsPossibleTupleContext(token, position)
End Function
+
+ Public Function IsNullLiteralExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsNullLiteralExpression
+ Return node.Kind() = SyntaxKind.NothingLiteralExpression
+ End Function
+
+ Public Sub GetPartsOfBinaryExpression(node As SyntaxNode, ByRef left As SyntaxNode, ByRef right As SyntaxNode) Implements ISyntaxFactsService.GetPartsOfBinaryExpression
+ Dim binaryExpression = DirectCast(node, BinaryExpressionSyntax)
+ left = binaryExpression.Left
+ right = binaryExpression.Right
+ End Sub
+
+ Public Sub GetPartsOfConditionalExpression(node As SyntaxNode, ByRef condition As SyntaxNode, ByRef whenTrue As SyntaxNode, ByRef whenFalse As SyntaxNode) Implements ISyntaxFactsService.GetPartsOfConditionalExpression
+ Dim conditionalExpression = DirectCast(node, TernaryConditionalExpressionSyntax)
+ condition = conditionalExpression.Condition
+ whenTrue = conditionalExpression.WhenTrue
+ whenFalse = conditionalExpression.WhenFalse
+ End Sub
+
+ Public Function WalkDownParentheses(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.WalkDownParentheses
+ Return If(TryCast(node, ExpressionSyntax)?.WalkDownParentheses(), node)
+ End Function
+
+ Public Function IsLogicalNotExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsLogicalNotExpression
+ Return node.IsKind(SyntaxKind.NotExpression)
+ Throw New NotImplementedException()
+ End Function
+
+ Public Function GetOperandOfPrefixUnaryExpression(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetOperandOfPrefixUnaryExpression
+ Return DirectCast(node, UnaryExpressionSyntax).Operand
+ End Function
+
+ Public Sub GetPartsOfMemberAccessExpression(node As SyntaxNode, ByRef expression As SyntaxNode, ByRef name As SyntaxNode) Implements ISyntaxFactsService.GetPartsOfMemberAccessExpression
+ Dim memberAccess = DirectCast(node, MemberAccessExpressionSyntax)
+ expression = memberAccess.Expression
+ name = memberAccess.Name
+ End Sub
End Class
End Namespace
\ No newline at end of file