diff --git a/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs b/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs
index e7048722e8a6..7e3f3eacd95d 100644
--- a/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs
+++ b/src/Framework/AspNetCoreAnalyzers/src/SourceGenerators/PublicTopLevelProgramGenerator.cs
@@ -12,7 +12,9 @@ public class PublicProgramSourceGenerator : IIncrementalGenerator
{
private const string PublicPartialProgramClassSource = """
//
+#pragma warning disable CS1591
public partial class Program { }
+#pragma warning restore CS1591
""";
public void Initialize(IncrementalGeneratorInitializationContext context)
@@ -21,17 +23,17 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
// Get the entry point associated with the compilation, this maps to the Main method definition
// Get the containing symbol of the entry point, this maps to the Program class
compilation.GetEntryPoint(cancellationToken)?.ContainingSymbol is INamedTypeSymbol
- {
- // If the discovered `Program` type is not a class then its not
- // generated and has been defined in source, so we can skip it
- // If the program class is already public, we don't need to generate anything.
- DeclaredAccessibility: not Accessibility.Public,
- TypeKind: TypeKind.Class,
- // If there are multiple partial declarations, then do nothing since we don't want
- // to trample on visibility explicitly set by the user
- DeclaringSyntaxReferences: { Length: 1 } declaringSyntaxReferences
- } &&
- // If the `Program` class is already declared in user code, we don't need to generate anything.
+ {
+ // If the program class is already public, we don't need to generate anything
+ DeclaredAccessibility: not Accessibility.Public,
+ // If the discovered `Program` type is not a class then its not
+ // generated and has been defined in source, so we can skip it
+ TypeKind: TypeKind.Class,
+ // If there are multiple partial declarations, then do nothing since we don't want
+ // to trample on visibility explicitly set by the user
+ DeclaringSyntaxReferences: { Length: 1 } declaringSyntaxReferences
+ } &&
+ // If the `Program` class is already declared in user code, we don't need to generate anything
declaringSyntaxReferences.Single().GetSyntax(cancellationToken) is not ClassDeclarationSyntax
);
diff --git a/src/Framework/AspNetCoreAnalyzers/test/SourceGenerators/PublicTopLevelProgramGeneratorTests.cs b/src/Framework/AspNetCoreAnalyzers/test/SourceGenerators/PublicTopLevelProgramGeneratorTests.cs
index c91a110031a7..173dfa7469f8 100644
--- a/src/Framework/AspNetCoreAnalyzers/test/SourceGenerators/PublicTopLevelProgramGeneratorTests.cs
+++ b/src/Framework/AspNetCoreAnalyzers/test/SourceGenerators/PublicTopLevelProgramGeneratorTests.cs
@@ -7,6 +7,13 @@ namespace Microsoft.AspNetCore.SourceGenerators.Tests;
public class PublicTopLevelProgramGeneratorTests
{
+ private const string ExpectedGeneratedSource = """
+//
+#pragma warning disable CS1591
+public partial class Program { }
+#pragma warning restore CS1591
+""";
+
[Fact]
public async Task GeneratesSource_ProgramWithTopLevelStatements()
{
@@ -20,12 +27,33 @@ public async Task GeneratesSource_ProgramWithTopLevelStatements()
app.Run();
""";
- var expected = """
-//
-public partial class Program { }
+ await VerifyCS.VerifyAsync(source, "PublicTopLevelProgram.Generated.g.cs", ExpectedGeneratedSource);
+ }
+
+ // The compiler synthesizes a Program class in the global namespace due to top-level statements
+ // The Foo.Program class is completely unrelated to the entry point and is just as any regular type
+ // Hence, we will expect to see the source generated in these scenarios
+ [Theory]
+ [InlineData("public partial class Program { }")]
+ [InlineData("internal partial class Program { }")]
+ public async Task GeneratesSource_IfProgramDefinedInANamespace (string declaration)
+ {
+ var source = $$"""
+using Microsoft.AspNetCore.Builder;
+
+var app = WebApplication.Create();
+
+app.MapGet("/", () => "Hello, World!");
+
+app.Run();
+
+namespace Foo
+{
+ {{declaration}}
+}
""";
- await VerifyCS.VerifyAsync(source, "PublicTopLevelProgram.Generated.g.cs", expected);
+ await VerifyCS.VerifyAsync(source, "PublicTopLevelProgram.Generated.g.cs", ExpectedGeneratedSource);
}
[Fact]
@@ -86,6 +114,31 @@ public static void Main()
await VerifyCS.VerifyAsync(source);
}
+ [Fact]
+ public async Task DoesNotGeneratorSource_ExplicitPublicProgramClassInNamespace()
+ {
+ var source = """
+using Microsoft.AspNetCore.Builder;
+
+namespace Foo
+{
+ public class Program
+ {
+ public static void Main()
+ {
+ var app = WebApplication.Create();
+
+ app.MapGet("/", () => "Hello, World!");
+
+ app.Run();
+ }
+ }
+}
+""";
+
+ await VerifyCS.VerifyAsync(source);
+ }
+
[Fact]
public async Task DoesNotGeneratorSource_ExplicitInternalProgramClass()
{
@@ -108,6 +161,31 @@ public static void Main()
await VerifyCS.VerifyAsync(source);
}
+ [Fact]
+ public async Task DoesNotGeneratorSource_ExplicitInternalProgramClassInNamespace()
+ {
+ var source = """
+using Microsoft.AspNetCore.Builder;
+
+namespace Foo
+{
+ internal class Program
+ {
+ public static void Main()
+ {
+ var app = WebApplication.Create();
+
+ app.MapGet("/", () => "Hello, World!");
+
+ app.Run();
+ }
+ }
+}
+""";
+
+ await VerifyCS.VerifyAsync(source);
+ }
+
[Theory]
[InlineData("interface")]
[InlineData("struct")]
@@ -127,6 +205,33 @@ public static void Main(string[] args)
app.Run();
}
}
+""";
+
+ await VerifyCS.VerifyAsync(source);
+ }
+
+ [Theory]
+ [InlineData("interface")]
+ [InlineData("struct")]
+ public async Task DoesNotGeneratorSource_ExplicitInternalProgramTypeInNamespace(string type)
+ {
+ var source = $$"""
+using Microsoft.AspNetCore.Builder;
+
+namespace Foo
+{
+ internal {{type}} Program
+ {
+ public static void Main(string[] args)
+ {
+ var app = WebApplication.Create();
+
+ app.MapGet("/", () => "Hello, World!");
+
+ app.Run();
+ }
+ }
+}
""";
await VerifyCS.VerifyAsync(source);