Skip to content
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ If you are already using other analyzers, you can check [which rules are duplica
|[MA0050](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0050.md)|Design|Validate arguments correctly in iterator methods|ℹ️|✔️|✔️|
|[MA0051](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0051.md)|Design|Method is too long|⚠️|✔️|❌|
|[MA0052](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0052.md)|Performance|Replace constant Enum.ToString with nameof|ℹ️|✔️|✔️|
|[MA0053](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md)|Design|Make class sealed|ℹ️|✔️|✔️|
|[MA0053](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md)|Design|Make class or record sealed|ℹ️|✔️|✔️|
|[MA0054](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0054.md)|Design|Embed the caught exception as innerException|⚠️|✔️|❌|
|[MA0055](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0055.md)|Design|Do not use finalizer|⚠️|✔️|❌|
|[MA0056](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0056.md)|Design|Do not call overridable members in constructor|⚠️|✔️|❌|
Expand Down
6 changes: 3 additions & 3 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@
|[MA0050](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0050.md)|Design|Validate arguments correctly in iterator methods|<span title='Info'>ℹ️</span>|✔️|✔️|
|[MA0051](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0051.md)|Design|Method is too long|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0052](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0052.md)|Performance|Replace constant Enum.ToString with nameof|<span title='Info'>ℹ️</span>|✔️|✔️|
|[MA0053](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md)|Design|Make class sealed|<span title='Info'>ℹ️</span>|✔️|✔️|
|[MA0053](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0053.md)|Design|Make class or record sealed|<span title='Info'>ℹ️</span>|✔️|✔️|
|[MA0054](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0054.md)|Design|Embed the caught exception as innerException|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0055](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0055.md)|Design|Do not use finalizer|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0056](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0056.md)|Design|Do not call overridable members in constructor|<span title='Warning'>⚠️</span>|✔️|❌|
Expand Down Expand Up @@ -342,7 +342,7 @@ dotnet_diagnostic.MA0051.severity = warning
# MA0052: Replace constant Enum.ToString with nameof
dotnet_diagnostic.MA0052.severity = suggestion

# MA0053: Make class sealed
# MA0053: Make class or record sealed
dotnet_diagnostic.MA0053.severity = suggestion

# MA0054: Embed the caught exception as innerException
Expand Down Expand Up @@ -874,7 +874,7 @@ dotnet_diagnostic.MA0051.severity = none
# MA0052: Replace constant Enum.ToString with nameof
dotnet_diagnostic.MA0052.severity = none

# MA0053: Make class sealed
# MA0053: Make class or record sealed
dotnet_diagnostic.MA0053.severity = none

# MA0054: Embed the caught exception as innerException
Expand Down
10 changes: 5 additions & 5 deletions docs/Rules/MA0053.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# MA0053 - Make class sealed
# MA0053 - Make class or record sealed
<!-- sources -->
Sources: [ClassMustBeSealedAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/ClassMustBeSealedAnalyzer.cs), [ClassMustBeSealedFixer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.CodeFixers/Rules/ClassMustBeSealedFixer.cs)
<!-- sources -->

Classes should be sealed when there is no inheritor.
Classes and records should be sealed when there is no inheritor.

- [Why Are So Many Of The Framework Classes Sealed?](https://blogs.msdn.microsoft.com/ericlippert/2004/01/22/why-are-so-many-of-the-framework-classes-sealed/)
- [Performance benefits of sealed class in .NET](https://www.meziantou.net/performance-benefits-of-sealed-class.htm)
Expand All @@ -27,15 +27,15 @@ public sealed class Bar : Foo
# Configuration

A Roslyn analyzer can only know the current project context, not the full solution.
Therefore it cannot know if a public class is used in another project hereby making it possibly inaccurate to report this diagnostic for `public` classes.
You can still enable this rule for `public` classes using the `.editorconfig`:
Therefore it cannot know if a public class or record is used in another project hereby making it possibly inaccurate to report this diagnostic for `public` types.
You can still enable this rule for `public` classes and records using the `.editorconfig`:

````
# .editorconfig file
MA0053.public_class_should_be_sealed = true
````

Classes with `virtual` members cannot be sealed. By default, these classes are not reported. You can enable this rule for classes with `virtual` members using the `.editorconfig`:
Classes and records with `virtual` members cannot be sealed. By default, these types are not reported. You can enable this rule for classes and records with `virtual` members using the `.editorconfig`:

````
# .editorconfig file
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ dotnet_diagnostic.MA0051.severity = warning
# MA0052: Replace constant Enum.ToString with nameof
dotnet_diagnostic.MA0052.severity = suggestion

# MA0053: Make class sealed
# MA0053: Make class or record sealed
dotnet_diagnostic.MA0053.severity = suggestion

# MA0054: Embed the caught exception as innerException
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ dotnet_diagnostic.MA0051.severity = none
# MA0052: Replace constant Enum.ToString with nameof
dotnet_diagnostic.MA0052.severity = none

# MA0053: Make class sealed
# MA0053: Make class or record sealed
dotnet_diagnostic.MA0053.severity = none

# MA0054: Embed the caught exception as innerException
Expand Down
7 changes: 3 additions & 4 deletions src/Meziantou.Analyzer/Rules/ClassMustBeSealedAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ public sealed class ClassMustBeSealedAnalyzer : DiagnosticAnalyzer
{
private static readonly DiagnosticDescriptor Rule = new(
RuleIdentifiers.ClassMustBeSealed,
title: "Make class sealed",
messageFormat: "Make class sealed",
title: "Make class or record sealed",
messageFormat: "Make class or record sealed",
RuleCategories.Design,
DiagnosticSeverity.Info,
isEnabledByDefault: true,
Expand Down Expand Up @@ -107,14 +107,13 @@ private bool IsPotentialSealed(AnalyzerOptions options, INamedTypeSymbol symbol,
if (symbol.IsTopLevelStatement(cancellationToken))
return false;

if (symbol.GetMembers().Any(member => member.IsVirtual) && !SealedClassWithVirtualMember(options, symbol))
if (symbol.GetMembers().Any(member => member.IsVirtual && member.CanBeReferencedByName && !member.IsImplicitlyDeclared) && !SealedClassWithVirtualMember(options, symbol))
return false;

var canBeInheritedOutsideOfAssembly = symbol.IsVisibleOutsideOfAssembly() && symbol.GetMembers().OfType<IMethodSymbol>().Any(member => member.MethodKind is MethodKind.Constructor && member.IsVisibleOutsideOfAssembly());
if (canBeInheritedOutsideOfAssembly && !PublicClassShouldBeSealed(options, symbol))
return false;


return true;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,68 @@ internal sealed record Sample();
.ValidateAsync();
}

[Fact]
public async Task Record_Inherited_Diagnostic()
{
await CreateProjectBuilder()
.WithSourceCode("""
record Base();

record [||]Derived() : Base();
""")
.ShouldFixCodeWith("""
record Base();

sealed record Derived() : Base();
""")
.ValidateAsync();
}

[Fact]
public async Task Record_ImplementInterface_Diagnostic()
{
await CreateProjectBuilder()
.WithSourceCode("""
interface ITest
{
}

record [||]Test() : ITest;
""")
.ShouldFixCodeWith("""
interface ITest
{
}

sealed record Test() : ITest;
""")
.ValidateAsync();
}

[Fact]
public async Task Record_Public_NotReported()
{
await CreateProjectBuilder()
.WithSourceCode("""
public record Sample();
""")
.ValidateAsync();
}

[Fact]
public async Task Record_Public_WithEditorConfig_Diagnostic()
{
await CreateProjectBuilder()
.AddAnalyzerConfiguration("MA0053.public_class_should_be_sealed", "true")
.WithSourceCode("""
public record [||]Sample();
""")
.ShouldFixCodeWith("""
public sealed record Sample();
""")
.ValidateAsync();
}

[Theory]
[InlineData("private")]
[InlineData("internal")]
Expand Down