diff --git a/src/Build.UnitTests/Evaluation/ExpressionShredder_Tests.cs b/src/Build.UnitTests/Evaluation/ExpressionShredder_Tests.cs index 5e451ce45c5..4817ddd049d 100644 --- a/src/Build.UnitTests/Evaluation/ExpressionShredder_Tests.cs +++ b/src/Build.UnitTests/Evaluation/ExpressionShredder_Tests.cs @@ -571,11 +571,12 @@ private static void VerifyAgainstCanonicalResults(string test, IDictionary actua public void ExtractItemVectorTransform1() { string expression = "@(i->'%(Meta0)'->'%(Filename)'->Substring($(Val)))"; - List expressions = ExpressionShredder.GetReferencedItemExpressions(expression); + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions = ExpressionShredder.GetReferencedItemExpressions(expression); + Assert.True(expressions.MoveNext()); - ExpressionShredder.ItemExpressionCapture capture = expressions[0]; + ExpressionShredder.ItemExpressionCapture capture = expressions.Current; - Assert.Single(expressions); + Assert.False(expressions.MoveNext()); Assert.Null(capture.Separator); Assert.Equal("i", capture.ItemType); Assert.Equal("%(Meta0)", capture.Captures[0].Value); @@ -591,43 +592,46 @@ public void ExtractItemVectorTransform1() [Fact] public void ItemExpressionMedleyRegressionTestAgainstOldRegex() { - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; foreach (string expression in _medleyTests) { expressions = ExpressionShredder.GetReferencedItemExpressions(expression); MatchCollection matches = s_itemVectorPattern.Matches(expression); + int expressionCount = 0; - if (expressions != null) + while (expressions.MoveNext()) { - Assert.Equal(matches.Count, expressions.Count); + Match match = matches[expressionCount]; + ExpressionShredder.ItemExpressionCapture capture = expressions.Current; - for (int n = 0; n < matches.Count; n++) - { - Match match = matches[n]; - ExpressionShredder.ItemExpressionCapture capture = expressions[n]; - - Assert.Equal(match.Value, capture.Value); + Assert.Equal(match.Value, capture.Value); - Group transformGroup = match.Groups["TRANSFORM"]; + Group transformGroup = match.Groups["TRANSFORM"]; - if (capture.Captures != null) - { - for (int i = 0; i < transformGroup.Captures.Count; i++) - { - Assert.Equal(transformGroup.Captures[i].Value, capture.Captures[i].Value); - } - } - else + if (capture.Captures != null) + { + for (int i = 0; i < transformGroup.Captures.Count; i++) { - Assert.Equal(0, transformGroup.Length); + Assert.Equal(transformGroup.Captures[i].Value, capture.Captures[i].Value); } } + else + { + Assert.Equal(0, transformGroup.Length); + } + + ++expressionCount; } - else + + if (expressionCount == 0) { Assert.Empty(matches); } + else + { + Assert.Equal(matches.Count, expressionCount); + } } } @@ -635,24 +639,25 @@ public void ItemExpressionMedleyRegressionTestAgainstOldRegex() public void ExtractItemVectorExpressionsSingleExpressionInvalid1() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; expression = "@(type->'%($(a)), '%'')"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - Assert.Null(expressions); + Assert.False(expressions.MoveNext()); } [Fact] public void ExtractItemVectorExpressionsSingleExpression1() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo)"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Null(capture.Separator); Assert.Null(capture.Captures); Assert.Equal("Foo", capture.ItemType); @@ -663,13 +668,14 @@ public void ExtractItemVectorExpressionsSingleExpression1() public void ExtractItemVectorExpressionsSingleExpression2() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo, ';')"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Null(capture.Captures); Assert.Equal(";", capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -680,13 +686,14 @@ public void ExtractItemVectorExpressionsSingleExpression2() public void ExtractItemVectorExpressionsSingleExpression3() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Fullpath)')"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Single(capture.Captures); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -698,13 +705,14 @@ public void ExtractItemVectorExpressionsSingleExpression3() public void ExtractItemVectorExpressionsSingleExpression4() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Fullpath)',';')"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Single(capture.Captures); Assert.Equal(";", capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -716,13 +724,14 @@ public void ExtractItemVectorExpressionsSingleExpression4() public void ExtractItemVectorExpressionsSingleExpression5() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->Bar(a,b))"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Single(capture.Captures); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -736,13 +745,14 @@ public void ExtractItemVectorExpressionsSingleExpression5() public void ExtractItemVectorExpressionsSingleExpression6() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->Bar(a,b),';')"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Single(capture.Captures); Assert.Equal(";", capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -756,13 +766,14 @@ public void ExtractItemVectorExpressionsSingleExpression6() public void ExtractItemVectorExpressionsSingleExpression7() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->Metadata('Meta0')->Directory())"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -778,13 +789,14 @@ public void ExtractItemVectorExpressionsSingleExpression7() public void ExtractItemVectorExpressionsSingleExpression8() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->Metadata('Meta0')->Directory(),';')"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Equal(";", capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -800,13 +812,14 @@ public void ExtractItemVectorExpressionsSingleExpression8() public void ExtractItemVectorExpressionsSingleExpression9() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Fullpath)'->Directory(), '|')"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Equal("|", capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -822,13 +835,14 @@ public void ExtractItemVectorExpressionsSingleExpression9() public void ExtractItemVectorExpressionsSingleExpression10() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Fullpath)'->Directory(),';')"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Equal(";", capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -844,13 +858,14 @@ public void ExtractItemVectorExpressionsSingleExpression10() public void ExtractItemVectorExpressionsSingleExpression11() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'$(SOMEPROP)%(Fullpath)')"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Single(capture.Captures); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -863,13 +878,14 @@ public void ExtractItemVectorExpressionsSingleExpression11() public void ExtractItemVectorExpressionsSingleExpression12() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Filename)'->Substring($(Val), $(Boo)))"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -885,13 +901,14 @@ public void ExtractItemVectorExpressionsSingleExpression12() public void ExtractItemVectorExpressionsSingleExpression13() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Filename)'->Substring(\"AA\", 'BB', `cc`))"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -907,13 +924,14 @@ public void ExtractItemVectorExpressionsSingleExpression13() public void ExtractItemVectorExpressionsSingleExpression14() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Filename)'->Substring('()', $(Boo), ')('))"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -929,13 +947,14 @@ public void ExtractItemVectorExpressionsSingleExpression14() public void ExtractItemVectorExpressionsSingleExpression15() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Filename)'->Substring(`()`, $(Boo), \"AA\"))"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -951,13 +970,14 @@ public void ExtractItemVectorExpressionsSingleExpression15() public void ExtractItemVectorExpressionsSingleExpression16() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Filename)'->Substring(`()`, $(Boo), \")(\"))"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -973,13 +993,14 @@ public void ExtractItemVectorExpressionsSingleExpression16() public void ExtractItemVectorExpressionsSingleExpression17() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Foo->'%(Filename)'->Substring(\"()\", $(Boo), `)(`))"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Single(expressions); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -995,15 +1016,19 @@ public void ExtractItemVectorExpressionsSingleExpression17() public void ExtractItemVectorExpressionsMultipleExpression1() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; + ExpressionShredder.ItemExpressionCapture firstCapture; ExpressionShredder.ItemExpressionCapture capture; expression = "@(Bar);@(Foo->'%(Filename)'->Substring(\"()\", $(Boo), `)(`))"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[1]; - Assert.Equal(2, expressions.Count); - Assert.Equal("Bar", expressions[0].ItemType); - Assert.Null(expressions[0].Captures); + Assert.True(expressions.MoveNext()); + firstCapture = expressions.Current; + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.False(expressions.MoveNext()); + Assert.Equal("Bar", firstCapture.ItemType); + Assert.Null(firstCapture.Captures); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -1019,39 +1044,47 @@ public void ExtractItemVectorExpressionsMultipleExpression1() public void ExtractItemVectorExpressionsMultipleExpression2() { string expression; - List expressions; - ExpressionShredder.ItemExpressionCapture capture; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; + ExpressionShredder.ItemExpressionCapture firstCapture; + ExpressionShredder.ItemExpressionCapture secondCapture; expression = "@(Foo->'%(Filename)'->Substring(\"()\", $(Boo), `)(`));@(Bar)"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Equal(2, expressions.Count); - Assert.Equal("Bar", expressions[1].ItemType); - Assert.Null(expressions[1].Captures); - Assert.Equal(2, capture.Captures.Count); - Assert.Null(capture.Separator); - Assert.Equal("Foo", capture.ItemType); - Assert.Equal("%(Filename)", capture.Captures[0].Value); - Assert.Null(capture.Captures[0].FunctionName); - Assert.Null(capture.Captures[0].FunctionArguments); - Assert.Equal("Substring(\"()\", $(Boo), `)(`)", capture.Captures[1].Value); - Assert.Equal("Substring", capture.Captures[1].FunctionName); - Assert.Equal("\"()\", $(Boo), `)(`", capture.Captures[1].FunctionArguments); + Assert.True(expressions.MoveNext()); + firstCapture = expressions.Current; + Assert.True(expressions.MoveNext()); + secondCapture = expressions.Current; + Assert.False(expressions.MoveNext()); + Assert.Equal("Bar", secondCapture.ItemType); + Assert.Null(secondCapture.Captures); + Assert.Equal(2, firstCapture.Captures.Count); + Assert.Null(firstCapture.Separator); + Assert.Equal("Foo", firstCapture.ItemType); + Assert.Equal("%(Filename)", firstCapture.Captures[0].Value); + Assert.Null(firstCapture.Captures[0].FunctionName); + Assert.Null(firstCapture.Captures[0].FunctionArguments); + Assert.Equal("Substring(\"()\", $(Boo), `)(`)", firstCapture.Captures[1].Value); + Assert.Equal("Substring", firstCapture.Captures[1].FunctionName); + Assert.Equal("\"()\", $(Boo), `)(`", firstCapture.Captures[1].FunctionArguments); } [Fact] public void ExtractItemVectorExpressionsMultipleExpression3() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; + ExpressionShredder.ItemExpressionCapture secondCapture; expression = "@(Foo->'%(Filename)'->Substring(\"()\", $(Boo), `)(`));AAAAAA;@(Bar)"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Equal(2, expressions.Count); - Assert.Equal("Bar", expressions[1].ItemType); - Assert.Null(expressions[1].Captures); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.True(expressions.MoveNext()); + secondCapture = expressions.Current; + Assert.False(expressions.MoveNext()); + Assert.Equal("Bar", secondCapture.ItemType); + Assert.Null(secondCapture.Captures); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -1067,15 +1100,19 @@ public void ExtractItemVectorExpressionsMultipleExpression3() public void ExtractItemVectorExpressionsMultipleExpression4() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; ExpressionShredder.ItemExpressionCapture capture; + ExpressionShredder.ItemExpressionCapture secondCapture; expression = "@(Foo->'%(Filename)'->Substring(\"()\", $(Boo), `)(\"`));@(;);@(aaa->;b);@(bbb->'d);@(`Foo->'%(Filename)'->Distinct());@(Bar)"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - capture = expressions[0]; - Assert.Equal(2, expressions.Count); - Assert.Equal("Bar", expressions[1].ItemType); - Assert.Null(expressions[1].Captures); + Assert.True(expressions.MoveNext()); + capture = expressions.Current; + Assert.True(expressions.MoveNext()); + secondCapture = expressions.Current; + Assert.False(expressions.MoveNext()); + Assert.Equal("Bar", secondCapture.ItemType); + Assert.Null(secondCapture.Captures); Assert.Equal(2, capture.Captures.Count); Assert.Null(capture.Separator); Assert.Equal("Foo", capture.ItemType); @@ -1091,25 +1128,32 @@ public void ExtractItemVectorExpressionsMultipleExpression4() public void ExtractItemVectorExpressionsMultipleExpression5() { string expression; - List expressions; + ExpressionShredder.ReferencedItemExpressionsEnumerator expressions; expression = "@(foo);@(foo,'-');@(foo);@(foo,',');@(foo)"; expressions = ExpressionShredder.GetReferencedItemExpressions(expression); - Assert.Equal(5, expressions.Count); - Assert.Equal("foo", expressions[0].ItemType); - Assert.Null(expressions[0].Separator); - Assert.Equal("foo", expressions[1].ItemType); - Assert.Equal("-", expressions[1].Separator); + Assert.True(expressions.MoveNext()); + Assert.Equal("foo", expressions.Current.ItemType); + Assert.Null(expressions.Current.Separator); + + Assert.True(expressions.MoveNext()); + Assert.Equal("foo", expressions.Current.ItemType); + Assert.Equal("-", expressions.Current.Separator); + + Assert.True(expressions.MoveNext()); + Assert.Equal("foo", expressions.Current.ItemType); + Assert.Null(expressions.Current.Separator); - Assert.Equal("foo", expressions[2].ItemType); - Assert.Null(expressions[2].Separator); + Assert.True(expressions.MoveNext()); + Assert.Equal("foo", expressions.Current.ItemType); + Assert.Equal(",", expressions.Current.Separator); - Assert.Equal("foo", expressions[3].ItemType); - Assert.Equal(",", expressions[3].Separator); + Assert.True(expressions.MoveNext()); + Assert.Equal("foo", expressions.Current.ItemType); + Assert.Null(expressions.Current.Separator); - Assert.Equal("foo", expressions[4].ItemType); - Assert.Null(expressions[4].Separator); + Assert.False(expressions.MoveNext()); } #region Original code to produce canonical results diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 47ebdcfd234..165d3b3539e 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -459,9 +459,9 @@ internal static bool ExpressionMayContainExpandableExpressions(string expression /// internal static bool ExpressionContainsItemVector(string expression) { - List transforms = ExpressionShredder.GetReferencedItemExpressions(expression); + ExpressionShredder.ReferencedItemExpressionsEnumerator transformsEnumerator = ExpressionShredder.GetReferencedItemExpressions(expression); - return transforms != null; + return transformsEnumerator.MoveNext(); } /// @@ -639,7 +639,7 @@ internal IList ExpandSingleItemVectorExpressionIntoItems(string expression return ItemExpander.ExpandSingleItemVectorExpressionIntoItems(this, expression, _items, itemFactory, options, includeNullItems, out isTransformExpression, elementLocation); } - internal static ExpressionShredder.ItemExpressionCapture ExpandSingleItemVectorExpressionIntoExpressionCapture( + internal static ExpressionShredder.ItemExpressionCapture? ExpandSingleItemVectorExpressionIntoExpressionCapture( string expression, ExpanderOptions options, IElementLocation elementLocation) { return ItemExpander.ExpandSingleItemVectorExpressionIntoExpressionCapture(expression, options, elementLocation); @@ -989,50 +989,45 @@ internal static string ExpandMetadataLeaveEscaped(string expression, IMetadataTa } else { - List itemVectorExpressions = ExpressionShredder.GetReferencedItemExpressions(expression); - - // The most common case is where the transform is the whole expression - // Also if there were no valid item vector expressions found, then go ahead and do the replacement on - // the whole expression (which is what Orcas did). - if (itemVectorExpressions?.Count == 1 && itemVectorExpressions[0].Value == expression && itemVectorExpressions[0].Separator == null) - { - return expression; - } + ExpressionShredder.ReferencedItemExpressionsEnumerator itemVectorExpressionsEnumerator = ExpressionShredder.GetReferencedItemExpressions(expression); // otherwise, run the more complex Regex to find item metadata references not contained in transforms using SpanBasedStringBuilder finalResultBuilder = Strings.GetSpanBasedStringBuilder(); int start = 0; - if (itemVectorExpressions != null && itemVectorExpressions.Count > 0) + if (itemVectorExpressionsEnumerator.MoveNext()) { MetadataMatchEvaluator matchEvaluator = new MetadataMatchEvaluator(metadata, options, elementLocation, loggingContext); - // Move over the expression, skipping those that have been recognized as an item vector expression - // Anything other than an item vector expression we want to expand bare metadata in. - for (int n = 0; n < itemVectorExpressions.Count; n++) - { - ExpressionShredder.ItemExpressionCapture itemExpressionCapture = itemVectorExpressions[n]; + ExpressionShredder.ItemExpressionCapture firstItemExpressionCapture = itemVectorExpressionsEnumerator.Current; - // Extract the part of the expression that appears before the item vector expression - // e.g. the ABC in ABC@(foo->'%(FullPath)') - string subExpressionToReplaceIn = expression.Substring(start, itemExpressionCapture.Index - start); - - RegularExpressions.ReplaceAndAppend(subExpressionToReplaceIn, MetadataMatchEvaluator.ExpandSingleMetadata, matchEvaluator, finalResultBuilder, RegularExpressions.NonTransformItemMetadataRegex); + if (itemVectorExpressionsEnumerator.MoveNext()) + { + // we're in the uncommon case with a partially enumerated enumerator. We need to process the first two items we enumerated and the remaining ones. + // Move over the expression, skipping those that have been recognized as an item vector expression + // Anything other than an item vector expression we want to expand bare metadata in. + start = ProcessItemExpressionCapture(expression, finalResultBuilder, matchEvaluator, start, firstItemExpressionCapture); + start = ProcessItemExpressionCapture(expression, finalResultBuilder, matchEvaluator, start, itemVectorExpressionsEnumerator.Current); - // Expand any metadata that appears in the item vector expression's separator - if (itemExpressionCapture.Separator != null) + while (itemVectorExpressionsEnumerator.MoveNext()) + { + start = ProcessItemExpressionCapture(expression, finalResultBuilder, matchEvaluator, start, itemVectorExpressionsEnumerator.Current); + } + } + else + { + // There is only one item. Check to see if we're in the common case. + if (firstItemExpressionCapture.Value == expression && firstItemExpressionCapture.Separator == null) { - RegularExpressions.ReplaceAndAppend(itemExpressionCapture.Value, MetadataMatchEvaluator.ExpandSingleMetadata, matchEvaluator, -1, itemVectorExpressions[n].SeparatorStart, finalResultBuilder, RegularExpressions.NonTransformItemMetadataRegex); + // The most common case is where the transform is the whole expression + // Also if there were no valid item vector expressions found, then go ahead and do the replacement on + // the whole expression (which is what Orcas did). + return expression; } else { - // Append the item vector expression as is - // e.g. the @(foo->'%(FullPath)') in ABC@(foo->'%(FullPath)') - finalResultBuilder.Append(itemExpressionCapture.Value); + start = ProcessItemExpressionCapture(expression, finalResultBuilder, matchEvaluator, start, firstItemExpressionCapture); } - - // Move onto the next part of the expression that isn't an item vector expression - start = (itemExpressionCapture.Index + itemExpressionCapture.Length); } } @@ -1067,6 +1062,31 @@ internal static string ExpandMetadataLeaveEscaped(string expression, IMetadataTa } return null; + + static int ProcessItemExpressionCapture(string expression, SpanBasedStringBuilder finalResultBuilder, MetadataMatchEvaluator matchEvaluator, int start, ExpressionShredder.ItemExpressionCapture itemExpressionCapture) + { + // Extract the part of the expression that appears before the item vector expression + // e.g. the ABC in ABC@(foo->'%(FullPath)') + string subExpressionToReplaceIn = expression.Substring(start, itemExpressionCapture.Index - start); + + RegularExpressions.ReplaceAndAppend(subExpressionToReplaceIn, MetadataMatchEvaluator.ExpandSingleMetadata, matchEvaluator, finalResultBuilder, RegularExpressions.NonTransformItemMetadataRegex); + + // Expand any metadata that appears in the item vector expression's separator + if (itemExpressionCapture.Separator != null) + { + RegularExpressions.ReplaceAndAppend(itemExpressionCapture.Value, MetadataMatchEvaluator.ExpandSingleMetadata, matchEvaluator, -1, itemExpressionCapture.SeparatorStart, finalResultBuilder, RegularExpressions.NonTransformItemMetadataRegex); + } + else + { + // Append the item vector expression as is + // e.g. the @(foo->'%(FullPath)') in ABC@(foo->'%(FullPath)') + finalResultBuilder.Append(itemExpressionCapture.Value); + } + + // Move onto the next part of the expression that isn't an item vector expression + start = (itemExpressionCapture.Index + itemExpressionCapture.Length); + return start; + } } } @@ -2038,11 +2058,11 @@ internal static IList ExpandSingleItemVectorExpressionIntoItems( return null; } - return ExpandExpressionCaptureIntoItems(expressionCapture, expander, items, itemFactory, options, includeNullEntries, + return ExpandExpressionCaptureIntoItems(expressionCapture.Value, expander, items, itemFactory, options, includeNullEntries, out isTransformExpression, elementLocation); } - internal static ExpressionShredder.ItemExpressionCapture ExpandSingleItemVectorExpressionIntoExpressionCapture( + internal static ExpressionShredder.ItemExpressionCapture? ExpandSingleItemVectorExpressionIntoExpressionCapture( string expression, ExpanderOptions options, IElementLocation elementLocation) { if (((options & ExpanderOptions.ExpandItems) == 0) || (expression.Length == 0)) @@ -2050,29 +2070,26 @@ internal static ExpressionShredder.ItemExpressionCapture ExpandSingleItemVectorE return null; } - List matches; if (!expression.Contains('@')) { return null; } - else - { - matches = ExpressionShredder.GetReferencedItemExpressions(expression); - if (matches == null) - { - return null; - } + ExpressionShredder.ReferencedItemExpressionsEnumerator matchesEnumerator = ExpressionShredder.GetReferencedItemExpressions(expression); + + if (!matchesEnumerator.MoveNext()) + { + return null; } - ExpressionShredder.ItemExpressionCapture match = matches[0]; + ExpressionShredder.ItemExpressionCapture match = matchesEnumerator.Current; // We have a single valid @(itemlist) reference in the given expression. // If the passed-in expression contains exactly one item list reference, // with nothing else concatenated to the beginning or end, then proceed // with itemizing it, otherwise error. ProjectErrorUtilities.VerifyThrowInvalidProject(match.Value == expression, elementLocation, "EmbeddedItemVectorCannotBeItemized", expression); - ErrorUtilities.VerifyThrow(matches.Count == 1, "Expected just one item vector"); + ErrorUtilities.VerifyThrow(!matchesEnumerator.MoveNext(), "Expected just one item vector"); return match; } @@ -2286,39 +2303,42 @@ internal static string ExpandItemVectorsIntoString(Expander expander, s ErrorUtilities.VerifyThrow(items != null, "Cannot expand items without providing items"); - List matches = ExpressionShredder.GetReferencedItemExpressions(expression); + ExpressionShredder.ReferencedItemExpressionsEnumerator matchesEnumerator = ExpressionShredder.GetReferencedItemExpressions(expression); - if (matches == null) + if (!matchesEnumerator.MoveNext()) { return expression; } using SpanBasedStringBuilder builder = Strings.GetSpanBasedStringBuilder(); + // As we walk through the matches, we need to copy out the original parts of the string which // are not covered by the match. This preserves original behavior which did not trim whitespace // from between separators. int lastStringIndex = 0; - for (int i = 0; i < matches.Count; i++) + do { - if (matches[i].Index > lastStringIndex) + ExpressionShredder.ItemExpressionCapture currentItem = matchesEnumerator.Current; + if (currentItem.Index > lastStringIndex) { if ((options & ExpanderOptions.BreakOnNotEmpty) != 0) { return null; } - builder.Append(expression, lastStringIndex, matches[i].Index - lastStringIndex); + builder.Append(expression, lastStringIndex, currentItem.Index - lastStringIndex); } - bool brokeEarlyNonEmpty = ExpandExpressionCaptureIntoStringBuilder(expander, matches[i], items, elementLocation, builder, options); + bool brokeEarlyNonEmpty = ExpandExpressionCaptureIntoStringBuilder(expander, currentItem, items, elementLocation, builder, options); if (brokeEarlyNonEmpty) { return null; } - lastStringIndex = matches[i].Index + matches[i].Length; + lastStringIndex = currentItem.Index + currentItem.Length; } + while (matchesEnumerator.MoveNext()); builder.Append(expression, lastStringIndex, expression.Length - lastStringIndex); diff --git a/src/Build/Evaluation/ExpressionShredder.cs b/src/Build/Evaluation/ExpressionShredder.cs index 9ec23207b31..0cd80692e54 100644 --- a/src/Build/Evaluation/ExpressionShredder.cs +++ b/src/Build/Evaluation/ExpressionShredder.cs @@ -95,83 +95,86 @@ internal static bool ContainsMetadataExpressionOutsideTransform(string expressio /// itemName and separator will be null if they are not found /// return value will be null if no transform expressions are found /// - internal static List GetReferencedItemExpressions(string expression) + internal static ReferencedItemExpressionsEnumerator GetReferencedItemExpressions(string expression) { return GetReferencedItemExpressions(expression, 0, expression.Length); } - /// - /// Given a subexpression, finds referenced sub transform expressions - /// itemName and separator will be null if they are not found - /// return value will be null if no transform expressions are found - /// - internal static List GetReferencedItemExpressions(string expression, int start, int end) + internal struct ReferencedItemExpressionsEnumerator { - List subExpressions = null; - - int startIndex = expression.IndexOf('@', start, end - start); + private readonly string expression; + private readonly int end; + private int currentIndex; - if (startIndex < 0) + public ReferencedItemExpressionsEnumerator(string expression, int start, int end) { - return null; + this.expression = expression; + this.end = end; + + currentIndex = expression.IndexOf('@', start, end - start); + if (currentIndex < 0) + { + currentIndex = int.MaxValue; + } } - for (int i = startIndex; i < end; i++) - { - int restartPoint; - int startPoint; + public ItemExpressionCapture Current { get; private set; } - if (Sink(expression, ref i, end, '@', '(')) + public bool MoveNext() + { + for (; currentIndex < end; currentIndex++) { - List transformExpressions = null; - string separator = null; - int separatorStart = -1; + if (!Sink(expression, ref currentIndex, end, '@', '(')) + { + continue; + } // Start of a possible item list expression // Store the index to backtrack to if this doesn't turn out to be a well // formed expression. (Subtract one for the increment when we loop around.) - restartPoint = i - 1; + int restartPoint = currentIndex - 1; // Store the expression's start point - startPoint = i - 2; + int startPoint = currentIndex - 2; - SinkWhitespace(expression, ref i); + SinkWhitespace(expression, ref currentIndex); - int startOfName = i; + int startOfName = currentIndex; - if (!SinkValidName(expression, ref i, end)) + if (!SinkValidName(expression, ref currentIndex, end)) { - i = restartPoint; + currentIndex = restartPoint; continue; } // '-' is a legitimate char in an item name, but we should match '->' as an arrow // in '@(foo->'x')' rather than as the last char of the item name. // The old regex accomplished this by being "greedy" - if (end > i && expression[i - 1] == '-' && expression[i] == '>') + if (end > currentIndex && expression[currentIndex - 1] == '-' && expression[currentIndex] == '>') { - i--; + currentIndex--; } // Grab the name, but continue to verify it's a well-formed expression // before we store it. - string itemName = Microsoft.NET.StringTools.Strings.WeakIntern(expression.AsSpan(startOfName, i - startOfName)); + string itemName = Microsoft.NET.StringTools.Strings.WeakIntern(expression.AsSpan(startOfName, currentIndex - startOfName)); - SinkWhitespace(expression, ref i); + SinkWhitespace(expression, ref currentIndex); bool transformOrFunctionFound = true; + List transformExpressions = null; // If there's an '->' eat it and the subsequent quoted expression or transform function - while (Sink(expression, ref i, end, '-', '>') && transformOrFunctionFound) + while (Sink(expression, ref currentIndex, end, '-', '>') && transformOrFunctionFound) { - SinkWhitespace(expression, ref i); - int startTransform = i; + SinkWhitespace(expression, ref currentIndex); + int startTransform = currentIndex; - bool isQuotedTransform = SinkSingleQuotedExpression(expression, ref i, end); + bool isQuotedTransform = SinkSingleQuotedExpression(expression, ref currentIndex, end); if (isQuotedTransform) { int startQuoted = startTransform + 1; - int endQuoted = i - 1; + int endQuoted = currentIndex - 1; if (transformExpressions == null) { transformExpressions = new List(); @@ -181,8 +184,8 @@ internal static List GetReferencedItemExpressions(string continue; } - startTransform = i; - ItemExpressionCapture functionCapture = SinkItemFunctionExpression(expression, startTransform, ref i, end); + startTransform = currentIndex; + ItemExpressionCapture? functionCapture = SinkItemFunctionExpression(expression, startTransform, ref currentIndex, end); if (functionCapture != null) { if (transformExpressions == null) @@ -190,13 +193,13 @@ internal static List GetReferencedItemExpressions(string transformExpressions = new List(); } - transformExpressions.Add(functionCapture); + transformExpressions.Add(functionCapture.Value); continue; } if (!isQuotedTransform && functionCapture == null) { - i = restartPoint; + currentIndex = restartPoint; transformOrFunctionFound = false; } } @@ -206,59 +209,71 @@ internal static List GetReferencedItemExpressions(string continue; } - SinkWhitespace(expression, ref i); + SinkWhitespace(expression, ref currentIndex); + + string separator = null; + int separatorStart = -1; // If there's a ',', eat it and the subsequent quoted expression - if (Sink(expression, ref i, ',')) + if (Sink(expression, ref currentIndex, ',')) { - SinkWhitespace(expression, ref i); + SinkWhitespace(expression, ref currentIndex); - if (!Sink(expression, ref i, '\'')) + if (!Sink(expression, ref currentIndex, '\'')) { - i = restartPoint; + currentIndex = restartPoint; continue; } - int closingQuote = expression.IndexOf('\'', i); + int closingQuote = expression.IndexOf('\'', currentIndex); if (closingQuote == -1) { - i = restartPoint; + currentIndex = restartPoint; continue; } - separatorStart = i - startPoint; - separator = expression.Substring(i, closingQuote - i); + separatorStart = currentIndex - startPoint; + separator = expression.Substring(currentIndex, closingQuote - currentIndex); - i = closingQuote + 1; + currentIndex = closingQuote + 1; } - SinkWhitespace(expression, ref i); + SinkWhitespace(expression, ref currentIndex); - if (!Sink(expression, ref i, ')')) + if (!Sink(expression, ref currentIndex, ')')) { - i = restartPoint; + currentIndex = restartPoint; continue; } - int endPoint = i; - i--; - - if (subExpressions == null) - { - subExpressions = new List(); - } + int endPoint = currentIndex; + currentIndex--; // Create an expression capture that encompasses the entire expression between the @( and the ) // with the item name and any separator contained within it // and each transform expression contained within it (i.e. each ->XYZ) ItemExpressionCapture expressionCapture = new ItemExpressionCapture(startPoint, endPoint - startPoint, Microsoft.NET.StringTools.Strings.WeakIntern(expression.AsSpan(startPoint, endPoint - startPoint)), itemName, separator, separatorStart, transformExpressions); - subExpressions.Add(expressionCapture); - continue; + Current = expressionCapture; + ++currentIndex; + + return true; } + + Current = default; + + return false; } + } - return subExpressions; + /// + /// Given a subexpression, finds referenced sub transform expressions + /// itemName and separator will be null if they are not found + /// return value will be null if no transform expressions are found + /// + internal static ReferencedItemExpressionsEnumerator GetReferencedItemExpressions(string expression, int start, int end) + { + return new ReferencedItemExpressionsEnumerator(expression, start, end); } /// @@ -320,7 +335,7 @@ private static void GetReferencedItemNamesAndMetadata(string expression, int sta continue; } - ItemExpressionCapture functionCapture = SinkItemFunctionExpression(expression, startTransform, ref i, end); + ItemExpressionCapture? functionCapture = SinkItemFunctionExpression(expression, startTransform, ref i, end); if (functionCapture != null) { continue; @@ -579,7 +594,7 @@ private static bool SinkUntilClosingQuote(char quoteChar, string expression, ref /// and ends before the specified end index. /// Leaves index one past the end of the closing paren. /// - private static ItemExpressionCapture SinkItemFunctionExpression(string expression, int startTransform, ref int i, int end) + private static ItemExpressionCapture? SinkItemFunctionExpression(string expression, int startTransform, ref int i, int end) { if (SinkValidName(expression, ref i, end)) { @@ -593,14 +608,15 @@ private static ItemExpressionCapture SinkItemFunctionExpression(string expressio { int endFunctionArguments = i - 1; - ItemExpressionCapture capture = new ItemExpressionCapture(startTransform, i - startTransform, expression.Substring(startTransform, i - startTransform)); - capture.FunctionName = expression.Substring(startTransform, endFunctionName - startTransform); - + string functionName = expression.Substring(startTransform, endFunctionName - startTransform); + string functionArguments = null; if (endFunctionArguments > startFunctionArguments) { - capture.FunctionArguments = Microsoft.NET.StringTools.Strings.WeakIntern(expression.AsSpan(startFunctionArguments, endFunctionArguments - startFunctionArguments)); + functionArguments = Microsoft.NET.StringTools.Strings.WeakIntern(expression.AsSpan(startFunctionArguments, endFunctionArguments - startFunctionArguments)); } + ItemExpressionCapture capture = new ItemExpressionCapture(startTransform, i - startTransform, expression.Substring(startTransform, i - startTransform), null, null, -1, null, functionName, functionArguments); + return capture; } @@ -686,59 +702,19 @@ private static void SinkWhitespace(string expression, ref int i) /// /// Represents one substring for a single successful capture. /// - internal class ItemExpressionCapture + internal struct ItemExpressionCapture { - /// - /// Captures within this capture - /// - private readonly List _captures; - - /// - /// The position in the original string where the first character of the captured - /// substring was found. - /// - private readonly int _index; - - /// - /// The length of the captured substring. - /// - private readonly int _length; - - /// - /// The captured substring from the input string. - /// - private readonly string _value; - - /// - /// The type of the item within this expression - /// - private readonly string _itemType; - - /// - /// The separator, if any, within this expression - /// - private readonly string _separator; - - /// - /// The starting character of the separator within the expression - /// - private readonly int _separatorStart; - - /// - /// The function name, if any, within this expression - /// - private string _functionName; - - /// - /// The function arguments, if any, within this expression - /// - private string _functionArguments; - /// /// Create an Expression Capture instance /// Represents a sub expression, shredded from a larger expression /// - public ItemExpressionCapture(int index, int length, string subExpression) : this(index, length, subExpression, null, null, -1, null) + public ItemExpressionCapture(int index, int length, string subExpression) + : this(index, length, subExpression, null, null, -1, null, null, null) + { + } + + public ItemExpressionCapture(int index, int length, string subExpression, string itemType, string separator, int separatorStart, List captures) + : this(index, length, subExpression, itemType, separator, separatorStart, captures, null, null) { } @@ -746,98 +722,71 @@ public ItemExpressionCapture(int index, int length, string subExpression) : this /// Create an Expression Capture instance /// Represents a sub expression, shredded from a larger expression /// - public ItemExpressionCapture(int index, int length, string subExpression, string itemType, string separator, int separatorStart, List captures) + public ItemExpressionCapture(int index, int length, string subExpression, string itemType, string separator, int separatorStart, List captures, string functionName, string functionArguments) { - _index = index; - _length = length; - _value = subExpression; - _itemType = itemType; - _separator = separator; - _separatorStart = separatorStart; - _captures = captures; + Index = index; + Length = length; + Value = subExpression; + ItemType = itemType; + Separator = separator; + SeparatorStart = separatorStart; + Captures = captures; + FunctionName = functionName; + FunctionArguments = functionArguments; } /// /// Captures within this capture /// - public List Captures - { - get { return _captures; } - } + public List Captures { get; } /// /// The position in the original string where the first character of the captured /// substring was found. /// - public int Index - { - get { return _index; } - } + public int Index { get; } /// /// The length of the captured substring. /// - public int Length - { - get { return _length; } - } + public int Length { get; } /// /// Gets the captured substring from the input string. /// - public string Value - { - get { return _value; } - } + public string Value { get; } /// /// Gets the captured itemtype. /// - public string ItemType - { - get { return _itemType; } - } + public string ItemType { get; } /// /// Gets the captured itemtype. /// - public string Separator - { - get { return _separator; } - } + public string Separator { get; } /// /// The starting character of the separator. /// - public int SeparatorStart - { - get { return _separatorStart; } - } + public int SeparatorStart { get; } /// /// The function name, if any, within this expression /// - public string FunctionName - { - get { return _functionName; } - set { _functionName = value; } - } + public string FunctionName { get; } /// /// The function arguments, if any, within this expression /// - public string FunctionArguments - { - get { return _functionArguments; } - set { _functionArguments = value; } - } + public string FunctionArguments { get; } /// /// Gets the captured substring from the input string. /// public override string ToString() { - return _value; + return Value; } } } diff --git a/src/Build/Evaluation/ItemSpec.cs b/src/Build/Evaluation/ItemSpec.cs index 04d1bf782c6..ea5bbfd0330 100644 --- a/src/Build/Evaluation/ItemSpec.cs +++ b/src/Build/Evaluation/ItemSpec.cs @@ -279,7 +279,7 @@ private ItemExpressionFragment ProcessItemExpression( isItemListExpression = true; - return new ItemExpressionFragment(capture, expression, this, projectDirectory); + return new ItemExpressionFragment(capture.Value, expression, this, projectDirectory); } /// diff --git a/src/Build/Evaluation/LazyItemEvaluator.cs b/src/Build/Evaluation/LazyItemEvaluator.cs index d56d20dddde..36510f4d7d3 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.cs @@ -673,7 +673,7 @@ private void AddItemReferences(string expression, OperationBuilder operationBuil } else { - ExpressionShredder.ItemExpressionCapture match = Expander.ExpandSingleItemVectorExpressionIntoExpressionCapture( + ExpressionShredder.ItemExpressionCapture? match = Expander.ExpandSingleItemVectorExpressionIntoExpressionCapture( expression, ExpanderOptions.ExpandItems, elementLocation); if (match == null) @@ -681,7 +681,7 @@ private void AddItemReferences(string expression, OperationBuilder operationBuil return; } - AddReferencedItemLists(operationBuilder, match); + AddReferencedItemLists(operationBuilder, match.Value); } }