diff --git a/src/Analyzers/CSharp/Tests/RemoveUnusedMembers/RemoveUnusedMembersTests.cs b/src/Analyzers/CSharp/Tests/RemoveUnusedMembers/RemoveUnusedMembersTests.cs index 85c1f27357a91..035e686a61848 100644 --- a/src/Analyzers/CSharp/Tests/RemoveUnusedMembers/RemoveUnusedMembersTests.cs +++ b/src/Analyzers/CSharp/Tests/RemoveUnusedMembers/RemoveUnusedMembersTests.cs @@ -5,6 +5,8 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp.RemoveUnusedMembers; +using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; +using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Testing; using Roslyn.Test.Utilities; @@ -356,6 +358,113 @@ class MyClass await VerifyCS.VerifyCodeFixAsync(code, code); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)] + public async Task EntryPointMethodNotFlagged_06() + { + var code = @" +return 0; +"; + + await new VerifyCS.Test + { + TestCode = code, + FixedCode = code, + ExpectedDiagnostics = + { + // error CS8805: Program using top-level statements must be an executable. + DiagnosticResult.CompilerError("CS8805"), + }, + LanguageVersion = LanguageVersionExtensions.CSharp9, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)] + public async Task EntryPointMethodNotFlagged_07() + { + var code = @" +return 0; +"; + + await new VerifyCS.Test + { + TestState = + { + Sources = { code, code }, + }, + FixedState = + { + Sources = { code, code }, + }, + ExpectedDiagnostics = + { + // error CS8805: Program using top-level statements must be an executable. + DiagnosticResult.CompilerError("CS8805"), + // /0/Test1.cs(2,1): error CS8802: Only one compilation unit can have top-level statements. + DiagnosticResult.CompilerError("CS8802").WithSpan("/0/Test1.cs", 2, 1, 2, 7), + }, + LanguageVersion = LanguageVersionExtensions.CSharp9, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)] + public async Task EntryPointMethodNotFlagged_08() + { + var code = @" +return 0; +"; + + await new VerifyCS.Test + { + TestCode = code, + FixedCode = code, + SolutionTransforms = + { + (solution, projectId) => + { + var project = solution.GetRequiredProject(projectId); + var compilationOptions = project.CompilationOptions; + return solution.WithProjectCompilationOptions(projectId, compilationOptions.WithOutputKind(OutputKind.ConsoleApplication)); + }, + }, + LanguageVersion = LanguageVersionExtensions.CSharp9, + }.RunAsync(); + } + + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)] + public async Task EntryPointMethodNotFlagged_09() + { + var code = @" +return 0; +"; + + await new VerifyCS.Test + { + TestState = + { + Sources = { code, code }, + }, + FixedState = + { + Sources = { code, code }, + }, + ExpectedDiagnostics = + { + // /0/Test1.cs(2,1): error CS8802: Only one compilation unit can have top-level statements. + DiagnosticResult.CompilerError("CS8802").WithSpan("/0/Test1.cs", 2, 1, 2, 7), + }, + SolutionTransforms = + { + (solution, projectId) => + { + var project = solution.GetRequiredProject(projectId); + var compilationOptions = project.CompilationOptions; + return solution.WithProjectCompilationOptions(projectId, compilationOptions.WithOutputKind(OutputKind.ConsoleApplication)); + }, + }, + LanguageVersion = LanguageVersionExtensions.CSharp9, + }.RunAsync(); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnusedMembers)] public async Task FieldIsUnused_ReadOnly() { diff --git a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs index 3a7911c084f3e..25a6556ddb684 100644 --- a/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/RemoveUnusedMembers/AbstractRemoveUnusedMembersDiagnosticAnalyzer.cs @@ -382,9 +382,16 @@ private void OnSymbolEnd(SymbolAnalysisContext symbolEndContext, bool hasUnsuppo ArrayBuilder debuggerDisplayAttributeArguments = null; try { + var entryPoint = symbolEndContext.Compilation.GetEntryPoint(symbolEndContext.CancellationToken); + var namedType = (INamedTypeSymbol)symbolEndContext.Symbol; foreach (var member in namedType.GetMembers()) { + if (SymbolEqualityComparer.Default.Equals(entryPoint, member)) + { + continue; + } + // Check if the underlying member is neither read nor a readable reference to the member is taken. // If so, we flag the member as either unused (never written) or unread (written but not read). if (TryRemove(member, out var valueUsageInfo) && @@ -689,7 +696,7 @@ private bool IsCandidateSymbol(ISymbol memberSymbol) } private bool IsEntryPoint(IMethodSymbol methodSymbol) - => methodSymbol.Name == WellKnownMemberNames.EntryPointMethodName && + => (methodSymbol.Name == WellKnownMemberNames.EntryPointMethodName || methodSymbol.Name == "$Main") && methodSymbol.IsStatic && (methodSymbol.ReturnsVoid || methodSymbol.ReturnType.SpecialType == SpecialType.System_Int32 ||