diff --git a/build/scripts/docker/Dockerfile b/build/scripts/docker/Dockerfile index f83c545ffeb6259ba04fa280f1009f9158ababa7..ada0b61b27a81173526bf0188b112b6c8121020f 100644 --- a/build/scripts/docker/Dockerfile +++ b/build/scripts/docker/Dockerfile @@ -33,10 +33,10 @@ RUN apt-get install -y libunwind8 \ # Install Mono RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 3FA7E0328081BFF6A14DA29AA6A19B38D3D831EF && \ - (echo "deb http://download.mono-project.com/repo/ubuntu stable-xenial/snapshots/5.8.0.108 main" | \ + (echo "deb http://download.mono-project.com/repo/ubuntu stable-xenial main" | \ tee /etc/apt/sources.list.d/mono-official.list) && \ apt-get update && \ - apt-get install -y mono-devel=5.8.0.108-0xamarin1+ubuntu1604b1 && \ + apt-get install -y mono-devel && \ apt-get clean # Setup User to match Host User, and give superuser permissions diff --git a/src/EditorFeatures/CSharpTest/ConvertAnonymousTypeToTuple/ConvertAnonymousTypeToTupleTests.cs b/src/EditorFeatures/CSharpTest/ConvertAnonymousTypeToTuple/ConvertAnonymousTypeToTupleTests.cs new file mode 100644 index 0000000000000000000000000000000000000000..697b6c8dfd2777a841dc90ee87a418389085ce68 --- /dev/null +++ b/src/EditorFeatures/CSharpTest/ConvertAnonymousTypeToTuple/ConvertAnonymousTypeToTupleTests.cs @@ -0,0 +1,480 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.ConvertAnonymousTypeToTuple; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics; +using Microsoft.CodeAnalysis.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.ConvertAnonymousTypeToTuple +{ + public partial class ConvertAnonymousTypeToTupleTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest + { + internal override (DiagnosticAnalyzer, CodeFixProvider) CreateDiagnosticProviderAndFixer(Workspace workspace) + => (new CSharpConvertAnonymousTypeToTupleDiagnosticAnalyzer(), new CSharpConvertAnonymousTypeToTupleCodeFixProvider()); + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task ConvertSingleAnonymousType() + { + var text = @" +class Test +{ + void Method() + { + var t1 = [||]new { a = 1, b = 2 }; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: 2); + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task NotOnEmptyAnonymousType() + { + await TestMissingInRegularAndScriptAsync(@" +class Test +{ + void Method() + { + var t1 = [||]new { }; + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task NotOnSingleFieldAnonymousType() + { + await TestMissingInRegularAndScriptAsync(@" +class Test +{ + void Method() + { + var t1 = [||]new { a = 1 }; + } +} +"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task ConvertSingleAnonymousTypeWithInferredName() + { + var text = @" +class Test +{ + void Method(int b) + { + var t1 = [||]new { a = 1, b }; + } +} +"; + var expected = @" +class Test +{ + void Method(int b) + { + var t1 = (a: 1, b); + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task ConvertMultipleInstancesInSameMethod() + { + var text = @" +class Test +{ + void Method() + { + var t1 = [||]new { a = 1, b = 2 }; + var t2 = new { a = 3, b = 4 }; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: 2); + var t2 = (a: 3, b: 4); + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task ConvertMultipleInstancesAcrossMethods() + { + var text = @" +class Test +{ + void Method() + { + var t1 = [||]new { a = 1, b = 2 }; + var t2 = new { a = 3, b = 4 }; + } + + void Method2() + { + var t1 = new { a = 1, b = 2 }; + var t2 = new { a = 3, b = 4 }; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: 2); + var t2 = (a: 3, b: 4); + } + + void Method2() + { + var t1 = new { a = 1, b = 2 }; + var t2 = new { a = 3, b = 4 }; + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task OnlyConvertMatchingTypesInSameMethod() + { + var text = @" +class Test +{ + void Method(int b) + { + var t1 = [||]new { a = 1, b = 2 }; + var t2 = new { a = 3, b }; + var t3 = new { a = 4 }; + var t4 = new { b = 5, a = 6 }; + } +} +"; + var expected = @" +class Test +{ + void Method(int b) + { + var t1 = (a: 1, b: 2); + var t2 = (a: 3, b); + var t3 = new { a = 4 }; + var t4 = new { b = 5, a = 6 }; + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task TestFixAllInSingleMethod() + { + var text = @" +class Test +{ + void Method(int b) + { + var t1 = {|FixAllInDocument:|}new { a = 1, b = 2 }; + var t2 = new { a = 3, b }; + var t3 = new { a = 4 }; + var t4 = new { b = 5, a = 6 }; + } +} +"; + var expected = @" +class Test +{ + void Method(int b) + { + var t1 = (a: 1, b: 2); + var t2 = (a: 3, b); + var t3 = new { a = 4 }; + var t4 = (b: 5, a: 6); + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task TestFixAllAcrossMethods() + { + var text = @" +class Test +{ + void Method() + { + var t1 = {|FixAllInDocument:|}new { a = 1, b = 2 }; + var t2 = new { a = 3, b = 4 }; + } + + void Method2() + { + var t1 = new { a = 1, b = 2 }; + var t2 = new { a = 3, b = 4 }; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: 2); + var t2 = (a: 3, b: 4); + } + + void Method2() + { + var t1 = (a: 1, b: 2); + var t2 = (a: 3, b: 4); + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task TestTrivia() + { + var text = @" +class Test +{ + void Method() + { + var t1 = /*1*/ [||]new /*2*/ { /*3*/ a /*4*/ = /*5*/ 1 /*7*/ , /*8*/ b /*9*/ = /*10*/ 2 /*11*/ } /*12*/ ; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = /*1*/ ( /*3*/ a /*4*/ : /*5*/ 1 /*7*/ , /*8*/ b /*9*/ : /*10*/ 2 /*11*/ ) /*12*/ ; + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task TestFixAllNestedTypes() + { + var text = @" +class Test +{ + void Method() + { + var t1 = {|FixAllInDocument:|}new { a = 1, b = new { c = 1, d = 2 } }; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: (c: 1, d: 2)); + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task ConvertMultipleNestedInstancesInSameMethod() + { + var text = @" +class Test +{ + void Method() + { + var t1 = [||]new { a = 1, b = (object)new { a = 1, b = default(object) } }; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: (object)(a: 1, b: default(object))); + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task ConvertWithLambda1() + { + var text = @" +class Test +{ + void Method() + { + var t1 = [||]new { a = 1, b = 2 }; + Action a = () => + { + var t2 = new { a = 3, b = 4 }; + }; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: 2); + Action a = () => + { + var t2 = (a: 3, b: 4); + }; + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task ConvertWithLambda2() + { + var text = @" +class Test +{ + void Method() + { + var t1 = new { a = 1, b = 2 }; + Action a = () => + { + var t2 = [||]new { a = 3, b = 4 }; + }; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: 2); + Action a = () => + { + var t2 = (a: 3, b: 4); + }; + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task ConvertWithLocalFunction1() + { + var text = @" +class Test +{ + void Method() + { + var t1 = [||]new { a = 1, b = 2 }; + void func() + { + var t2 = new { a = 3, b = 4 }; + } + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: 2); + void func() + { + var t2 = (a: 3, b: 4); + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task ConvertWithLocalFunction2() + { + var text = @" +class Test +{ + void Method() + { + var t1 = new { a = 1, b = 2 }; + void func() + { + var t2 = [||]new { a = 3, b = 4 }; + } + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: 1, b: 2); + void func() + { + var t2 = (a: 3, b: 4); + } + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertAnonymousTypeToTuple)] + public async Task TestIncompleteAnonymousType() + { + var text = @" +class Test +{ + void Method() + { + var t1 = [||]new { a = , b = }; + } +} +"; + var expected = @" +class Test +{ + void Method() + { + var t1 = (a: , b: ); + } +} +"; + await TestInRegularAndScriptAsync(text, expected); + } + } +} diff --git a/src/EditorFeatures/VisualBasicTest/ConvertAnonymousTypeToTuple/ConvertAnonymousTypeToTupleTests.vb b/src/EditorFeatures/VisualBasicTest/ConvertAnonymousTypeToTuple/ConvertAnonymousTypeToTupleTests.vb new file mode 100644 index 0000000000000000000000000000000000000000..277e0fa0f92b0781f7ad720ce9e7652d4c7ad6cb --- /dev/null +++ b/src/EditorFeatures/VisualBasicTest/ConvertAnonymousTypeToTuple/ConvertAnonymousTypeToTupleTests.vb @@ -0,0 +1,332 @@ +' 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.VisualBasic.ConvertAnonymousTypeToTuple + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.ConvertAnonymousTypeToTuple + Partial Public Class ConvertAnonymousTypeToTupleTests + Inherits AbstractVisualBasicDiagnosticProviderBasedUserDiagnosticTest + + Friend Overrides Function CreateDiagnosticProviderAndFixer(workspace As Workspace) As (DiagnosticAnalyzer, CodeFixProvider) + Return (New VisualBasicConvertAnonymousTypeToTupleDiagnosticAnalyzer(), New VisualBasicConvertAnonymousTypeToTupleCodeFixProvider()) + End Function + + + Public Async Function ConvertSingleAnonymousType() As Task + Dim text = " +class Test + sub Method() + dim t1 = [||]new with { .a = 1, .b = 2 } + end sub +end class +" + Dim expected = " +class Test + sub Method() + dim t1 = (a:=1, b:=2) + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function NotOnEmptyAnonymousType() As Task + Await TestMissingInRegularAndScriptAsync(" +class Test + sub Method() + dim t1 = [||]new with { } + end sub +end class +") + End Function + + + Public Async Function NotOnSingleFieldAnonymousType() As Task + Await TestMissingInRegularAndScriptAsync(" +class Test + sub Method() + dim t1 = [||]new with { .a = 1 } + end sub +end class +") + End Function + + + Public Async Function ConvertSingleAnonymousTypeWithInferredName() As Task + Dim text = " +class Test + sub Method(b as integer) + dim t1 = [||]new with { .a = 1, b } + end sub +end class +" + Dim expected = " +class Test + sub Method(b as integer) + dim t1 = (a:=1, b) + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function ConvertMultipleInstancesInSameMethod() As Task + Dim text = " +class Test + sub Method() + dim t1 = [||]new with { .a = 1, .b = 2 } + dim t2 = new with { .a = 3, .b = 4 } + end sub +end class +" + Dim expected = " +class Test + sub Method() + dim t1 = (a:=1, b:=2) + dim t2 = (a:=3, b:=4) + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function ConvertMultipleInstancesAcrossMethods() As Task + Dim text = " +class Test + sub Method() + dim t1 = [||]new with { .a = 1, .b = 2 } + dim t2 = new with { .a = 3, .b = 4 } + end sub + + sub Method2() + dim t1 = new with { .a = 1, .b = 2 } + dim t2 = new with { .a = 3, .b = 4 } + end sub +end class +" + Dim expected = " +class Test + sub Method() + dim t1 = (a:=1, b:=2) + dim t2 = (a:=3, b:=4) + end sub + + sub Method2() + dim t1 = new with { .a = 1, .b = 2 } + dim t2 = new with { .a = 3, .b = 4 } + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function OnlyConvertMatchingTypesInSameMethod() As Task + Dim text = " +class Test + sub Method(b as integer) + dim t1 = [||]new with { .a = 1, .b = 2 } + dim t2 = new with { .a = 3, b } + dim t3 = new with { .a = 4 } + dim t4 = new with { .b = 5, .a = 6 } + end sub +end class +" + Dim expected = " +class Test + sub Method(b as integer) + dim t1 = (a:=1, b:=2) + dim t2 = (a:=3, b) + dim t3 = new with { .a = 4 } + dim t4 = new with { .b = 5, .a = 6 } + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function TestFixAllInSingleMethod() As Task + Dim text = " +class Test + sub Method(b as integer) + dim t1 = {|FixAllInDocument:|}new with { .a = 1, .b = 2 } + dim t2 = new with { .a = 3, b } + dim t3 = new with { .a = 4 } + dim t4 = new with { .b = 5, .a = 6 } + end sub +end class +" + Dim expected = " +class Test + sub Method(b as integer) + dim t1 = (a:=1, b:=2) + dim t2 = (a:=3, b) + dim t3 = new with { .a = 4 } + dim t4 = (b:=5, a:=6) + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function TestFixAllAcrossMethods() As Task + Dim text = " +class Test + sub Method() + dim t1 = {|FixAllInDocument:|}new with { .a = 1, .b = 2 } + dim t2 = new with { .a = 3, .b = 4 } + end sub + + sub Method2() + dim t1 = new with { .a = 1, .b = 2 } + dim t2 = new with { .a = 3, .b = 4 } + end sub +end class +" + Dim expected = " +class Test + sub Method() + dim t1 = (a:=1, b:=2) + dim t2 = (a:=3, b:=4) + end sub + + sub Method2() + dim t1 = (a:=1, b:=2) + dim t2 = (a:=3, b:=4) + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function TestFixAllNestedTypes() As Task + Dim text = " +class Test + sub Method() + dim t1 = {|FixAllInDocument:|}new with { .a = 1, .b = new with { .c = 1, .d = 2 } } + end sub +end class +" + Dim expected = " +class Test + sub Method() + dim t1 = (a:=1, b:=(c:=1, d:=2)) + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function ConvertMultipleNestedInstancesInSameMethod() As Task + Dim text = " +class Test + sub Method() + dim t1 = [||]new with { .a = 1, .b = directcast(new with { .a = 1, .b = directcast(nothing, object) }, object) } + end sub +end class +" + Dim expected = " +class Test + sub Method() + dim t1 = (a:=1, b:=directcast((a:=1, b:=directcast(nothing, object)), object)) + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function TestInLambda1() As Task + Dim text = " +Imports System + +class Test + sub Method() + dim t1 = [||]new with { .a = 1, .b = 2 } + dim a as Action = + sub() + dim t2 = new with { .a = 3, .b = 4 } + end sub + end sub +end class +" + Dim expected = " +Imports System + +class Test + sub Method() + dim t1 = (a:=1, b:=2) + dim a as Action = + sub() + dim t2 = (a:=3, b:=4) + end sub + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function TestInLambda2() As Task + Dim text = " +Imports System + +class Test + sub Method() + dim t1 = new with { .a = 1, .b = 2 } + dim a as Action = + sub() + dim t2 = [||]new with { .a = 3, .b = 4 } + end sub + end sub +end class +" + Dim expected = " +Imports System + +class Test + sub Method() + dim t1 = (a:=1, b:=2) + dim a as Action = + sub() + dim t2 = (a:=3, b:=4) + end sub + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + + + Public Async Function TestIncomplete() As Task + Dim text = " +Imports System + +class Test + sub Method() + dim t1 = [||]new with { .a = , .b = } + end sub +end class +" + Dim expected = " +Imports System + +class Test + sub Method() + dim t1 = (a:= , b:= ) + end sub +end class +" + Await TestInRegularAndScriptAsync(text, expected) + End Function + End Class +End Namespace diff --git a/src/Features/CSharp/Portable/ConvertAnonymousTypeToTuple/CSharpConvertAnonymousTypeToTupleCodeFixProvider.cs b/src/Features/CSharp/Portable/ConvertAnonymousTypeToTuple/CSharpConvertAnonymousTypeToTupleCodeFixProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..bd0b78f9e7c92dd94c19bea989adbc249dfb6630 --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertAnonymousTypeToTuple/CSharpConvertAnonymousTypeToTupleCodeFixProvider.cs @@ -0,0 +1,40 @@ +// 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.Composition; +using System.Linq; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.ConvertAnonymousTypeToTuple; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CSharp.ConvertAnonymousTypeToTuple +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(CSharpConvertAnonymousTypeToTupleCodeFixProvider)), Shared] + internal class CSharpConvertAnonymousTypeToTupleCodeFixProvider + : AbstractConvertAnonymousTypeToTupleCodeFixProvider< + ExpressionSyntax, + TupleExpressionSyntax, + AnonymousObjectCreationExpressionSyntax> + { + protected override TupleExpressionSyntax ConvertToTuple(AnonymousObjectCreationExpressionSyntax anonCreation) + => SyntaxFactory.TupleExpression( + SyntaxFactory.Token(SyntaxKind.OpenParenToken).WithTriviaFrom(anonCreation.OpenBraceToken), + ConvertInitializers(anonCreation.Initializers), + SyntaxFactory.Token(SyntaxKind.CloseParenToken).WithTriviaFrom(anonCreation.CloseBraceToken)) + .WithPrependedLeadingTrivia(anonCreation.GetLeadingTrivia()); + + private static SeparatedSyntaxList ConvertInitializers(SeparatedSyntaxList initializers) + => SyntaxFactory.SeparatedList(initializers.Select(ConvertInitializer), initializers.GetSeparators()); + + private static ArgumentSyntax ConvertInitializer(AnonymousObjectMemberDeclaratorSyntax declarator) + => SyntaxFactory.Argument(ConvertName(declarator.NameEquals), default, declarator.Expression) + .WithTriviaFrom(declarator); + + private static NameColonSyntax ConvertName(NameEqualsSyntax nameEquals) + => nameEquals == null + ? null + : SyntaxFactory.NameColon( + nameEquals.Name, + SyntaxFactory.Token(SyntaxKind.ColonToken).WithTriviaFrom(nameEquals.EqualsToken)); + } +} diff --git a/src/Features/CSharp/Portable/ConvertAnonymousTypeToTuple/CSharpConvertAnonymousTypeToTupleDiagnosticAnalyzer.cs b/src/Features/CSharp/Portable/ConvertAnonymousTypeToTuple/CSharpConvertAnonymousTypeToTupleDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000000000000000000000000000000..7b6c870f15197b675f58d418ddb96b97733936e1 --- /dev/null +++ b/src/Features/CSharp/Portable/ConvertAnonymousTypeToTuple/CSharpConvertAnonymousTypeToTupleDiagnosticAnalyzer.cs @@ -0,0 +1,21 @@ +// 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.ConvertAnonymousTypeToTuple; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.CSharp.ConvertAnonymousTypeToTuple +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal class CSharpConvertAnonymousTypeToTupleDiagnosticAnalyzer + : AbstractConvertAnonymousTypeToTupleDiagnosticAnalyzer< + SyntaxKind, + AnonymousObjectCreationExpressionSyntax> + { + protected override SyntaxKind GetAnonymousObjectCreationExpressionSyntaxKind() + => SyntaxKind.AnonymousObjectCreationExpression; + + protected override int GetInitializerCount(AnonymousObjectCreationExpressionSyntax anonymousType) + => anonymousType.Initializers.Count; + } +} diff --git a/src/Features/Core/Portable/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleCodeFixProvider.cs b/src/Features/Core/Portable/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleCodeFixProvider.cs new file mode 100644 index 0000000000000000000000000000000000000000..54d450e5228a983524f659105b6887b8f870a1ea --- /dev/null +++ b/src/Features/Core/Portable/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleCodeFixProvider.cs @@ -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. + +using System; +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Formatting; +using Microsoft.CodeAnalysis.LanguageServices; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.ConvertAnonymousTypeToTuple +{ + internal abstract class AbstractConvertAnonymousTypeToTupleCodeFixProvider< + TExpressionSyntax, + TTupleExpressionSyntax, + TAnonymousObjectCreationExpressionSyntax> + : SyntaxEditorBasedCodeFixProvider + where TExpressionSyntax : SyntaxNode + where TTupleExpressionSyntax : TExpressionSyntax + where TAnonymousObjectCreationExpressionSyntax : TExpressionSyntax + { + protected abstract TTupleExpressionSyntax ConvertToTuple(TAnonymousObjectCreationExpressionSyntax anonCreation); + + public override ImmutableArray FixableDiagnosticIds { get; } = + ImmutableArray.Create(IDEDiagnosticIds.ConvertAnonymousTypeToTupleDiagnosticId); + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + context.RegisterCodeFix( + new MyCodeAction(c => FixAllWithEditorAsync(context.Document, + e => FixInCurrentMember(context.Document, e, context.Diagnostics[0], c), c)), + context.Diagnostics); + + return Task.CompletedTask; + } + + private async Task FixInCurrentMember( + Document document, SyntaxEditor editor, + Diagnostic diagnostic, CancellationToken cancellationToken) + { + // For the standard invocation of the code-fix, we want to fixup all creations of the + // "same" anonymous type within the containing method. We define same-ness as meaning + // "they have the type symbol". This means both have the same member names, in the same + // order, with the same member types. We fix all these up in the method because the + // user may be creating several instances of this anonymous type in that method and + // then combining them in interesting ways (i.e. checking them for equality, using them + // in collections, etc.). The language guarantees within a method boundary that these + // will be the same type and can be used together in this fashion. + + var creationNode = TryGetCreationNode(diagnostic, cancellationToken); + if (creationNode == null) + { + Debug.Fail("We should always be able to find the anonymous creation we were invoked from."); + return; + } + + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var anonymousType = semanticModel.GetTypeInfo(creationNode, cancellationToken).Type; + if (anonymousType == null) + { + Debug.Fail("We should always be able to get an anonymous type for any anonymous creation node."); + return; + } + + var syntaxFacts = document.GetLanguageService(); + var containingMember = creationNode.FirstAncestorOrSelf(syntaxFacts.IsMethodLevelMember) ?? creationNode; + + var childCreationNodes = containingMember.DescendantNodesAndSelf() + .OfType(); + foreach (var childCreation in childCreationNodes) + { + var childType = semanticModel.GetTypeInfo(childCreation, cancellationToken).Type; + if (childType == null) + { + Debug.Fail("We should always be able to get an anonymous type for any anonymous creation node."); + continue; + } + + if (anonymousType.Equals(childType)) + { + ReplaceWithTuple(editor, childCreation); + } + } + } + + protected override Task FixAllAsync( + Document document, ImmutableArray diagnostics, + SyntaxEditor editor, CancellationToken cancellationToken) + { + foreach (var diagnostic in diagnostics) + { + // During a fix-all we don't need to bother with the work to go to the containing + // method. Because it's a fix-all, by definition, we'll always be processing all + // the anon-creation nodes for any given method that is within our scope. + var node = TryGetCreationNode(diagnostic, cancellationToken); + if (node == null) + { + Debug.Fail("We should always be able to find the anonymous creation we were invoked from."); + continue; + } + + ReplaceWithTuple(editor, node); + } + + return Task.CompletedTask; + } + + private void ReplaceWithTuple(SyntaxEditor editor, TAnonymousObjectCreationExpressionSyntax node) + => editor.ReplaceNode( + node, (current, _) => + { + // Use the callback form as anonymous types may be nested, and we want to + // properly replace them even in that case. + var anonCreation = current as TAnonymousObjectCreationExpressionSyntax; + if (anonCreation == null) + { + return current; + } + + return ConvertToTuple(anonCreation).WithAdditionalAnnotations(Formatter.Annotation); + }); + + private static TAnonymousObjectCreationExpressionSyntax TryGetCreationNode(Diagnostic diagnostic, CancellationToken cancellationToken) + => diagnostic.Location.FindToken(cancellationToken).Parent as TAnonymousObjectCreationExpressionSyntax; + + private class MyCodeAction : CodeAction.DocumentChangeAction + { + public MyCodeAction(Func> createChangedDocument) + : base(FeaturesResources.Convert_to_tuple, createChangedDocument, FeaturesResources.Convert_to_tuple) + { + } + } + } +} diff --git a/src/Features/Core/Portable/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleDiagnosticAnalyzer.cs b/src/Features/Core/Portable/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000000000000000000000000000000..34697e84629056e5e87a56cf0444ce3657ed6845 --- /dev/null +++ b/src/Features/Core/Portable/ConvertAnonymousTypeToTuple/AbstractConvertAnonymousTypeToTupleDiagnosticAnalyzer.cs @@ -0,0 +1,53 @@ +// 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 Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.CodeAnalysis.ConvertAnonymousTypeToTuple +{ + internal abstract class AbstractConvertAnonymousTypeToTupleDiagnosticAnalyzer< + TSyntaxKind, + TAnonymousObjectCreationExpressionSyntax> + : AbstractCodeStyleDiagnosticAnalyzer + where TSyntaxKind : struct + where TAnonymousObjectCreationExpressionSyntax : SyntaxNode + { + protected AbstractConvertAnonymousTypeToTupleDiagnosticAnalyzer() + : base(IDEDiagnosticIds.ConvertAnonymousTypeToTupleDiagnosticId, + new LocalizableResourceString(nameof(FeaturesResources.Convert_to_tuple), FeaturesResources.ResourceManager, typeof(FeaturesResources)), + new LocalizableResourceString(nameof(FeaturesResources.Convert_to_tuple), FeaturesResources.ResourceManager, typeof(FeaturesResources))) + { + } + + protected abstract TSyntaxKind GetAnonymousObjectCreationExpressionSyntaxKind(); + protected abstract int GetInitializerCount(TAnonymousObjectCreationExpressionSyntax anonymousType); + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() + => DiagnosticAnalyzerCategory.SyntaxAnalysis; + + public override bool OpenFileOnly(Workspace workspace) + => false; + + protected override void InitializeWorker(AnalysisContext context) + => context.RegisterSyntaxNodeAction( + AnalyzeSyntax, + GetAnonymousObjectCreationExpressionSyntaxKind()); + + // Analysis is trivial. All anonymous types with more than two fields are marked as being + // convertible to a tuple. + private void AnalyzeSyntax(SyntaxNodeAnalysisContext context) + { + var anonymousType = (TAnonymousObjectCreationExpressionSyntax)context.Node; + if (GetInitializerCount(anonymousType) < 2) + { + return; + } + + context.ReportDiagnostic( + DiagnosticHelper.Create( + Descriptor, context.Node.GetFirstToken().GetLocation(), ReportDiagnostic.Hidden, + additionalLocations: null, properties: null)); + } + } +} diff --git a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs index 68df2773a9d6bacf97a3b1f5bd7f603c3ae1da64..f67839646545c2c54435d2ef3372138ad367a616 100644 --- a/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs +++ b/src/Features/Core/Portable/Diagnostics/Analyzers/IDEDiagnosticIds.cs @@ -77,6 +77,7 @@ internal static class IDEDiagnosticIds public const string PreferBuiltInOrFrameworkTypeDiagnosticId = "IDE0049"; + public const string ConvertAnonymousTypeToTupleDiagnosticId = "IDE0050"; // Analyzer error Ids public const string AnalyzerChangedId = "IDE1001"; diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs index 520f316e7f84050918a03e0502f8213bd2e07559..8df1e78a6ce2da8a35748c1b8307dc6ead41c236 100644 --- a/src/Features/Core/Portable/FeaturesResources.Designer.cs +++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs @@ -977,6 +977,15 @@ internal class FeaturesResources { return ResourceManager.GetString("Convert_to_struct", resourceCulture); } } + + /// + /// Looks up a localized string similar to Convert to tuple. + /// + internal static string Convert_to_tuple { + get { + return ResourceManager.GetString("Convert_to_tuple", resourceCulture); + } + } /// /// Looks up a localized string similar to Could not extract interface: The selection is not inside a class/interface/struct.. diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index c043a9780ebe154cef867027b4c8690a4f6b61cd..05c2d86b854c1e8be332c4ac3d1082e3d0150c9b 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -1376,6 +1376,9 @@ This version used in: {2} Convert to conditional expression + + Convert to tuple + Convert to class diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf index 9d1bfe64272a80934c74562545d9cc898f2db972..05262fd44b17b1093e72107f8b5f98925dd63451 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.cs.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf index b348f3024b21936c618935034671e0566d52c43b..25cf34909244a5bc35b99fe0b51f37371dc76894 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.de.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf index 1d5935fc7b4c14ab0c6385f10cc57c29e0e82a28..6630976d62573d2e6dae47ba8d182538a6b64644 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.es.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf index 43875d81481e8e270c12854d6915fdefaac86571..62f39bed67da8b67f6d669ee573e605c12aeaea3 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.fr.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf index d27fed2c03f14f5ef402ab059580c106732edb72..3e040056e31c62fde2027c626e5a80378b829ea6 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.it.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf index 7271dcd7aa28c7b58e8f5c397e922e565b7c774a..dc0ca5987119684832956a8cdf6e72a22fa38004 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ja.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf index 65fa3053310743023acd65eaa54107adb633e5cc..a722573369444c82c5226af57d262ecd2b5a3182 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ko.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf index 7dd510eeb5f7648ec7e618f1fcbb1999a41ef131..bc655fa8abe914c256122ec2ad3f0d351dccecf4 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pl.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf index 8554a4e5beae62595aed5bd2d5107f16bc253b69..46871095b1efa1259b39948bd63b1eef8534cbd6 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.pt-BR.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf index 67a257a495bbd97e1533f37d58f0469e980da9d6..e45c189c2b331fbc7ec45c05b39a816866d22241 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.ru.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf index 13c46e9bd362ba8f493b002926451d5b5416363f..16c51c9e2fdce2d50a8275d648ac200fe4530ba0 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.tr.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf index 6a536bb3e24f7b9a5e6a0985edfebc794ed9199c..04a1a2fef1546b67efe1caaa619c7b1771e1f552 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hans.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf index b32c63c418551a7243c87b31a54fb6533e9782d0..e041f12eff2b1db7d8b00d200f1fd8b770f31dec 100644 --- a/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf +++ b/src/Features/Core/Portable/xlf/FeaturesResources.zh-Hant.xlf @@ -37,6 +37,11 @@ Convert to struct + + Convert to tuple + Convert to tuple + + Fix typo '{0}' Fix typo '{0}' diff --git a/src/Features/VisualBasic/Portable/ConvertAnonymousTypeToTuple/VisualBasicConvertAnonymousTypeToTupleCodeFixProvider.vb b/src/Features/VisualBasic/Portable/ConvertAnonymousTypeToTuple/VisualBasicConvertAnonymousTypeToTupleCodeFixProvider.vb new file mode 100644 index 0000000000000000000000000000000000000000..60c3e0472fc4c76fe88dc6e36d16dfbe6f52f4b4 --- /dev/null +++ b/src/Features/VisualBasic/Portable/ConvertAnonymousTypeToTuple/VisualBasicConvertAnonymousTypeToTupleCodeFixProvider.vb @@ -0,0 +1,50 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports System.Composition +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.ConvertAnonymousTypeToTuple +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertAnonymousTypeToTuple + + Friend Class VisualBasicConvertAnonymousTypeToTupleCodeFixProvider + Inherits AbstractConvertAnonymousTypeToTupleCodeFixProvider(Of + ExpressionSyntax, + TupleExpressionSyntax, + AnonymousObjectCreationExpressionSyntax) + + Protected Overrides Function ConvertToTuple(anonCreation As AnonymousObjectCreationExpressionSyntax) As TupleExpressionSyntax + Return SyntaxFactory.TupleExpression( + SyntaxFactory.Token(SyntaxKind.OpenParenToken).WithTriviaFrom(anonCreation.Initializer.OpenBraceToken), + ConvertInitializers(anonCreation.Initializer.Initializers), + SyntaxFactory.Token(SyntaxKind.CloseParenToken).WithTriviaFrom(anonCreation.Initializer.CloseBraceToken)). + WithPrependedLeadingTrivia(anonCreation.GetLeadingTrivia()) + End Function + + Private Function ConvertInitializers(initializers As SeparatedSyntaxList(Of FieldInitializerSyntax)) As SeparatedSyntaxList(Of SimpleArgumentSyntax) + Return SyntaxFactory.SeparatedList(initializers.Select(AddressOf ConvertInitializer), initializers.GetSeparators()) + End Function + + Private Function ConvertInitializer(field As FieldInitializerSyntax) As SimpleArgumentSyntax + Return SyntaxFactory.SimpleArgument( + GetNameEquals(field), + GetExpression(field)).WithTriviaFrom(field) + End Function + + Private Function GetNameEquals(field As FieldInitializerSyntax) As NameColonEqualsSyntax + Dim namedField = TryCast(field, NamedFieldInitializerSyntax) + If namedField Is Nothing Then + Return Nothing + End If + + Return SyntaxFactory.NameColonEquals( + namedField.Name, + SyntaxFactory.Token(SyntaxKind.ColonEqualsToken).WithTriviaFrom(namedField.EqualsToken)) + End Function + + Private Function GetExpression(field As FieldInitializerSyntax) As ExpressionSyntax + Return If(TryCast(field, InferredFieldInitializerSyntax)?.Expression, + TryCast(field, NamedFieldInitializerSyntax)?.Expression) + End Function + End Class +End Namespace diff --git a/src/Features/VisualBasic/Portable/ConvertAnonymousTypeToTuple/VisualBasicConvertAnonymousTypeToTupleDiagnosticAnalyzer.vb b/src/Features/VisualBasic/Portable/ConvertAnonymousTypeToTuple/VisualBasicConvertAnonymousTypeToTupleDiagnosticAnalyzer.vb new file mode 100644 index 0000000000000000000000000000000000000000..560eea8a5fa428b72df0a570ffd83952729e2582 --- /dev/null +++ b/src/Features/VisualBasic/Portable/ConvertAnonymousTypeToTuple/VisualBasicConvertAnonymousTypeToTupleDiagnosticAnalyzer.vb @@ -0,0 +1,21 @@ +' 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.ConvertAnonymousTypeToTuple +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.ConvertAnonymousTypeToTuple + + Friend Class VisualBasicConvertAnonymousTypeToTupleDiagnosticAnalyzer + Inherits AbstractConvertAnonymousTypeToTupleDiagnosticAnalyzer(Of + SyntaxKind, AnonymousObjectCreationExpressionSyntax) + + Protected Overrides Function GetAnonymousObjectCreationExpressionSyntaxKind() As SyntaxKind + Return SyntaxKind.AnonymousObjectCreationExpression + End Function + + Protected Overrides Function GetInitializerCount(anonymousType As AnonymousObjectCreationExpressionSyntax) As Integer + Return anonymousType.Initializer.Initializers.Count + End Function + End Class +End Namespace diff --git a/src/Test/Utilities/Portable/Traits/Traits.cs b/src/Test/Utilities/Portable/Traits/Traits.cs index d27a1090bc0da3095c2d401ad84fbe97068ded05..78bd6dbf48ca88ddfcda183d75087bc892ebc8d3 100644 --- a/src/Test/Utilities/Portable/Traits/Traits.cs +++ b/src/Test/Utilities/Portable/Traits/Traits.cs @@ -49,6 +49,7 @@ public static class Features public const string CodeActionsChangeToIEnumerable = "CodeActions.ChangeToIEnumerable"; public const string CodeActionsChangeToYield = "CodeActions.ChangeToYield"; public const string CodeActionsConvertAnonymousTypeToClass = "CodeActions.ConvertAnonymousTypeToClass"; + public const string CodeActionsConvertAnonymousTypeToTuple = "CodeActions.ConvertAnonymousTypeToTuple"; public const string CodeActionsConvertNumericLiteral = "CodeActions.ConvertNumericLiteral"; public const string CodeActionsConvertToInterpolatedString = "CodeActions.ConvertToInterpolatedString"; public const string CodeActionsConvertToIterator = "CodeActions.ConvertToIterator"; diff --git a/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj b/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj index 43f901ab962a0cc6cf8c4aafe4c7084fa6212759..a39e901fd81a9e05296ef6dbbf1a81471d4dd78a 100644 --- a/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj +++ b/src/VisualStudio/Core/Def/ServicesVisualStudio.csproj @@ -79,13 +79,11 @@ - - diff --git a/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj b/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj index e086436b3b99c86b7f950d49050e634bb53f1a5a..a24aa74c9abcb30ea56aa74ce4d479859afa08c9 100644 --- a/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj +++ b/src/VisualStudio/Core/Impl/ServicesVisualStudioImpl.csproj @@ -4,12 +4,10 @@ - - diff --git a/src/VisualStudio/Core/Test/Snippets/SnippetTestState.vb b/src/VisualStudio/Core/Test/Snippets/SnippetTestState.vb index 28095a8cc077cd13d26e56d2e9ef1b03be404de7..726c7776ca9ba077bf057e8bb47cc90f2b5618f1 100644 --- a/src/VisualStudio/Core/Test/Snippets/SnippetTestState.vb +++ b/src/VisualStudio/Core/Test/Snippets/SnippetTestState.vb @@ -14,6 +14,7 @@ Imports Microsoft.CodeAnalysis.Test.Utilities Imports Microsoft.VisualStudio.Commanding Imports Microsoft.VisualStudio.Composition Imports Microsoft.VisualStudio.Editor +Imports Microsoft.VisualStudio.Language.Intellisense Imports Microsoft.VisualStudio.LanguageServices.Implementation.Snippets Imports Microsoft.VisualStudio.Shell Imports Microsoft.VisualStudio.Text @@ -29,7 +30,9 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.Snippets Inherits AbstractCommandHandlerTestState Public Sub New(workspaceElement As XElement, languageName As String, startActiveSession As Boolean, extraParts As IEnumerable(Of Type), Optional workspaceKind As String = Nothing) - MyBase.New(workspaceElement, extraParts:=CreatePartCatalog(extraParts), workspaceKind:=workspaceKind) + ' Remove the default completion presenters to prevent them from conflicting with the test one + ' that we are adding. + MyBase.New(workspaceElement, extraParts:=CreatePartCatalog(extraParts), workspaceKind:=workspaceKind, excludedTypes:={GetType(IIntelliSensePresenter(Of ICompletionPresenterSession, ICompletionSession))}) Workspace.Options = Workspace.Options.WithChangedOption(InternalFeatureOnOffOptions.Snippets, True)