internal static object ExpandPropertiesLeaveTypedAndEscaped()

in src/Build/Evaluation/Expander.cs [906:1099]


            internal static object ExpandPropertiesLeaveTypedAndEscaped(string expression, IPropertyProvider<T> properties, ExpanderOptions options, IElementLocation elementLocation, UsedUninitializedProperties usedUninitializedProperties)
            {
                if (((options & ExpanderOptions.ExpandProperties) == 0) || String.IsNullOrEmpty(expression))
                {
                    return expression;
                }

                ErrorUtilities.VerifyThrow(properties != null, "Cannot expand properties without providing properties");

                // These are also zero-based indices into the expression, but
                // these tell us where the current property tag begins and ends.
                int propertyStartIndex, propertyEndIndex;

                // If there are no substitutions, then just return the string.
                propertyStartIndex = s_invariantCompareInfo.IndexOf(expression, "$(", CompareOptions.Ordinal);
                if (propertyStartIndex == -1)
                {
                    return expression;
                }

                // We will build our set of results as object components
                // so that we can either maintain the object's type in the event
                // that we have a single component, or convert to a string
                // if concatenation is required.
                List<object> results = null;
                object lastResult = null;

                // The sourceIndex is the zero-based index into the expression,
                // where we've essentially read up to and copied into the target string.
                int sourceIndex = 0;
                int expressionLength = expression.Length;

                // Search for "$(" in the expression.  Loop until we don't find it 
                // any more.
                while (propertyStartIndex != -1)
                {
                    if (lastResult != null)
                    {
                        if (results == null)
                        {
                            results = new List<object>(4);
                        }

                        results.Add(lastResult);
                    }

                    bool tryExtractPropertyFunction = false;
                    bool tryExtractRegistryFunction = false;

                    // Append the result with the portion of the expression up to
                    // (but not including) the "$(", and advance the sourceIndex pointer.
                    if (propertyStartIndex - sourceIndex > 0)
                    {
                        if (results == null)
                        {
                            results = new List<object>(4);
                        }

                        results.Add(expression.Substring(sourceIndex, propertyStartIndex - sourceIndex));
                    }

                    sourceIndex = propertyStartIndex;

                    // Following the "$(" we need to locate the matching ')'
                    // Scan for the matching closing bracket, skipping any nested ones
                    // This is a very complete, fast validation of parenthesis matching including for nested
                    // function calls.
                    propertyEndIndex = ScanForClosingParenthesis(expression, propertyStartIndex + 2, out tryExtractPropertyFunction, out tryExtractRegistryFunction);

                    if (propertyEndIndex == -1)
                    {
                        // If we didn't find the closing parenthesis, that means this
                        // isn't really a well-formed property tag.  Just literally
                        // copy the remainder of the expression (starting with the "$("
                        // that we found) into the result, and quit.
                        lastResult = expression.Substring(propertyStartIndex, expression.Length - propertyStartIndex);
                        sourceIndex = expression.Length;
                    }
                    else
                    {
                        // Aha, we found the closing parenthesis.  All the stuff in
                        // between the "$(" and the ")" constitutes the property body.
                        // Note: Current propertyStartIndex points to the "$", and 
                        // propertyEndIndex points to the ")".  That's why we have to
                        // add 2 for the start of the substring, and subtract 2 for 
                        // the length.
                        string propertyBody;

                        // A property value of null will indicate that we're calling a static function on a type
                        object propertyValue = null;

                        // Compat: $() should return String.Empty
                        if (propertyStartIndex + 2 == propertyEndIndex)
                        {
                            propertyValue = String.Empty;
                        }
#if FEATURE_WIN32_REGISTRY
                        else if ((expression.Length - (propertyStartIndex + 2)) > 9 && tryExtractRegistryFunction && s_invariantCompareInfo.IndexOf(expression, "Registry:", propertyStartIndex + 2, 9, CompareOptions.OrdinalIgnoreCase) == propertyStartIndex + 2)
                        {
                            propertyBody = expression.Substring(propertyStartIndex + 2, propertyEndIndex - propertyStartIndex - 2);

                            // If the property body starts with any of our special objects, then deal with them
                            // This is a registry reference, like $(Registry:HKEY_LOCAL_MACHINE\Software\Vendor\Tools@TaskLocation)
                            propertyValue = ExpandRegistryValue(propertyBody, elementLocation);
                        }
#endif

                        // Compat hack: as a special case, $(HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\VSTSDB@VSTSDBDirectory) should return String.Empty
                        // In this case, tryExtractRegistryFunction will be false. Note that very few properties are exactly 77 chars, so this check should be fast.
                        else if ((propertyEndIndex - (propertyStartIndex + 2)) == 77 && s_invariantCompareInfo.IndexOf(expression, @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\9.0\VSTSDB@VSTSDBDirectory", propertyStartIndex + 2, 77, CompareOptions.OrdinalIgnoreCase) == propertyStartIndex + 2)
                        {
                            propertyValue = String.Empty;
                        }

                        // Compat hack: WebProjects may have an import with a condition like:
                        //       Condition=" '$(Solutions.VSVersion)' == '8.0'" 
                        // These would have been '' in prior versions of msbuild but would be treated as a possible string function in current versions.
                        // Be compatible by returning an empty string here.
                        else if ((propertyEndIndex - (propertyStartIndex + 2)) == 19 && String.Equals(expression, "$(Solutions.VSVersion)", StringComparison.Ordinal))
                        {
                            propertyValue = String.Empty;
                        }
                        else if (tryExtractPropertyFunction)
                        {
                            propertyBody = expression.Substring(propertyStartIndex + 2, propertyEndIndex - propertyStartIndex - 2);

                            // This is likely to be a function expression
                            propertyValue = ExpandPropertyBody(propertyBody, null, properties, options, elementLocation, usedUninitializedProperties);
                        }
                        else // This is a regular property
                        {
                            propertyValue = LookupProperty(properties, expression, propertyStartIndex + 2, propertyEndIndex - 1, elementLocation, usedUninitializedProperties);
                        }

                        // Record our result, and advance
                        // our sourceIndex pointer to the character just after the closing
                        // parenthesis.
                        lastResult = propertyValue;
                        sourceIndex = propertyEndIndex + 1;
                    }

                    propertyStartIndex = s_invariantCompareInfo.IndexOf(expression, "$(", sourceIndex, CompareOptions.Ordinal);
                }

                // If we have only a single result, then just return it
                if (results == null && expression.Length == sourceIndex)
                {
                    var resultString = lastResult as string;
                    return resultString != null ? FileUtilities.MaybeAdjustFilePath(resultString) : lastResult;
                }
                else
                {
                    // The expression is constant, return it as is
                    if (sourceIndex == 0)
                    {
                        return expression;
                    }

                    // We have more than one result collected, therefore we need to concatenate
                    // into the final result string. This does mean that we will lose type information.
                    // However since the user wanted contatenation, then they clearly wanted that to happen.

                    // Initialize our output string to empty string.
                    // This method is called very often - of the order of 3,000 times per project.
                    // With the reuseable string builder, there's no particular need to initialize the length as it will already have grown.
                    using (var result = new ReuseableStringBuilder())
                    {
                        // Append our collected results
                        if (results != null)
                        {
                            // Create a combined result string from the result components that we've gathered
                            foreach (object component in results)
                            {
                                result.Append(FileUtilities.MaybeAdjustFilePath(component.ToString()));
                            }
                        }

                        // Append the last result we collected (it wasn't added to the list)
                        if (lastResult != null)
                        {
                            result.Append(FileUtilities.MaybeAdjustFilePath(lastResult.ToString()));
                        }

                        // And if we couldn't find anymore property tags in the expression,
                        // so just literally copy the remainder into the result.
                        if (expression.Length - sourceIndex > 0)
                        {
                            result.Append(expression, sourceIndex, expression.Length - sourceIndex);
                        }

                        return OpportunisticIntern.InternableToString(result);
                    }
                }
            }