File: src\msbuild\src\Shared\ResourceUtilities.cs
Web Access
Project: src\msbuild\src\Utilities\Microsoft.Build.Utilities.csproj (Microsoft.Build.Utilities.Core)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Diagnostics;
using System.Resources;
using Microsoft.Build.Framework.Utilities;

namespace Microsoft.Build.Shared
{
    /// <summary>
    /// This class contains utility methods for dealing with resources.
    /// </summary>
    internal static class ResourceUtilities
    {
        /// <summary>
        /// Retrieves the MSBuild F1-help keyword for the given resource string. Help keywords are used to index help topics in
        /// host IDEs.
        /// </summary>
        /// <param name="resourceName">Resource string to get the MSBuild F1-keyword for.</param>
        /// <returns>The MSBuild F1-help keyword string.</returns>
        private static string GetHelpKeyword(string resourceName)
            => "MSBuild." + resourceName;

        /// <summary>
        /// Retrieves the contents of the named resource string.
        /// </summary>
        /// <param name="resourceName">Resource string name.</param>
        /// <returns>Resource string contents.</returns>
        internal static string GetResourceString(string resourceName)
            => AssemblyResources.GetString(resourceName);

        /// <summary>
        /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they too are returned.
        ///
        /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for
        /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios.
        /// </summary>
        /// <remarks>This method is thread-safe.</remarks>
        /// <param name="code">[out] The MSBuild message code, or null.</param>
        /// <param name="helpKeyword">[out] The MSBuild F1-help keyword for the host IDE, or null.</param>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="args">Optional arguments for formatting the resource string.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, params object?[] args)
        {
            helpKeyword = GetHelpKeyword(resourceName);
            string message = MessageFormatter.Format(GetResourceString(resourceName), args);

            return MessageParser.TryParseMSBuildCode(message, out code, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        // Overloads with 0-3 arguments to avoid array allocations.

        /// <summary>
        /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they too are returned.
        /// </summary>
        /// <remarks>This method is thread-safe.</remarks>
        /// <param name="code">[out] The MSBuild message code, or null.</param>
        /// <param name="helpKeyword">[out] The MSBuild F1-help keyword for the host IDE, or null.</param>
        /// <param name="resourceName">Resource string to load.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName)
        {
            helpKeyword = GetHelpKeyword(resourceName);
            string message = GetResourceString(resourceName);

            return MessageParser.TryParseMSBuildCode(message, out code, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        /// <summary>
        /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they too are returned.
        /// </summary>
        /// <param name="code">[out] The MSBuild message code, or null.</param>
        /// <param name="helpKeyword">[out] The MSBuild F1-help keyword for the host IDE, or null.</param>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="arg1">Argument for formatting the resource string.</param>
        internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1)
        {
            helpKeyword = GetHelpKeyword(resourceName);
            string message = MessageFormatter.Format(GetResourceString(resourceName), arg1);

            return MessageParser.TryParseMSBuildCode(message, out code, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        /// <summary>
        /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they too are returned.
        /// </summary>
        /// <param name="code">[out] The MSBuild message code, or null.</param>
        /// <param name="helpKeyword">[out] The MSBuild F1-help keyword for the host IDE, or null.</param>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="arg1">First argument for formatting the resource string.</param>
        /// <param name="arg2">Second argument for formatting the resource string.</param>
        internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1, object? arg2)
        {
            helpKeyword = GetHelpKeyword(resourceName);
            string message = MessageFormatter.Format(GetResourceString(resourceName), arg1, arg2);

            return MessageParser.TryParseMSBuildCode(message, out code, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        /// <summary>
        /// Loads the specified string resource and formats it with the arguments passed in. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they too are returned.
        /// </summary>
        /// <param name="code">[out] The MSBuild message code, or null.</param>
        /// <param name="helpKeyword">[out] The MSBuild F1-help keyword for the host IDE, or null.</param>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="arg1">First argument for formatting the resource string.</param>
        /// <param name="arg2">Second argument for formatting the resource string.</param>
        /// <param name="arg3">Third argument for formatting the resource string.</param>
        internal static string FormatResourceStringStripCodeAndKeyword(out string? code, out string? helpKeyword, string resourceName, object? arg1, object? arg2, object? arg3)
        {
            helpKeyword = GetHelpKeyword(resourceName);
            string message = MessageFormatter.Format(GetResourceString(resourceName), arg1, arg2, arg3);

            return MessageParser.TryParseMSBuildCode(message, out code, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        /// <summary>
        /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they are discarded.
        ///
        /// PERF WARNING: calling a method that takes a variable number of arguments is expensive, because memory is allocated for
        /// the array of arguments -- do not call this method repeatedly in performance-critical scenarios.
        /// </summary>
        /// <remarks>This method is thread-safe.</remarks>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="args">Optional arguments for formatting the resource string.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, params object?[] args)
        {
            string message = MessageFormatter.Format(GetResourceString(resourceName), args);

            return MessageParser.TryStripMSBuildCode(message, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        // Overloads with 0-3 arguments to avoid array allocations.

        /// <summary>
        /// Looks up a string in the resources. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they are discarded.
        /// </summary>
        /// <remarks>This method is thread-safe.</remarks>
        /// <param name="resourceName">Resource string to load.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringStripCodeAndKeyword(string resourceName)
        {
            string message = GetResourceString(resourceName);

            return MessageParser.TryStripMSBuildCode(message, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        /// <summary>
        /// Looks up a string in the resources, and formats it with the argument passed in. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they are discarded.
        /// </summary>
        /// <remarks>This method is thread-safe.</remarks>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="arg1">Argument for formatting the resource string.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1)
        {
            string message = MessageFormatter.Format(GetResourceString(resourceName), arg1);

            return MessageParser.TryStripMSBuildCode(message, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        /// <summary>
        /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they are discarded.
        /// </summary>
        /// <remarks>This method is thread-safe.</remarks>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="arg1">First argument for formatting the resource string.</param>
        /// <param name="arg2">Second argument for formatting the resource string.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1, object? arg2)
        {
            string message = MessageFormatter.Format(GetResourceString(resourceName), arg1, arg2);

            return MessageParser.TryStripMSBuildCode(message, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        /// <summary>
        /// Looks up a string in the resources, and formats it with the arguments passed in. If the string resource has an MSBuild
        /// message code and help keyword associated with it, they are discarded.
        /// </summary>
        /// <remarks>This method is thread-safe.</remarks>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="arg1">First argument for formatting the resource string.</param>
        /// <param name="arg2">Second argument for formatting the resource string.</param>
        /// <param name="arg3">Third argument for formatting the resource string.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringStripCodeAndKeyword(string resourceName, object? arg1, object? arg2, object? arg3)
        {
            string message = MessageFormatter.Format(GetResourceString(resourceName), arg1, arg2, arg3);

            return MessageParser.TryStripMSBuildCode(message, out string? strippedMessage)
                ? strippedMessage
                : message;
        }

        /// <summary>
        /// Formats the resource string with the given arguments.
        /// Ignores error codes and keywords.
        /// </summary>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="args">Optional arguments for formatting the resource string.</param>
        /// <returns>The formatted resource string.</returns>
        /// <remarks>the AssemblyResources.GetString() method is thread-safe.</remarks>
        internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, params object?[] args)
            => MessageFormatter.Format(GetResourceString(resourceName), args);

        // Overloads with 0-3 arguments to avoid array allocations.

        /// <summary>
        /// Formats the resource string.
        /// Ignores error codes and keywords.
        /// </summary>
        /// <param name="resourceName">Resource string to load.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName)
            => GetResourceString(resourceName);

        /// <summary>
        /// Formats the resource string with the given argument.
        /// Ignores error codes and keywords.
        /// </summary>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="arg1">Argument for formatting the resource string.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, object? arg1)
            => MessageFormatter.Format(GetResourceString(resourceName), arg1);

        /// <summary>
        /// Formats the resource string with the given arguments.
        /// Ignores error codes and keywords.
        /// </summary>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="arg1">First argument for formatting the resource string.</param>
        /// <param name="arg2">Second argument for formatting the resource string.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, object? arg1, object? arg2)
            => MessageFormatter.Format(GetResourceString(resourceName), arg1, arg2);

        /// <summary>
        /// Formats the resource string with the given arguments.
        /// Ignores error codes and keywords.
        /// </summary>
        /// <param name="resourceName">Resource string to load.</param>
        /// <param name="arg1">First argument for formatting the resource string.</param>
        /// <param name="arg2">Second argument for formatting the resource string.</param>
        /// <param name="arg3">Third argument for formatting the resource string.</param>
        /// <returns>The formatted resource string.</returns>
        internal static string FormatResourceStringIgnoreCodeAndKeyword(string resourceName, object? arg1, object? arg2, object? arg3)
            => MessageFormatter.Format(GetResourceString(resourceName), arg1, arg2, arg3);

        /// <summary>
        /// Verifies that a particular resource string actually exists in the string table. This will only be called in debug
        /// builds. It helps catch situations where a dev calls VerifyThrowXXX with a new resource string, but forgets to add the
        /// resource string to the string table, or misspells it!
        /// </summary>
        /// <remarks>This method is thread-safe.</remarks>
        /// <param name="resourceName">Resource string to check.</param>
        [Conditional("DEBUG")]
        internal static void VerifyResourceStringExists(string resourceName)
        {
            try
            {
                // Look up the resource string in the engine's string table.
                // NOTE: the AssemblyResources.GetString() method is thread-safe
                string unformattedMessage = AssemblyResources.GetString(resourceName);

                Assumed.NotNull(unformattedMessage, $"The resource string \"{resourceName}\" was not found.");
            }
            catch (ArgumentException e)
            {
#if FEATURE_DEBUG_LAUNCH
                Debug.Fail("The resource string \"" + resourceName + "\" was not found.");
#endif
                InternalError.Throw(e.Message);
            }
            catch (InvalidOperationException e)
            {
#if FEATURE_DEBUG_LAUNCH
                Debug.Fail("The resource string \"" + resourceName + "\" was not found.");
#endif
                InternalError.Throw(e.Message);
            }
            catch (MissingManifestResourceException e)
            {
#if FEATURE_DEBUG_LAUNCH
                Debug.Fail("The resource string \"" + resourceName + "\" was not found.");
#endif
                InternalError.Throw(e.Message);
            }
        }
    }
}