Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,40 @@
<value>Duplicate directives are not supported: {0}</value>
<comment>{0} is the directive type and name.</comment>
</data>
<data name="QuoteInDirective" xml:space="preserve">
<value>Directives currently cannot contain double quotes (").</value>
<data name="UnterminatedQuoteInDirective" xml:space="preserve">
<value>Unterminated double quote (") in directive.</value>
</data>
<data name="InvalidQuoteInDirective" xml:space="preserve">
<value>Double quotes (") in a directive must enclose an entire value, for example: 'Name="a b"' or '"a b"'.</value>
<comment>{Locked="Name=&quot;a b&quot;"}{Locked="&quot;a b&quot;"}</comment>
</data>
<data name="InvalidStringLiteralInDirective" xml:space="preserve">
<value>Invalid quoted value in directive: {0}</value>
<comment>{0} is the underlying C# string literal error message, for example 'Unrecognized escape sequence.'.</comment>
</data>
<data name="ExpectedSimpleStringLiteralInDirective" xml:space="preserve">
<value>Expected a simple string literal in the directive value. Raw and verbatim string literals are not supported in directive values. Found: {0}</value>
<comment>{0} is the offending C# token text, for example '"""abc"""'.</comment>
</data>
Comment thread
jjonescz marked this conversation as resolved.
Resolved
<data name="InvalidDirectiveMetadata" xml:space="preserve">
<value>Directive metadata must be in the form 'Name=Value'. Invalid metadata: '{0}'.</value>
<comment>{Locked="'Name=Value'"}{0} is the offending metadata text.</comment>
</data>
<data name="DirectiveMetadataInvalidName" xml:space="preserve">
<value>Invalid directive metadata name '{0}': {1}</value>
<comment>{0} is the metadata name. {1} is the inner exception message.</comment>
</data>
<data name="ConflictingDirectiveMetadata" xml:space="preserve">
<value>Directive metadata '{0}' conflicts with a value already specified by the directive.</value>
<comment>{0} is the metadata name.</comment>
</data>
<data name="DuplicateDirectiveMetadata" xml:space="preserve">
<value>Directive metadata name '{0}' is specified more than once.</value>
<comment>{0} is the duplicate metadata name.</comment>
</data>
<data name="UnexpectedDirectiveText" xml:space="preserve">
<value>The '{0}' directive has unexpected content. To include whitespace in a value, enclose it in double quotes (").</value>
<comment>{0} is the directive kind like 'property' or 'sdk'.</comment>
</data>
<data name="InvalidProjectDirective" xml:space="preserve">
<value>The '#:project' directive is invalid: {0}</value>
Expand Down
Loading