Skip to content

Commit

Permalink
Caller identification does not handle all arguments using new (#1794)
Browse files Browse the repository at this point in the history
* Caller identification does not handle certain "new" constructs
  • Loading branch information
dennisdoomen committed Jan 30, 2022
1 parent d2d02a8 commit 6bb4d46
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 9 deletions.
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+)");
}

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)
* Resolve an issue preventing `HaveAccessModifier` from correctly recognizing internal interfaces and enums - [#1793](https://github.com/fluentassertions/fluentassertions/issues/1793)

### Fixes (Extensibility)
Expand Down

0 comments on commit 6bb4d46

Please sign in to comment.