Skip to content

Commit f4399e6

Browse files
committed
Added LastOfType and LastOfTypeOrDefault.
1 parent d6a663a commit f4399e6

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

src/MrKWatkins.Ast.Tests/ChildrenTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,38 @@ public void FirstOfType()
522522
.WithMessage("Expected ANode to have a child of type CNode but found none.");
523523
}
524524

525+
[Test]
526+
public void LastOfTypeOrDefault()
527+
{
528+
var a = new ANode();
529+
var b1 = new BNode();
530+
var b2 = new BNode();
531+
532+
var parent = new ANode(b1, a, b2);
533+
534+
var @default = new CNode();
535+
536+
parent.Children.LastOfTypeOrDefault<ANode>().Should().BeSameAs(a);
537+
parent.Children.LastOfTypeOrDefault<BNode>().Should().BeSameAs(b2);
538+
parent.Children.LastOfTypeOrDefault(@default).Should().BeSameAs(@default);
539+
}
540+
541+
[Test]
542+
public void LastOfType()
543+
{
544+
var a = new ANode();
545+
var b1 = new BNode();
546+
var b2 = new BNode();
547+
548+
var parent = new ANode(b1, a, b2);
549+
550+
parent.Children.LastOfType<ANode>().Should().BeSameAs(a);
551+
parent.Children.LastOfType<BNode>().Should().BeSameAs(b2);
552+
parent.Children.Invoking(c => c.LastOfType<CNode>())
553+
.Should().Throw<InvalidOperationException>()
554+
.WithMessage("Expected ANode to have a child of type CNode but found none.");
555+
}
556+
525557
[Test]
526558
public void SingleOfTypeOrDefault()
527559
{

src/MrKWatkins.Ast/Children.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,39 @@ public TChild FirstOfType<TChild>()
283283
where TChild : TNode =>
284284
FirstOfTypeOrDefault<TChild>() ?? throw new InvalidOperationException($"Expected {parent.GetType().SimpleName()} to have a child of type {typeof(TChild).SimpleName()} but found none.");
285285

286+
/// <summary>
287+
/// Returns the last node in the collection of the specified type or a specified default if it doesn't contain any nodes of the specified type.
288+
/// </summary>
289+
/// <typeparam name="TChild">The type of the node to return.</typeparam>
290+
/// <param name="default">The default value to return if the collection does not contain any nodes of type <typeparamref name="TChild"/>.</param>
291+
/// <returns>The last node if it is of the specified type or <paramref name="default"/> if it doesn't contain any nodes of the specified type.</returns>
292+
[Pure]
293+
public TChild? LastOfTypeOrDefault<TChild>(TChild? @default = null)
294+
where TChild : TNode
295+
{
296+
// Manually iterating for performance. Looks like LINQ's Reverse() doesn't optimise for IList<T>.
297+
for (var f = Count - 1; f >= 0; f--)
298+
{
299+
if (nodes[f] is TChild child)
300+
{
301+
return child;
302+
}
303+
}
304+
305+
return @default;
306+
}
307+
308+
/// <summary>
309+
/// Returns the last node in the collection of the specified type or throws otherwise.
310+
/// </summary>
311+
/// <typeparam name="TChild">The type of the node to return.</typeparam>
312+
/// <returns>The last node if it is of the specified type.</returns>
313+
/// <exception cref="InvalidOperationException">If the collection doesn't contain any nodes of the specified type.</exception>
314+
[Pure]
315+
public TChild LastOfType<TChild>()
316+
where TChild : TNode =>
317+
LastOfTypeOrDefault<TChild>() ?? throw new InvalidOperationException($"Expected {parent.GetType().SimpleName()} to have a child of type {typeof(TChild).SimpleName()} but found none.");
318+
286319
/// <summary>
287320
/// Returns the only node in the collection of the specified type. Returns the specified default if there are no nodes in the collection of the
288321
/// specified type. Throws if there are multiple nodes in the collection of the specified type.

0 commit comments

Comments
 (0)