提交 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 ...@@ -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(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.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.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> 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.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 virtual Microsoft.CodeAnalysis.Diagnostics.AnalysisContext.RegisterOperationBlockAction(System.Action<Microsoft.CodeAnalysis.Diagnostics.OperationBlockAnalysisContext> action) -> void
......
...@@ -573,5 +573,15 @@ IEnumerator IEnumerable.GetEnumerator() ...@@ -573,5 +573,15 @@ IEnumerator IEnumerable.GetEnumerator()
return SpecializedCollections.EmptyEnumerator<TNode>(); 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 @@ ...@@ -145,6 +145,7 @@
<Compile Include="Classification\TotalClassifierTests.cs" /> <Compile Include="Classification\TotalClassifierTests.cs" />
<Compile Include="Classification\TotalClassifierTests_Dynamic.cs" /> <Compile Include="Classification\TotalClassifierTests_Dynamic.cs" />
<Compile Include="CodeActions\AbstractCSharpCodeActionTest.cs" /> <Compile Include="CodeActions\AbstractCSharpCodeActionTest.cs" />
<Compile Include="CodeActions\ConvertToInterpolatedString\ConvertToInterpolatedStringTests.cs" />
<Compile Include="CodeActions\EncapsulateField\EncapsulateFieldTests.cs" /> <Compile Include="CodeActions\EncapsulateField\EncapsulateFieldTests.cs" />
<Compile Include="CodeActions\ExtractMethod\ExtractMethodTests.cs" /> <Compile Include="CodeActions\ExtractMethod\ExtractMethodTests.cs" />
<Compile Include="CodeActions\GenerateDefaultConstructors\GenerateDefaultConstructorsTests.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) ...@@ -455,11 +455,6 @@ public bool ContainsInMemberBody(SyntaxNode node, TextSpan span)
throw new NotImplementedException(); 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) public SyntaxToken FindTokenOnLeftOfPosition(SyntaxNode node, int position, bool includeSkipped = true, bool includeDirectives = false, bool includeDocumentationComments = false)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
...@@ -807,7 +802,7 @@ public bool IsStartOfUnicodeEscapeSequence(char c) ...@@ -807,7 +802,7 @@ public bool IsStartOfUnicodeEscapeSequence(char c)
throw new NotImplementedException(); throw new NotImplementedException();
} }
public bool IsStringLiteral(SyntaxToken token) public bool IsStringLiteralOrInterpolatedStringLiteral(SyntaxToken token)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
...@@ -961,6 +956,36 @@ public bool IsOperandOfIncrementExpression(SyntaxNode node) ...@@ -961,6 +956,36 @@ public bool IsOperandOfIncrementExpression(SyntaxNode node)
{ {
throw new NotImplementedException(); 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 ...@@ -37,6 +37,7 @@ public static class Features
public const string CodeActionsChangeToAsync = "CodeActions.ChangeToAsync"; public const string CodeActionsChangeToAsync = "CodeActions.ChangeToAsync";
public const string CodeActionsChangeToIEnumerable = "CodeActions.ChangeToIEnumerable"; public const string CodeActionsChangeToIEnumerable = "CodeActions.ChangeToIEnumerable";
public const string CodeActionsChangeToYield = "CodeActions.ChangeToYield"; public const string CodeActionsChangeToYield = "CodeActions.ChangeToYield";
public const string CodeActionsConvertToInterpolatedString = "CodeActions.ConvertToInterpolatedString";
public const string CodeActionsConvertToIterator = "CodeActions.CodeActionsConvertToIterator"; public const string CodeActionsConvertToIterator = "CodeActions.CodeActionsConvertToIterator";
public const string CodeActionsCorrectExitContinue = "CodeActions.CorrectExitContinue"; public const string CodeActionsCorrectExitContinue = "CodeActions.CorrectExitContinue";
public const string CodeActionsCorrectFunctionReturnType = "CodeActions.CorrectFunctionReturnType"; public const string CodeActionsCorrectFunctionReturnType = "CodeActions.CorrectFunctionReturnType";
......
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
<Compile Include="Classification\SemanticClassifierTests.vb" /> <Compile Include="Classification\SemanticClassifierTests.vb" />
<Compile Include="Classification\SyntacticClassifierTests.vb" /> <Compile Include="Classification\SyntacticClassifierTests.vb" />
<Compile Include="CodeActions\AbstractVisualBasicCodeActionTest.vb" /> <Compile Include="CodeActions\AbstractVisualBasicCodeActionTest.vb" />
<Compile Include="CodeActions\ConvertToInterpolatedString\ConvertToInterpolatedStringTests.vb" />
<Compile Include="CodeActions\EncapsulateField\EncapsulateFieldTests.vb" /> <Compile Include="CodeActions\EncapsulateField\EncapsulateFieldTests.vb" />
<Compile Include="CodeActions\ExtractMethod\ExtractMethodTests.vb" /> <Compile Include="CodeActions\ExtractMethod\ExtractMethodTests.vb" />
<Compile Include="CodeActions\GenerateDefaultConstructors\GenerateDefaultConstructorsTests.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() ...@@ -454,10 +454,6 @@ End Class]]></a>.Value.NormalizeLineEndings()
Throw New NotImplementedException() Throw New NotImplementedException()
End Function 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 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() Throw New NotImplementedException()
End Function End Function
...@@ -733,7 +729,7 @@ End Class]]></a>.Value.NormalizeLineEndings() ...@@ -733,7 +729,7 @@ End Class]]></a>.Value.NormalizeLineEndings()
Throw New NotImplementedException() Throw New NotImplementedException()
End Function 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() Throw New NotImplementedException()
End Function End Function
...@@ -852,6 +848,30 @@ End Class]]></a>.Value.NormalizeLineEndings() ...@@ -852,6 +848,30 @@ End Class]]></a>.Value.NormalizeLineEndings()
Public Function IsOperandOfIncrementOrDecrementExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsOperandOfIncrementOrDecrementExpression Public Function IsOperandOfIncrementOrDecrementExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsOperandOfIncrementOrDecrementExpression
Throw New NotImplementedException() Throw New NotImplementedException()
End Function 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 Class End Class
End Namespace End Namespace
...@@ -92,6 +92,7 @@ ...@@ -92,6 +92,7 @@
<Compile Include="CodeFixes\Suppression\CSharpSuppressionCodeFixProvider.cs" /> <Compile Include="CodeFixes\Suppression\CSharpSuppressionCodeFixProvider.cs" />
<Compile Include="CodeFixes\TypeStyle\UseExplicitTypeCodeFixProvider.cs" /> <Compile Include="CodeFixes\TypeStyle\UseExplicitTypeCodeFixProvider.cs" />
<Compile Include="CodeFixes\TypeStyle\UseImplicitTypeCodeFixProvider.cs" /> <Compile Include="CodeFixes\TypeStyle\UseImplicitTypeCodeFixProvider.cs" />
<Compile Include="CodeRefactorings\ConvertToInterpolatedString\ConvertToInterpolatedStringRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\EncapsulateField\EncapsulateFieldCodeRefactoringProvider.cs" /> <Compile Include="CodeRefactorings\EncapsulateField\EncapsulateFieldCodeRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\ExtractInterface\ExtractInterfaceCodeRefactoringProvider.cs" /> <Compile Include="CodeRefactorings\ExtractInterface\ExtractInterfaceCodeRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\ExtractMethod\ExtractMethodCodeRefactoringProvider.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 ...@@ -17,5 +17,6 @@ internal static class PredefinedCodeRefactoringProviderNames
public const string InvertIf = "Invert If Code Action Provider"; public const string InvertIf = "Invert If Code Action Provider";
public const string MoveDeclarationNearReference = "Move Declaration Near Reference 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 SimplifyLambda = "Simplify Lambda Code Action Provider";
public const string ConvertToInterpolatedString = "Convert To Interpolated String Code Action Provider";
} }
} }
...@@ -146,6 +146,7 @@ ...@@ -146,6 +146,7 @@
<Compile Include="CodeRefactorings\CodeRefactoringContextExtensions.cs" /> <Compile Include="CodeRefactorings\CodeRefactoringContextExtensions.cs" />
<Compile Include="CodeRefactorings\CodeRefactoring.cs" /> <Compile Include="CodeRefactorings\CodeRefactoring.cs" />
<Compile Include="CodeRefactorings\CodeRefactoringService.cs" /> <Compile Include="CodeRefactorings\CodeRefactoringService.cs" />
<Compile Include="CodeRefactorings\ConvertToInterpolatedString\AbstractConvertToInterpolatedStringRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\ExtractMethod\AbstractExtractMethodCodeRefactoringProvider.cs" /> <Compile Include="CodeRefactorings\ExtractMethod\AbstractExtractMethodCodeRefactoringProvider.cs" />
<Compile Include="CodeRefactorings\ICodeRefactoring.cs" /> <Compile Include="CodeRefactorings\ICodeRefactoring.cs" />
<Compile Include="CodeRefactorings\ICodeRefactoringHelpersService.cs" /> <Compile Include="CodeRefactorings\ICodeRefactoringHelpersService.cs" />
......
...@@ -619,6 +619,15 @@ internal class FeaturesResources { ...@@ -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> /// <summary>
/// Looks up a localized string similar to Could not extract interface: The selection is not inside a class/interface/struct.. /// Looks up a localized string similar to Could not extract interface: The selection is not inside a class/interface/struct..
/// </summary> /// </summary>
......
...@@ -1031,4 +1031,7 @@ This version used in: {2}</value> ...@@ -1031,4 +1031,7 @@ This version used in: {2}</value>
<data name="Property_cannot_safely_be_replaced_with_a_method_call" xml:space="preserve"> <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> <value>Property cannot safely be replaced with a method call</value>
</data> </data>
<data name="ConvertToInterpolatedString" xml:space="preserve">
<value>Convert to interpolated string</value>
</data>
</root> </root>
\ No newline at end of file
...@@ -126,6 +126,7 @@ ...@@ -126,6 +126,7 @@
<Compile Include="CodeFixes\SimplifyTypeNames\SimplifyTypeNamesCodeFixProvider.vb" /> <Compile Include="CodeFixes\SimplifyTypeNames\SimplifyTypeNamesCodeFixProvider.vb" />
<Compile Include="CodeFixes\Spellcheck\VisualBasicSpellCheckCodeFixProvider.vb" /> <Compile Include="CodeFixes\Spellcheck\VisualBasicSpellCheckCodeFixProvider.vb" />
<Compile Include="CodeFixes\Suppression\VisualBasicSuppressionCodeFixProvider.vb" /> <Compile Include="CodeFixes\Suppression\VisualBasicSuppressionCodeFixProvider.vb" />
<Compile Include="CodeRefactorings\ConvertToInterpolatedString\ConvertToInterpolatedStringRefactoringProvider.vb" />
<Compile Include="CodeRefactorings\EncapsulateField\EncapsulateFieldRefactoringProvider.vb" /> <Compile Include="CodeRefactorings\EncapsulateField\EncapsulateFieldRefactoringProvider.vb" />
<Compile Include="CodeRefactorings\ExtractInterface\ExtractInterfaceCodeRefactoringProvider.vb" /> <Compile Include="CodeRefactorings\ExtractInterface\ExtractInterfaceCodeRefactoringProvider.vb" />
<Compile Include="CodeRefactorings\ExtractMethod\ExtractMethodCodeActionProvider.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 ...@@ -12,8 +12,14 @@ internal partial class SyntaxNodeExtensions
{ {
internal class SingleLineRewriter : CSharpSyntaxRewriter internal class SingleLineRewriter : CSharpSyntaxRewriter
{ {
private bool useElasticTrivia;
private bool _lastTokenEndedInWhitespace; private bool _lastTokenEndedInWhitespace;
public SingleLineRewriter(bool useElasticTrivia)
{
this.useElasticTrivia = useElasticTrivia;
}
public override SyntaxToken VisitToken(SyntaxToken token) public override SyntaxToken VisitToken(SyntaxToken token)
{ {
if (_lastTokenEndedInWhitespace) if (_lastTokenEndedInWhitespace)
...@@ -22,12 +28,26 @@ public override SyntaxToken VisitToken(SyntaxToken token) ...@@ -22,12 +28,26 @@ public override SyntaxToken VisitToken(SyntaxToken token)
} }
else if (token.LeadingTrivia.Count > 0) 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) 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; _lastTokenEndedInWhitespace = true;
} }
else else
......
...@@ -372,7 +372,7 @@ public static bool IsReturnableConstruct(this SyntaxNode node) ...@@ -372,7 +372,7 @@ public static bool IsReturnableConstruct(this SyntaxNode node)
return node.WithLeadingTrivia(leadingTrivia).WithTrailingTrivia(trailingTrivia); 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 where TNode : SyntaxNode
{ {
if (node == null) if (node == null)
...@@ -380,7 +380,7 @@ public static TNode ConvertToSingleLine<TNode>(this TNode node) ...@@ -380,7 +380,7 @@ public static TNode ConvertToSingleLine<TNode>(this TNode node)
return node; return node;
} }
var rewriter = new SingleLineRewriter(); var rewriter = new SingleLineRewriter(useElasticTrivia);
return (TNode)rewriter.Visit(node); return (TNode)rewriter.Visit(node);
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting.Rules; using Microsoft.CodeAnalysis.Formatting.Rules;
using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Options;
using System.Linq;
namespace Microsoft.CodeAnalysis.CSharp.Formatting namespace Microsoft.CodeAnalysis.CSharp.Formatting
{ {
...@@ -174,6 +175,12 @@ private void AddSpecificNodesSuppressOperations(List<SuppressOperation> list, Sy ...@@ -174,6 +175,12 @@ private void AddSpecificNodesSuppressOperations(List<SuppressOperation> list, Sy
AddSuppressWrappingIfOnSingleLineOperation(list, finallyClause.FinallyKeyword, finallyClause.Block.CloseBraceToken); 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) private void AddStatementExceptBlockSuppressOperations(List<SuppressOperation> list, SyntaxNode node)
......
...@@ -462,11 +462,16 @@ public bool IsLiteral(SyntaxToken token) ...@@ -462,11 +462,16 @@ public bool IsLiteral(SyntaxToken token)
return false; return false;
} }
public bool IsStringLiteral(SyntaxToken token) public bool IsStringLiteralOrInterpolatedStringLiteral(SyntaxToken token)
{ {
return token.IsKind(SyntaxKind.StringLiteralToken, SyntaxKind.InterpolatedStringTextToken); 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) public bool IsTypeNamedVarInVariableOrFieldDeclaration(SyntaxToken token, SyntaxNode parent)
{ {
var typedToken = token; var typedToken = token;
...@@ -566,6 +571,11 @@ public SyntaxNode GetExpressionOfConditionalMemberAccessExpression(SyntaxNode no ...@@ -566,6 +571,11 @@ public SyntaxNode GetExpressionOfConditionalMemberAccessExpression(SyntaxNode no
return (node as ConditionalAccessExpressionSyntax)?.Expression; return (node as ConditionalAccessExpressionSyntax)?.Expression;
} }
public SyntaxNode GetExpressionOfInterpolation(SyntaxNode node)
{
return (node as InterpolationSyntax)?.Expression;
}
public bool IsInStaticContext(SyntaxNode node) public bool IsInStaticContext(SyntaxNode node)
{ {
return node.IsInStaticContext(); return node.IsInStaticContext();
...@@ -671,9 +681,9 @@ public bool IsElementAccessExpression(SyntaxNode node) ...@@ -671,9 +681,9 @@ public bool IsElementAccessExpression(SyntaxNode node)
return node.Kind() == SyntaxKind.ElementAccessExpression; 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) public SyntaxToken ToIdentifierToken(string name)
...@@ -1644,5 +1654,20 @@ public bool IsOperandOfIncrementOrDecrementExpression(SyntaxNode node) ...@@ -1644,5 +1654,20 @@ public bool IsOperandOfIncrementOrDecrementExpression(SyntaxNode node)
{ {
return IsOperandOfIncrementExpression(node) || IsOperandOfDecrementExpression(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 ...@@ -27,7 +27,9 @@ internal interface ISyntaxFactsService : ILanguageService
bool IsPreprocessorKeyword(SyntaxToken token); bool IsPreprocessorKeyword(SyntaxToken token);
bool IsHashToken(SyntaxToken token); bool IsHashToken(SyntaxToken token);
bool IsLiteral(SyntaxToken token); bool IsLiteral(SyntaxToken token);
bool IsStringLiteralOrInterpolatedStringLiteral(SyntaxToken token);
bool IsStringLiteral(SyntaxToken token); bool IsStringLiteral(SyntaxToken token);
bool IsNumericLiteralExpression(SyntaxNode node);
bool IsTypeNamedVarInVariableOrFieldDeclaration(SyntaxToken token, SyntaxNode parent); bool IsTypeNamedVarInVariableOrFieldDeclaration(SyntaxToken token, SyntaxNode parent);
bool IsTypeNamedDynamic(SyntaxToken token, SyntaxNode parent); bool IsTypeNamedDynamic(SyntaxToken token, SyntaxNode parent);
...@@ -71,12 +73,14 @@ internal interface ISyntaxFactsService : ILanguageService ...@@ -71,12 +73,14 @@ internal interface ISyntaxFactsService : ILanguageService
SyntaxNode GetExpressionOfMemberAccessExpression(SyntaxNode node); SyntaxNode GetExpressionOfMemberAccessExpression(SyntaxNode node);
SyntaxNode GetExpressionOfConditionalMemberAccessExpression(SyntaxNode node); SyntaxNode GetExpressionOfConditionalMemberAccessExpression(SyntaxNode node);
SyntaxNode GetExpressionOfArgument(SyntaxNode node); SyntaxNode GetExpressionOfArgument(SyntaxNode node);
SyntaxNode GetExpressionOfInterpolation(SyntaxNode node);
bool IsConditionalMemberAccessExpression(SyntaxNode node); bool IsConditionalMemberAccessExpression(SyntaxNode node);
SyntaxNode GetNameOfAttribute(SyntaxNode node); SyntaxNode GetNameOfAttribute(SyntaxNode node);
SyntaxToken GetIdentifierOfGenericName(SyntaxNode node); SyntaxToken GetIdentifierOfGenericName(SyntaxNode node);
RefKind GetRefKindOfArgument(SyntaxNode node); RefKind GetRefKindOfArgument(SyntaxNode node);
void GetNameAndArityOfSimpleName(SyntaxNode node, out string name, out int arity); 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 IsUsingDirectiveName(SyntaxNode node);
bool IsGenericName(SyntaxNode node); bool IsGenericName(SyntaxNode node);
...@@ -139,7 +143,7 @@ internal interface ISyntaxFactsService : ILanguageService ...@@ -139,7 +143,7 @@ internal interface ISyntaxFactsService : ILanguageService
SyntaxNode Parenthesize(SyntaxNode expression, bool includeElasticTrivia = true); SyntaxNode Parenthesize(SyntaxNode expression, bool includeElasticTrivia = true);
SyntaxNode ConvertToSingleLine(SyntaxNode node); SyntaxNode ConvertToSingleLine(SyntaxNode node, bool useElasticTrivia = false);
SyntaxToken ToIdentifierToken(string name); SyntaxToken ToIdentifierToken(string name);
......
...@@ -416,7 +416,7 @@ private static async Task AddLocationsToRenameInStringsAsync(Document document, ...@@ -416,7 +416,7 @@ private static async Task AddLocationsToRenameInStringsAsync(Document document,
var renameStringsAndPositions = root var renameStringsAndPositions = root
.DescendantTokens() .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)); .Select(t => Tuple.Create(t.ToString(), t.Span.Start, t.Span));
if (renameStringsAndPositions.Any()) if (renameStringsAndPositions.Any())
......
...@@ -12,23 +12,29 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions ...@@ -12,23 +12,29 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
Friend Class SingleLineRewriter Friend Class SingleLineRewriter
Inherits VisualBasicSyntaxRewriter Inherits VisualBasicSyntaxRewriter
Private useElasticTrivia As Boolean
Private _lastTokenEndedInWhitespace 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 Public Overrides Function VisitToken(token As SyntaxToken) As SyntaxToken
If _lastTokenEndedInWhitespace Then If _lastTokenEndedInWhitespace Then
token = token.WithLeadingTrivia(Enumerable.Empty(Of SyntaxTrivia)()) token = token.WithLeadingTrivia(Enumerable.Empty(Of SyntaxTrivia)())
ElseIf token.LeadingTrivia.Count > 0 Then ElseIf token.LeadingTrivia.Count > 0 Then
token = token.WithLeadingTrivia(s_space) If useElasticTrivia Then
End If token = token.WithLeadingTrivia(SyntaxFactory.ElasticSpace)
Else
#If False Then token = token.WithLeadingTrivia(SyntaxFactory.Space)
If token.Kind = SyntaxKind.StatementTerminatorToken Then End If
token = Syntax.Token(token.LeadingTrivia, SyntaxKind.StatementTerminatorToken, token.TrailingTrivia, ":")
End If End If
#End If
If token.TrailingTrivia.Count > 0 Then 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 _lastTokenEndedInWhitespace = True
Else Else
_lastTokenEndedInWhitespace = False _lastTokenEndedInWhitespace = False
......
...@@ -284,12 +284,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions ...@@ -284,12 +284,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.Extensions
End Function End Function
<Extension()> <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 If node Is Nothing Then
Return node Return node
End If End If
Dim rewriter = New SingleLineRewriter() Dim rewriter = New SingleLineRewriter(useElasticTrivia)
Return DirectCast(rewriter.Visit(node), TNode) Return DirectCast(rewriter.Visit(node), TNode)
End Function End Function
......
...@@ -409,10 +409,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ...@@ -409,10 +409,14 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return False Return False
End Function 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) Return token.IsKind(SyntaxKind.StringLiteralToken, SyntaxKind.InterpolatedStringTextToken)
End Function 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 Public Function IsBindableToken(token As Microsoft.CodeAnalysis.SyntaxToken) As Boolean Implements ISyntaxFactsService.IsBindableToken
Return Me.IsWord(token) OrElse Return Me.IsWord(token) OrElse
Me.IsLiteral(token) OrElse Me.IsLiteral(token) OrElse
...@@ -444,6 +448,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ...@@ -444,6 +448,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return TryCast(node, ConditionalAccessExpressionSyntax)?.Expression Return TryCast(node, ConditionalAccessExpressionSyntax)?.Expression
End Function 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 Public Function IsInNamespaceOrTypeContext(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsInNamespaceOrTypeContext
Return SyntaxFacts.IsInNamespaceOrTypeContext(node) Return SyntaxFacts.IsInNamespaceOrTypeContext(node)
End Function End Function
...@@ -553,10 +561,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ...@@ -553,10 +561,6 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return node.Kind = SyntaxKind.InvocationExpression OrElse node.Kind = SyntaxKind.DictionaryAccessExpression Return node.Kind = SyntaxKind.InvocationExpression OrElse node.Kind = SyntaxKind.DictionaryAccessExpression
End Function 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 Public Function ToIdentifierToken(name As String) As SyntaxToken Implements ISyntaxFactsService.ToIdentifierToken
Return name.ToIdentifierToken() Return name.ToIdentifierToken()
End Function End Function
...@@ -1319,5 +1323,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic ...@@ -1319,5 +1323,21 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Public Function IsOperandOfIncrementOrDecrementExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsOperandOfIncrementOrDecrementExpression Public Function IsOperandOfIncrementOrDecrementExpression(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsOperandOfIncrementOrDecrementExpression
Return False Return False
End Function 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 Class
End Namespace End Namespace
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册