diff --git a/documentation/general/dotnet-run-file.md b/documentation/general/dotnet-run-file.md index 49a4946936d5..fff05c0734b3 100644 --- a/documentation/general/dotnet-run-file.md +++ b/documentation/general/dotnet-run-file.md @@ -267,8 +267,9 @@ We do not limit these directives to appear only in entry point files because it - which also makes it possible to share it independently or symlink it to multiple script folders, - and it's similar to `global using`s which users usually put into a single file but don't have to. -We disallow duplicate `#:` directives to allow us design some deduplication mechanism in the future. +We disallow duplicate `#:` directives (except `#:project` and `#:ref`) to allow us to design some deduplication mechanism in the future. Specifically, directives are considered duplicate if their type and name (case insensitive) are equal. +`#:project` and `#:ref` duplicates are allowed because MSBuild allows duplicate `` items. Later with deduplication, separate "self-contained" utilities could reference overlapping sets of packages even if they end up in the same compilation. For example, properties could be concatenated via `;`, more specific package versions could override less specific ones. diff --git a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs index 2303d48b4c2e..711cb07fb8fb 100644 --- a/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs +++ b/src/Cli/Microsoft.DotNet.FileBasedPrograms/FileLevelDirectiveHelpers.cs @@ -156,15 +156,19 @@ public static void FindLeadingDirectives( if (CSharpDirective.Parse(context) is { } directive) { - // If the directive is already present, report an error. - if (deduplicated.TryGetValue(directive, out var existingDirective)) + // Duplicate #:project and #:ref directives are allowed (MSBuild can handle that). + if (directive is not (CSharpDirective.Project or CSharpDirective.Ref)) { - var typeAndName = $"#:{existingDirective.GetType().Name.ToLowerInvariant()} {existingDirective.Name}"; - context.ReportError(directive.Info.Span, string.Format(FileBasedProgramsResources.DuplicateDirective, typeAndName)); - } - else - { - deduplicated.Add(directive, directive); + // If the directive is already present, report an error. + if (deduplicated.TryGetValue(directive, out var existingDirective)) + { + var typeAndName = $"#:{existingDirective.GetType().Name.ToLowerInvariant()} {existingDirective.Name}"; + context.ReportError(directive.Info.Span, string.Format(FileBasedProgramsResources.DuplicateDirective, typeAndName)); + } + else + { + deduplicated.Add(directive, directive); + } } builder?.Add(directive); diff --git a/test/dotnet.Tests/CommandTests/Run/RunFileTests_Directives.cs b/test/dotnet.Tests/CommandTests/Run/RunFileTests_Directives.cs index e4f228613be1..4d28b6ac08af 100644 --- a/test/dotnet.Tests/CommandTests/Run/RunFileTests_Directives.cs +++ b/test/dotnet.Tests/CommandTests/Run/RunFileTests_Directives.cs @@ -276,6 +276,7 @@ public void ProjectReference_Duplicate(string? subdir) """); + // Duplicate #:project directives are allowed (MSBuild can handle that). File.WriteAllText(filePath, """ #:project dir/ #:project dir/ @@ -285,8 +286,8 @@ public void ProjectReference_Duplicate(string? subdir) new DotnetCommand(Log, "run", relativeFilePath) .WithWorkingDirectory(testInstance.Path) .Execute() - .Should().Fail() - .And.HaveStdErrContaining(DirectiveError(filePath, 2, FileBasedProgramsResources.DuplicateDirective, "#:project dir/")); + .Should().Pass() + .And.HaveStdOut("Hello"); File.WriteAllText(filePath, """ #:project dir/ @@ -294,7 +295,6 @@ public void ProjectReference_Duplicate(string? subdir) Console.WriteLine("Hello"); """); - // https://github.com/dotnet/sdk/issues/51139: we should detect the duplicate project reference new DotnetCommand(Log, "run", relativeFilePath) .WithWorkingDirectory(testInstance.Path) .Execute() @@ -307,7 +307,6 @@ public void ProjectReference_Duplicate(string? subdir) Console.WriteLine("Hello"); """); - // https://github.com/dotnet/sdk/issues/51139: we should detect the duplicate project reference new DotnetCommand(Log, "run", relativeFilePath) .WithWorkingDirectory(testInstance.Path) .Execute() @@ -574,6 +573,7 @@ public static class Greeter } """); + // Duplicate #:ref directives are allowed (MSBuild can handle that). File.WriteAllText(filePath, """ #:ref lib.cs #:ref lib.cs @@ -583,8 +583,8 @@ public static class Greeter new DotnetCommand(Log, "run", relativeFilePath) .WithWorkingDirectory(testInstance.Path) .Execute() - .Should().Fail() - .And.HaveStdErrContaining(DirectiveError(filePath, 2, FileBasedProgramsResources.DuplicateDirective, "#:ref lib.cs")); + .Should().Pass() + .And.HaveStdOut("Hello!"); File.WriteAllText(filePath, """ #:ref lib.cs @@ -592,7 +592,6 @@ public static class Greeter Console.WriteLine(MyLib.Greeter.Greet()); """); - // https://github.com/dotnet/sdk/issues/51139: we should detect the duplicate ref new DotnetCommand(Log, "run", relativeFilePath) .WithWorkingDirectory(testInstance.Path) .Execute() @@ -605,7 +604,6 @@ public static class Greeter Console.WriteLine(MyLib.Greeter.Greet()); """); - // https://github.com/dotnet/sdk/issues/51139: we should detect the duplicate ref new DotnetCommand(Log, "run", relativeFilePath) .WithWorkingDirectory(testInstance.Path) .Execute()