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
9 changes: 5 additions & 4 deletions documentation/general/dotnet-run-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,14 +153,15 @@ Other directives result in an error, reserving them for future use.

```cs
#:sdk Microsoft.NET.Sdk.Web
#:property TargetFramework net11.0
#:property LangVersion preview
#:property TargetFramework=net11.0
#:property LangVersion=preview
#:package System.CommandLine@2.0.0-*
```

The value must be separated from the name of the directive by white space (`@` is additionally allowed separator for the package directive)
The value must be separated from the kind (`package`/`sdk`/`property`) of the directive by whitespace
and any leading and trailing white space is not considered part of the value.
Any value can optionally have two parts separated by a space (more whitespace characters could be allowed in the future).
Any value can optionally have two parts separated by `@` in case of `package`/`sdk` or `=` in case of `property`
and whitespace is trimmed from the two parts around the separator.
The value of the first `#:sdk` is injected into `<Project Sdk="{0}">` with the separator (if any) replaced with `/`,
and the subsequent `#:sdk` directive values are split by the separator and injected as `<Sdk Name="{0}" Version="{1}" />` elements (or without the `Version` attribute if there is no separator).
It is an error if the first part (name) is empty (the version is allowed to be empty, but that results in empty `Version=""`).
Expand Down
6 changes: 5 additions & 1 deletion src/Cli/dotnet/Commands/CliCommandStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -1506,9 +1506,13 @@ Tool '{1}' (version '{2}') was successfully installed. Entry is added to the man
<comment>{0} is the file path and line number. {1} is an inner exception message.</comment>
</data>
<data name="PropertyDirectiveMissingParts" xml:space="preserve">
<value>The property directive needs to have two parts separated by a space like 'PropertyName PropertyValue': {0}</value>
<value>The property directive needs to have two parts separated by '=' like '#:property PropertyName=PropertyValue': {0}</value>
<comment>{0} is the file path and line number.</comment>
</data>
<data name="InvalidDirectiveName" xml:space="preserve">
<value>The directive at '{2}' should contain a name without special characters and an optional version separated by '{1}' like '#:{0} Abc{1}Xyz'.</value>
<comment>{0} is the directive type like 'package' or 'sdk'. {1} is the expected separator like '@' or '='. {2} is the file path and line number.</comment>
</data>
<data name="CannotConvertDirective" xml:space="preserve">
<value>Some directives cannot be converted: the first error is at {0}. Run the file to see all compilation errors. Specify '--force' to convert anyway.</value>
<comment>{Locked="--force"}. {0} is the file path and line number.</comment>
Expand Down
31 changes: 18 additions & 13 deletions src/Cli/dotnet/Commands/Run/VirtualProjectBuildingCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -895,6 +895,9 @@ internal static partial class Patterns
{
[GeneratedRegex("""\s+""")]
public static partial Regex Whitespace { get; }

[GeneratedRegex("""[\s@=/]""")]
public static partial Regex DisallowedNameCharacters { get; }
}

/// <summary>
Expand Down Expand Up @@ -934,25 +937,29 @@ private CSharpDirective() { }
}
}

private static (string, string?)? ParseOptionalTwoParts(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText, SearchValues<char>? separators = null)
private static (string, string?)? ParseOptionalTwoParts(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText, char separator)
{
var i = separators != null
? directiveText.AsSpan().IndexOfAny(separators)
: directiveText.IndexOf(' ', StringComparison.Ordinal);
var firstPart = i < 0 ? directiveText : directiveText[..i];
var i = directiveText.IndexOf(separator, StringComparison.Ordinal);
var firstPart = (i < 0 ? directiveText : directiveText.AsSpan(..i)).TrimEnd();
Copy link
Member

@RikkiGibson RikkiGibson Jun 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

micro-nit to make it clear that directiveText is always converted to span. Take it or not at your discretion.

Suggested change
var firstPart = (i < 0 ? directiveText : directiveText.AsSpan(..i)).TrimEnd();
var firstPart = directiveText.AsSpan(..(i < 0 ? ^0 : i)).TrimEnd();


if (string.IsNullOrWhiteSpace(firstPart))
if (firstPart.IsWhiteSpace())
{
return ReportError<(string, string?)?>(errors, sourceFile, span, string.Format(CliCommandStrings.MissingDirectiveName, directiveKind, sourceFile.GetLocationString(span)));
}

// If the name contains characters that resemble separators, report an error to avoid any confusion.
if (Patterns.DisallowedNameCharacters.IsMatch(firstPart))
{
return ReportError<(string, string?)?>(errors, sourceFile, span, string.Format(CliCommandStrings.InvalidDirectiveName, directiveKind, separator, sourceFile.GetLocationString(span)));
}

var secondPart = i < 0 ? [] : directiveText.AsSpan((i + 1)..).TrimStart();
if (i < 0 || secondPart.IsWhiteSpace())
{
return (firstPart, null);
return (firstPart.ToString(), null);
}

return (firstPart, secondPart.ToString());
return (firstPart.ToString(), secondPart.ToString());
}

/// <summary>
Expand All @@ -976,7 +983,7 @@ private Sdk() { }

public static new Sdk? Parse(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText)
{
if (ParseOptionalTwoParts(errors, sourceFile, span, directiveKind, directiveText) is not var (sdkName, sdkVersion))
if (ParseOptionalTwoParts(errors, sourceFile, span, directiveKind, directiveText, separator: '@') is not var (sdkName, sdkVersion))
{
return null;
}
Expand Down Expand Up @@ -1006,7 +1013,7 @@ private Property() { }

public static new Property? Parse(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText)
{
if (ParseOptionalTwoParts(errors, sourceFile, span, directiveKind, directiveText) is not var (propertyName, propertyValue))
if (ParseOptionalTwoParts(errors, sourceFile, span, directiveKind, directiveText, separator: '=') is not var (propertyName, propertyValue))
{
return null;
}
Expand Down Expand Up @@ -1039,15 +1046,13 @@ private Property() { }
/// </summary>
public sealed class Package : Named
{
private static readonly SearchValues<char> s_separators = SearchValues.Create(' ', '@');

private Package() { }

public string? Version { get; init; }

public static new Package? Parse(ImmutableArray<SimpleDiagnostic>.Builder? errors, SourceFile sourceFile, TextSpan span, string directiveKind, string directiveText)
{
if (ParseOptionalTwoParts(errors, sourceFile, span, directiveKind, directiveText, s_separators) is not var (packageName, packageVersion))
if (ParseOptionalTwoParts(errors, sourceFile, span, directiveKind, directiveText, separator: '@') is not var (packageName, packageVersion))
{
return null;
}
Expand Down
11 changes: 8 additions & 3 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.it.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 7 additions & 2 deletions src/Cli/dotnet/Commands/xlf/CliCommandStrings.ja.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading