File: SyncedSource\FileBasedPrograms\FileBasedProgramDirectiveValueHelpers.cs
Web Access
Project: Microsoft.CodeAnalysis.CSharp.Workspaces.csproj (Microsoft.CodeAnalysis.CSharp.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable enable

using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Xml;
using Microsoft.CodeAnalysis.CSharp;

namespace Microsoft.DotNet.FileBasedPrograms;

/// <summary>
/// Low-level primitives for parsing and formatting the values of file-based program <c>#:</c> directives.
/// These are source-shared between the CLI directive parser (<c>FileLevelDirectiveHelpers</c>)
/// and the analyzer that flags the deprecated unquoted form (<c>FileBasedProgramDirectiveQuoting</c>).
/// </summary>
internal static class FileBasedProgramDirectiveValueHelpers
{
    /// <summary>
    /// Returns whether <paramref name="name"/> contains a character that is not allowed in a directive
    /// or metadata name (whitespace or one of the separator characters <c>@</c>, <c>=</c>, <c>/</c>).
    /// </summary>
    public static bool ContainsDisallowedNameCharacter(string name)
    {
        foreach (var c in name)
        {
            if (char.IsWhiteSpace(c) || c is '@' or '=' or '/')
            {
                return true;
            }
        }

        return false;
    }

    /// <summary>
    /// Validates that <paramref name="name"/> is a valid XML NCName,
    /// the constraint MSBuild applies to property and item-metadata names
    /// (an NCName additionally disallows the ':' that a plain XML name permits).
    /// </summary>
    public static bool IsValidMSBuildName(string name, [NotNullWhen(returnValue: false)] out string? errorMessage)
    {
        try
        {
            XmlConvert.VerifyNCName(name);
            errorMessage = null;
            return true;
        }
        catch (XmlException ex)
        {
            errorMessage = ex.Message;
            return false;
        }
    }

    /// <summary>
    /// Returns whether every token from <paramref name="start"/> onwards
    /// is a valid <c>Name=Value</c> item-metadata pair
    /// (a valid MSBuild name, then <c>'='</c>, then any value).
    /// </summary>
    public static bool AllValidMetadata(ImmutableArray<string> tokens, int start)
    {
        for (var i = start; i < tokens.Length; i++)
        {
            var token = tokens[i];
            var separatorIndex = token.IndexOf('=');
            if (separatorIndex <= 0)
            {
                return false;
            }

            if (!IsValidMSBuildName(token.Substring(0, separatorIndex), out _))
            {
                return false;
            }
        }

        return true;
    }

    /// <summary>
    /// Wraps <paramref name="value"/> in a C# string literal when it contains a character (whitespace or a double quote)
    /// that cannot appear in a bare directive token. Otherwise returns it unchanged.
    /// </summary>
    public static string QuoteIfNeeded(string value)
    {
        foreach (var c in value)
        {
            if (char.IsWhiteSpace(c) || c == '"')
            {
                return SymbolDisplay.FormatLiteral(value, quote: true);
            }
        }

        return value;
    }
}