diff --git a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs index 167ce515088f1..87110afe253f7 100644 --- a/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs +++ b/src/EditorFeatures/CSharpTest/CodeActions/IntroduceVariable/IntroduceVariableTests.cs @@ -307,7 +307,7 @@ public async Task TestNameVerbatimIdentifier1() { await TestAsync( @"static class G { public class @class { } public static void Add(object t) { } } class Program { static void Main() { G.Add([|new G.@class()|]); } }", -@"static class G { public class @class { } public static void Add(object t) { } } class Program { static void Main() { var {|Rename:@class|} = new G.@class(); G.Add(@class); } }", +@"static class G { public class @class { } public static void Add(object t) { } } class Program { static void Main() { var {|Rename:t|} = new G.@class(); G.Add(t); } }", index: 0); } @@ -316,7 +316,7 @@ public async Task TestNameVerbatimIdentifier1NoVar() { await TestAsync( @"static class G { public class @class { } public static void Add(object t) { } } class Program { static void Main() { G.Add([|new G.@class()|]); } }", -@"static class G { public class @class { } public static void Add(object t) { } } class Program { static void Main() { G.@class {|Rename:@class|} = new G.@class(); G.Add(@class); } }", +@"static class G { public class @class { } public static void Add(object t) { } } class Program { static void Main() { G.@class {|Rename:t|} = new G.@class(); G.Add(t); } }", index: 0, options: new Dictionary { { new OptionKey(CSharpCodeStyleOptions.UseVarWhenDeclaringLocals), false } }); } @@ -326,7 +326,7 @@ public async Task TestNameVerbatimIdentifier2() { await TestAsync( @"static class G { public class @class { } public static void Add(object t) { } static void Main() { G.Add([|new G.@class()|]); } }", -@"static class G { public class @class { } public static void Add(object t) { } static void Main() { var {|Rename:class1|} = new G.@class(); G.Add(class1); } }"); +@"static class G { public class @class { } public static void Add(object t) { } static void Main() { var {|Rename:t|} = new G.@class(); G.Add(t); } }"); } [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] @@ -334,7 +334,7 @@ public async Task TestNameVerbatimIdentifier2NoVar() { await TestAsync( @"static class G { public class @class { } public static void Add(object t) { } static void Main() { G.Add([|new G.@class()|]); } }", -@"static class G { public class @class { } public static void Add(object t) { } static void Main() { G.@class {|Rename:class1|} = new G.@class(); G.Add(class1); } }", +@"static class G { public class @class { } public static void Add(object t) { } static void Main() { G.@class {|Rename:t|} = new G.@class(); G.Add(t); } }", options: new Dictionary { { new OptionKey(CSharpCodeStyleOptions.UseVarWhenDeclaringLocals), false } }); } @@ -1176,7 +1176,7 @@ public async Task TestInSwitchSection() { await TestAsync( @"class Program { int Main ( int i ) { switch ( 1 ) { case 0 : var f = Main ( [|1 + 1|] ) ; Console . WriteLine ( f ) ; } } } ", -@"class Program { int Main ( int i ) { switch ( 1 ) { case 0 : const int {|Rename:V|} = 1 + 1 ; var f = Main ( V ) ; Console . WriteLine ( f ) ; } } } ", +@"class Program { int Main ( int i ) { switch ( 1 ) { case 0 : const int {|Rename:i1|} = 1 + 1 ; var f = Main ( i1 ) ; Console . WriteLine ( f ) ; } } } ", index: 2); } @@ -1259,7 +1259,7 @@ public async Task TestIntroduceLocalRemovesUnnecessaryCast() { await TestAsync( @"using System.Collections.Generic; class C { static void Main(string[] args) { var set = new HashSet(); set.Add([|set.ToString()|]); } } ", -@"using System.Collections.Generic; class C { static void Main(string[] args) { var set = new HashSet(); var {|Rename:v|} = set.ToString(); set.Add(v); } } "); +@"using System.Collections.Generic; class C { static void Main(string[] args) { var set = new HashSet(); var {|Rename:item|} = set.ToString(); set.Add(item); } } "); } [WorkItem(655498, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/655498")] @@ -1353,8 +1353,8 @@ class Program static void Main(string[] args) { var d = new Dictionary(); - var {|Rename:exception|} = new Exception(); - d.Add(""a"", exception); + var {|Rename:value|} = new Exception(); + d.Add(""a"", value); } }", compareTokens: false); @@ -1897,8 +1897,8 @@ class Complex int real; int imaginary; public static Complex operator +(Complex a, Complex b) { - var {|Rename:v|} = b.real + 1; - return a.Add(v); + var {|Rename:b1|} = b.real + 1; + return a.Add(b1); } private Complex Add(int b) @@ -1967,7 +1967,7 @@ public struct DBBool @"using System; public struct DBBool { - private const int {|Rename:V|} = 1; + private const int {|Rename:value1|} = 1; public static readonly DBBool dbFalse = new DBBool(-1); int value; @@ -1976,7 +1976,7 @@ public struct DBBool this.value = value; } - public static implicit operator DBBool(bool x) => x ? new DBBool(V) : dbFalse; + public static implicit operator DBBool(bool x) => x ? new DBBool(value1) : dbFalse; }"; await TestAsync(code, expected, index: 0, compareTokens: false); @@ -2380,8 +2380,8 @@ class TestClass { static void Test(string[] args) { - var {|Rename:v|} = $""{DateTime.Now.ToString()}Text{args[0]}""; - Console.WriteLine(v); + var {|Rename:value|} = $""{DateTime.Now.ToString()}Text{args[0]}""; + Console.WriteLine(value); } }"; @@ -2409,9 +2409,9 @@ class TestClass { static void Test(string[] args) { - var {|Rename:v|} = $""Text{{s}}""; - Console.WriteLine(v); - Console.WriteLine(v); + var {|Rename:value|} = $""Text{{s}}""; + Console.WriteLine(value); + Console.WriteLine(value); } }"; @@ -2571,8 +2571,8 @@ class C { public async Task M() { - FormattableString {|Rename:v|} = $""""; - var f = FormattableString.Invariant(v); + FormattableString {|Rename:formattable|} = $""""; + var f = FormattableString.Invariant(formattable); } } }"; @@ -2640,5 +2640,236 @@ class C }"; await TestAsync(code, expected, index: 2, compareTokens: false); } + + //Kasey Tests + [WorkItem(2423, "https://github.com/dotnet/roslyn/issues/2423")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task IntroduceLocalFromParameterWithNamedParameters() + { + var code = + @"class Program +{ + void Main(string a, string b) + { + var y = new TextSpan(second:[|int.Parse(a)|], first:int.Parse(b)); + } +} + +internal class TextSpan +{ + public TextSpan(int first, int second) + { + } +}"; + var expected = + @"class Program +{ + void Main(string a, string b) + { + var {|Rename:second|} = int.Parse(a); + var y = new TextSpan(second:second, first:int.Parse(b)); + } +} + +internal class TextSpan +{ + public TextSpan(int first, int second) + { + } +}"; + await TestAsync(code, expected, index: 0); + } + + [WorkItem(2423, "https://github.com/dotnet/roslyn/issues/2423")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task IntroduceLocalFromParameterWithUnNamedParameters() + { + var code = + @"class Program +{ + void Main(string a, string b) + { + var y = new TextSpan([|int.Parse(a)|], int.Parse(b)); + } +} + +internal class TextSpan +{ + public TextSpan(int first, int second) + { + } +}"; + var expected = + @"class Program +{ + void Main(string a, string b) + { + var {|Rename:first|} = int.Parse(a); + var y = new TextSpan(first, int.Parse(b)); + } +} + +internal class TextSpan +{ + public TextSpan(int first, int second) + { + } +}"; + await TestAsync(code, expected, index: 0); + } + + [WorkItem(2423, "https://github.com/dotnet/roslyn/issues/2423")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task IntroduceLocalFromParameterWithNameConflict() + { + var code = + @"class Program +{ + void Main(string a, string b) + { + var y = new TextSpan([|int.Parse(a)|], int.Parse(b)); + } +} + +internal class TextSpan +{ + public TextSpan(int a, int b) + { + } +}"; + var expected = + @"class Program +{ + void Main(string a, string b) + { + var {|Rename:a1|} = int.Parse(a); + var y = new TextSpan(a1, int.Parse(b)); + } +} + +internal class TextSpan +{ + public TextSpan(int a, int b) + { + } +}"; + await TestAsync(code, expected, index: 0); + } + + [WorkItem(2423, "https://github.com/dotnet/roslyn/issues/2423")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task IntroduceLocalConstantFromParameterWithParamsArray() + { + var code = + @"class Program +{ + static void Main(string a, string b) + { + var paramsarray = Foo([|1|], 2, 3, 4); + } + + private static object Foo(params int[] parameters) + { + throw new NotImplementedException(); + } +}"; + var expected = + @"class Program +{ + static void Main(string a, string b) + { + const int {|Rename:V|} = 1; + var paramsarray = Foo(V, 2, 3, 4); + } + + private static object Foo(params int[] parameters) + { + throw new NotImplementedException(); + } +}"; + await TestAsync(code, expected, index: 2); + } + + [WorkItem(2423, "https://github.com/dotnet/roslyn/issues/2423")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task IntroduceLocalFromParameterWithReducedExtension() + { + var code = + @"using System.Collections.Generic; +using System.Linq; +class Program +{ + static void Main(string[] args) + { + IEnumerable ints = new[] { 1, 2, 3 }; + ints.Select([|a => a|]); + } +}"; + var expected = + @"using System.Collections.Generic; +using System.Linq; +class Program +{ + static void Main(string[] args) + { + IEnumerable ints = new[] { 1, 2, 3 }; + System.Func {|Rename:selector|} = a => a; + ints.Select(selector); + } +}"; + await TestAsync(code, expected, index: 0); + } + + [WorkItem(2423, "https://github.com/dotnet/roslyn/issues/2423")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task IntroduceLocalFromParameterInvocationExpressionWithUnNamedParameters() + { + var code = + @"class Program +{ + static void Main(string a, string b) + { + M(""hello"", [|""kitty""|]); + } + public static void M(string foo, string bar){} +}"; + var expected = + @"class Program +{ + static void Main(string a, string b) + { + const string {|Rename:bar|} = ""kitty""; + M(""hello"", bar); + } + public static void M(string foo, string bar){} +}"; + await TestAsync(code, expected, index: 2); + } + + [WorkItem(2423, "https://github.com/dotnet/roslyn/issues/2423")] + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsIntroduceVariable)] + public async Task IntroduceLocalFromParameterInvocationExpressionWithNamedParameters() + { + var code = + @"class Program +{ + static void Main(string a, string b) + { + M(bar:""hello"", foo:[|""kitty""|]); + } + public static void M(string foo, string bar){} +}"; + var expected = + @"class Program +{ + static void Main(string a, string b) + { + const string {|Rename:foo|} = ""kitty""; + M(bar:""hello"", foo:foo); + } + public static void M(string foo, string bar){} +}"; + await TestAsync(code, expected, index: 2); + } } } \ No newline at end of file diff --git a/src/Workspaces/CSharp/Portable/Extensions/SemanticModelExtensions.cs b/src/Workspaces/CSharp/Portable/Extensions/SemanticModelExtensions.cs index 16642c22dda34..c9ab29750f9bb 100644 --- a/src/Workspaces/CSharp/Portable/Extensions/SemanticModelExtensions.cs +++ b/src/Workspaces/CSharp/Portable/Extensions/SemanticModelExtensions.cs @@ -203,6 +203,38 @@ public static string GenerateNameForArgument( public static string GenerateNameForExpression( this SemanticModel semanticModel, ExpressionSyntax expression, bool capitalize = false) { + + // Check if expression is ArgumentSyntax. If we have an expression that + // is an argument, return the name of the parameter. + var parent = expression.Parent as ArgumentSyntax; + if (parent != null) + { + if (parent.NameColon != null) + { + return parent.NameColon.Name.Identifier.ValueText.ToCamelCase(); + } + else + { + var argumentList = parent.Parent as ArgumentListSyntax; + if (argumentList != null) + { + var argumentIndex = argumentList.Arguments.IndexOf(parent); + var argumentInfo = GetArgumentInfo(semanticModel, argumentList.Arguments[argumentIndex]); + if (argumentInfo.Parameter != null) + { + if (!argumentInfo.Parameter.IsParams) + { + return argumentInfo.Parameter.Name.ToCamelCase(); + } + else if (!expression.IsAnyLiteralExpression()) + { + return argumentInfo.Parameter.Name.ToCamelCase(); + } + } + } + } + } + // Try to find a usable name node that we can use to name the // parameter. If we have an expression that has a name as part of it // then we try to use that part. @@ -445,5 +477,103 @@ private static TypeSyntax GetOutermostType(TypeSyntax type) { return type.GetAncestorsOrThis().Last(); } + + + // API that gets semantic info for a specific arguments from @dustinca + //https://github.com/DustinCampbell/CSharpEssentials/blob/master/Source/CSharpEssentials/Extensions.cs + // supposedly there is a bug on github somewhere for him to add this api to the compiler + internal struct ArgumentInfo + { + public readonly ISymbol MethodOrProperty; + public readonly IParameterSymbol Parameter; + + public ArgumentInfo(ISymbol methodOrProperty, IParameterSymbol parameter) + { + this.MethodOrProperty = methodOrProperty; + this.Parameter = parameter; + } + } + + public static ArgumentInfo GetArgumentInfo(this SemanticModel semanticModel, ArgumentSyntax argument) + { + if (semanticModel == null) + { + throw new System.ArgumentNullException(nameof(semanticModel)); + } + + if (argument == null) + { + throw new System.ArgumentNullException(nameof(argument)); + } + + var argumentList = argument.Parent as ArgumentListSyntax; + if (argumentList == null) + { + return default(ArgumentInfo); + } + + var expression = argumentList.Parent as ExpressionSyntax; + if (expression == null) + { + return default(ArgumentInfo); + } + + var methodOrProperty = semanticModel.GetSymbolInfo(expression).Symbol; + if (methodOrProperty == null) + { + return default(ArgumentInfo); + } + + var parameters = methodOrProperty.GetParameters(); + if (parameters.Length == 0) + { + return default(ArgumentInfo); + } + + if (argument.NameColon != null) + { + if (argument.NameColon.Name == null) + { + return default(ArgumentInfo); + } + + // We've got a named argument... + var nameText = argument.NameColon.Name.Identifier.ValueText; + if (nameText == null) + { + return default(ArgumentInfo); + } + + foreach (var parameter in parameters) + { + if (string.Equals(parameter.Name, nameText, System.StringComparison.Ordinal)) + { + return new ArgumentInfo(methodOrProperty, parameter); + } + } + } + else + { + // Positional argument... + var index = argumentList.Arguments.IndexOf(argument); + if (index < 0) + { + return default(ArgumentInfo); + } + + if (index < parameters.Length) + { + return new ArgumentInfo(methodOrProperty, parameters[index]); + } + + if (index >= parameters.Length && + parameters[parameters.Length - 1].IsParams) + { + return new ArgumentInfo(methodOrProperty, parameters[parameters.Length - 1]); + } + } + + return default(ArgumentInfo); + } } }