-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fixed incorrect offer to interpolate string if used string.Format with named arguments. #20832
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
using Microsoft.CodeAnalysis.Simplification; | ||
using Microsoft.CodeAnalysis.Formatting; | ||
using Microsoft.CodeAnalysis.CodeRefactorings; | ||
using System.Collections.Generic; | ||
|
||
namespace Microsoft.CodeAnalysis.ConvertToInterpolatedString | ||
{ | ||
|
@@ -80,7 +81,7 @@ private bool TryFindInvocation( | |
var arguments = syntaxFactsService.GetArgumentsOfInvocationExpression(invocation); | ||
if (arguments.Count >= 2) | ||
{ | ||
var firstArgumentExpression = syntaxFactsService.GetExpressionOfArgument(arguments[0]) as TLiteralExpressionSyntax; | ||
var firstArgumentExpression = syntaxFactsService.GetExpressionOfArgument(GetFormatArgument(arguments, syntaxFactsService)) as TLiteralExpressionSyntax; | ||
if (firstArgumentExpression != null && syntaxFactsService.IsStringLiteral(firstArgumentExpression.GetFirstToken())) | ||
{ | ||
invocationSymbol = semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol; | ||
|
@@ -106,7 +107,7 @@ private bool IsArgumentListCorrect( | |
CancellationToken cancellationToken) | ||
{ | ||
var arguments = nullableArguments.Value; | ||
var firstExpression = syntaxFactsService.GetExpressionOfArgument(arguments[0]) as TLiteralExpressionSyntax; | ||
var firstExpression = syntaxFactsService.GetExpressionOfArgument(GetFormatArgument(arguments, syntaxFactsService)) as TLiteralExpressionSyntax; | ||
if (arguments.Count >= 2 && | ||
firstExpression != null && | ||
syntaxFactsService.IsStringLiteral(firstExpression.GetFirstToken())) | ||
|
@@ -116,7 +117,7 @@ private bool IsArgumentListCorrect( | |
// string[] args; | ||
// String.Format("{0}{1}{2}", args); | ||
return IsArgumentListNotPassingArrayToParams( | ||
syntaxFactsService.GetExpressionOfArgument(arguments[1]), | ||
syntaxFactsService.GetExpressionOfArgument(GetParamsArgument(arguments, syntaxFactsService)), | ||
invocationSymbol, | ||
formatMethods, | ||
semanticModel, | ||
|
@@ -126,7 +127,6 @@ private bool IsArgumentListCorrect( | |
return false; | ||
} | ||
|
||
|
||
private async Task<Document> CreateInterpolatedString( | ||
TInvocationExpressionSyntax invocation, | ||
Document document, | ||
|
@@ -135,7 +135,7 @@ private async Task<Document> CreateInterpolatedString( | |
{ | ||
var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); | ||
var arguments = syntaxFactsService.GetArgumentsOfInvocationExpression(invocation); | ||
var literalExpression = syntaxFactsService.GetExpressionOfArgument(arguments[0]) as TLiteralExpressionSyntax; | ||
var literalExpression = syntaxFactsService.GetExpressionOfArgument(GetFormatArgument(arguments, syntaxFactsService)) as TLiteralExpressionSyntax; | ||
var text = literalExpression.GetFirstToken().ToString(); | ||
var syntaxGenerator = document.Project.LanguageServices.GetService<SyntaxGenerator>(); | ||
var expandedArguments = GetExpandedArguments(semanticModel, arguments, syntaxGenerator, syntaxFactsService); | ||
|
@@ -146,6 +146,31 @@ private async Task<Document> CreateInterpolatedString( | |
return document.WithSyntaxRoot(newRoot); | ||
} | ||
|
||
private string GetArgumentName(TArgumentSyntax argument, ISyntaxFactsService syntaxFactsService) | ||
=> syntaxFactsService.GetNameForArgument(argument); | ||
|
||
private SyntaxNode GetParamsArgument(SeparatedSyntaxList<TArgumentSyntax> arguments, ISyntaxFactsService syntaxFactsService) | ||
{ | ||
var i = arguments.IndexOf(argument => GetArgumentName(argument, syntaxFactsService) == StringFormatArguments.ArgsArgumentName); | ||
return i >= 0 ? arguments[i] : arguments[1]; | ||
} | ||
|
||
private TArgumentSyntax GetFormatArgument(SeparatedSyntaxList<TArgumentSyntax> arguments, ISyntaxFactsService syntaxFactsService) | ||
{ | ||
var index = arguments.IndexOf(argument => GetArgumentName(argument, syntaxFactsService) == StringFormatArguments.FormatArgumentName); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How will this work in a case-insensitive language like Visual Basic? |
||
return index >= 0 ? arguments[index] : arguments[0]; | ||
} | ||
|
||
private TArgumentSyntax GetArgument(SeparatedSyntaxList<TArgumentSyntax> arguments, int index, ISyntaxFactsService syntaxFactsService) | ||
{ | ||
if (arguments.Count > 4) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. needs explanation. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if argument more than 4 it means that it is used the following overload There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i meant: needs explanation in the code (i.e. add a comment) :) |
||
{ | ||
return arguments[index]; | ||
} | ||
|
||
return arguments.FirstOrDefault(argument => GetArgumentName(argument, syntaxFactsService) == StringFormatArguments.ParamsArgumentNames[index]) ?? arguments[index]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How will this work in a case-insensitive language like Visual Basic? |
||
} | ||
|
||
private ImmutableArray<TExpressionSyntax> GetExpandedArguments( | ||
SemanticModel semanticModel, | ||
SeparatedSyntaxList<TArgumentSyntax> arguments, | ||
|
@@ -155,7 +180,7 @@ private ImmutableArray<TExpressionSyntax> GetExpandedArguments( | |
var builder = ArrayBuilder<TExpressionSyntax>.GetInstance(); | ||
for (int i = 1; i < arguments.Count; i++) | ||
{ | ||
var argumentExpression = syntaxFactsService.GetExpressionOfArgument(arguments[i]); | ||
var argumentExpression = syntaxFactsService.GetExpressionOfArgument(GetArgument(arguments, i, syntaxFactsService)); | ||
var convertedType = semanticModel.GetTypeInfo(argumentExpression).ConvertedType; | ||
if (convertedType == null) | ||
{ | ||
|
@@ -214,7 +239,7 @@ private static bool ShouldIncludeFormatMethod(IMethodSymbol methodSymbol) | |
} | ||
|
||
var firstParameter = methodSymbol.Parameters[0]; | ||
if (firstParameter?.Name != "format") | ||
if (firstParameter?.Name != StringFormatArguments.FormatArgumentName) | ||
{ | ||
return false; | ||
} | ||
|
@@ -246,5 +271,19 @@ public ConvertToInterpolatedStringCodeAction(string title, Func<CancellationToke | |
{ | ||
} | ||
} | ||
|
||
private static class StringFormatArguments | ||
{ | ||
public const string FormatArgumentName = "format"; | ||
|
||
public const string ArgsArgumentName = "args"; | ||
|
||
public static readonly Dictionary<int, string> ParamsArgumentNames = new Dictionary<int, string> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 If you are just going to index into it (which the code currently is doing), we can benefit from both immutability and performance by changing this to public static readonly ImmutableArray<string> ParamsArgumentNames =
ImmutableArray.Create("", "arg0", "arg1", "arg2"); |
||
{ | ||
[1] = "arg0", | ||
[2] = "arg1", | ||
[3] = "arg2" | ||
}; | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will this work in a case-insensitive language like Visual Basic?