Skip to content

Suppress CS1591 warnings for PublicTopLevelProgram.Generated.g.cs #60727

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 4, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ public class PublicProgramSourceGenerator : IIncrementalGenerator
{
private const string PublicPartialProgramClassSource = """
// <auto-generated />
#pragma warning disable CS1591
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is right here. AFAIK, analyzers shouldn't trigger on generated code.

In the past, I've noticed that the // <auto-generated /> marker on the file isn't sufficient. We might also want to emit [GeneratedCode] here to mark this type as generated.

You can see how we do it for the OpenAPI XML generator and the minimal API source generator.

Copy link
Member

Choose a reason for hiding this comment

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

This is not an analyzer, it's a compile warning. There were discussions in dotnet/roslyn to special case that specific one for generated code, but I'm not sure if anything was done yet, or if any decision was taken.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Projects that have <GenerateDocumentationFile>true</GenerateDocumentationFile> in the .csproj will have the CS1591 compiler warning when the current generated code is output preventing building the solution

CS1591 looks as follows in this case
Missing XML comment for publicly visible type or member 'Program'

This #pragma should suppress this warning

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Something important to note is that the project in the issue only has the CS1591 warning preventing the build due to them also having
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
That being said we still don't want this warning to flag for the generated code when the below option is stated in the project
<GenerateDocumentationFile>true</GenerateDocumentationFile>

public partial class Program { }
#pragma warning restore CS1591
""";

public void Initialize(IncrementalGeneratorInitializationContext context)
Expand All @@ -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
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ namespace Microsoft.AspNetCore.SourceGenerators.Tests;

public class PublicTopLevelProgramGeneratorTests
{
private const string ExpectedGeneratedSource = """
// <auto-generated />
#pragma warning disable CS1591
public partial class Program { }
#pragma warning restore CS1591
""";

[Fact]
public async Task GeneratesSource_ProgramWithTopLevelStatements()
{
Expand All @@ -20,12 +27,33 @@ public async Task GeneratesSource_ProgramWithTopLevelStatements()
app.Run();
""";

var expected = """
// <auto-generated />
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]
Expand Down Expand Up @@ -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()
{
Expand All @@ -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")]
Expand All @@ -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);
Expand Down
Loading