Skip to content

Commit 4589e62

Browse files
committed
Improving translation of nested blocks with multi-line, single-statement return values
1 parent 77db62b commit 4589e62

File tree

6 files changed

+157
-29
lines changed

6 files changed

+157
-29
lines changed

ReadableExpressions.UnitTests/WhenFormattingCode.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,7 @@ public void ShouldOnlyRemoveParenthesesIfNecessary()
477477

478478
var objectVariable = Expression.Variable(typeof(object), "o");
479479
var objectCastToInt = Expression.Convert(objectVariable, typeof(int));
480-
var intToStringMethod = typeof(int).GetMethods().First(m => m.Name == "ToString");
480+
var intToStringMethod = typeof(object).GetMethod("ToString");
481481
var intToStringCall = Expression.Call(objectCastToInt, intToStringMethod);
482482

483483
Expression<Func<string>> emptyString = () => string.Empty;

ReadableExpressions.UnitTests/WhenTranslatingAssignments.cs

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ public void ShouldTranslateAMultiLineCheckedSubtractionAssignment()
8282
const string EXPECTED = @"
8383
checked
8484
{
85-
i -=
85+
i -=
8686
{
8787
var one = Console.Read();
8888
var two = Console.Read();
@@ -305,7 +305,7 @@ public void ShouldAssignTheResultOfATryCatch()
305305
var translated = assignReadOrDefault.ToReadableString();
306306

307307
const string EXPECTED = @"
308-
i =
308+
i =
309309
{
310310
try
311311
{
@@ -500,7 +500,7 @@ public void ShouldTranslateANestedBlockAssignment()
500500
var translated = resultOneAssignment.ToReadableString();
501501

502502
const string EXPECTED = @"
503-
result =
503+
result =
504504
{
505505
var one = Console.Read();
506506
var two = Console.Read();
@@ -529,7 +529,7 @@ public void ShouldTranslateMultiStatementValueBlockAssignments()
529529
var translated = resultOneAssignment.ToReadableString();
530530

531531
const string EXPECTED = @"
532-
result =
532+
result =
533533
{
534534
List<int> ints;
535535
Console.Read();
@@ -573,7 +573,7 @@ public void ShouldTranslateSingleStatementValueBlockAssignments()
573573
var translated = resultOneAssignment.ToReadableString();
574574

575575
const string EXPECTED = @"
576-
result =
576+
result =
577577
{
578578
List<int> ints;
579579
return (ints == null)
@@ -599,6 +599,102 @@ public void ShouldTranslateSingleStatementValueBlockAssignments()
599599
Assert.AreEqual(EXPECTED.TrimStart(), translated);
600600
}
601601

602+
[TestMethod]
603+
public void ShouldTranslateAssignmentsOfNestedVariableBlocksWithATernaryReturnValue()
604+
{
605+
var objectVariable = Expression.Variable(typeof(object), "id");
606+
var objectValue = Expression.Variable(typeof(object), "value");
607+
var guidVariable = Expression.Variable(typeof(Guid), "guid");
608+
var guidValue = Expression.Variable(typeof(Guid), "guidValue");
609+
610+
var guidTryParseMethod = typeof(Guid)
611+
.GetMethods()
612+
.First(m => m.Name == "TryParse" && m.GetParameters().Length == 2);
613+
614+
var objectNotNull = Expression.NotEqual(objectVariable, Expression.Default(typeof(object)));
615+
var defaultGuid = Expression.Default(typeof(Guid));
616+
617+
var guidTryParse = Expression.Call(
618+
null,
619+
guidTryParseMethod,
620+
Expression.Condition(
621+
objectNotNull,
622+
Expression.Call(objectVariable, typeof(object).GetMethod("ToString")),
623+
Expression.Default(typeof(string))),
624+
guidValue);
625+
626+
var objectAsGuidOrDefault = Expression.Condition(guidTryParse, guidValue, defaultGuid);
627+
628+
var guidParseInnerBlock = Expression.Block(new[] { guidValue }, objectAsGuidOrDefault);
629+
630+
var guidParseOuterBlock = Expression.Block(
631+
new[] { objectVariable },
632+
Expression.Assign(objectVariable, objectValue),
633+
guidParseInnerBlock);
634+
635+
var guidAssignment = Expression.Assign(guidVariable, guidParseOuterBlock);
636+
637+
var translated = guidAssignment.ToReadableString();
638+
639+
const string EXPECTED = @"
640+
guid =
641+
{
642+
var id = value;
643+
Guid guidValue;
644+
return Guid.TryParse((id != null) ? id.ToString() : null, out guidValue) ? guidValue : default(Guid);
645+
}";
646+
Assert.AreEqual(EXPECTED.TrimStart(), translated);
647+
}
648+
649+
[TestMethod]
650+
public void ShouldTranslateAssignmentsOfNestedVariableBlocksWithANestedTernaryReturnValue()
651+
{
652+
var objectVariable = Expression.Variable(typeof(object), "id");
653+
var objectValue = Expression.Variable(typeof(object), "value");
654+
var guidVariable = Expression.Variable(typeof(Guid), "guid");
655+
var guidValue = Expression.Variable(typeof(Guid), "guidValue");
656+
657+
var guidTryParseMethod = typeof(Guid)
658+
.GetMethods()
659+
.First(m => m.Name == "TryParse" && m.GetParameters().Length == 2);
660+
661+
var guidTryParse = Expression.Call(
662+
null,
663+
guidTryParseMethod,
664+
Expression.Call(objectVariable, typeof(object).GetMethod("ToString")),
665+
guidValue);
666+
667+
var objectNotNull = Expression.NotEqual(objectVariable, Expression.Default(typeof(object)));
668+
var defaultGuid = Expression.Default(typeof(Guid));
669+
670+
var objectAsGuidOrDefault = Expression.Condition(
671+
objectNotNull,
672+
Expression.Condition(guidTryParse, guidValue, defaultGuid),
673+
defaultGuid);
674+
675+
var guidParseInnerBlock = Expression.Block(new[] { guidValue }, objectAsGuidOrDefault);
676+
677+
var guidParseOuterBlock = Expression.Block(
678+
new[] { objectVariable },
679+
Expression.Assign(objectVariable, objectValue),
680+
guidParseInnerBlock);
681+
682+
var guidAssignment = Expression.Assign(guidVariable, guidParseOuterBlock);
683+
684+
var translated = guidAssignment.ToReadableString();
685+
686+
const string EXPECTED = @"
687+
guid =
688+
{
689+
var id = value;
690+
Guid guidValue;
691+
return (id != null)
692+
? Guid.TryParse(id.ToString(), out guidValue) ? guidValue : default(Guid)
693+
: default(Guid);
694+
}";
695+
Assert.AreEqual(EXPECTED.TrimStart(), translated);
696+
}
697+
602698
private static Expression GetReturnStatementBlock(out ParameterExpression existingInts)
603699
{
604700
existingInts = Expression.Variable(typeof(List<int>), "ints");

ReadableExpressions/StringExtensions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ public static bool StartsWith(this string value, char character)
135135

136136
public static bool EndsWith(this string value, char character)
137137
{
138+
if (value == string.Empty)
139+
{
140+
return false;
141+
}
142+
138143
return value[value.Length - 1] == character;
139144
}
140145

ReadableExpressions/Translators/AssignmentExpressionTranslator.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,14 @@ internal string GetAssignment(
5858
? _defaultTranslator.Translate((DefaultExpression)value)
5959
: GetValueTranslation(value, context);
6060

61-
var assignment = $"{target} {symbol} {valueString}";
61+
var assignment = target + " " + symbol;
62+
63+
if (!valueString.StartsWithNewLine())
64+
{
65+
assignment += " ";
66+
}
67+
68+
assignment += valueString;
6269

6370
assignment = AdjustForCheckedAssignmentIfAppropriate(assignmentType, assignment);
6471

ReadableExpressions/Translators/BlockExpressionTranslator.cs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
namespace AgileObjects.ReadableExpressions.Translators
22
{
33
using System;
4+
using System.CodeDom;
45
using System.Collections.Generic;
56
using System.Linq;
67
using System.Linq.Expressions;
78
using Extensions;
9+
using Formatting;
810

911
internal class BlockExpressionTranslator : ExpressionTranslatorBase
1012
{
@@ -146,7 +148,19 @@ private static IEnumerable<string> ProcessBlockContents(IList<string> lines, Blo
146148
continue;
147149
}
148150

149-
yield return IncludeReturnStatement(block, lines) ? "return " + line : line;
151+
if (DoNotAddReturnStatement(block, lines))
152+
{
153+
yield return line;
154+
yield break;
155+
}
156+
157+
if (CodeBlock.IsSingleStatement(line.SplitToLines()))
158+
{
159+
yield return "return " + line;
160+
yield break;
161+
}
162+
163+
yield return CodeBlock.InsertReturnKeyword(line);
150164
}
151165
}
152166

@@ -168,9 +182,9 @@ private static bool IsMultiLineStatement(string line)
168182
.Any(l => !l.IsTerminated());
169183
}
170184

171-
private static bool IncludeReturnStatement(BlockExpression block, ICollection<string> lines)
185+
private static bool DoNotAddReturnStatement(BlockExpression block, ICollection<string> lines)
172186
{
173-
return (lines.Count != 1) && block.IsReturnable();
187+
return (lines.Count <= 1) || !block.IsReturnable();
174188
}
175189
}
176190
}

ReadableExpressions/Translators/Formatting/CodeBlock.cs

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public CodeBlock(
2020
IsASingleStatement = IsSingleStatement(_blockLines);
2121
}
2222

23-
private static bool IsSingleStatement(IList<string> blockLines)
23+
public static bool IsSingleStatement(IList<string> blockLines)
2424
{
2525
if (blockLines.Count != 1)
2626
{
@@ -140,12 +140,19 @@ private CodeBlock WithReturn()
140140
return this;
141141
}
142142

143-
return new CodeBlock(_expression, GetBlockLinesWithInsert());
143+
return new CodeBlock(_expression, GetBlockLinesWithReturnKeyword(_blockLines));
144144
}
145145

146-
private string[] GetBlockLinesWithInsert()
146+
public static string InsertReturnKeyword(string multiLineStatement)
147147
{
148-
var blockInfo = new BlockInfo(_blockLines);
148+
var lines = multiLineStatement.SplitToLines();
149+
150+
return string.Join(Environment.NewLine, GetBlockLinesWithReturnKeyword(lines));
151+
}
152+
153+
private static string[] GetBlockLinesWithReturnKeyword(string[] lines)
154+
{
155+
var blockInfo = new BlockInfo(lines);
149156

150157
return blockInfo.GetBlockLinesWithReturnKeyword();
151158
}
@@ -168,48 +175,47 @@ private void AddSemiColonIfRequired()
168175
private class BlockInfo
169176
{
170177
private readonly string[] _blockLines;
171-
private readonly int _lastNonIndentedStatementIndex;
178+
private readonly string _lastNonIndentedStatement;
179+
private readonly string[] _lastStatementLines;
172180
private readonly string _lastNonIndentedLine;
173-
private readonly int _lastNonIndentedLineIndex;
174181

175182
public BlockInfo(string[] blockLines)
176183
{
177184
_blockLines = blockLines;
178-
var lastNonIndentedStatement = blockLines.Last(line => line.IsNotIndented());
179-
_lastNonIndentedStatementIndex = Array.IndexOf(blockLines, lastNonIndentedStatement);
185+
_lastNonIndentedStatement = blockLines.Last(line => line.IsNotIndented());
180186

181-
var lastStatementLines = lastNonIndentedStatement.SplitToLines();
182-
_lastNonIndentedLine = lastStatementLines.Last(line => line.IsNotIndented());
183-
_lastNonIndentedLineIndex = Array.IndexOf(lastStatementLines, _lastNonIndentedLine);
187+
_lastStatementLines = _lastNonIndentedStatement.SplitToLines();
188+
_lastNonIndentedLine = _lastStatementLines.Last(line => line.IsNotIndented());
184189
}
185190

186191
public bool LastLineHasReturnKeyword =>
187192
_lastNonIndentedLine.StartsWith("return ", StringComparison.Ordinal);
188193

189194
public string[] GetBlockLinesWithReturnKeyword()
190195
{
191-
var lastNonIndentedStatement = _blockLines.Last(line => line.IsNotIndented());
192-
193196
var updatedBlockLines = new List<string>();
194197

195-
var preLastStatementStatements = _blockLines
196-
.Take(_blockLines.Length - (_lastNonIndentedStatementIndex + 1));
198+
var lastNonIndentedStatementIndex = Array.IndexOf(_blockLines, _lastNonIndentedStatement);
199+
var preLastStatementStatements = _blockLines.Take(lastNonIndentedStatementIndex);
197200

198201
updatedBlockLines.AddRange(preLastStatementStatements);
199202

200-
var lastStatementLines = lastNonIndentedStatement.SplitToLines();
203+
var lastNonIndentedLineIndex = Array.IndexOf(_lastStatementLines, _lastNonIndentedLine);
201204

202-
var preLastLineLines = lastStatementLines
203-
.Take(_lastNonIndentedLineIndex)
205+
var preLastLineLines = _lastStatementLines
206+
.Take(lastNonIndentedLineIndex)
204207
.ToArray();
205208

206-
var postLastLineLines = lastStatementLines
209+
var postLastLineLines = _lastStatementLines
207210
.Skip(preLastLineLines.Length + 1);
208211

209212
updatedBlockLines.AddRange(preLastLineLines);
210213
updatedBlockLines.Add("return " + _lastNonIndentedLine);
211214
updatedBlockLines.AddRange(postLastLineLines);
212215

216+
var finalIndentedLines = _blockLines.Skip(lastNonIndentedStatementIndex + 1);
217+
updatedBlockLines.AddRange(finalIndentedLines);
218+
213219
return updatedBlockLines.ToArray();
214220
}
215221
}

0 commit comments

Comments
 (0)