Skip to content

Commit 08a3e47

Browse files
Bart KoelmanBart Koelman
authored andcommitted
Fixed invalid warning on global namespace for compiler-generated top-level Program class
1 parent bd75c09 commit 08a3e47

File tree

2 files changed

+49
-1
lines changed

2 files changed

+49
-1
lines changed

src/CSharpGuidelinesAnalyzer/CSharpGuidelinesAnalyzer.Test/Specs/Maintainability/NamespaceShouldMatchAssemblyNameSpecs.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using CSharpGuidelinesAnalyzer.Rules.Maintainability;
22
using CSharpGuidelinesAnalyzer.Test.TestDataBuilders;
3+
using Microsoft.CodeAnalysis;
34
using Microsoft.CodeAnalysis.Diagnostics;
45
using Xunit;
56

@@ -380,6 +381,44 @@ class [|C|]
380381
"Type 'C' is declared in namespace 'WrongRoot.JetBrains.Annotations', which does not match with assembly name 'Company.ProductName'.");
381382
}
382383

384+
[Fact]
385+
internal void When_top_level_program_it_must_be_skipped()
386+
{
387+
// Arrange
388+
ParsedSourceCode source = new TypeSourceCodeBuilder()
389+
.InAssemblyNamed("Some.Scope.Example")
390+
.WithOutputKind(OutputKind.ConsoleApplication)
391+
.InGlobalScope(@"
392+
System.Console.WriteLine("""");
393+
")
394+
.Build();
395+
396+
// Act and assert
397+
VerifyGuidelineDiagnostic(source);
398+
}
399+
400+
[Fact]
401+
internal void When_explicit_program_in_global_namespace_it_must_be_reported()
402+
{
403+
// Arrange
404+
ParsedSourceCode source = new TypeSourceCodeBuilder()
405+
.InAssemblyNamed("Some.Scope.Example")
406+
.WithOutputKind(OutputKind.ConsoleApplication)
407+
.InGlobalScope(@"
408+
static class [|Program|]
409+
{
410+
static void Main()
411+
{
412+
System.Console.WriteLine("""");
413+
}
414+
}
415+
")
416+
.Build();
417+
418+
// Act and assert
419+
VerifyGuidelineDiagnostic(source, "Type 'Program' is declared in global namespace, which does not match with assembly name 'Some.Scope.Example'.");
420+
}
421+
383422
protected override DiagnosticAnalyzer CreateAnalyzer()
384423
{
385424
return new NamespaceShouldMatchAssemblyNameAnalyzer();

src/CSharpGuidelinesAnalyzer/CSharpGuidelinesAnalyzer/Rules/Maintainability/NamespaceShouldMatchAssemblyNameAnalyzer.cs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ namespace CSharpGuidelinesAnalyzer.Rules.Maintainability
1212
[DiagnosticAnalyzer(LanguageNames.CSharp)]
1313
public sealed class NamespaceShouldMatchAssemblyNameAnalyzer : DiagnosticAnalyzer
1414
{
15+
// Copied from Microsoft.CodeAnalysis.WellKnownMemberNames, which provides these in later versions.
16+
private const string TopLevelStatementsEntryPointTypeName = "Program";
17+
private const string TopLevelStatementsEntryPointMethodName = "<Main>$";
18+
1519
private const string Title = "Namespace should match with assembly name";
1620
private const string NamespaceMessageFormat = "Namespace '{0}' does not match with assembly name '{1}'.";
1721
private const string TypeInNamespaceMessageFormat = "Type '{0}' is declared in namespace '{1}', which does not match with assembly name '{2}'.";
@@ -94,12 +98,17 @@ private static void AnalyzeNamedType(SymbolAnalysisContext context)
9498
{
9599
var type = (INamedTypeSymbol)context.Symbol;
96100

97-
if (type.ContainingNamespace.IsGlobalNamespace && !type.IsSynthesized())
101+
if (type.ContainingNamespace.IsGlobalNamespace && !type.IsSynthesized() && !IsTopLevelStatementsContainer(type))
98102
{
99103
context.ReportDiagnostic(Diagnostic.Create(GlobalTypeRule, type.Locations[0], type.Name, type.ContainingAssembly.Name));
100104
}
101105
}
102106

107+
private static bool IsTopLevelStatementsContainer([NotNull] INamedTypeSymbol type)
108+
{
109+
return type.Name == TopLevelStatementsEntryPointTypeName && type.GetMembers(TopLevelStatementsEntryPointMethodName).Any();
110+
}
111+
103112
private sealed class TypesInNamespaceVisitor : SymbolVisitor
104113
{
105114
[ItemNotNull]

0 commit comments

Comments
 (0)