From c49aaf365f0541527cf7a7c714571046cc6405a8 Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Tue, 6 Dec 2016 01:05:27 +0330 Subject: [PATCH] Add a code fix to add argument names --- .../CSharpEditorServicesTest.csproj | 1 + .../UseNamedArgumentsTests.cs | 205 ++++++++++++++++++ src/EditorFeatures/TestUtilities/Traits.cs | 1 + .../BasicEditorServicesTest.vbproj | 1 + .../UseNamedArgumentsTests.vb | 194 +++++++++++++++++ .../CSharp/Portable/CSharpFeatures.csproj | 1 + ...seNamedArgumentsCodeRefactoringProvider.cs | 120 ++++++++++ ...seNamedArgumentsCodeRefactoringProvider.cs | 105 +++++++++ src/Features/Core/Portable/Features.csproj | 3 +- .../Portable/FeaturesResources.Designer.cs | 9 + .../Core/Portable/FeaturesResources.resx | 3 + .../VisualBasic/Portable/BasicFeatures.vbproj | 3 +- ...seNamedArgumentsCodeRefactoringProvider.vb | 69 ++++++ 13 files changed, 713 insertions(+), 2 deletions(-) create mode 100644 src/EditorFeatures/CSharpTest/CodeActions/UseNamedArguments/UseNamedArgumentsTests.cs create mode 100644 src/EditorFeatures/VisualBasicTest/CodeActions/UseNamedArguments/UseNamedArgumentsTests.vb create mode 100644 src/Features/CSharp/Portable/CodeRefactorings/UseNamedArguments/CSharpUseNamedArgumentsCodeRefactoringProvider.cs create mode 100644 src/Features/Core/Portable/CodeRefactorings/UseNamedArguments/AbstractUseNamedArgumentsCodeRefactoringProvider.cs create mode 100644 src/Features/VisualBasic/Portable/CodeRefactorings/UseNamedArguments/VisualBasicUseNamedArgumentsCodeRefactoringProvider.vb diff --git a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj index ca8145196a7..f01ad4d493e 100644 --- a/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj +++ b/src/EditorFeatures/CSharpTest/CSharpEditorServicesTest.csproj @@ -187,6 +187,7 @@ + diff --git a/src/EditorFeatures/CSharpTest/CodeActions/UseNamedArguments/UseNamedArgumentsTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/UseNamedArguments/UseNamedArgumentsTests.cs new file mode 100644 index 00000000000..ac9a610e68d --- /dev/null +++ b/src/EditorFeatures/CSharpTest/CodeActions/UseNamedArguments/UseNamedArgumentsTests.cs @@ -0,0 +1,205 @@ +// 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.CodeRefactorings; +using Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseNamedArguments; +using Roslyn.Test.Utilities; +using Xunit; + +namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings.UseNamedArguments +{ + public class UseNamedArgumentsTests : AbstractCSharpCodeActionTest + { + protected override CodeRefactoringProvider CreateCodeRefactoringProvider(Workspace workspace) + { + return new CSharpUseNamedArgumentsCodeRefactoringProvider(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestFirstArgument() + { + await TestAsync( +@"class C { void M(int arg1, int arg2) => M([||]1, 2); }", +@"class C { void M(int arg1, int arg2) => M(arg1: 1, arg2: 2); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestNonFirstArgument() + { + await TestAsync( +@"class C { void M(int arg1, int arg2) => M(1, [||]2); }", +@"class C { void M(int arg1, int arg2) => M(1, arg2: 2); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestDelegate() + { + await TestAsync( +@"class C { void M(System.Action f) => f([||]1); }", +@"class C { void M(System.Action f) => f(obj: 1); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestConditionalMethod() + { + await TestAsync( +@"class C { void M(int arg1, int arg2) => this?.M([||]1, 2); }", +@"class C { void M(int arg1, int arg2) => this?.M(arg1: 1, arg2: 2); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestConditionalIndexer() + { + await TestAsync( +@"class C { int? this[int arg1, int arg2] => this?[[||]1, 2]; }", +@"class C { int? this[int arg1, int arg2] => this?[arg1: 1, arg2: 2]; }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestThisConstructorInitializer() + { + await TestAsync( +@"class C { C(int arg1, int arg2) {} C() : this([||]1, 2) {} }", +@"class C { C(int arg1, int arg2) {} C() : this(arg1: 1, arg2: 2) {} }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestBaseConstructorInitializer() + { + await TestAsync( +@"class C { public C(int arg1, int arg2) {} } class D : C { D() : base([||]1, 2) {} }", +@"class C { public C(int arg1, int arg2) {} } class D : C { D() : base(arg1: 1, arg2: 2) {} }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestConstructor() + { + await TestAsync( +@"class C { C(int arg1, int arg2) { new C([||]1, 2); } }", +@"class C { C(int arg1, int arg2) { new C(arg1: 1, arg2: 2); } }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestIndexer() + { + await TestAsync( +@"class C { char M(string arg1) => arg1[[||]0]; }", +@"class C { char M(string arg1) => arg1[index: 0]; }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestMissingOnArrayIndexer() + { + await TestMissingAsync( +@"class C { int M(int[] arg1) => arg1[[||]0]; }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestMissingOnConditionalArrayIndexer() + { + await TestMissingAsync( +@"class C { int? M(int[] arg1) => arg1?[[||]0]; }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestMissingOnEmptyArgumentList() + { + await TestMissingAsync( +@"class C { void M() => M([||]); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestMissingOnExistingArgumentName() + { + await TestMissingAsync( +@"class C { void M(int arg) => M([||]arg: 1); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestEmptyParams() + { + await TestAsync( +@"class C { void M(int arg1, params int[] arg2) => M([||]1); }", +@"class C { void M(int arg1, params int[] arg2) => M(arg1: 1); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestSingleParams() + { + await TestAsync( +@"class C { void M(int arg1, params int[] arg2) => M([||]1, 2); }", +@"class C { void M(int arg1, params int[] arg2) => M(arg1: 1, arg2: 2); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestNamedParams() + { + await TestAsync( +@"class C { void M(int arg1, params int[] arg2) => M([||]1, arg2: new int[0]); }", +@"class C { void M(int arg1, params int[] arg2) => M(arg1: 1, arg2: new int[0]); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestExistingArgumentNames() + { + await TestAsync( +@"class C { void M(int arg1, int arg2) => M([||]1, arg2: 2); }", +@"class C { void M(int arg1, int arg2) => M(arg1: 1, arg2: 2); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestExistingUnorderedArgumentNames() + { + await TestAsync( +@"class C { void M(int arg1, int arg2, int arg3) => M([||]1, arg3: 3, arg2: 2); }", +@"class C { void M(int arg1, int arg2, int arg3) => M(arg1: 1, arg3: 3, arg2: 2); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestPreserveTrivia() + { + await TestAsync( +@"class C { void M(int arg1, ref int arg2) => M( + + [||]1, + + ref arg1 + + ); }", +@"class C { void M(int arg1, ref int arg2) => M( + + arg1: 1, + + arg2: ref arg1 + + ); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestMissingOnNameOf() + { + await TestMissingAsync( +@"class C { string M() => nameof([||]M); }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestAttirbute() + { + await TestAsync( +@"[C([||]1, 2)] +class C : System.Attribute { public C(int arg1, int arg2) {} }", +@"[C(arg1: 1, arg2: 2)] +class C : System.Attribute { public C(int arg1, int arg2) {} }"); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNamedArguments)] + public async Task TestAttirbuteWithNamedProperties() + { + await TestAsync( +@"[C([||]1, P = 2)] +class C : System.Attribute { public C(int arg1) {} public int P { get; set; } }", +@"[C(arg1: 1, P = 2)] +class C : System.Attribute { public C(int arg1) {} public int P { get; set; } }"); + } + } +} \ No newline at end of file diff --git a/src/EditorFeatures/TestUtilities/Traits.cs b/src/EditorFeatures/TestUtilities/Traits.cs index 3cb635c4396..c49b843f542 100644 --- a/src/EditorFeatures/TestUtilities/Traits.cs +++ b/src/EditorFeatures/TestUtilities/Traits.cs @@ -91,6 +91,7 @@ public static class Features public const string CodeActionsUseExplicitTupleName = "CodeActions.UseExplicitTupleName"; public const string CodeActionsUseFrameworkType = "CodeActions.UseFrameworkType"; public const string CodeActionsUseNullPropagation = "CodeActions.UseNullPropagation"; + public const string CodeActionsUseNamedArguments = "CodeActions.UseNamedArguments"; public const string CodeActionsUseObjectInitializer = "CodeActions.UseObjectInitializer"; public const string CodeActionsUseThrowExpression = "CodeActions.UseThrowExpression"; public const string CodeGeneration = nameof(CodeGeneration); diff --git a/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj b/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj index 7876fd9818a..6e9e7311f4e 100644 --- a/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj +++ b/src/EditorFeatures/VisualBasicTest/BasicEditorServicesTest.vbproj @@ -184,6 +184,7 @@ + diff --git a/src/EditorFeatures/VisualBasicTest/CodeActions/UseNamedArguments/UseNamedArgumentsTests.vb b/src/EditorFeatures/VisualBasicTest/CodeActions/UseNamedArguments/UseNamedArgumentsTests.vb new file mode 100644 index 00000000000..ca6e1044e29 --- /dev/null +++ b/src/EditorFeatures/VisualBasicTest/CodeActions/UseNamedArguments/UseNamedArgumentsTests.vb @@ -0,0 +1,194 @@ +' 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.Threading.Tasks +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings +Imports Microsoft.CodeAnalysis.ReplaceMethodWithProperty +Imports Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.UseNamedArguments +Imports Roslyn.Test.Utilities +Imports Xunit + +Namespace Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeActions.UseNamedArguments + Public Class UseNamedArgumentsTests + Inherits AbstractVisualBasicCodeActionTest + + Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace) As CodeRefactoringProvider + Return New VisualBasicUseNamedArgumentsCodeRefactoringProvider() + End Function + + + Public Async Function TestFirstArgument() As Task + Await TestAsync( +NewLines("Class C \n Sub M(arg1 As Integer, arg2 As Integer) \n M([||]1, 2) \n End Sub \n End Class"), +NewLines("Class C \n Sub M(arg1 As Integer, arg2 As Integer) \n M(arg1:=1, arg2:=2) \n End Sub \n End Class")) + End Function + + + Public Async Function TestNonFirstArgument() As Task + Await TestAsync( +NewLines("Class C \n Sub M(arg1 As Integer, arg2 As Integer) \n M(1, [||]2) \n End Sub \n End Class"), +NewLines("Class C \n Sub M(arg1 As Integer, arg2 As Integer) \n M(1, arg2:=2) \n End Sub \n End Class")) + End Function + + + Public Async Function TestDelegate() As Task + Await TestAsync( +"Class C + Sub M() + Dim f = Sub (arg) + End Sub + f([||]1) + End Sub +End Class", +"Class C + Sub M() + Dim f = Sub (arg) + End Sub + f(arg:=1) + End Sub +End Class") + End Function + + + Public Async Function TestConditionalDelegate() As Task + Await TestAsync( +"Class C + Sub M() + Dim f = Sub (arg) + End Sub + f?([||]1) + End Sub +End Class", +"Class C + Sub M() + Dim f = Sub (arg) + End Sub + f?(arg:=1) + End Sub +End Class") + End Function + + + Public Async Function TestConditionalMethod() As Task + Await TestAsync( +NewLines("Class C \n Sub M(arg1 as Integer, arg2 as Integer) \n Me?.M([||]1, 2) \n End Sub \n End Class"), +NewLines("Class C \n Sub M(arg1 as Integer, arg2 as Integer) \n Me?.M(arg1:=1, arg2:=2) \n End Sub \n End Class")) + End Function + + + Public Async Function TestConditionalIndexer() As Task + Await TestAsync( +"Class C + Sub M(arg1 as String) + Dim r = arg1?([||]0) + End Sub +End Class", +"Class C + Sub M(arg1 as String) + Dim r = arg1?(index:=0) + End Sub +End Class") + End Function + + + Public Async Function TestConstructor() As Task + Await TestAsync( +NewLines("Class C \n Sub New(arg1 As Integer, arg2 As Integer) \n Dim c = New C([||]1, 2) \n End Sub \n End Class"), +NewLines("Class C \n Sub New(arg1 As Integer, arg2 As Integer) \n Dim c = New C(arg1:=1, arg2:=2) \n End Sub \n End Class")) + End Function + + + Public Async Function TestIndexer() As Task + Await TestAsync( +"Class C + Function M(arg1 as String) As Char + Return arg1([||]0) + End Function +End Class", +"Class C + Function M(arg1 as String) As Char + Return arg1(index:=0) + End Function +End Class") + End Function + + + Public Async Function TestMissingOnArrayIndexer() As Task + Await TestMissingAsync( +"Class C + Function M(arg1 as Integer()) As Integer + Return arg1([||]0) + End Function +End Class") + End Function + + + Public Async Function TestMissingOnConditionalArrayIndexer() As Task + Await TestMissingAsync( +"Class C + Function M(arg1 as Integer()) As Integer + Return arg1?([||]0) + End Function +End Class") + End Function + + + Public Async Function TestMissingOnEmptyArgumentList() As Task + Await TestMissingAsync( +NewLines("Class C \n Sub M() \n M([||]) \n End Sub \n End Class")) + End Function + + + Public Async Function TestMissingOnNamedArgument() As Task + Await TestMissingAsync( +NewLines("Class C \n Sub M(arg as Integer) \n M([||]arg:=1) \n End Sub \n End Class")) + End Function + + + Public Async Function TestMissingOnParamArray() As Task + Await TestMissingAsync( +NewLines("Class C \n Sub M(ParamArray arg1 As Integer()) \n M([||]1) \n End Sub \n End Class")) + End Function + + + Public Async Function TestEmptyParamArray() As Task + Await TestAsync( +NewLines("Class C \n Sub M(arg1 As Integer, ParamArray arg2 As Integer()) \n M([||]1) \n End Sub \n End Class"), +NewLines("Class C \n Sub M(arg1 As Integer, ParamArray arg2 As Integer()) \n M(arg1:=1) \n End Sub \n End Class")) + End Function + + + Public Async Function TestOmittedArguments() As Task + Await TestAsync( +NewLines("Class C \n Sub M(arg1 As Integer, optional arg2 As Integer=1, optional arg3 as Integer=1) \n M([||]1,,3) \n End Sub \n End Class"), +NewLines("Class C \n Sub M(arg1 As Integer, optional arg2 As Integer=1, optional arg3 as Integer=1) \n M(arg1:=1, arg3:=3) \n End Sub \n End Class")) + End Function + + + Public Async Function TestMissingOnOmittedArgument() As Task + Await TestMissingAsync( +NewLines("Class C \n Sub M(optional arg1 As Integer=1, optional arg2 As Integer=1) \n M([||], arg2:=2) \n End Sub \n End Class")) + End Function + + + Public Async Function TestMissingOnNameOf() As Task + Await TestMissingAsync( +"Class C + Function M() As String + Return NameOf([||]M) + End Function +End Class") + End Function + + + Public Async Function TestMissingOnAttribute() As Task + Await TestMissingAsync( +" +Class C + Inherits System.Attribute + Public Sub New(arg As Integer) + End Sub +End Class") + End Function + End Class +End Namespace diff --git a/src/Features/CSharp/Portable/CSharpFeatures.csproj b/src/Features/CSharp/Portable/CSharpFeatures.csproj index eaff8179938..cfdc97b02cb 100644 --- a/src/Features/CSharp/Portable/CSharpFeatures.csproj +++ b/src/Features/CSharp/Portable/CSharpFeatures.csproj @@ -58,6 +58,7 @@ InternalUtilities\LambdaUtilities.cs + diff --git a/src/Features/CSharp/Portable/CodeRefactorings/UseNamedArguments/CSharpUseNamedArgumentsCodeRefactoringProvider.cs b/src/Features/CSharp/Portable/CodeRefactorings/UseNamedArguments/CSharpUseNamedArgumentsCodeRefactoringProvider.cs new file mode 100644 index 00000000000..ddd4cc3aecc --- /dev/null +++ b/src/Features/CSharp/Portable/CodeRefactorings/UseNamedArguments/CSharpUseNamedArgumentsCodeRefactoringProvider.cs @@ -0,0 +1,120 @@ +// 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.Composition; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis.CodeRefactorings; +using Microsoft.CodeAnalysis.CodeRefactorings.UseNamedArguments; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.UseNamedArguments +{ + [ExtensionOrder(After = PredefinedCodeRefactoringProviderNames.IntroduceVariable)] + [ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(CSharpUseNamedArgumentsCodeRefactoringProvider)), Shared] + internal class CSharpUseNamedArgumentsCodeRefactoringProvider : AbstractUseNamedArgumentsCodeRefactoringProvider + { + protected override SyntaxNode GetOrSynthesizeNamedArguments(ImmutableArray parameters, SyntaxNode argumentList, int index) + { + switch (argumentList.Kind()) + { + case SyntaxKind.ArgumentList: + { + var node = (ArgumentListSyntax)argumentList; + var namedArguments = node.Arguments + .Select((argument, i) => i < index || argument.NameColon != null + ? argument : argument.WithNameColon(SyntaxFactory.NameColon(parameters[i].Name) + .WithTriviaFrom(argument))); + + return node.WithArguments(SyntaxFactory.SeparatedList(namedArguments)); + } + + case SyntaxKind.BracketedArgumentList: + { + var node = (BracketedArgumentListSyntax)argumentList; + var namedArguments = node.Arguments + .Select((argument, i) => i < index || argument.NameColon != null + ? argument : argument.WithNameColon(SyntaxFactory.NameColon(parameters[i].Name) + .WithTriviaFrom(argument))); + + return node.WithArguments(SyntaxFactory.SeparatedList(namedArguments)); + } + + case SyntaxKind.AttributeArgumentList: + { + var node = (AttributeArgumentListSyntax)argumentList; + var namedArguments = node.Arguments + .Select((argument, i) => i < index || argument.NameColon != null || argument.NameEquals != null + ? argument : argument.WithNameColon(SyntaxFactory.NameColon(parameters[i].Name) + .WithTriviaFrom(argument))); + + return node.WithArguments(SyntaxFactory.SeparatedList(namedArguments)); + } + + default: + return null; + } + } + + protected override SyntaxNode GetReceiver(SyntaxNode argument) + { + switch (argument.Parent.Kind()) + { + case SyntaxKind.ArgumentList: + case SyntaxKind.BracketedArgumentList: + case SyntaxKind.AttributeArgumentList: + return argument.Parent.Parent; + + default: + return null; + } + } + + protected override ValueTuple GetArgumentListIndexAndCount(SyntaxNode node) + { + switch (node.Parent.Kind()) + { + case SyntaxKind.ArgumentList: + case SyntaxKind.BracketedArgumentList: + var argumentListSyntax = (BaseArgumentListSyntax)node.Parent; + return ValueTuple.Create(argumentListSyntax.Arguments.IndexOf((ArgumentSyntax)node), argumentListSyntax.Arguments.Count); + + case SyntaxKind.AttributeArgumentList: + var attributeArgumentSyntax = (AttributeArgumentListSyntax)node.Parent; + return ValueTuple.Create(attributeArgumentSyntax.Arguments.IndexOf((AttributeArgumentSyntax)node), attributeArgumentSyntax.Arguments.Count); + + default: + return default(ValueTuple); + } + } + + protected override bool IsCandidate(SyntaxNode node) + { + return node.IsKind(SyntaxKind.Argument) + || node.IsKind(SyntaxKind.AttributeArgument); + } + + protected override bool IsPositionalArgument(SyntaxNode node) + { + switch (node.Kind()) + { + case SyntaxKind.Argument: + var argument = (ArgumentSyntax)node; + return argument.NameColon == null; + + case SyntaxKind.AttributeArgument: + var attributeArgument = (AttributeArgumentSyntax)node; + return attributeArgument.NameColon == null + && attributeArgument.NameEquals == null; + + default: + return false; + } + } + + protected override bool IsLegalToAddNamedArguments(ImmutableArray parameters, int argumentCount) + { + return !parameters.LastOrDefault().IsParams || parameters.Length >= argumentCount; + } + } +} diff --git a/src/Features/Core/Portable/CodeRefactorings/UseNamedArguments/AbstractUseNamedArgumentsCodeRefactoringProvider.cs b/src/Features/Core/Portable/CodeRefactorings/UseNamedArguments/AbstractUseNamedArgumentsCodeRefactoringProvider.cs new file mode 100644 index 00000000000..ed4f4f91f8c --- /dev/null +++ b/src/Features/Core/Portable/CodeRefactorings/UseNamedArguments/AbstractUseNamedArgumentsCodeRefactoringProvider.cs @@ -0,0 +1,105 @@ +// 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.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.Shared.Extensions; + +namespace Microsoft.CodeAnalysis.CodeRefactorings.UseNamedArguments +{ + internal abstract class AbstractUseNamedArgumentsCodeRefactoringProvider : CodeRefactoringProvider + { + public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) + { + var document = context.Document; + if (document.Project.Solution.Workspace.Kind == WorkspaceKind.MiscellaneousFiles) + { + return; + } + + var cancellationToken = context.CancellationToken; + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + var argument = root.FindNode(context.Span).FirstAncestorOrSelf(IsCandidate); + if (argument == null) + { + return; + } + + if (!IsPositionalArgument(argument)) + { + return; + } + + var receiver = GetReceiver(argument); + if (receiver == null) + { + return; + } + + if (receiver.ContainsDiagnostics) + { + return; + } + + var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var symbol = semanticModel.GetSymbolInfo(receiver, cancellationToken).Symbol; + if (symbol == null) + { + return; + } + + var parameters = symbol.GetParameters(); + if (parameters.IsDefaultOrEmpty) + { + return; + } + + var t = GetArgumentListIndexAndCount(argument); + if (t.Item1 < 0) + { + return; + } + + if (!IsLegalToAddNamedArguments(parameters, t.Item2)) + { + return; + } + + var argumentName = parameters[t.Item1].Name; + context.RegisterRefactoring(new MyCodeAction(string.Format(FeaturesResources.Add_argument_name_0, argumentName), + c => AddNamedArgumentsAsync(root, document, argument, parameters, t.Item1))); + } + + private Task AddNamedArgumentsAsync( + SyntaxNode root, + Document document, + SyntaxNode firstArgument, + ImmutableArray parameters, + int index) + { + var argumentList = firstArgument.Parent; + var newArgumentList = GetOrSynthesizeNamedArguments(parameters, argumentList, index); + var newRoot = root.ReplaceNode(argumentList, newArgumentList); + return Task.FromResult(document.WithSyntaxRoot(newRoot)); + } + + protected abstract bool IsCandidate(SyntaxNode node); + protected abstract bool IsPositionalArgument(SyntaxNode argument); + protected abstract bool IsLegalToAddNamedArguments(ImmutableArray parameters, int argumentCount); + protected abstract ValueTuple GetArgumentListIndexAndCount(SyntaxNode argument); + protected abstract SyntaxNode GetReceiver(SyntaxNode argument); + protected abstract SyntaxNode GetOrSynthesizeNamedArguments(ImmutableArray parameters, SyntaxNode argumentList, int index); + + private class MyCodeAction : CodeAction.DocumentChangeAction + { + public MyCodeAction(string title, Func> createChangedDocument) + : base(title, createChangedDocument) + { + } + } + } +} diff --git a/src/Features/Core/Portable/Features.csproj b/src/Features/Core/Portable/Features.csproj index b3bacc9cddb..818be20ed16 100644 --- a/src/Features/Core/Portable/Features.csproj +++ b/src/Features/Core/Portable/Features.csproj @@ -99,6 +99,7 @@ + @@ -713,4 +714,4 @@ - + \ No newline at end of file diff --git a/src/Features/Core/Portable/FeaturesResources.Designer.cs b/src/Features/Core/Portable/FeaturesResources.Designer.cs index 2eb2d021671..f06726926d8 100644 --- a/src/Features/Core/Portable/FeaturesResources.Designer.cs +++ b/src/Features/Core/Portable/FeaturesResources.Designer.cs @@ -98,6 +98,15 @@ internal class FeaturesResources { } } + /// + /// Looks up a localized string similar to Add argument name '{0}'. + /// + internal static string Add_argument_name_0 { + get { + return ResourceManager.GetString("Add_argument_name_0", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add both. /// diff --git a/src/Features/Core/Portable/FeaturesResources.resx b/src/Features/Core/Portable/FeaturesResources.resx index 043d9bd9c1e..d7d7a6951b2 100644 --- a/src/Features/Core/Portable/FeaturesResources.resx +++ b/src/Features/Core/Portable/FeaturesResources.resx @@ -1178,4 +1178,7 @@ This version used in: {2} Add document '{0}' + + Add argument name '{0}' + \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/BasicFeatures.vbproj b/src/Features/VisualBasic/Portable/BasicFeatures.vbproj index fdfc06c1050..cc9317d620e 100644 --- a/src/Features/VisualBasic/Portable/BasicFeatures.vbproj +++ b/src/Features/VisualBasic/Portable/BasicFeatures.vbproj @@ -103,6 +103,7 @@ + @@ -472,4 +473,4 @@ - + \ No newline at end of file diff --git a/src/Features/VisualBasic/Portable/CodeRefactorings/UseNamedArguments/VisualBasicUseNamedArgumentsCodeRefactoringProvider.vb b/src/Features/VisualBasic/Portable/CodeRefactorings/UseNamedArguments/VisualBasicUseNamedArgumentsCodeRefactoringProvider.vb new file mode 100644 index 00000000000..b04a3b7abd8 --- /dev/null +++ b/src/Features/VisualBasic/Portable/CodeRefactorings/UseNamedArguments/VisualBasicUseNamedArgumentsCodeRefactoringProvider.vb @@ -0,0 +1,69 @@ +' 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 +Imports System.Composition +Imports System.Collections.Immutable +Imports System.Linq +Imports Microsoft.CodeAnalysis.CodeRefactorings +Imports Microsoft.CodeAnalysis.CodeRefactorings.UseNamedArguments +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax + +Namespace Microsoft.CodeAnalysis.VisualBasic.CodeRefactorings.UseNamedArguments + + + Friend Class VisualBasicUseNamedArgumentsCodeRefactoringProvider + Inherits AbstractUseNamedArgumentsCodeRefactoringProvider + + Protected Overrides Function IsCandidate(node As SyntaxNode) As Boolean + Return node.IsKind(SyntaxKind.SimpleArgument) + End Function + + Protected Overrides Function IsPositionalArgument(node As SyntaxNode) As Boolean + Dim argument = DirectCast(node, SimpleArgumentSyntax) + Return argument.NameColonEquals Is Nothing + End Function + + Protected Overrides Function IsLegalToAddNamedArguments(parameters As ImmutableArray(Of IParameterSymbol), argumentCount As Integer) As Boolean + Return Not parameters.LastOrDefault().IsParams OrElse parameters.Length > argumentCount + End Function + + Protected Overrides Function GetArgumentListIndexAndCount(node As SyntaxNode) As ValueTuple(Of Integer, Integer) + Dim argumentList = DirectCast(node.Parent, ArgumentListSyntax) + Return ValueTuple.Create(argumentList.Arguments.IndexOf(DirectCast(node, SimpleArgumentSyntax)), argumentList.Arguments.Count) + End Function + + Protected Overrides Function GetReceiver(argument As SyntaxNode) As SyntaxNode + If argument.Parent?.Parent?.IsKind(SyntaxKind.Attribute) = True Then + Return Nothing + End If + Return argument.Parent.Parent + End Function + + Private Shared Iterator Function GetNamedAruments(parameters As ImmutableArray(Of IParameterSymbol), + argumentList As ArgumentListSyntax, index As Integer) As IEnumerable(Of SyntaxNode) + Dim arguments = argumentList.Arguments + For i As Integer = 0 To arguments.Count - 1 + Dim argument = DirectCast(arguments(i), ArgumentSyntax) + If i < index Then + Yield argument + ElseIf argument.IsNamed Then + Yield argument + ElseIf argument.IsOmitted Then + Continue For + Else + Dim parameter = parameters(i) + Dim simpleArgument = DirectCast(argument, SimpleArgumentSyntax) + Yield simpleArgument.WithNameColonEquals(SyntaxFactory.NameColonEquals(SyntaxFactory.IdentifierName(parameter.Name))).WithTriviaFrom(argument) + End If + Next + End Function + + Protected Overrides Function GetOrSynthesizeNamedArguments(parameters As ImmutableArray(Of IParameterSymbol), + argumentList As SyntaxNode, index As Integer) As SyntaxNode + Dim argumentListSyntax = DirectCast(argumentList, ArgumentListSyntax) + Dim namedArguments = GetNamedAruments(parameters, argumentListSyntax, index) + Return argumentListSyntax.WithArguments(SyntaxFactory.SeparatedList(namedArguments)) + End Function + End Class +End Namespace -- GitLab