Skip to content

Commit

Permalink
String.Concat refactoring: preserve linebreaks, fixes #23
Browse files Browse the repository at this point in the history
- Additionally determine whether to use a normal or verbatim string based on whether a verbatim string is used in the source that has either linebreaks or quotes inside
  • Loading branch information
Suchiman committed Aug 23, 2017
1 parent 42036e3 commit 7926e1a
Show file tree
Hide file tree
Showing 5 changed files with 370 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -781,6 +781,133 @@ public static void Test()
VerifyCSharpFix(src, fixtest);
}

[TestMethod]
public void TestStringConcatWithLineBreaks()
{
string src = @"
using Serilog;
class TypeName
{
public static void Test()
{
string name = null;
Log.Verbose(""Hello World\nName: '"" + name + ""'\n"");
}
}";

var expected = new DiagnosticResult
{
Id = "Serilog004",
Message = String.Format("MessageTemplate argument {0} is not constant", @"""Hello World\nName: '"" + name + ""'\n"""),
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 9, 21, 37)
}
};
VerifyCSharpDiagnostic(src, expected);

var fixtest = @"
using Serilog;
class TypeName
{
public static void Test()
{
string name = null;
Log.Verbose(""Hello World\nName: '{Name}'\n"", name);
}
}";
VerifyCSharpFix(src, fixtest);
}

[TestMethod]
public void TestStringConcatWithVerbatim()
{
string src = @"
using Serilog;
class TypeName
{
public static void Test()
{
string name = null;
Log.Verbose(@""Hello World
Name: '"" + name + ""'\r\n"");
}
}";

var expected = new DiagnosticResult
{
Id = "Serilog004",
Message = String.Format("MessageTemplate argument {0} is not constant", @"@""Hello World
Name: '"" + name + ""'\r\n"""),
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 9, 21, 40)
}
};
VerifyCSharpDiagnostic(src, expected);

var fixtest = @"
using Serilog;
class TypeName
{
public static void Test()
{
string name = null;
Log.Verbose(@""Hello World
Name: '{Name}'
"", name);
}
}";
VerifyCSharpFix(src, fixtest);
}

[TestMethod]
public void TestStringConcatWithVerbatimIgnored()
{
string src = @"
using Serilog;
class TypeName
{
public static void Test()
{
string name = null;
Log.Verbose(""Hello World\nName: '"" + name + @""'"");
}
}";

var expected = new DiagnosticResult
{
Id = "Serilog004",
Message = String.Format("MessageTemplate argument {0} is not constant", @"""Hello World\nName: '"" + name + @""'"""),
Severity = DiagnosticSeverity.Warning,
Locations = new[]
{
new DiagnosticResultLocation("Test0.cs", 9, 21, 36)
}
};
VerifyCSharpDiagnostic(src, expected);

var fixtest = @"
using Serilog;
class TypeName
{
public static void Test()
{
string name = null;
Log.Verbose(""Hello World\nName: '{Name}'"", name);
}
}";
VerifyCSharpFix(src, fixtest);
}

[TestMethod]
public void TestStringConcatComplex()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,17 @@ void FindExpressions(ExpressionSyntax exp)
}
FindExpressions(stringConcat);

var sb = new StringBuilder("$\"");
var sb = new StringBuilder();
var replacements = new List<string>();
bool shouldUseVerbatim = false;
int argumentPosition = 0;
foreach (var child in concatExpressions)
{
switch (child)
{
case LiteralExpressionSyntax literal:
sb.Append(literal.Token.ValueText);
shouldUseVerbatim |= literal.Token.Text.StartsWith("@", System.StringComparison.Ordinal) && ContainsQuotesOrLineBreaks(literal.Token.ValueText);
break;
case ExpressionSyntax exp:

Expand All @@ -76,10 +78,51 @@ void FindExpressions(ExpressionSyntax exp)
break;
}
}
sb.Append("\"");

format = (InterpolatedStringExpressionSyntax)SyntaxFactory.ParseExpression(sb.ToString());
if (shouldUseVerbatim)
{
for (int i = 0; i < sb.Length; i++)
{
if (IsForbiddenInVerbatimString(sb[i]))
{
shouldUseVerbatim = false;
break;
}
}
}

var text = ObjectDisplay.FormatLiteral(sb.ToString(), useQuotes: true, escapeNonPrintable: !shouldUseVerbatim);

format = (InterpolatedStringExpressionSyntax)SyntaxFactory.ParseExpression("$" + text);
expressions = concatExpressions.Where(x => !(x is LiteralExpressionSyntax)).ToList();
}

private static bool IsForbiddenInVerbatimString(char c)
{
switch (c)
{
case '\a':
case '\b':
case '\f':
case '\v':
case '\0':
return true;
}

return false;
}

private static bool ContainsQuotesOrLineBreaks(string s)
{
foreach (char c in s)
{
if (c == '\r' || c == '\n' || c == '"')
{
return true;
}
}

return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,12 @@ private static async Task<Document> InlineFormatAndArgumentsIntoLoggerStatementA
var loggerArguments = logger.ArgumentList.Arguments;
var argumentIndex = loggerArguments.IndexOf(x => x.Expression == originalTemplateExpression);

var sb = new StringBuilder("\"");
var sb = new StringBuilder();
if (format.StringStartToken.ValueText.Contains("@"))
{
sb.Append('@');
}
sb.Append('"');

var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken);
Expand Down Expand Up @@ -153,7 +158,7 @@ private static async Task<Document> InlineFormatAndArgumentsIntoLoggerStatementA
}
}

sb.Append("\"");
sb.Append('"');
var messageTemplate = SyntaxFactory.Argument(SyntaxFactory.ParseExpression(sb.ToString()));

var seperatedSyntax = loggerArguments.Replace(loggerArguments[argumentIndex], messageTemplate);
Expand Down
Loading

0 comments on commit 7926e1a

Please sign in to comment.