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; }