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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|⚠️|✔️|❌|

<!-- rules -->

Expand Down
7 changes: 7 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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|<span title='Info'>ℹ️</span>|✔️|✔️|
|[MA0195](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0195.md)|Usage|Do not use static fields before they are initialized|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0196](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0196.md)|Design|Do not use inheritdoc on non-inheriting members|<span title='Warning'>⚠️</span>|✔️|❌|
|[MA0197](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0197.md)|Design|Do not use inheritdoc on types|<span title='Warning'>⚠️</span>|✔️|❌|

|Id|Suppressed rule|Justification|
|--|---------------|-------------|
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
```
31 changes: 31 additions & 0 deletions docs/Rules/MA0197.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# MA0197 - Do not use inheritdoc on types
<!-- sources -->
Source: [InheritdocShouldNotBeUsedOnTypesAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/InheritdocShouldNotBeUsedOnTypesAnalyzer.cs)
<!-- sources -->

Use `<inheritdoc />` on members only.
Types usually represent dedicated concepts, so inheriting the full documentation of another type is often a design smell.

This rule reports `<inheritdoc />` on type declarations (`class`, `struct`, `interface`, `record`), including when `cref` is present.

````csharp
// Non-compliant
/// <inheritdoc />
class Sample
{
}
````

````csharp
// Compliant
class BaseType
{
public virtual void M() { }
}

class Sample : BaseType
{
/// <inheritdoc />
public override void M() { }
}
````
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions src/Meziantou.Analyzer.Pack/configuration/none.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions src/Meziantou.Analyzer/RuleIdentifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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 '<inheritdoc />' on types; use it on members only",
RuleCategories.Design,
DiagnosticSeverity.Warning,
isEnabledByDefault: true,
description: "",
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.InheritdocShouldNotBeUsedOnTypes));

public override ImmutableArray<DiagnosticDescriptor> 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<XmlEmptyElementSyntax>())
{
if (!IsInheritdocElement(element.Name))
continue;

context.ReportDiagnostic(Rule, element);
}

foreach (var element in documentation.DescendantNodes().OfType<XmlElementSyntax>())
{
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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,84 @@ public void M() { }
.ValidateAsync();
}
}

public sealed class InheritdocShouldNotBeUsedOnTypesAnalyzerTests
{
private static ProjectBuilder CreateProjectBuilder()
{
return new ProjectBuilder()
.WithAnalyzer<InheritdocShouldNotBeUsedOnTypesAnalyzer>()
.WithTargetFramework(TargetFramework.NetLatest);
}

[Fact]
public async Task ReportDiagnostic_Class()
{
await CreateProjectBuilder()
.WithSourceCode("""
/// [|<inheritdoc />|]
class Sample
{
}
""")
.ValidateAsync();
}

[Fact]
public async Task ReportDiagnostic_WhenCrefIsPresent()
{
await CreateProjectBuilder()
.WithSourceCode("""
/// [|<inheritdoc cref="object" />|]
class Sample
{
}
""")
.ValidateAsync();
}

[Fact]
public async Task ReportDiagnostic_Interface()
{
await CreateProjectBuilder()
.WithSourceCode("""
/// [|<inheritdoc />|]
interface ITest
{
}
""")
.ValidateAsync();
}

[Fact]
public async Task NoDiagnostic_WhenUsedOnMember()
{
await CreateProjectBuilder()
.WithSourceCode("""
class Sample
{
/// <inheritdoc />
public override string ToString() => base.ToString();
}
""")
.ValidateAsync();
}

[Fact]
public async Task ReportDiagnostic_ForEachPartialDeclaration()
{
await CreateProjectBuilder()
.WithSourceCode("""
/// [|<inheritdoc />|]
partial class Sample
{
}

/// [|<inheritdoc />|]
partial class Sample
{
}
""")
.ValidateAsync();
}
}
Loading