Skip to content
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

Caller identification does not handle all arguments using new #1794

Merged
merged 3 commits into from Jan 30, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -4,13 +4,30 @@ namespace FluentAssertions.CallerIdentification
{
internal class AddNonEmptySymbolParsingStrategy : IParsingStrategy
{
private Mode mode = Mode.RemoveAllWhitespace;
private char? precedingSymbol;

public ParsingState Parse(char symbol, StringBuilder statement)
{
if (!char.IsWhiteSpace(symbol))
{
statement.Append(symbol);
mode = Mode.RemoveSuperfluousWhitespace;
}
else if (mode is Mode.RemoveSuperfluousWhitespace)
{
if (precedingSymbol.HasValue && !char.IsWhiteSpace(precedingSymbol.Value))
{
statement.Append(symbol);
}
}
else
{
// skip the rest
}

precedingSymbol = symbol;

return ParsingState.GoToNextSymbol;
}

Expand All @@ -21,6 +38,21 @@ public bool IsWaitingForContextEnd()

public void NotifyEndOfLineReached()
{
// Assume all new lines start with whitespace
mode = Mode.RemoveAllWhitespace;
}

private enum Mode
{
/// <summary>
/// Remove all whitespace until we find a non-whitespace character
/// </summary>
RemoveAllWhitespace,

/// <summary>
/// Only keep one whitespace character if more than one follow each other.
/// </summary>
RemoveSuperfluousWhitespace,
}
}
}
Expand Up @@ -4,7 +4,7 @@ namespace FluentAssertions.CallerIdentification
{
internal class AwaitParsingStrategy : IParsingStrategy
{
private const string KeywordToSkip = "await";
private const string KeywordToSkip = "await ";

public ParsingState Parse(char symbol, StringBuilder statement)
{
Expand Down
6 changes: 3 additions & 3 deletions Src/FluentAssertions/CallerIdentifier.cs
Expand Up @@ -173,7 +173,7 @@ private static string ExtractVariableNameFrom(StackFrame frame)
{
Logger(statement);
if (!IsBooleanLiteral(statement) && !IsNumeric(statement) && !IsStringLiteral(statement) &&
!UsesNewKeyword(statement))
!StartsWithNewKeyword(statement))
{
caller = statement;
}
Expand Down Expand Up @@ -233,9 +233,9 @@ private static string GetSourceCodeStatementFrom(StackFrame frame, StreamReader
return sb.ToString();
}

private static bool UsesNewKeyword(string candidate)
private static bool StartsWithNewKeyword(string candidate)
{
return Regex.IsMatch(candidate, @"new(?:\s?\[|\s?\{|\s\w+)");
return Regex.IsMatch(candidate, @"(^|s+)new(?:\s?\[|\s?\{|\s\w+)");
dennisdoomen marked this conversation as resolved.
Show resolved Hide resolved
}

private static bool IsStringLiteral(string candidate)
Expand Down
82 changes: 77 additions & 5 deletions Tests/FluentAssertions.Specs/Execution/CallerIdentifierSpecs.cs
@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
using FluentAssertions;
using FluentAssertions.Execution;
Expand Down Expand Up @@ -158,7 +160,7 @@ public void When_the_caller_contains_multiple_members_it_should_include_them_all

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected foo.GetFoo(test1).GetFooStatic(\"test\"+2).GetFoo(foo.Field) to be <null>*");
.WithMessage("Expected foo.GetFoo(test1).GetFooStatic(\"test\" + 2).GetFoo(foo.Field) to be <null>*");
}

[Fact]
Expand All @@ -181,7 +183,7 @@ public void When_the_caller_contains_multiple_members_across_multiple_lines_it_s

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected foo.GetFoo(test1).GetFooStatic(\"test\"+2).GetFoo(foo.Field) to be <null>*");
.WithMessage("Expected foo.GetFoo(test1).GetFooStatic(\"test\" + 2).GetFoo(foo.Field) to be <null>*");
}

[Fact]
Expand Down Expand Up @@ -256,7 +258,7 @@ public void When_arguments_contain_verbatim_string_it_should_include_that_to_the

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected foo.BarMethod(@\"test\",argument2:$@\"test2\",argument3:@$\"test3\") to be <null>*");
.WithMessage("Expected foo.BarMethod(@\"test\", argument2: $@\"test2\", argument3: @$\"test3\") to be <null>*");
}

[Fact]
Expand Down Expand Up @@ -302,7 +304,7 @@ public void When_arguments_contain_concatenations_it_should_include_that_to_the_

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected foo.BarMethod(\"1\"+2) to be <null>*");
.WithMessage("Expected foo.BarMethod(\"1\" + 2) to be <null>*");
}

[Fact]
Expand All @@ -319,7 +321,7 @@ public void When_arguments_contain_multi_line_concatenations_it_should_include_t

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected foo.BarMethod(\"abc\"+\"def\") to be <null>*");
.WithMessage("Expected foo.BarMethod(\"abc\"+ \"def\") to be <null>*");
}

[Fact]
Expand Down Expand Up @@ -468,6 +470,76 @@ public async Task Caller_identification_should_also_work_for_statements_followin
act.Should().Throw<XunitException>()
.WithMessage("*someText*", "it should capture the variable name");
}

[Fact]
public void A_method_taking_an_array_initializer_is_an_identifier()
{
// Arrange
var foo = new Foo();

// Act
Action act = () => foo.GetFoo(new[] { 1, 2, 3 }.Sum() + "")
.Should()
.BeNull();

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected foo.GetFoo(new[] { 1, 2, 3 }.Sum() + \"\") to be <null>*");
}

[Fact]
public void A_method_taking_a_target_typed_new_expression_is_an_identifier()
{
// Arrange
var foo = new Foo();

// Act
Action act = () => foo.GetFoo(new('a', 10))
.Should()
.BeNull();

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected foo.GetFoo(new('a', 10)) to be <null>*");
}

[Fact]
public void A_method_taking_a_list_is_an_identifier()
{
// Arrange
var foo = new Foo();

// Act
Action act = () => foo.GetFoo(new List<int> { 1, 2, 3 }.Sum() + "")
.Should()
.BeNull();

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected foo.GetFoo(new List<int> { 1, 2, 3 }*");
}

[Fact]
public void An_array_initializer_preceding_an_assertion_is_not_an_identifier()
{
// Act
Action act = () => new[] { 1, 2, 3 }.Should().BeEmpty();

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected collection to be empty*");
}

[Fact]
public void An_object_initializer_preceding_an_assertion_is_not_an_identifier()
{
// Act
Action act = () => new { Property = "blah" }.Should().BeNull();

// Assert
act.Should().Throw<XunitException>()
.WithMessage("Expected object to be*");
}
}

[SuppressMessage("The name of a C# element does not begin with an upper-case letter", "SA1300")]
Expand Down
1 change: 1 addition & 0 deletions docs/_pages/releases.md
Expand Up @@ -14,6 +14,7 @@ sidebar:

### Fixes
* Improved the documentation on `BeLowerCased` and `BeUpperCased` for strings with non-alphabetic characters - [#1792](https://github.com/fluentassertions/fluentassertions/pull/1792)
* Caller identification does not handle all arguments using `new` - [#1794](https://github.com/fluentassertions/fluentassertions/pull/1794)

### Fixes (Extensibility)
* Fixed a continuation issue when using `ClearExpectation` - [#1791](https://github.com/fluentassertions/fluentassertions/pull/1791)
Expand Down