diff --git a/src/Meziantou.Analyzer/Rules/ClassMustBeSealedAnalyzer.cs b/src/Meziantou.Analyzer/Rules/ClassMustBeSealedAnalyzer.cs index 9e075571..b225dada 100644 --- a/src/Meziantou.Analyzer/Rules/ClassMustBeSealedAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/ClassMustBeSealedAnalyzer.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using System.Threading; @@ -113,9 +113,11 @@ private bool IsPotentialSealed(AnalyzerOptions options, INamedTypeSymbol symbol, if (symbol.GetMembers().Any(member => member.IsVirtual) && !SealedClassWithVirtualMember(options, symbol)) return false; - if (symbol.IsVisibleOutsideOfAssembly() && !PublicClassShouldBeSealed(options, symbol)) + var canBeInheritedOutsideOfAssembly = symbol.IsVisibleOutsideOfAssembly() && symbol.GetMembers().OfType().Where(member => member.MethodKind is MethodKind.Constructor).Any(member => member.IsVisibleOutsideOfAssembly()); + if (canBeInheritedOutsideOfAssembly && !PublicClassShouldBeSealed(options, symbol)) return false; + return true; } diff --git a/tests/Meziantou.Analyzer.Test/Rules/ClassMustBeSealedAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/ClassMustBeSealedAnalyzerTests.cs index 230c32db..f5e9e154 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/ClassMustBeSealedAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/ClassMustBeSealedAnalyzerTests.cs @@ -1,4 +1,4 @@ -using System.Threading.Tasks; +using System.Threading.Tasks; using Meziantou.Analyzer.Rules; using TestHelper; using Xunit; @@ -256,6 +256,72 @@ internal sealed record Sample(); .ValidateAsync(); } + [Theory] + [InlineData("private")] + [InlineData("internal")] + [InlineData("private protected")] + public async Task ClassWithPrivateCtor(string visibility) + { + await CreateProjectBuilder() + .WithSourceCode($$""" + public class [||]Sample + { + {{visibility}} Sample() { } + } + """) + .ValidateAsync(); + } + + [Theory] + [InlineData("private")] + [InlineData("internal")] + [InlineData("private protected")] + public async Task ClassWithMultiplePrivateCtors(string visibility) + { + await CreateProjectBuilder() + .WithSourceCode($$""" + public class [||]Sample + { + private Sample(int a) { } + {{visibility}} Sample() { } + } + """) + .ValidateAsync(); + } + + [Theory] + [InlineData("public")] + [InlineData("protected")] + [InlineData("protected internal")] + public async Task ClassWithPublicCtor(string visibility) + { + await CreateProjectBuilder() + .WithSourceCode($$""" + public class Sample + { + {{visibility}} Sample() { } + } + """) + .ValidateAsync(); + } + + [Theory] + [InlineData("public")] + [InlineData("protected")] + [InlineData("protected internal")] + public async Task ClassWithPrivateAndPublicCtor(string visibility) + { + await CreateProjectBuilder() + .WithSourceCode($$""" + public class Sample + { + private Sample(int a) { } + {{visibility}} Sample() { } + } + """) + .ValidateAsync(); + } + #if CSHARP10_OR_GREATER [Fact] public async Task TopLevelStatement_10()