Skip to content

Commit df9f517

Browse files
authored
MA0053 takes ctor visibility into account to determine if a class should be sealed (#815)
1 parent 46104a9 commit df9f517

File tree

2 files changed

+71
-3
lines changed

2 files changed

+71
-3
lines changed

src/Meziantou.Analyzer/Rules/ClassMustBeSealedAnalyzer.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Collections.Generic;
1+
using System.Collections.Generic;
22
using System.Collections.Immutable;
33
using System.Linq;
44
using System.Threading;
@@ -113,9 +113,11 @@ private bool IsPotentialSealed(AnalyzerOptions options, INamedTypeSymbol symbol,
113113
if (symbol.GetMembers().Any(member => member.IsVirtual) && !SealedClassWithVirtualMember(options, symbol))
114114
return false;
115115

116-
if (symbol.IsVisibleOutsideOfAssembly() && !PublicClassShouldBeSealed(options, symbol))
116+
var canBeInheritedOutsideOfAssembly = symbol.IsVisibleOutsideOfAssembly() && symbol.GetMembers().OfType<IMethodSymbol>().Where(member => member.MethodKind is MethodKind.Constructor).Any(member => member.IsVisibleOutsideOfAssembly());
117+
if (canBeInheritedOutsideOfAssembly && !PublicClassShouldBeSealed(options, symbol))
117118
return false;
118119

120+
119121
return true;
120122
}
121123

tests/Meziantou.Analyzer.Test/Rules/ClassMustBeSealedAnalyzerTests.cs

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Threading.Tasks;
1+
using System.Threading.Tasks;
22
using Meziantou.Analyzer.Rules;
33
using TestHelper;
44
using Xunit;
@@ -256,6 +256,72 @@ internal sealed record Sample();
256256
.ValidateAsync();
257257
}
258258

259+
[Theory]
260+
[InlineData("private")]
261+
[InlineData("internal")]
262+
[InlineData("private protected")]
263+
public async Task ClassWithPrivateCtor(string visibility)
264+
{
265+
await CreateProjectBuilder()
266+
.WithSourceCode($$"""
267+
public class [||]Sample
268+
{
269+
{{visibility}} Sample() { }
270+
}
271+
""")
272+
.ValidateAsync();
273+
}
274+
275+
[Theory]
276+
[InlineData("private")]
277+
[InlineData("internal")]
278+
[InlineData("private protected")]
279+
public async Task ClassWithMultiplePrivateCtors(string visibility)
280+
{
281+
await CreateProjectBuilder()
282+
.WithSourceCode($$"""
283+
public class [||]Sample
284+
{
285+
private Sample(int a) { }
286+
{{visibility}} Sample() { }
287+
}
288+
""")
289+
.ValidateAsync();
290+
}
291+
292+
[Theory]
293+
[InlineData("public")]
294+
[InlineData("protected")]
295+
[InlineData("protected internal")]
296+
public async Task ClassWithPublicCtor(string visibility)
297+
{
298+
await CreateProjectBuilder()
299+
.WithSourceCode($$"""
300+
public class Sample
301+
{
302+
{{visibility}} Sample() { }
303+
}
304+
""")
305+
.ValidateAsync();
306+
}
307+
308+
[Theory]
309+
[InlineData("public")]
310+
[InlineData("protected")]
311+
[InlineData("protected internal")]
312+
public async Task ClassWithPrivateAndPublicCtor(string visibility)
313+
{
314+
await CreateProjectBuilder()
315+
.WithSourceCode($$"""
316+
public class Sample
317+
{
318+
private Sample(int a) { }
319+
{{visibility}} Sample() { }
320+
}
321+
""")
322+
.ValidateAsync();
323+
}
324+
259325
#if CSHARP10_OR_GREATER
260326
[Fact]
261327
public async Task TopLevelStatement_10()

0 commit comments

Comments
 (0)