Skip to content

File-level directives: allow quoting and additional properties - #55592

Merged
jjonescz merged 28 commits into
dotnet:mainfrom
jjonescz:52399-sprint-directive-properties
Aug 27, 2026
Merged

File-level directives: allow quoting and additional properties#55592
jjonescz merged 28 commits into
dotnet:mainfrom
jjonescz:52399-sprint-directive-properties

Conversation

@jjonescz

@jjonescz jjonescz commented Aug 4, 2026

Copy link
Copy Markdown
Member

Resolves #52399.
Resolves #49367.
Docs: dotnet/docs#55352

@jjonescz jjonescz added the Area-run-file Items related to the "dotnet run <file>" effort label Aug 4, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates file-based program #: directive parsing/emission to support double-quoted values (for whitespace) and additional MSBuild item metadata on #:package, #:project, and #:ref, and adds a new analyzer (CA2267) to help migrate legacy unquoted-whitespace directives.

Changes:

  • Extend file-level directive parsing to support quoted values and trailing Name=Value metadata, and emit metadata into generated MSBuild items.
  • Add/expand CLI and conversion tests for quoting and metadata round-tripping.
  • Introduce CA2267 (“Prefer quoted file-based program directive values”) with code fix, plus documentation/resource updates.

Reviewed changes

Copilot reviewed 46 out of 46 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/dotnet.Tests/CommandTests/Run/RunFileTests_Directives.cs Adds E2E coverage for #:ref metadata emitted into <ProjectReference>.
test/dotnet.Tests/CommandTests/Run/FileBasedAppSourceEditorTests.cs Tests directive editing/round-tripping with metadata and quoting.
test/dotnet.Tests/CommandTests/Project/Convert/DotnetProjectConvertTests.cs Adds conversion tests for quoting/metadata and updates escaping/whitespace expectations.
src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Usage/PreferQuotedFileBasedProgramDirectiveTests.cs New unit tests for CA2267 analyzer + fixer.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt Extends Usage ID range to include CA2267.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf Adds localized entries for new CA2267 strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Usage/PreferQuotedFileBasedProgramDirective.Fixer.cs Adds shared fixer base for CA2267.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/Usage/PreferQuotedFileBasedProgramDirective.cs Adds shared diagnostic descriptor for CA2267.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx Adds CA2267 resource strings.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers/AnalyzerReleases.Unshipped.md Registers CA2267 in unshipped analyzer list.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.sarif.template Adds CA2267 to SARIF template metadata.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.NetAnalyzers.md Adds generated documentation entry for CA2267.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.CSharp.NetAnalyzers/Microsoft.NetCore.Analyzers/Usage/FileBasedProgramDirectiveQuoting.cs Adds shared quoting detection/rewrite logic for analyzer/fixer.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.CSharp.NetAnalyzers/Microsoft.NetCore.Analyzers/Usage/CSharpPreferQuotedFileBasedProgramDirective.Fixer.cs Implements C# code fix for CA2267.
src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.CSharp.NetAnalyzers/Microsoft.NetCore.Analyzers/Usage/CSharpPreferQuotedFileBasedProgramDirective.cs Implements C# analyzer for CA2267.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.zh-Hant.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.zh-Hans.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.tr.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ru.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.pt-BR.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.pl.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ko.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.ja.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.it.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.fr.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.es.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.de.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/xlf/FileBasedProgramsResources.cs.xlf Adds localized CLI strings for new directive parsing errors.
src/Cli/Microsoft.DotNet.FileBasedPrograms/VirtualProjectBuilder.cs Emits directive metadata as child elements on generated MSBuild items.
src/Cli/Microsoft.DotNet.FileBasedPrograms/InternalAPI.Unshipped.txt Tracks new internal API surface for directive metadata properties.
src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs Implements tokenization, quoted values, and metadata parsing for directives.
src/Cli/Microsoft.DotNet.FileBasedPrograms/FileBasedProgramsResources.resx Adds/updates directive parsing error messages.
src/Cli/dotnet/Commands/Project/Convert/ProjectConvertCommand.cs Preserves #:ref metadata when converting to a project.
documentation/general/dotnet-run-file.md Documents quoting rules, legacy behavior, and new metadata support.
Suppressed comments (1)

src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs:565

  • Metadata element names are emitted directly into the generated project file. XmlConvert.VerifyName allows ':' (QName) which can yield invalid MSBuild XML without an xmlns declaration. Use VerifyNCName here to ensure generated elements are always safe.
            try
            {
                name = XmlConvert.VerifyName(name);
            }

Comment thread src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs Outdated
Comment thread src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 46 out of 46 changed files in this pull request and generated 1 comment.

Suppressed comments (5)

src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs:366

  • Token.ContainsDiagnostics is not specific to unterminated quotes; it will also be true for invalid escape sequences (e.g. "\q"). Reporting UnterminatedQuoteInDirective for all diagnostics can mislead users. Distinguish the unterminated-quote case (missing closing ") from other invalid string literal diagnostics and use InvalidQuoteInDirective for the latter.
                var token = SyntaxFactory.ParseToken(text, offset: i);
                if (token.ContainsDiagnostics)
                {
                    context.ReportError(FileBasedProgramsResources.UnterminatedQuoteInDirective);
                    return null;

src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs:572

  • Metadata names are emitted as XML element names. XmlConvert.VerifyName permits ':' (QName), which can yield invalid MSBuild XML when written as <a:b> without a namespace binding. Prefer XmlConvert.VerifyNCName here to prevent generating malformed project files.
            try
            {
                name = XmlConvert.VerifyName(name);
            }
            catch (XmlException ex)

src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs:563

  • ParseMetadata treats tokens like "=value" as valid because it only checks separatorIndex < 0. XmlConvert.VerifyName("") will throw, which can surface as an unhandled exception instead of a directive parse error. Treat separatorIndex <= 0 as invalid metadata and report InvalidDirectiveMetadata.
            var separatorIndex = token.IndexOf('=');
            if (separatorIndex < 0)
            {
                context.ReportError(string.Format(FileBasedProgramsResources.InvalidDirectiveMetadata, token));
                return null;

src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs:984

  • Ref.Metadata is left uninitialized (default ImmutableArray), unlike Package/Project which default to Empty. Default ImmutableArray can throw on common operations and forces callers to handle IsDefault. Initialize to ImmutableArray.Empty for consistency and safer consumption.
        public ImmutableArray<(string Name, string Value)> Metadata { get; init; }

test/dotnet.Tests/CommandTests/Run/RunFileTests_Directives.cs:350

  • The new RefDirective_Metadata test summary claims it validates that trailing #:ref metadata is emitted into the generated , but the test only asserts the app output. If metadata emission regresses (metadata dropped), the build/run would likely still succeed and this test would not fail. Add an assertion that inspects the generated virtual project and verifies test and a b c are present under the ProjectReference.

Comment thread src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 48 out of 48 changed files in this pull request and generated 1 comment.

@jjonescz
jjonescz force-pushed the 52399-sprint-directive-properties branch from 1808247 to e02c989 Compare August 21, 2026 09:43
@jjonescz
jjonescz requested a balanced review from Copilot August 21, 2026 09:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 48 out of 48 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Usage/PreferQuotedFileBasedProgramDirectiveTests.cs:71

  • The linked CA2267 documentation PR says this fixer removes whitespace around separators and shows #:property Prop=Value, but this expected output deliberately preserves the whitespace and only quotes the value. Update the linked documentation to match the shipped fixer behavior.
    src/Microsoft.CodeAnalysis.NetAnalyzers/tests/Microsoft.CodeAnalysis.NetAnalyzers.UnitTests/Microsoft.NetCore.Analyzers/Usage/PreferQuotedFileBasedProgramDirectiveTests.cs:85
  • The implementation and tests include #:ref, but the linked CA2267 documentation PR's applicability list omits ref. Add it there so users of this experimental directive are not surprised by an undocumented warning.
    src/Microsoft.CodeAnalysis.NetAnalyzers/src/Microsoft.CodeAnalysis.CSharp.NetAnalyzers/Microsoft.NetCore.Analyzers/Usage/FileBasedProgramDirectiveQuoting.cs:98
  • IsLegacyForm reports this build-warning rule for malformed directives as well as valid legacy ones. For example, #:property Name Value and #:sdk My SDK take these branches, but the CLI parser rejects them because the required separator is absent and the resulting name contains whitespace; quoting cannot make either directive valid. This contradicts CA2267's contract that it only diagnoses valid legacy values with an equivalent quoted form. Validate the kind-specific name/separator shape the same way as the parser before returning true, and add negative tests for malformed directives.
        return kind switch
        {
            "property" or "sdk" or "include" or "exclude" => true,
            "package" or "project" or "ref" => !AllValidMetadata(tokens, start: 1),
            _ => false,

@jjonescz
jjonescz force-pushed the 52399-sprint-directive-properties branch from e02c989 to bc050cd Compare August 21, 2026 10:01
@github-actions

This comment has been minimized.

@github-actions

This comment was marked as outdated.

@jjonescz
jjonescz marked this pull request as ready for review August 21, 2026 11:18
@jjonescz
jjonescz requested a review from 333fred August 21, 2026 11:18
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

The remainder of a directive (after the kind) is split into whitespace-separated tokens.
Whitespace inside a value is not allowed unless the value is enclosed in double quotes (`"`).
A value is written either bare or wrapped entirely in double quotes.
A quoted value is lexed as a regular C# string literal (the same way `#r`/`#load` directives lex their argument),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is this true? On Windows I was successful writing the following script structure:

// file.csx
#load "util\util.csx"

Console.WriteLine(typeof(Util).Name);
// util/util.csx
public class Util { }
$ dotnet script file.csx
Util

util\\util.csx also worked, but, I think that's just because doubling up on slashes in paths generally works.

Neither \ or \\ worked on Linux. I think the csx engine doesn't do any msbuild-like normalization of the separators.

Also, I can see there are a number of pre-existing tests for #line which expect directives like #line 60 "C:\X.cs" to work.


I am wondering if copying the msbuild semantics for #: directives is possible or desirable here. e.g. treating #:include "C:\My Folder\Path" similarly to <Compile Include="C:\My Folder\Path" />.

@jjonescz jjonescz Aug 25, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

That's a good catch. Being consistent with #r/#load means we won't be able to support quoted characters like \" in directive values though1, so I'm not sure if we want to do that. #r/#load probably don't need that since they are for files only, but with our directives we support arbitrary text, like in #:property PackageDescription="Maybe I want \"quotes\" here"

Footnotes

  1. not even in the future without a breaking change or some new syntax inconsistent with rest of C#

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm leaning towards the suggestion of copying MSBuild's property value semantics here as that's what these values map to.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Is there a feeling that using "C# string" behavior is going to be more capable and easier to understand than using "MSBuild attribute string" behavior? It feels like with the latter, most/all things should be expressible using xml entities such as &quot; for ". But, it might seem strange to be using XML-isms from a context which doesn't look like XML.

I don't have a super strong leaning here, just, the claim about matching pre-existing directives looked off to me and set off alarm bells in terms of motivation. I think as long as we have a motivation for what we're doing, we should def be free to choose a new behavior for the strings in these new directives. Thanks!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

OK leaning towards C# string behavior now in the spirit of "this is a .cs file" and that expressing paths is easy with forward slashes as they get normalized by MSBuild anyway

@jjonescz jjonescz Aug 26, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks, discussed offline as well, and seems we are going with standard C# string literal behavior (as already implemented in this PR). I will just update the docs.

@jjonescz
jjonescz requested a review from RikkiGibson August 26, 2026 12:18

@RikkiGibson RikkiGibson left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I reviewed the doc and skimmed through the tests, didn't spot any issues.

@jjonescz
jjonescz merged commit c149b62 into dotnet:main Aug 27, 2026
22 checks passed
@jjonescz
jjonescz deleted the 52399-sprint-directive-properties branch August 27, 2026 06:54
@jjonescz

Copy link
Copy Markdown
Member Author

/backport to release/11.0.1xx

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/11.0.1xx (link to workflow run)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-run-file Items related to the "dotnet run <file>" effort

Projects

None yet

6 participants