From 1c51e26b4afb49f8a3df9916f2d7f1f3121f2b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Thu, 7 May 2026 12:35:43 -0400 Subject: [PATCH 1/3] Add MA0197 for type-level inheritdoc Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 1 + docs/README.md | 7 ++ docs/Rules/MA0197.md | 30 +++++++ docs/comparison-with-other-analyzers.md | 1 + .../configuration/all-errors.editorconfig | 3 + .../all-suggestions.editorconfig | 3 + .../configuration/all-warnings.editorconfig | 3 + .../configuration/default.editorconfig | 3 + .../configuration/none.editorconfig | 3 + src/Meziantou.Analyzer/RuleIdentifiers.cs | 1 + ...nheritdocShouldNotBeUsedOnTypesAnalyzer.cs | 77 ++++++++++++++++++ ...ldBeUsedOnInheritingMemberAnalyzerTests.cs | 81 +++++++++++++++++++ 12 files changed, 213 insertions(+) create mode 100644 docs/Rules/MA0197.md create mode 100644 src/Meziantou.Analyzer/Rules/InheritdocShouldNotBeUsedOnTypesAnalyzer.cs diff --git a/README.md b/README.md index ad53d9208..c0ff41ea4 100755 --- a/README.md +++ b/README.md @@ -215,6 +215,7 @@ If you are already using other analyzers, you can check [which rules are duplica |[MA0194](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0194.md)|Usage|Merge is expressions on the same value|ℹ️|✔️|✔️| |[MA0195](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0195.md)|Usage|Do not use static fields before they are initialized|⚠️|✔️|❌| |[MA0196](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0196.md)|Design|Do not use inheritdoc on non-inheriting members|⚠️|✔️|❌| +|[MA0197](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0197.md)|Design|Do not use inheritdoc on types|⚠️|✔️|❌| diff --git a/docs/README.md b/docs/README.md index 064141b95..8e9a6eb1d 100755 --- a/docs/README.md +++ b/docs/README.md @@ -195,6 +195,7 @@ |[MA0194](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0194.md)|Usage|Merge is expressions on the same value|ℹ️|✔️|✔️| |[MA0195](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0195.md)|Usage|Do not use static fields before they are initialized|⚠️|✔️|❌| |[MA0196](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0196.md)|Design|Do not use inheritdoc on non-inheriting members|⚠️|✔️|❌| +|[MA0197](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0197.md)|Design|Do not use inheritdoc on types|⚠️|✔️|❌| |Id|Suppressed rule|Justification| |--|---------------|-------------| @@ -795,6 +796,9 @@ dotnet_diagnostic.MA0195.severity = warning # MA0196: Do not use inheritdoc on non-inheriting members dotnet_diagnostic.MA0196.severity = warning + +# MA0197: Do not use inheritdoc on types +dotnet_diagnostic.MA0197.severity = warning ``` # .editorconfig - all rules disabled @@ -1381,4 +1385,7 @@ dotnet_diagnostic.MA0195.severity = none # MA0196: Do not use inheritdoc on non-inheriting members dotnet_diagnostic.MA0196.severity = none + +# MA0197: Do not use inheritdoc on types +dotnet_diagnostic.MA0197.severity = none ``` diff --git a/docs/Rules/MA0197.md b/docs/Rules/MA0197.md new file mode 100644 index 000000000..66c77f95d --- /dev/null +++ b/docs/Rules/MA0197.md @@ -0,0 +1,30 @@ +# MA0197 - Do not use inheritdoc on types + +Source: [InheritdocShouldNotBeUsedOnTypesAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/InheritdocShouldNotBeUsedOnTypesAnalyzer.cs) + + +Use `` on members only. + +This rule reports `` on type declarations (`class`, `struct`, `interface`, `record`), including when `cref` is present. + +````csharp +// Non-compliant +/// +class Sample +{ +} +```` + +````csharp +// Compliant +class BaseType +{ + public virtual void M() { } +} + +class Sample : BaseType +{ + /// + public override void M() { } +} +```` diff --git a/docs/comparison-with-other-analyzers.md b/docs/comparison-with-other-analyzers.md index 500b9d9df..649be417b 100644 --- a/docs/comparison-with-other-analyzers.md +++ b/docs/comparison-with-other-analyzers.md @@ -71,5 +71,6 @@ | [CA2208](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2208?WT.mc_id=DT-MVP-5003978) | [MA0015](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0015.md) | MA reports more cases | | [IDE0330](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0330?WT.mc_id=DT-MVP-5003978) | [MA0158](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0158.md) | MA supports variables in methods | | [SA1648](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1648.md) | [MA0196](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0196.md) | MA targets method/property/event members and allows `` with `cref` | +| [SA1648](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1648.md) | [MA0197](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0197.md) | SA1648 allows `` on inheriting types, while MA0197 forbids `` on all types | | [SA1649](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1649.md) | [MA0048](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0048.md) | SA supports configuration of file naming convention and skips partial classes | | [S6580](https://rules.sonarsource.com/csharp/RSPEC-6580/) | [MA0011](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0011.md) | S6580 only applies to parse methods, and also reports diagnostic when `null` is passed as value for format provider argument.| diff --git a/src/Meziantou.Analyzer.Pack/configuration/all-errors.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/all-errors.editorconfig index f230995b3..aebb4c583 100644 --- a/src/Meziantou.Analyzer.Pack/configuration/all-errors.editorconfig +++ b/src/Meziantou.Analyzer.Pack/configuration/all-errors.editorconfig @@ -583,3 +583,6 @@ dotnet_diagnostic.MA0195.severity = error # MA0196: Do not use inheritdoc on non-inheriting members dotnet_diagnostic.MA0196.severity = error + +# MA0197: Do not use inheritdoc on types +dotnet_diagnostic.MA0197.severity = error diff --git a/src/Meziantou.Analyzer.Pack/configuration/all-suggestions.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/all-suggestions.editorconfig index 3a0c5fec6..be2752216 100644 --- a/src/Meziantou.Analyzer.Pack/configuration/all-suggestions.editorconfig +++ b/src/Meziantou.Analyzer.Pack/configuration/all-suggestions.editorconfig @@ -583,3 +583,6 @@ dotnet_diagnostic.MA0195.severity = suggestion # MA0196: Do not use inheritdoc on non-inheriting members dotnet_diagnostic.MA0196.severity = suggestion + +# MA0197: Do not use inheritdoc on types +dotnet_diagnostic.MA0197.severity = suggestion diff --git a/src/Meziantou.Analyzer.Pack/configuration/all-warnings.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/all-warnings.editorconfig index 02917a5cc..5b1139c51 100644 --- a/src/Meziantou.Analyzer.Pack/configuration/all-warnings.editorconfig +++ b/src/Meziantou.Analyzer.Pack/configuration/all-warnings.editorconfig @@ -583,3 +583,6 @@ dotnet_diagnostic.MA0195.severity = warning # MA0196: Do not use inheritdoc on non-inheriting members dotnet_diagnostic.MA0196.severity = warning + +# MA0197: Do not use inheritdoc on types +dotnet_diagnostic.MA0197.severity = warning diff --git a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig index 69a4f67f4..bd86d76d0 100644 --- a/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig +++ b/src/Meziantou.Analyzer.Pack/configuration/default.editorconfig @@ -583,3 +583,6 @@ dotnet_diagnostic.MA0195.severity = warning # MA0196: Do not use inheritdoc on non-inheriting members dotnet_diagnostic.MA0196.severity = warning + +# MA0197: Do not use inheritdoc on types +dotnet_diagnostic.MA0197.severity = warning diff --git a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig index 0d6941381..7ea7e00de 100644 --- a/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig +++ b/src/Meziantou.Analyzer.Pack/configuration/none.editorconfig @@ -583,3 +583,6 @@ dotnet_diagnostic.MA0195.severity = none # MA0196: Do not use inheritdoc on non-inheriting members dotnet_diagnostic.MA0196.severity = none + +# MA0197: Do not use inheritdoc on types +dotnet_diagnostic.MA0197.severity = none diff --git a/src/Meziantou.Analyzer/RuleIdentifiers.cs b/src/Meziantou.Analyzer/RuleIdentifiers.cs index eb34d051d..1c59c4743 100755 --- a/src/Meziantou.Analyzer/RuleIdentifiers.cs +++ b/src/Meziantou.Analyzer/RuleIdentifiers.cs @@ -196,6 +196,7 @@ internal static class RuleIdentifiers public const string MergeIsPatternChecks = "MA0194"; public const string DoNotUseNotYetInitializedStaticField = "MA0195"; public const string InheritdocShouldBeUsedOnInheritingMember = "MA0196"; + public const string InheritdocShouldNotBeUsedOnTypes = "MA0197"; public static string GetHelpUri(string identifier) { diff --git a/src/Meziantou.Analyzer/Rules/InheritdocShouldNotBeUsedOnTypesAnalyzer.cs b/src/Meziantou.Analyzer/Rules/InheritdocShouldNotBeUsedOnTypesAnalyzer.cs new file mode 100644 index 000000000..29d0452e1 --- /dev/null +++ b/src/Meziantou.Analyzer/Rules/InheritdocShouldNotBeUsedOnTypesAnalyzer.cs @@ -0,0 +1,77 @@ +using System.Collections.Immutable; +using Meziantou.Analyzer.Internals; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Meziantou.Analyzer.Rules; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class InheritdocShouldNotBeUsedOnTypesAnalyzer : DiagnosticAnalyzer +{ + private static readonly DiagnosticDescriptor Rule = new( + RuleIdentifiers.InheritdocShouldNotBeUsedOnTypes, + title: "Do not use inheritdoc on types", + messageFormat: "Do not use '' on types; use it on members only", + RuleCategories.Design, + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + description: "", + helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.InheritdocShouldNotBeUsedOnTypes)); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType); + } + + private static void AnalyzeSymbol(SymbolAnalysisContext context) + { + if (context.Symbol is not INamedTypeSymbol symbol) + return; + + if (symbol.IsImplicitlyDeclared || symbol.TypeKind is not (TypeKind.Class or TypeKind.Struct or TypeKind.Interface)) + return; + + if (symbol.IsImplicitClass || symbol.Name.Contains('$', StringComparison.Ordinal)) + return; + + foreach (var syntaxReference in symbol.DeclaringSyntaxReferences) + { + var syntax = syntaxReference.GetSyntax(context.CancellationToken); + if (!syntax.HasStructuredTrivia) + continue; + + foreach (var trivia in syntax.GetLeadingTrivia()) + { + if (trivia.GetStructure() is not DocumentationCommentTriviaSyntax documentation) + continue; + + foreach (var element in documentation.DescendantNodes().OfType()) + { + if (!IsInheritdocElement(element.Name)) + continue; + + context.ReportDiagnostic(Rule, element); + } + + foreach (var element in documentation.DescendantNodes().OfType()) + { + if (!IsInheritdocElement(element.StartTag.Name)) + continue; + + context.ReportDiagnostic(Rule, element.StartTag); + } + } + } + } + + private static bool IsInheritdocElement(XmlNameSyntax name) + { + return string.Equals(name.LocalName.Text, "inheritdoc", StringComparison.OrdinalIgnoreCase); + } +} diff --git a/tests/Meziantou.Analyzer.Test/Rules/InheritdocShouldBeUsedOnInheritingMemberAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/InheritdocShouldBeUsedOnInheritingMemberAnalyzerTests.cs index 2d8239986..8566b1099 100644 --- a/tests/Meziantou.Analyzer.Test/Rules/InheritdocShouldBeUsedOnInheritingMemberAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/InheritdocShouldBeUsedOnInheritingMemberAnalyzerTests.cs @@ -112,3 +112,84 @@ public void M() { } .ValidateAsync(); } } + +public sealed class InheritdocShouldNotBeUsedOnTypesAnalyzerTests +{ + private static ProjectBuilder CreateProjectBuilder() + { + return new ProjectBuilder() + .WithAnalyzer() + .WithTargetFramework(TargetFramework.NetLatest); + } + + [Fact] + public async Task ReportDiagnostic_Class() + { + await CreateProjectBuilder() + .WithSourceCode(""" + /// [||] + class Sample + { + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task ReportDiagnostic_WhenCrefIsPresent() + { + await CreateProjectBuilder() + .WithSourceCode(""" + /// [||] + class Sample + { + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task ReportDiagnostic_Interface() + { + await CreateProjectBuilder() + .WithSourceCode(""" + /// [||] + interface ITest + { + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task NoDiagnostic_WhenUsedOnMember() + { + await CreateProjectBuilder() + .WithSourceCode(""" + class Sample + { + /// + public override string ToString() => base.ToString(); + } + """) + .ValidateAsync(); + } + + [Fact] + public async Task ReportDiagnostic_ForEachPartialDeclaration() + { + await CreateProjectBuilder() + .WithSourceCode(""" + /// [||] + partial class Sample + { + } + + /// [||] + partial class Sample + { + } + """) + .ValidateAsync(); + } +} From 890fe29bc2a7b31438c3b605e27c92bc628ef020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Thu, 7 May 2026 12:47:01 -0400 Subject: [PATCH 2/3] Revert MA0197 comparison entry Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/comparison-with-other-analyzers.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/comparison-with-other-analyzers.md b/docs/comparison-with-other-analyzers.md index 649be417b..500b9d9df 100644 --- a/docs/comparison-with-other-analyzers.md +++ b/docs/comparison-with-other-analyzers.md @@ -71,6 +71,5 @@ | [CA2208](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/quality-rules/ca2208?WT.mc_id=DT-MVP-5003978) | [MA0015](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0015.md) | MA reports more cases | | [IDE0330](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/style-rules/ide0330?WT.mc_id=DT-MVP-5003978) | [MA0158](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0158.md) | MA supports variables in methods | | [SA1648](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1648.md) | [MA0196](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0196.md) | MA targets method/property/event members and allows `` with `cref` | -| [SA1648](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1648.md) | [MA0197](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0197.md) | SA1648 allows `` on inheriting types, while MA0197 forbids `` on all types | | [SA1649](https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1649.md) | [MA0048](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0048.md) | SA supports configuration of file naming convention and skips partial classes | | [S6580](https://rules.sonarsource.com/csharp/RSPEC-6580/) | [MA0011](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0011.md) | S6580 only applies to parse methods, and also reports diagnostic when `null` is passed as value for format provider argument.| From 2f92c8e13576d3a11406f25e6104f67138db5e5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Thu, 7 May 2026 13:05:18 -0400 Subject: [PATCH 3/3] Clarify MA0197 rationale Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/Rules/MA0197.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Rules/MA0197.md b/docs/Rules/MA0197.md index 66c77f95d..05b4b1178 100644 --- a/docs/Rules/MA0197.md +++ b/docs/Rules/MA0197.md @@ -4,6 +4,7 @@ Source: [InheritdocShouldNotBeUsedOnTypesAnalyzer.cs](https://github.com/meziant Use `` on members only. +Types usually represent dedicated concepts, so inheriting the full documentation of another type is often a design smell. This rule reports `` on type declarations (`class`, `struct`, `interface`, `record`), including when `cref` is present.