File-level directives: allow quoting and additional properties - #55592
Conversation
|
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. |
There was a problem hiding this comment.
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=Valuemetadata, 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);
}
There was a problem hiding this comment.
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.
1808247 to
e02c989
Compare
There was a problem hiding this comment.
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 omitsref. 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 IsLegacyFormreports this build-warning rule for malformed directives as well as valid legacy ones. For example,#:property Name Valueand#:sdk My SDKtake 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 returningtrue, 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,
e02c989 to
bc050cd
Compare
This comment has been minimized.
This comment has been minimized.
This comment was marked as outdated.
This comment was marked as outdated.
|
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), |
There was a problem hiding this comment.
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
Utilutil\\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" />.
There was a problem hiding this comment.
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
-
not even in the future without a breaking change or some new syntax inconsistent with rest of C# ↩
There was a problem hiding this comment.
I'm leaning towards the suggestion of copying MSBuild's property value semantics here as that's what these values map to.
There was a problem hiding this comment.
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 " 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!
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
RikkiGibson
left a comment
There was a problem hiding this comment.
I reviewed the doc and skimmed through the tests, didn't spot any issues.
|
/backport to release/11.0.1xx |
|
Started backporting to |
Resolves #52399.
Resolves #49367.
Docs: dotnet/docs#55352