diff --git a/src/Build.UnitTests/Scanner_Tests.cs b/src/Build.UnitTests/Scanner_Tests.cs
index 518af020622..7843e239d6c 100644
--- a/src/Build.UnitTests/Scanner_Tests.cs
+++ b/src/Build.UnitTests/Scanner_Tests.cs
@@ -6,6 +6,7 @@
using Microsoft.Build.Evaluation;
using Microsoft.Build.Exceptions;
using Microsoft.Build.Utilities;
+using Shouldly;
using Xunit;
@@ -67,11 +68,7 @@ public void ErrorPosition()
///
private void AdvanceToScannerError(Scanner lexer)
{
- while (true)
- {
- if (!lexer.Advance()) break;
- if (lexer.IsNext(Token.TokenType.EndOfInput)) break;
- }
+ while (lexer.Advance() && !lexer.IsNext(Token.TokenType.EndOfInput));
}
///
@@ -104,16 +101,32 @@ public void IllFormedProperty()
///
/// Tests the space errors case
///
- [Fact]
- public void SpaceProperty()
+ [Theory]
+ [InlineData("$(x )")]
+ [InlineData("$( x)")]
+ [InlineData("$([MSBuild]::DoSomething($(space ))")]
+ [InlineData("$([MSBuild]::DoSomething($(_space ))")]
+ public void SpaceProperty(string pattern)
{
- Scanner lexer = new Scanner("$(x )", ParserOptions.AllowProperties);
+ Scanner lexer = new Scanner(pattern, ParserOptions.AllowProperties);
AdvanceToScannerError(lexer);
Assert.Equal("IllFormedPropertySpaceInCondition", lexer.GetErrorResource());
+ }
- lexer = new Scanner("$( x)", ParserOptions.AllowProperties);
+ ///
+ /// Tests the space not next to end so no errors case
+ ///
+ [Theory]
+ [InlineData("$(x.StartsWith( 'y' ))")]
+ [InlineData("$(x.StartsWith ('y'))")]
+ [InlineData("$( x.StartsWith( $(SpacelessProperty) ) )")]
+ [InlineData("$( x.StartsWith( $(_SpacelessProperty) ) )")]
+ [InlineData("$(x.StartsWith('Foo', StringComparison.InvariantCultureIgnoreCase))")]
+ public void SpaceInMiddleOfProperty(string pattern)
+ {
+ Scanner lexer = new Scanner(pattern, ParserOptions.AllowProperties);
AdvanceToScannerError(lexer);
- Assert.Equal("IllFormedPropertySpaceInCondition", lexer.GetErrorResource());
+ lexer._errorState.ShouldBeFalse();
}
[Fact]
diff --git a/src/Build/Evaluation/Conditionals/Scanner.cs b/src/Build/Evaluation/Conditionals/Scanner.cs
index 684d6dac5df..5d24ea0949b 100644
--- a/src/Build/Evaluation/Conditionals/Scanner.cs
+++ b/src/Build/Evaluation/Conditionals/Scanner.cs
@@ -27,7 +27,7 @@ internal sealed class Scanner
private string _expression;
private int _parsePoint;
private Token _lookahead;
- private bool _errorState;
+ internal bool _errorState;
private int _errorPosition;
// What we found instead of what we were looking for
private string _unexpectedlyFound = null;
@@ -321,8 +321,9 @@ private string ParsePropertyOrItemMetadata()
private static bool ScanForPropertyExpressionEnd(string expression, int index, out int indexResult)
{
int nestLevel = 0;
- bool whitespaceCheck = false;
-
+ bool whitespaceFound = false;
+ bool nonIdentifierCharacterFound = false;
+ indexResult = -1;
unsafe
{
fixed (char* pchar = expression)
@@ -333,17 +334,28 @@ private static bool ScanForPropertyExpressionEnd(string expression, int index, o
if (character == '(')
{
nestLevel++;
- whitespaceCheck = true;
}
else if (character == ')')
{
nestLevel--;
- whitespaceCheck = false;
}
- else if (whitespaceCheck && char.IsWhiteSpace(character) && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_10))
+ else if (char.IsWhiteSpace(character))
{
+ whitespaceFound = true;
indexResult = index;
- return false;
+ }
+ else if (!XmlUtilities.IsValidSubsequentElementNameCharacter(character))
+ {
+ nonIdentifierCharacterFound = true;
+ }
+
+ if (character == '$' && index < expression.Length - 1 && pchar[index + 1] == '(')
+ {
+ if (!ScanForPropertyExpressionEnd(expression, index + 1, out index))
+ {
+ indexResult = index;
+ return false;
+ }
}
// We have reached the end of the parenthesis nesting
@@ -351,6 +363,11 @@ private static bool ScanForPropertyExpressionEnd(string expression, int index, o
// If it is not then the calling code will determine that
if (nestLevel == 0)
{
+ if (whitespaceFound && !nonIdentifierCharacterFound && ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave16_10))
+ {
+ return false;
+ }
+
indexResult = index;
return true;
}