in src/Build/Evaluation/ExpressionShredder.cs [358:543]
private static void GetReferencedItemNamesAndMetadata(string expression, int start, int end, ref ItemsAndMetadataPair pair, ShredderOptions whatToShredFor)
{
for (int i = start; i < end; i++)
{
int restartPoint;
if (Sink(expression, ref i, end, '@', '('))
{
// Start of a possible item list expression
// Store the index to backtrack to if this doesn't turn out to be a well
// formed metadata expression. (Subtract one for the increment when we loop around.)
restartPoint = i - 1;
SinkWhitespace(expression, ref i);
int startOfName = i;
if (!SinkValidName(expression, ref i, end))
{
i = 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] == '>')
{
i--;
}
// Grab the name, but continue to verify it's a well-formed expression
// before we store it.
string name = expression.Substring(startOfName, i - startOfName);
SinkWhitespace(expression, ref i);
bool transformOrFunctionFound = true;
// If there's an '->' eat it and the subsequent quoted expression or transform function
while (Sink(expression, ref i, end, '-', '>') && transformOrFunctionFound)
{
SinkWhitespace(expression, ref i);
int startTransform = i;
bool isQuotedTransform = SinkSingleQuotedExpression(expression, ref i, end);
if (isQuotedTransform)
{
continue;
}
ItemExpressionCapture functionCapture = SinkItemFunctionExpression(expression, startTransform, ref i, end);
if (functionCapture != null)
{
continue;
}
if (!isQuotedTransform && functionCapture == null)
{
i = restartPoint;
transformOrFunctionFound = false;
}
}
if (!transformOrFunctionFound)
{
continue;
}
SinkWhitespace(expression, ref i);
// If there's a ',', eat it and the subsequent quoted expression
if (Sink(expression, ref i, ','))
{
SinkWhitespace(expression, ref i);
if (!Sink(expression, ref i, '\''))
{
i = restartPoint;
continue;
}
int closingQuote = expression.IndexOf('\'', i);
if (closingQuote == -1)
{
i = restartPoint;
continue;
}
// Look for metadata in the separator expression
// e.g., @(foo, '%(bar)') contains batchable metadata 'bar'
GetReferencedItemNamesAndMetadata(expression, i, closingQuote, ref pair, ShredderOptions.MetadataOutsideTransforms);
i = closingQuote + 1;
}
SinkWhitespace(expression, ref i);
if (!Sink(expression, ref i, ')'))
{
i = restartPoint;
continue;
}
// If we've got this far, we know the item expression was
// well formed, so make sure the name's in the table
if ((whatToShredFor & ShredderOptions.ItemTypes) != 0)
{
pair.Items = pair.Items ?? new HashSet<string>(MSBuildNameIgnoreCaseComparer.Default);
pair.Items.Add(name);
}
i--;
continue;
}
if (Sink(expression, ref i, end, '%', '('))
{
// Start of a possible metadata expression
// Store the index to backtrack to if this doesn't turn out to be a well
// formed metadata expression. (Subtract one for the increment when we loop around.)
restartPoint = i - 1;
SinkWhitespace(expression, ref i);
int startOfText = i;
if (!SinkValidName(expression, ref i, end))
{
i = restartPoint;
continue;
}
// Grab this, but we don't know if it's an item or metadata name yet
string firstPart = expression.Substring(startOfText, i - startOfText);
string itemName = null;
string metadataName;
string qualifiedMetadataName;
SinkWhitespace(expression, ref i);
bool qualified = Sink(expression, ref i, '.');
if (qualified)
{
SinkWhitespace(expression, ref i);
startOfText = i;
if (!SinkValidName(expression, ref i, end))
{
i = restartPoint;
continue;
}
itemName = firstPart;
metadataName = expression.Substring(startOfText, i - startOfText);
qualifiedMetadataName = itemName + "." + metadataName;
}
else
{
metadataName = firstPart;
qualifiedMetadataName = metadataName;
}
SinkWhitespace(expression, ref i);
if (!Sink(expression, ref i, ')'))
{
i = restartPoint;
continue;
}
if ((whatToShredFor & ShredderOptions.MetadataOutsideTransforms) != 0)
{
pair.Metadata = pair.Metadata ?? new Dictionary<string, MetadataReference>(MSBuildNameIgnoreCaseComparer.Default);
pair.Metadata[qualifiedMetadataName] = new MetadataReference(itemName, metadataName);
}
i--;
}
}
}