提交 d05c6c8c 编写于 作者: J Jonathon Marolf 提交者: Jonathon Marolf

Port from CSharpEssentials of Convert-To-Interpolated-String (#11415)

* initial port from CSharpEssentials

* responding to feedback.
removing syntax rewriter and incorporating formatting rules into formatter

* responding to cryus' feedback

- general logic cleanup
- moving methods to abstract base class
上级 105c5403
......@@ -780,6 +780,8 @@ static Microsoft.CodeAnalysis.Semantics.UnaryAndBinaryOperationExtensions.GetSim
static Microsoft.CodeAnalysis.Semantics.UnaryAndBinaryOperationExtensions.GetUnaryOperandKind(Microsoft.CodeAnalysis.Semantics.UnaryOperationKind kind) -> Microsoft.CodeAnalysis.Semantics.UnaryOperandKind
static Microsoft.CodeAnalysis.Semantics.UnaryAndBinaryOperationExtensions.GetUnaryOperandKind(this Microsoft.CodeAnalysis.Semantics.IIncrementExpression increment) -> Microsoft.CodeAnalysis.Semantics.UnaryOperandKind
static Microsoft.CodeAnalysis.Semantics.UnaryAndBinaryOperationExtensions.GetUnaryOperandKind(this Microsoft.CodeAnalysis.Semantics.IUnaryOperatorExpression unary) -> Microsoft.CodeAnalysis.Semantics.UnaryOperandKind
static Microsoft.CodeAnalysis.SeparatedSyntaxList<TNode>.implicit operator Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.SyntaxNode>(Microsoft.CodeAnalysis.SeparatedSyntaxList<TNode> nodes) -> Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.SyntaxNode>
static Microsoft.CodeAnalysis.SeparatedSyntaxList<TNode>.implicit operator Microsoft.CodeAnalysis.SeparatedSyntaxList<TNode>(Microsoft.CodeAnalysis.SeparatedSyntaxList<Microsoft.CodeAnalysis.SyntaxNode> nodes) -> Microsoft.CodeAnalysis.SeparatedSyntaxList<TNode>
static Microsoft.CodeAnalysis.SourceGeneratorExtensions.GenerateSource(this Microsoft.CodeAnalysis.Compilation compilation, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.SourceGenerator> generators, string path, bool writeToDisk, System.Threading.CancellationToken cancellationToken) -> System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.SyntaxTree>
virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationAnalysisContext> action, System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.OperationKind> operationKinds) -> void
virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationBlockAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext> action) -> void
......
......@@ -573,5 +573,15 @@ IEnumerator IEnumerable.GetEnumerator()
return SpecializedCollections.EmptyEnumerator<TNode>();
}
public static implicit operator SeparatedSyntaxList<SyntaxNode>(SeparatedSyntaxList<TNode> nodes)
{
return new SeparatedSyntaxList<SyntaxNode>(nodes._list);
}
public static implicit operator SeparatedSyntaxList<TNode>(SeparatedSyntaxList<SyntaxNode> nodes)
{
return new SeparatedSyntaxList<TNode>(nodes._list);
}
}
}
......@@ -145,6 +145,7 @@
<Compile Include="Classification\TotalClassifierTests.cs" />
<Compile Include="Classification\TotalClassifierTests_Dynamic.cs" />
<Compile Include="CodeActions\AbstractCSharpCodeActionTest.cs" />
<Compile Include="CodeActions\ConvertToInterpolatedString\ConvertToInterpolatedStringTests.cs" />
<Compile Include="CodeActions\EncapsulateField\EncapsulateFieldTests.cs" />
<Compile Include="CodeActions\ExtractMethod\ExtractMethodTests.cs" />
<Compile Include="CodeActions\GenerateDefaultConstructors\GenerateDefaultConstructorsTests.cs" />
......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CSharp.CodeRefactorings.ConvertToInterpolatedString;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeRefactorings;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.CodeActions.ConvertToInterpolatedString
{
public class ConvertToInterpolatedStringTests : AbstractCSharpCodeActionTest
{
protected override object CreateCodeRefactoringProvider(Workspace workspace) =>
new ConvertToInterpolatedStringRefactoringProvider();
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestSingleItemSubstitution()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{0}"", 1)|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{1}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestItemOrdering()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{0}{1}{2}"", 1, 2, 3)|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{1}{2}{3}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestItemOrdering2()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{0}{2}{1}"", 1, 2, 3)|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{1}{3}{2}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestItemOrdering3()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{0}{0}{0}"", 1, 2, 3)|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{1}{1}{1}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestItemOutsideRange()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{4}{5}{6}"", 1, 2, 3)|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{4}{5}{6}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestItemDoNotHaveCast()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{0}{1}{2}"", 0.5, ""Hello"", 3)|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{0.5}{""Hello""}{3}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestItemWithSyntaxErrorDoesHaveCast()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{0}"", new object)|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{ (object)new object}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestItemWithoutSyntaxErrorDoesNotHaveCast()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{0}"", new object())|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{new object()}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestParenthesisAddedForTernaryExpression()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{0}"", true ? ""Yes"" : ""No"")|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{(true ? ""Yes"" : ""No"")}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestDoesNotAddDoubleParenthesisForTernaryExpression()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(""{0}"", (true ? ""Yes"" : ""No""))|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{(true ? ""Yes"" : ""No"")}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestMultiLineExpression()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|string.Format(
""{0}"",
true
? ""Yes""
: false as object)|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{(true ? ""Yes"" : false as object)}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestFormatSpecifiers()
{
await TestAsync(
@"using System;
class T
{
void M()
{
Decimal pricePerOunce = 17.36m;
String s = [|String.Format(""The current price is { 0:C2} per ounce."",
pricePerOunce)|];
}
}",
@"using System;
class T
{
void M()
{
Decimal pricePerOunce = 17.36m;
String s = $""The current price is { pricePerOunce:C2} per ounce."";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestFormatSpecifiers2()
{
await TestAsync(
@"using System;
class T
{
void M()
{
string s = [|String.Format(""It is now {0:d} at {0:t}"", DateTime.Now)|];
}
}",
@"using System;
class T
{
void M()
{
string s = $""It is now {DateTime.Now:d} at {DateTime.Now:t}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestFormatSpecifiers3()
{
await TestAsync(
@"using System;
class T
{
void M()
{
int[] years = { 2013, 2014, 2015 };
int[] population = { 1025632, 1105967, 1148203 };
String s = String.Format(""{0,6} {1,15}\n\n"", ""Year"", ""Population"");
for (int index = 0; index < years.Length; index++)
s += [|String.Format(""{0,6} {1,15:N0}\n"",
years[index], population[index])|];
}
}",
@"using System;
class T
{
void M()
{
int[] years = { 2013, 2014, 2015 };
int[] population = { 1025632, 1105967, 1148203 };
String s = String.Format(""{0,6} {1,15}\n\n"", ""Year"", ""Population"");
for (int index = 0; index < years.Length; index++)
s += $""{years[index],6} {population[index],15:N0}\n"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestFormatSpecifiers4()
{
await TestAsync(
@"using System;
class T
{
void M()
{
var a = [|String.Format(""{ 0,-10:C}"", 126347.89m)|];
}
}",
@"using System;
class T
{
void M()
{
var a = $""{ 126347.89m,-10:C}"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestFormatSpecifiers5()
{
await TestAsync(
@"using System;
public class T
{
public static void M()
{
Tuple<string, DateTime, int, DateTime, int>[] cities =
{ Tuple.Create(""Los Angeles"", new DateTime(1940, 1, 1), 1504277,
new DateTime(1950, 1, 1), 1970358),
Tuple.Create(""New York"", new DateTime(1940, 1, 1), 7454995,
new DateTime(1950, 1, 1), 7891957),
Tuple.Create(""Chicago"", new DateTime(1940, 1, 1), 3396808,
new DateTime(1950, 1, 1), 3620962),
Tuple.Create(""Detroit"", new DateTime(1940, 1, 1), 1623452,
new DateTime(1950, 1, 1), 1849568) };
string output;
foreach (var city in cities)
{
output = [|String.Format(""{0,-12}{1,8:yyyy}{2,12:N0}{3,8:yyyy}{4,12:N0}{5,14:P1}"",
city.Item1, city.Item2, city.Item3, city.Item4, city.Item5,
(city.Item5 - city.Item3) / (double)city.Item3)|];
}
}
}",
@"using System;
public class T
{
public static void M()
{
Tuple<string, DateTime, int, DateTime, int>[] cities =
{ Tuple.Create(""Los Angeles"", new DateTime(1940, 1, 1), 1504277,
new DateTime(1950, 1, 1), 1970358),
Tuple.Create(""New York"", new DateTime(1940, 1, 1), 7454995,
new DateTime(1950, 1, 1), 7891957),
Tuple.Create(""Chicago"", new DateTime(1940, 1, 1), 3396808,
new DateTime(1950, 1, 1), 3620962),
Tuple.Create(""Detroit"", new DateTime(1940, 1, 1), 1623452,
new DateTime(1950, 1, 1), 1849568) };
string output;
foreach (var city in cities)
{
output = $""{city.Item1,-12}{city.Item2,8:yyyy}{city.Item3,12:N0}{city.Item4,8:yyyy}{city.Item5,12:N0}{(city.Item5 - city.Item3) / (double)city.Item3,14:P1}"";
}
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestFormatSpecifiers6()
{
await TestAsync(
@"using System;
public class T
{
public static void M()
{
short[] values = { Int16.MinValue, -27, 0, 1042, Int16.MaxValue };
foreach (short value in values)
{
string formatString = [|String.Format(""{0,10:G}: {0,10:X}"", value)|];
}
}
}",
@"using System;
public class T
{
public static void M()
{
short[] values = { Int16.MinValue, -27, 0, 1042, Int16.MaxValue };
foreach (short value in values)
{
string formatString = $""{value,10:G}: {value,10:X}"";
}
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestVerbatimStringLiteral()
{
await TestAsync(
@"using System;
public class T
{
public static void M()
{
int value1 = 16932;
int value2 = 15421;
string result = [|string.Format(@""
{0,10} ({0,8:X8})
And {1,10} ({1,8:X8})
= {2,10} ({2,8:X8})"",
value1, value2, value1 & value2)|];
}
}",
@"using System;
public class T
{
public static void M()
{
int value1 = 16932;
int value2 = 15421;
string result = $@""
{value1,10} ({value1,8:X8})
And {value2,10} ({value2,8:X8})
= {value1 & value2,10} ({value1 & value2,8:X8})"";
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestFormatWithParams()
{
await TestMissingAsync(
@"using System;
public class T
{
public static void M()
{
DateTime date1 = new DateTime(2009, 7, 1);
TimeSpan hiTime = new TimeSpan(14, 17, 32);
decimal hiTemp = 62.1m;
TimeSpan loTime = new TimeSpan(3, 16, 10);
decimal loTemp = 54.8m;
string result = [|String.Format(@""Temperature on {0:d}:
{1,11}: {2} degrees (hi)
{3,11}: {4} degrees (lo)"",
new object[] { date1, hiTime, hiTemp, loTime, loTemp })|];
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)]
public async Task TestInvalidInteger()
{
await TestAsync(
@"using System;
public class T
{
public static void M()
{
string result = [|String.Format(""{0L}"", 5)|];
}
}",
@"using System;
public class T
{
public static void M()
{
string result = $""{5}"";
}
}");
}
}
}
......@@ -455,11 +455,6 @@ public bool ContainsInMemberBody(SyntaxNode node, TextSpan span)
throw new NotImplementedException();
}
public SyntaxNode ConvertToSingleLine(SyntaxNode node)
{
throw new NotImplementedException();
}
public SyntaxToken FindTokenOnLeftOfPosition(SyntaxNode node, int position, bool includeSkipped = true, bool includeDirectives = false, bool includeDocumentationComments = false)
{
throw new NotImplementedException();
......@@ -807,7 +802,7 @@ public bool IsStartOfUnicodeEscapeSequence(char c)
throw new NotImplementedException();
}
public bool IsStringLiteral(SyntaxToken token)
public bool IsStringLiteralOrInterpolatedStringLiteral(SyntaxToken token)
{
throw new NotImplementedException();
}
......@@ -961,6 +956,36 @@ public bool IsOperandOfIncrementExpression(SyntaxNode node)
{
throw new NotImplementedException();
}
public bool IsNumericLiteralExpression(SyntaxNode node)
{
throw new NotImplementedException();
}
public SyntaxNode GetExpressionOfInterpolation(SyntaxNode node)
{
throw new NotImplementedException();
}
public SyntaxList<SyntaxNode> GetContentsOfInterpolatedString(SyntaxNode interpolatedString)
{
throw new NotImplementedException();
}
public bool IsStringLiteral(SyntaxToken token)
{
throw new NotImplementedException();
}
public SeparatedSyntaxList<SyntaxNode> GetArgumentsForInvocationExpression(SyntaxNode invocationExpression)
{
throw new NotImplementedException();
}
public SyntaxNode ConvertToSingleLine(SyntaxNode node, bool useElasticTrivia = false)
{
throw new NotImplementedException();
}
}
}
}
......
......@@ -37,6 +37,7 @@ public static class Features
public const string CodeActionsChangeToAsync = "CodeActions.ChangeToAsync";
public const string CodeActionsChangeToIEnumerable = "CodeActions.ChangeToIEnumerable";
public const string CodeActionsChangeToYield = "CodeActions.ChangeToYield";
public const string CodeActionsConvertToInterpolatedString = "CodeActions.ConvertToInterpolatedString";
public const string CodeActionsConvertToIterator = "CodeActions.CodeActionsConvertToIterator";
public const string CodeActionsCorrectExitContinue = "CodeActions.CorrectExitContinue";
public const string CodeActionsCorrectFunctionReturnType = "CodeActions.CorrectFunctionReturnType";
......
......@@ -126,6 +126,7 @@
<Compile Include="Classification\SemanticClassifierTests.vb" />
<Compile Include="Classification\SyntacticClassifierTests.vb" />
<Compile Include="CodeActions\AbstractVisualBasicCodeActionTest.vb" />
<Compile Include="CodeActions\ConvertToInterpolatedString\ConvertToInterpolatedStringTests.vb" />
<Compile Include="CodeActions\EncapsulateField\EncapsulateFieldTests.vb" />
<Compile Include="CodeActions\ExtractMethod\ExtractMethodTests.vb" />
<Compile Include="CodeActions\GenerateDefaultConstructors\GenerateDefaultConstructorsTests.vb" />
......
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests
Imports Microsoft.CodeAnalysis.Editor.VisualBasic.UnitTests.CodeRefactorings
Public Class ConvertToInterpolatedStringTests
Inherits AbstractVisualBasicCodeActionTest
Protected Overrides Function CreateCodeRefactoringProvider(workspace As Workspace) As Object
Return New ConvertToInterpolatedStringRefactoringProvider()
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestSingleItemSubstitution() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim a = [|String.Format("{0}", 1)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim a = $"{1}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestItemOrdering() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim a = [|String.Format("{0}{1}{2}", 1, 2, 3)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim a = $"{1}{2}{3}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestItemOrdering2() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim a = [|String.Format("{0}{2}{1}", 1, 2, 3)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim a = $"{1}{3}{2}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestItemOrdering3() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim a = [|String.Format("{0}{0}{0}", 1, 2, 3)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim a = $"{1}{1}{1}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestItemOutsideRange() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim a = [|String.Format("{4}{5}{6}", 1, 2, 3)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim a = $"{4}{5}{6}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestItemDoNotHaveCast() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim a = [|String.Format("{0}{1}{2}", 0.5, "Hello", 3)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim a = $"{0.5}{"Hello"}{3}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestItemWithoutSyntaxErrorDoesNotHaveCast() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim a = [|String.Format("{0}{1}{2}", 0.5, "Hello", 3)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim a = $"{0.5}{"Hello"}{3}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestPreserveParenthesis() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim a = [|String.Format("{0}", (New Object))|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim a = $"{(New Object)}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestMultiLineExpression() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim a = [|String.Format("{0}", If(True,
"Yes",
TryCast(False, Object)))|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim a = $"{If(True,
"Yes",
TryCast(False, Object))}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestFormatSpecifiers() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim pricePerOunce As Decimal = 17.36
Dim s = [|String.Format("The current price Is {0:C2} per ounce.",
pricePerOunce)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim pricePerOunce As Decimal = 17.36
Dim s = $"The current price Is {pricePerOunce:C2} per ounce."
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestFormatSpecifiers2() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim s = [|String.Format("It Is now {0:d} at {0:T}", DateTime.Now)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim s = $"It Is now {DateTime.Now:d} at {DateTime.Now:T}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestFormatSpecifiers3() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim years As Integer() = {2013, 2014, 2015}
Dim population As Integer() = {1025632, 1105967, 1148203}
Dim s = String.Format("{0,6} {1,15}\n\n", "Year", "Population")
For index = 0 To years.Length - 1
s += [|String.Format("{0, 6} {1, 15: N0}\n",
years(index), population(index))|]
Next
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim years As Integer() = {2013, 2014, 2015}
Dim population As Integer() = {1025632, 1105967, 1148203}
Dim s = String.Format("{0,6} {1,15}\n\n", "Year", "Population")
For index = 0 To years.Length - 1
s += $"{years(index),6} {population(index),15: N0}\n"
Next
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestFormatSpecifiers4() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim s = [|String.Format("{0,-10:C}", 126347.89)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim s = $"{126347.89,-10:C}"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestFormatSpecifiers5() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim cities As Tuple(Of String, DateTime, Integer, DateTime, Integer)() =
{Tuple.Create("Los Angeles", New DateTime(1940, 1, 1), 1504277,
New DateTime(1950, 1, 1), 1970358),
Tuple.Create("New York", New DateTime(1940, 1, 1), 7454995,
New DateTime(1950, 1, 1), 7891957),
Tuple.Create("Chicago", New DateTime(1940, 1, 1), 3396808,
New DateTime(1950, 1, 1), 3620962),
Tuple.Create("Detroit", New DateTime(1940, 1, 1), 1623452,
New DateTime(1950, 1, 1), 1849568)}
Dim output As String
For Each city In cities
output = [|String.Format("{0,-12}{1,8:yyyy}{2,12:N0}{3,8:yyyy}{4,12:N0}{5,14:P1}",
city.Item1, city.Item2, city.Item3, city.Item4, city.Item5,
(city.Item5 - city.Item3) / CType(city.Item3, Double))|]
Next
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim cities As Tuple(Of String, DateTime, Integer, DateTime, Integer)() =
{Tuple.Create("Los Angeles", New DateTime(1940, 1, 1), 1504277,
New DateTime(1950, 1, 1), 1970358),
Tuple.Create("New York", New DateTime(1940, 1, 1), 7454995,
New DateTime(1950, 1, 1), 7891957),
Tuple.Create("Chicago", New DateTime(1940, 1, 1), 3396808,
New DateTime(1950, 1, 1), 3620962),
Tuple.Create("Detroit", New DateTime(1940, 1, 1), 1623452,
New DateTime(1950, 1, 1), 1849568)}
Dim output As String
For Each city In cities
output = $"{city.Item1,-12}{city.Item2,8:yyyy}{city.Item3,12:N0}{city.Item4,8:yyyy}{city.Item5,12:N0}{(city.Item5 - city.Item3) / CType(city.Item3, Double),14:P1}"
Next
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestFormatSpecifiers6() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim values As Short() = {Int16.MaxValue, -27, 0, 1042, Int16.MaxValue}
For Each value In values
Dim s = [|String.Format("{0,10:G}: {0,10:X}", value)|]
Next
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim values As Short() = {Int16.MaxValue, -27, 0, 1042, Int16.MaxValue}
For Each value In values
Dim s = $"{value,10:G}: {value,10:X}"
Next
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestMultilineStringLiteral2() As Task
Dim text = <File>
Imports System
Module T
Sub M()
Dim value1 = 16932
Dim value2 = 15421
Dim result = [|String.Format("
{0,10} ({0,8:X8})
And {1,10} ({1,8:X8})
= {2,10} ({2,8:X8})",
value1, value2, value1 And value2)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Dim expected = <File>
Imports System
Module T
Sub M()
Dim value1 = 16932
Dim value2 = 15421
Dim result = $"
{value1,10} ({value1,8:X8})
And {value2,10} ({value2,8:X8})
= {value1 And value2,10} ({value1 And value2,8:X8})"
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestAsync(text, expected)
End Function
<Fact, Trait(Traits.Feature, Traits.Features.CodeActionsConvertToInterpolatedString)>
Public Async Function TestParamsArray() As Task
Dim text = <File>
Imports System
Module T
Sub M(args As String())
Dim s = [|String.Format("{0}", args)|]
End Sub
End Module</File>.ConvertTestSourceTag()
Await TestMissingAsync(text)
End Function
End Class
......@@ -454,10 +454,6 @@ End Class]]></a>.Value.NormalizeLineEndings()
Throw New NotImplementedException()
End Function
Public Function ConvertToSingleLine(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.ConvertToSingleLine
Throw New NotImplementedException()
End Function
Public Function FindTokenOnLeftOfPosition(node As SyntaxNode, position As Integer, Optional includeSkipped As Boolean = True, Optional includeDirectives As Boolean = False, Optional includeDocumentationComments As Boolean = False) As SyntaxToken Implements ISyntaxFactsService.FindTokenOnLeftOfPosition
Throw New NotImplementedException()
End Function
......@@ -733,7 +729,7 @@ End Class]]></a>.Value.NormalizeLineEndings()
Throw New NotImplementedException()
End Function
Public Function IsStringLiteral(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsStringLiteral
Public Function IsStringLiteralOrInterpolatedStringLiteral(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsStringLiteralOrInterpolatedStringLiteral
Throw New NotImplementedException()
End Function
......@@ -852,6 +848,30 @@ End Class]]></a>.Value.NormalizeLineEndings()
Public Function IsOperandOfIncrementOrDecrementExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsOperandOfIncrementOrDecrementExpression
Throw New NotImplementedException()
End Function
Public Function IsNumericLiteralExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsNumericLiteralExpression
Throw New NotImplementedException()
End Function
Public Function GetExpressionOfInterpolation(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetExpressionOfInterpolation
Throw New NotImplementedException()
End Function
Public Function GetContentsOfInterpolatedString(interpolatedString As SyntaxNode) As SyntaxList(Of SyntaxNode) Implements ISyntaxFactsService.GetContentsOfInterpolatedString
Throw New NotImplementedException()
End Function
Public Function IsStringLiteral(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsStringLiteral
Throw New NotImplementedException()
End Function
Public Function GetArgumentsForInvocationExpression(invocationExpression As SyntaxNode) As SeparatedSyntaxList(Of SyntaxNode) Implements ISyntaxFactsService.GetArgumentsForInvocationExpression
Throw New NotImplementedException()
End Function
Public Function ConvertToSingleLine(node As SyntaxNode, Optional useElasticTrivia As Boolean = False) As SyntaxNode Implements ISyntaxFactsService.ConvertToSingleLine
Throw New NotImplementedException()
End Function
End Class
End Class
End Namespace
......@@ -92,6 +92,7 @@
<Compile Include="CodeFixes\Suppression\CSharpSuppressionCodeFixProvider.cs" />
<Compile Include="CodeFixes\TypeStyle\UseExplicitTypeCodeFixProvider.cs" />
<Compile Include="CodeFixes\TypeStyle\UseImplicitTypeCodeFixProvider.cs" />
<Compile Include="CodeRefactorings\ConvertToInterpolatedString\ConvertToInterpolatedStringRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\EncapsulateField\EncapsulateFieldCodeRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\ExtractInterface\ExtractInterfaceCodeRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\ExtractMethod\ExtractMethodCodeRefactoringProvider.cs" />
......
using System;
using System.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using Microsoft.CodeAnalysis.Editing;
namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings.ConvertToInterpolatedString
{
[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = PredefinedCodeRefactoringProviderNames.ConvertToInterpolatedString), Shared]
internal partial class ConvertToInterpolatedStringRefactoringProvider :
AbstractConvertToInterpolatedStringRefactoringProvider <InvocationExpressionSyntax, ExpressionSyntax, ArgumentSyntax, LiteralExpressionSyntax>
{
protected override SyntaxNode GetInterpolatedString(string text) =>
ParseExpression("$" + text) as InterpolatedStringExpressionSyntax;
}
}
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.LanguageServices;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Simplification;
using Microsoft.CodeAnalysis.Formatting;
namespace Microsoft.CodeAnalysis.CodeRefactorings
{
internal abstract class AbstractConvertToInterpolatedStringRefactoringProvider<TInvocationExpressionSyntax, TExpressionSyntax, TArgumentSyntax, TLiteralExpressionSyntax> : CodeRefactoringProvider
where TExpressionSyntax : SyntaxNode
where TInvocationExpressionSyntax : TExpressionSyntax
where TArgumentSyntax : SyntaxNode
where TLiteralExpressionSyntax : SyntaxNode
{
protected abstract SyntaxNode GetInterpolatedString(string text);
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
var stringType = semanticModel.Compilation.GetSpecialType(SpecialType.System_String);
if (stringType == null)
{
return;
}
var formatMethods = stringType
.GetMembers("Format")
.OfType<IMethodSymbol>()
.Where(ShouldIncludeFormatMethod)
.ToImmutableArray();
if (formatMethods.Length == 0)
{
return;
}
var syntaxFactsService = context.Document.GetLanguageService<ISyntaxFactsService>();
if (syntaxFactsService == null)
{
return;
}
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
TInvocationExpressionSyntax invocation;
ISymbol invocationSymbol;
if (TryFindInvocation(context.Span, root, semanticModel, formatMethods, syntaxFactsService, context.CancellationToken, out invocation, out invocationSymbol) &&
IsArgumentListCorrect(syntaxFactsService.GetArgumentsForInvocationExpression(invocation), invocationSymbol, formatMethods, semanticModel, syntaxFactsService, context.CancellationToken))
{
context.RegisterRefactoring(
new ConvertToInterpolatedStringCodeAction(
FeaturesResources.ConvertToInterpolatedString,
c => CreateInterpolatedString(invocation, context.Document, syntaxFactsService, c)));
}
}
private bool TryFindInvocation(
TextSpan span,
SyntaxNode root,
SemanticModel semanticModel,
ImmutableArray<IMethodSymbol> formatMethods,
ISyntaxFactsService syntaxFactsService,
CancellationToken cancellationToken,
out TInvocationExpressionSyntax invocation,
out ISymbol invocationSymbol)
{
invocationSymbol = null;
invocation = root.FindNode(span, getInnermostNodeForTie: true)?.FirstAncestorOrSelf<TInvocationExpressionSyntax>();
while (invocation != null)
{
var arguments = syntaxFactsService.GetArgumentsForInvocationExpression(invocation);
if (arguments.Count >= 2)
{
var firstArgumentExpression = syntaxFactsService.GetExpressionOfArgument(arguments[0]) as TLiteralExpressionSyntax;
if (firstArgumentExpression != null && syntaxFactsService.IsStringLiteral(firstArgumentExpression.GetFirstToken()))
{
invocationSymbol = semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol;
if (formatMethods.Contains(invocationSymbol))
{
break;
}
}
}
invocation = invocation.Parent?.FirstAncestorOrSelf<TInvocationExpressionSyntax>();
}
return invocation != null;
}
private bool IsArgumentListCorrect(
SeparatedSyntaxList<TArgumentSyntax>? nullableArguments,
ISymbol invocationSymbol,
ImmutableArray<IMethodSymbol> formatMethods,
SemanticModel semanticModel,
ISyntaxFactsService syntaxFactsService,
CancellationToken cancellationToken)
{
var arguments = nullableArguments.Value;
var firstExpression = syntaxFactsService.GetExpressionOfArgument(arguments[0]) as TLiteralExpressionSyntax;
if (arguments.Count >= 2 &&
firstExpression != null &&
syntaxFactsService.IsStringLiteral(firstExpression.GetFirstToken()))
{
// We do not want to substitute the expression if it is being passed to params array argument
// Example:
// string[] args;
// String.Format("{0}{1}{2}", args);
return IsArgumentListNotPassingArrayToParams(
syntaxFactsService.GetExpressionOfArgument(arguments[1]),
invocationSymbol,
formatMethods,
semanticModel,
cancellationToken);
}
return false;
}
private async Task<Document> CreateInterpolatedString(
TInvocationExpressionSyntax invocation,
Document document,
ISyntaxFactsService syntaxFactsService,
CancellationToken cancellationToken)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var arguments = syntaxFactsService.GetArgumentsForInvocationExpression(invocation);
var literalExpression = syntaxFactsService.GetExpressionOfArgument(arguments[0]) as TLiteralExpressionSyntax;
var text = literalExpression.GetFirstToken().ToString();
var syntaxGenerator = document.Project.LanguageServices.GetService<SyntaxGenerator>();
var expandedArguments = GetExpandedArguments(semanticModel, arguments, syntaxGenerator, syntaxFactsService);
var interpolatedString = GetInterpolatedString(text);
var newInterpolatedString = VisitArguments(expandedArguments, interpolatedString, syntaxFactsService);
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var newRoot = root.ReplaceNode(invocation, newInterpolatedString.WithTriviaFrom(invocation));
return document.WithSyntaxRoot(newRoot);
}
private ImmutableArray<TExpressionSyntax> GetExpandedArguments(
SemanticModel semanticModel,
SeparatedSyntaxList<TArgumentSyntax> arguments,
SyntaxGenerator syntaxGenerator,
ISyntaxFactsService syntaxFactsService)
{
var builder = ImmutableArray.CreateBuilder<TExpressionSyntax>();
for (int i = 1; i < arguments.Count; i++)
{
var argumentExpression = syntaxFactsService.GetExpressionOfArgument(arguments[i]);
var convertedType = semanticModel.GetTypeInfo(argumentExpression).ConvertedType;
if (convertedType == null)
{
builder.Add(syntaxFactsService.Parenthesize(argumentExpression) as TExpressionSyntax);
}
else
{
var castExpression = syntaxGenerator.CastExpression(convertedType, syntaxFactsService.Parenthesize(argumentExpression)).WithAdditionalAnnotations(Simplifier.Annotation);
builder.Add(castExpression as TExpressionSyntax);
}
}
var expandedArguments = builder.ToImmutable();
return expandedArguments;
}
private SyntaxNode VisitArguments(
ImmutableArray<TExpressionSyntax> expandedArguments,
SyntaxNode interpolatedString,
ISyntaxFactsService syntaxFactsService)
{
return interpolatedString.ReplaceNodes(syntaxFactsService.GetContentsOfInterpolatedString(interpolatedString), (oldNode, newNode) =>
{
var interpolationSyntaxNode = newNode;
if (interpolationSyntaxNode != null)
{
var literalExpression = syntaxFactsService.GetExpressionOfInterpolation(interpolationSyntaxNode) as TLiteralExpressionSyntax;
if (literalExpression != null && syntaxFactsService.IsNumericLiteralExpression(literalExpression))
{
int index;
if (int.TryParse(literalExpression.GetFirstToken().ValueText, out index))
{
if (index >= 0 && index < expandedArguments.Length)
{
return interpolationSyntaxNode.ReplaceNode(
syntaxFactsService.GetExpressionOfInterpolation(interpolationSyntaxNode),
syntaxFactsService.ConvertToSingleLine(expandedArguments[index], useElasticTrivia: true).WithAdditionalAnnotations(Formatter.Annotation));
}
}
}
}
return newNode;
});
}
private static bool ShouldIncludeFormatMethod(IMethodSymbol methodSymbol)
{
if (!methodSymbol.IsStatic)
{
return false;
}
if (methodSymbol.Parameters.Length == 0)
{
return false;
}
var firstParameter = methodSymbol.Parameters[0];
if (firstParameter?.Name != "format")
{
return false;
}
return true;
}
private static bool IsArgumentListNotPassingArrayToParams(
SyntaxNode expression,
ISymbol invocationSymbol,
ImmutableArray<IMethodSymbol> formatMethods,
SemanticModel semanticModel,
CancellationToken cancellationToken)
{
var formatMethodsAcceptingParamsArray = formatMethods
.Where(x => x.Parameters.Length > 1 && x.Parameters[1].Type.Kind == SymbolKind.ArrayType);
if (formatMethodsAcceptingParamsArray.Contains(invocationSymbol))
{
return semanticModel.GetTypeInfo(expression, cancellationToken).Type?.Kind != SymbolKind.ArrayType;
}
return true;
}
private class ConvertToInterpolatedStringCodeAction : CodeAction.DocumentChangeAction
{
public ConvertToInterpolatedStringCodeAction(string title, Func<CancellationToken, Task<Document>> createChangedDocument) :
base(title, createChangedDocument)
{
}
}
}
}
......@@ -17,5 +17,6 @@ internal static class PredefinedCodeRefactoringProviderNames
public const string InvertIf = "Invert If Code Action Provider";
public const string MoveDeclarationNearReference = "Move Declaration Near Reference Code Action Provider";
public const string SimplifyLambda = "Simplify Lambda Code Action Provider";
public const string ConvertToInterpolatedString = "Convert To Interpolated String Code Action Provider";
}
}
......@@ -146,6 +146,7 @@
<Compile Include="CodeRefactorings\CodeRefactoringContextExtensions.cs" />
<Compile Include="CodeRefactorings\CodeRefactoring.cs" />
<Compile Include="CodeRefactorings\CodeRefactoringService.cs" />
<Compile Include="CodeRefactorings\ConvertToInterpolatedString\AbstractConvertToInterpolatedStringRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\ExtractMethod\AbstractExtractMethodCodeRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\ICodeRefactoring.cs" />
<Compile Include="CodeRefactorings\ICodeRefactoringHelpersService.cs" />
......
......@@ -619,6 +619,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Convert to interpolated string.
/// </summary>
internal static string ConvertToInterpolatedString {
get {
return ResourceManager.GetString("ConvertToInterpolatedString", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Could not extract interface: The selection is not inside a class/interface/struct..
/// </summary>
......
......@@ -1031,4 +1031,7 @@ This version used in: {2}</value>
<data name="Property_cannot_safely_be_replaced_with_a_method_call" xml:space="preserve">
<value>Property cannot safely be replaced with a method call</value>
</data>
<data name="ConvertToInterpolatedString" xml:space="preserve">
<value>Convert to interpolated string</value>
</data>
</root>
\ No newline at end of file
......@@ -126,6 +126,7 @@
<Compile Include="CodeFixes\SimplifyTypeNames\SimplifyTypeNamesCodeFixProvider.vb" />
<Compile Include="CodeFixes\Spellcheck\VisualBasicSpellCheckCodeFixProvider.vb" />
<Compile Include="CodeFixes\Suppression\VisualBasicSuppressionCodeFixProvider.vb" />
<Compile Include="CodeRefactorings\ConvertToInterpolatedString\ConvertToInterpolatedStringRefactoringProvider.vb" />
<Compile Include="CodeRefactorings\EncapsulateField\EncapsulateFieldRefactoringProvider.vb" />
<Compile Include="CodeRefactorings\ExtractInterface\ExtractInterfaceCodeRefactoringProvider.vb" />
<Compile Include="CodeRefactorings\ExtractMethod\ExtractMethodCodeActionProvider.vb" />
......
Imports System.Composition
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.CodeRefactorings
Imports Microsoft.CodeAnalysis.VisualBasic
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Microsoft.CodeAnalysis.VisualBasic.SyntaxFactory
<ExportCodeRefactoringProvider(LanguageNames.VisualBasic, Name:=PredefinedCodeRefactoringProviderNames.ExtractMethod), [Shared]>
Partial Friend Class ConvertToInterpolatedStringRefactoringProvider
Inherits AbstractConvertToInterpolatedStringRefactoringProvider(Of InvocationExpressionSyntax, ExpressionSyntax, ArgumentSyntax, LiteralExpressionSyntax)
Protected Overrides Function GetInterpolatedString(text As String) As SyntaxNode
Return TryCast(ParseExpression("$" + text), InterpolatedStringExpressionSyntax)
End Function
End Class
......@@ -12,8 +12,14 @@ internal partial class SyntaxNodeExtensions
{
internal class SingleLineRewriter : CSharpSyntaxRewriter
{
private bool useElasticTrivia;
private bool _lastTokenEndedInWhitespace;
public SingleLineRewriter(bool useElasticTrivia)
{
this.useElasticTrivia = useElasticTrivia;
}
public override SyntaxToken VisitToken(SyntaxToken token)
{
if (_lastTokenEndedInWhitespace)
......@@ -22,12 +28,26 @@ public override SyntaxToken VisitToken(SyntaxToken token)
}
else if (token.LeadingTrivia.Count > 0)
{
token = token.WithLeadingTrivia(SyntaxFactory.Space);
if (useElasticTrivia)
{
token = token.WithLeadingTrivia(SyntaxFactory.ElasticSpace);
}
else
{
token = token.WithLeadingTrivia(SyntaxFactory.Space);
}
}
if (token.TrailingTrivia.Count > 0)
{
token = token.WithTrailingTrivia(SyntaxFactory.Space);
if (useElasticTrivia)
{
token = token.WithTrailingTrivia(SyntaxFactory.ElasticSpace);
}
else
{
token = token.WithTrailingTrivia(SyntaxFactory.Space);
}
_lastTokenEndedInWhitespace = true;
}
else
......
......@@ -372,7 +372,7 @@ public static bool IsReturnableConstruct(this SyntaxNode node)
return node.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia);
}
public static TNode ConvertToSingleLine<TNode>(this TNode node)
public static TNode ConvertToSingleLine<TNode>(this TNode node, bool useElasticTrivia = false)
where TNode : SyntaxNode
{
if (node == null)
......@@ -380,7 +380,7 @@ public static TNode ConvertToSingleLine<TNode>(this TNode node)
return node;
}
var rewriter = new SingleLineRewriter();
var rewriter = new SingleLineRewriter(useElasticTrivia);
return (TNode)rewriter.Visit(node);
}
......
......@@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Options;
using System.Linq;
namespace Microsoft.CodeAnalysis.CSharp.Formatting
{
......@@ -174,6 +175,12 @@ private void AddSpecificNodesSuppressOperations(List<SuppressOperation> list, Sy
AddSuppressWrappingIfOnSingleLineOperation(list, finallyClause.FinallyKeyword, finallyClause.Block.CloseBraceToken);
}
}
var interpolatedStringExpression = node as InterpolatedStringExpressionSyntax;
if (interpolatedStringExpression != null)
{
AddSuppressWrappingIfOnSingleLineOperation(list, interpolatedStringExpression.StringStartToken, interpolatedStringExpression.StringEndToken);
}
}
private void AddStatementExceptBlockSuppressOperations(List<SuppressOperation> list, SyntaxNode node)
......
......@@ -462,11 +462,16 @@ public bool IsLiteral(SyntaxToken token)
return false;
}
public bool IsStringLiteral(SyntaxToken token)
public bool IsStringLiteralOrInterpolatedStringLiteral(SyntaxToken token)
{
return token.IsKind(SyntaxKind.StringLiteralToken, SyntaxKind.InterpolatedStringTextToken);
}
public bool IsNumericLiteralExpression(SyntaxNode node)
{
return node?.IsKind(SyntaxKind.NumericLiteralExpression) == true;
}
public bool IsTypeNamedVarInVariableOrFieldDeclaration(SyntaxToken token, SyntaxNode parent)
{
var typedToken = token;
......@@ -566,6 +571,11 @@ public SyntaxNode GetExpressionOfConditionalMemberAccessExpression(SyntaxNode no
return (node as ConditionalAccessExpressionSyntax)?.Expression;
}
public SyntaxNode GetExpressionOfInterpolation(SyntaxNode node)
{
return (node as InterpolationSyntax)?.Expression;
}
public bool IsInStaticContext(SyntaxNode node)
{
return node.IsInStaticContext();
......@@ -671,9 +681,9 @@ public bool IsElementAccessExpression(SyntaxNode node)
return node.Kind() == SyntaxKind.ElementAccessExpression;
}
public SyntaxNode ConvertToSingleLine(SyntaxNode node)
public SyntaxNode ConvertToSingleLine(SyntaxNode node, bool useElasticTrivia = false)
{
return node.ConvertToSingleLine();
return node.ConvertToSingleLine(useElasticTrivia);
}
public SyntaxToken ToIdentifierToken(string name)
......@@ -1644,5 +1654,20 @@ public bool IsOperandOfIncrementOrDecrementExpression(SyntaxNode node)
{
return IsOperandOfIncrementExpression(node) || IsOperandOfDecrementExpression(node);
}
public SyntaxList<SyntaxNode> GetContentsOfInterpolatedString(SyntaxNode interpolatedString)
{
return ((interpolatedString as InterpolatedStringExpressionSyntax)?.Contents).Value;
}
public bool IsStringLiteral(SyntaxToken token)
{
return token.IsKind(SyntaxKind.StringLiteralToken);
}
public SeparatedSyntaxList<SyntaxNode> GetArgumentsForInvocationExpression(SyntaxNode invocationExpression)
{
return ((invocationExpression as InvocationExpressionSyntax)?.ArgumentList.Arguments).Value;
}
}
}
......@@ -27,7 +27,9 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsPreprocessorKeyword(SyntaxToken token);
bool IsHashToken(SyntaxToken token);
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);
......@@ -71,12 +73,14 @@ internal interface ISyntaxFactsService : ILanguageService
SyntaxNode GetExpressionOfMemberAccessExpression(SyntaxNode node);
SyntaxNode GetExpressionOfConditionalMemberAccessExpression(SyntaxNode node);
SyntaxNode GetExpressionOfArgument(SyntaxNode node);
SyntaxNode GetExpressionOfInterpolation(SyntaxNode node);
bool IsConditionalMemberAccessExpression(SyntaxNode node);
SyntaxNode GetNameOfAttribute(SyntaxNode node);
SyntaxToken GetIdentifierOfGenericName(SyntaxNode node);
RefKind GetRefKindOfArgument(SyntaxNode node);
void GetNameAndArityOfSimpleName(SyntaxNode node, out string name, out int arity);
SyntaxList<SyntaxNode> GetContentsOfInterpolatedString(SyntaxNode interpolatedString);
SeparatedSyntaxList<SyntaxNode> GetArgumentsForInvocationExpression(SyntaxNode invocationExpression);
bool IsUsingDirectiveName(SyntaxNode node);
bool IsGenericName(SyntaxNode node);
......@@ -139,7 +143,7 @@ internal interface ISyntaxFactsService : ILanguageService
SyntaxNode Parenthesize(SyntaxNode expression, bool includeElasticTrivia = true);
SyntaxNode ConvertToSingleLine(SyntaxNode node);
SyntaxNode ConvertToSingleLine(SyntaxNode node, bool useElasticTrivia = false);
SyntaxToken ToIdentifierToken(string name);
......
......@@ -416,7 +416,7 @@ private static async Task AddLocationsToRenameInStringsAsync(Document document,
var renameStringsAndPositions = root
.DescendantTokens()
.Where(t => syntaxFactsService.IsStringLiteral(t) && t.Span.Length >= renameTextLength)
.Where(t => syntaxFactsService.IsStringLiteralOrInterpolatedStringLiteral(t) && t.Span.Length >= renameTextLength)
.Select(t => Tuple.Create(t.ToString(), t.Span.Start, t.Span));
if (renameStringsAndPositions.Any())
......
......@@ -12,23 +12,29 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Friend Class SingleLineRewriter
Inherits VisualBasicSyntaxRewriter
Private useElasticTrivia As Boolean
Private _lastTokenEndedInWhitespace As Boolean
Private Shared ReadOnly s_space As SyntaxTriviaList = SyntaxTriviaList.Create(SyntaxFactory.WhitespaceTrivia(" "))
Public Sub New(Optional useElasticTrivia As Boolean = False)
Me.useElasticTrivia = useElasticTrivia
End Sub
Public Overrides Function VisitToken(token As SyntaxToken) As SyntaxToken
If _lastTokenEndedInWhitespace Then
token = token.WithLeadingTrivia(Enumerable.Empty(Of SyntaxTrivia)())
ElseIf token.LeadingTrivia.Count > 0 Then
token = token.WithLeadingTrivia(s_space)
End If
#If False Then
If token.Kind = SyntaxKind.StatementTerminatorToken Then
token = Syntax.Token(token.LeadingTrivia, SyntaxKind.StatementTerminatorToken, token.TrailingTrivia, ":")
If useElasticTrivia Then
token = token.WithLeadingTrivia(SyntaxFactory.ElasticSpace)
Else
token = token.WithLeadingTrivia(SyntaxFactory.Space)
End If
End If
#End If
If token.TrailingTrivia.Count > 0 Then
token = token.WithTrailingTrivia(s_space)
If useElasticTrivia Then
token = token.WithTrailingTrivia(SyntaxFactory.ElasticSpace)
Else
token = token.WithTrailingTrivia(SyntaxFactory.Space)
End If
_lastTokenEndedInWhitespace = True
Else
_lastTokenEndedInWhitespace = False
......
......@@ -284,12 +284,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
End Function
<Extension()>
Public Function ConvertToSingleLine(Of TNode As SyntaxNode)(node As TNode) As TNode
Public Function ConvertToSingleLine(Of TNode As SyntaxNode)(node As TNode, Optional useElasticTrivia As Boolean = False) As TNode
If node Is Nothing Then
Return node
End If
Dim rewriter = New SingleLineRewriter()
Dim rewriter = New SingleLineRewriter(useElasticTrivia)
Return DirectCast(rewriter.Visit(node), TNode)
End Function
......
......@@ -409,10 +409,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return False
End Function
Public Function IsStringLiteral(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsStringLiteral
Public Function IsStringLiteralOrInterpolatedStringLiteral(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsStringLiteralOrInterpolatedStringLiteral
Return token.IsKind(SyntaxKind.StringLiteralToken, SyntaxKind.InterpolatedStringTextToken)
End Function
Public Function IsNumericLiteralExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsNumericLiteralExpression
Return If(node Is Nothing, False, node.IsKind(SyntaxKind.NumericLiteralExpression))
End Function
Public Function IsBindableToken(token As Microsoft.CodeAnalysis.SyntaxToken) As Boolean Implements ISyntaxFactsService.IsBindableToken
Return Me.IsWord(token) OrElse
Me.IsLiteral(token) OrElse
......@@ -444,6 +448,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return TryCast(node, ConditionalAccessExpressionSyntax)?.Expression
End Function
Public Function GetExpressionOfInterpolation(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.GetExpressionOfInterpolation
Return TryCast(node, InterpolationSyntax)?.Expression
End Function
Public Function IsInNamespaceOrTypeContext(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsInNamespaceOrTypeContext
Return SyntaxFacts.IsInNamespaceOrTypeContext(node)
End Function
......@@ -553,10 +561,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return node.Kind = SyntaxKind.InvocationExpression OrElse node.Kind = SyntaxKind.DictionaryAccessExpression
End Function
Public Function ConvertToSingleLine(node As SyntaxNode) As SyntaxNode Implements ISyntaxFactsService.ConvertToSingleLine
Return node.ConvertToSingleLine()
End Function
Public Function ToIdentifierToken(name As String) As SyntaxToken Implements ISyntaxFactsService.ToIdentifierToken
Return name.ToIdentifierToken()
End Function
......@@ -1319,5 +1323,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Public Function IsOperandOfIncrementOrDecrementExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsOperandOfIncrementOrDecrementExpression
Return False
End Function
Public Function GetContentsOfInterpolatedString(interpolatedString As SyntaxNode) As SyntaxList(Of SyntaxNode) Implements ISyntaxFactsService.GetContentsOfInterpolatedString
Return (TryCast(interpolatedString, InterpolatedStringExpressionSyntax)?.Contents).Value
End Function
Public Function IsStringLiteral(token As SyntaxToken) As Boolean Implements ISyntaxFactsService.IsStringLiteral
Return token.IsKind(SyntaxKind.StringLiteralToken)
End Function
Public Function GetArgumentsForInvocationExpression(invocationExpression As SyntaxNode) As SeparatedSyntaxList(Of SyntaxNode) Implements ISyntaxFactsService.GetArgumentsForInvocationExpression
Return (TryCast(invocationExpression, InvocationExpressionSyntax)?.ArgumentList.Arguments).Value
End Function
Public Function ConvertToSingleLine(node As SyntaxNode, Optional useElasticTrivia As Boolean = False) As SyntaxNode Implements ISyntaxFactsService.ConvertToSingleLine
Return node.ConvertToSingleLine(useElasticTrivia)
End Function
End Class
End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册