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
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,9 @@ 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|⚠️|✔️|❌|
|[MA0197](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0197.md)|Design|Add dedicated documentation on types|ℹ️|✔️|❌|
|[MA0198](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0198.md)|Design|Specify cref for ambiguous inheritdoc on types|⚠️|✔️|✔️|
|[MA0199](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0199.md)|Design|Do not use inheritdoc on types without inheritance source|⚠️|✔️|❌|

<!-- rules -->

Expand Down
22 changes: 18 additions & 4 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,9 @@
|[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>|✔️|❌|
|[MA0197](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0197.md)|Design|Add dedicated documentation on types|<span title='Info'>ℹ️</span>|✔️|❌|
|[MA0198](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0198.md)|Design|Specify cref for ambiguous inheritdoc on types|<span title='Warning'>⚠️</span>|✔️|✔️|
|[MA0199](https://github.com/meziantou/Meziantou.Analyzer/blob/main/docs/Rules/MA0199.md)|Design|Do not use inheritdoc on types without inheritance source|<span title='Warning'>⚠️</span>|✔️|❌|

|Id|Suppressed rule|Justification|
|--|---------------|-------------|
Expand Down Expand Up @@ -797,8 +799,14 @@ 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
# MA0197: Add dedicated documentation on types
dotnet_diagnostic.MA0197.severity = suggestion

# MA0198: Specify cref for ambiguous inheritdoc on types
dotnet_diagnostic.MA0198.severity = warning

# MA0199: Do not use inheritdoc on types without inheritance source
dotnet_diagnostic.MA0199.severity = warning
```

# .editorconfig - all rules disabled
Expand Down Expand Up @@ -1386,6 +1394,12 @@ 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
# MA0197: Add dedicated documentation on types
dotnet_diagnostic.MA0197.severity = none

# MA0198: Specify cref for ambiguous inheritdoc on types
dotnet_diagnostic.MA0198.severity = none

# MA0199: Do not use inheritdoc on types without inheritance source
dotnet_diagnostic.MA0199.severity = none
```
29 changes: 15 additions & 14 deletions docs/Rules/MA0197.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,37 @@
# MA0197 - Do not use inheritdoc on types
# MA0197 - Add dedicated documentation 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.
Types should have dedicated documentation that describes their purpose directly.
Using `<inheritdoc />` on a type tends to copy intent from another type instead of documenting the type itself.

This rule reports `<inheritdoc />` on type declarations (`class`, `struct`, `interface`, `record`) unless `cref` is specified.
This rule reports `<inheritdoc />` on type declarations (`class`, `struct`, `interface`, `record`) when no `cref` is specified and the inheritdoc source is unambiguous.

For ambiguous or missing inheritdoc sources, see:
- [MA0198](MA0198.md)
- [MA0199](MA0199.md)

````csharp
// Non-compliant
class BaseType
{
}

/// <inheritdoc />
class Sample
class Sample : BaseType
{
}
````

````csharp
// Compliant
/// <inheritdoc cref="BaseType" />
class DerivedType
/// <summary>Represents a dedicated concept for this type.</summary>
class Sample : BaseType
{
}

class BaseType
{
public virtual void M() { }
}

class Sample : BaseType
{
/// <inheritdoc />
public override void M() { }
}
````
35 changes: 35 additions & 0 deletions docs/Rules/MA0198.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# MA0198 - Specify cref for ambiguous inheritdoc on types
<!-- sources -->
Sources: [InheritdocShouldNotBeAmbiguousOnTypesAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/InheritdocShouldNotBeAmbiguousOnTypesAnalyzer.cs), [InheritdocShouldNotBeUsedOnTypesFixer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer.CodeFixers/Rules/InheritdocShouldNotBeUsedOnTypesFixer.cs)
<!-- sources -->

When a type has no base type (other than `System.Object` or `System.ValueType`) and declares multiple interfaces, `<inheritdoc />` is ambiguous without `cref`.

This rule reports `<inheritdoc />` on type declarations (`class`, `struct`, `interface`, `record`) when:
- no `cref` is specified
- there is no applicable base type
- there are multiple declared interfaces

Only declared interfaces are considered. Inherited interfaces from those declared interfaces are ignored.

````csharp
// Non-compliant
interface IInterface1 { }
interface IInterface2 { }

/// <inheritdoc />
class Sample : IInterface1, IInterface2
{
}
````

````csharp
// Compliant
interface IInterface1 { }
interface IInterface2 { }

/// <inheritdoc cref="IInterface1" />
class Sample : IInterface1, IInterface2
{
}
````
28 changes: 28 additions & 0 deletions docs/Rules/MA0199.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# MA0199 - Do not use inheritdoc on types without inheritance source
<!-- sources -->
Source: [InheritdocShouldHaveSourceOnTypesAnalyzer.cs](https://github.com/meziantou/Meziantou.Analyzer/blob/main/src/Meziantou.Analyzer/Rules/InheritdocShouldHaveSourceOnTypesAnalyzer.cs)
<!-- sources -->

`<inheritdoc />` requires an inheritance source.
When a type has no base type (other than `System.Object` or `System.ValueType`) and no declared interface, inheritdoc has no source unless `cref` is specified.

This rule reports `<inheritdoc />` on type declarations (`class`, `struct`, `interface`, `record`) when:
- no `cref` is specified
- there is no applicable base type
- there are no declared interfaces

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

````csharp
// Compliant
/// <summary>Represents a dedicated concept for this type.</summary>
class Sample
{
}
````
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
using System.Collections.Immutable;
using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;

namespace Meziantou.Analyzer.Rules;

[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class InheritdocShouldNotBeUsedOnTypesFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds => ImmutableArray.Create(RuleIdentifiers.InheritdocShouldNotBeAmbiguousOnTypes);

public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var nodeToFix = root?.FindNode(context.Span, getInnermostNodeForTie: true, findInsideTrivia: true);
if (!TryGetInheritdocNode(nodeToFix, out var inheritdocNode, out var attributes))
return;

if (HasCrefAttribute(attributes))
return;

var typeDeclaration = nodeToFix?.FirstAncestorOrSelf<TypeDeclarationSyntax>();
if (typeDeclaration is null)
return;

var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
if (semanticModel?.GetDeclaredSymbol(typeDeclaration, context.CancellationToken) is not INamedTypeSymbol typeSymbol)
return;

if (HasBaseType(typeSymbol) || typeSymbol.Interfaces.Length <= 1)
return;

if (inheritdocNode is XmlEmptyElementSyntax emptyElement)
{
RegisterCodeFixes(context, typeSymbol.Interfaces, emptyElement);
return;
}

if (inheritdocNode is XmlElementStartTagSyntax startTag)
{
RegisterCodeFixes(context, typeSymbol.Interfaces, startTag);
}
}

private static void RegisterCodeFixes(CodeFixContext context, ImmutableArray<INamedTypeSymbol> interfaces, XmlEmptyElementSyntax inheritdocNode)
{
foreach (var interfaceSymbol in interfaces)
{
RegisterCodeFix(context, interfaceSymbol, cancellationToken => AddCrefAttribute(context.Document, inheritdocNode, interfaceSymbol, cancellationToken));
}
}

private static void RegisterCodeFixes(CodeFixContext context, ImmutableArray<INamedTypeSymbol> interfaces, XmlElementStartTagSyntax inheritdocNode)
{
foreach (var interfaceSymbol in interfaces)
{
RegisterCodeFix(context, interfaceSymbol, cancellationToken => AddCrefAttribute(context.Document, inheritdocNode, interfaceSymbol, cancellationToken));
}
}

private static void RegisterCodeFix(CodeFixContext context, INamedTypeSymbol interfaceSymbol, Func<CancellationToken, Task<Document>> createChangedDocument)
{
var interfaceDisplayName = interfaceSymbol.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
var crefValue = ToCrefValue(interfaceSymbol);
var title = $"Add cref=\"{interfaceDisplayName}\"";
var equivalenceKey = $"{title}:{crefValue}";

context.RegisterCodeFix(
CodeAction.Create(
title,
createChangedDocument,
equivalenceKey: equivalenceKey),
context.Diagnostics);
}

private static async Task<Document> AddCrefAttribute(Document document, XmlEmptyElementSyntax inheritdocNode, INamedTypeSymbol interfaceSymbol, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var newNode = inheritdocNode.WithAttributes(inheritdocNode.Attributes.Add(XmlTextAttribute("cref", ToCrefValue(interfaceSymbol))));
editor.ReplaceNode(inheritdocNode, newNode);
return editor.GetChangedDocument();
}

private static async Task<Document> AddCrefAttribute(Document document, XmlElementStartTagSyntax inheritdocNode, INamedTypeSymbol interfaceSymbol, CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
var newNode = inheritdocNode.WithAttributes(inheritdocNode.Attributes.Add(XmlTextAttribute("cref", ToCrefValue(interfaceSymbol))));
editor.ReplaceNode(inheritdocNode, newNode);
return editor.GetChangedDocument();
}

private static bool TryGetInheritdocNode(SyntaxNode? node, out SyntaxNode? inheritdocNode, out SyntaxList<XmlAttributeSyntax> attributes)
{
if (node?.FirstAncestorOrSelf<XmlEmptyElementSyntax>() is { } emptyElement && IsInheritdocElement(emptyElement.Name))
{
inheritdocNode = emptyElement;
attributes = emptyElement.Attributes;
return true;
}

if (node?.FirstAncestorOrSelf<XmlElementStartTagSyntax>() is { } startTag && IsInheritdocElement(startTag.Name))
{
inheritdocNode = startTag;
attributes = startTag.Attributes;
return true;
}

inheritdocNode = null;
attributes = default;
return false;
}

private static bool IsInheritdocElement(XmlNameSyntax name)
{
return string.Equals(name.LocalName.Text, "inheritdoc", StringComparison.OrdinalIgnoreCase);
}

private static bool HasCrefAttribute(SyntaxList<XmlAttributeSyntax> attributes)
{
foreach (var attribute in attributes)
{
if (string.Equals(attribute.Name.LocalName.Text, "cref", StringComparison.OrdinalIgnoreCase))
return true;
}

return false;
}

private static bool HasBaseType(INamedTypeSymbol symbol)
{
return symbol.BaseType is { SpecialType: not (SpecialType.System_Object or SpecialType.System_ValueType) };
}

private static string ToCrefValue(INamedTypeSymbol symbol)
{
return DocumentationCommentId.CreateReferenceId(symbol);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -584,5 +584,11 @@ 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
# MA0197: Add dedicated documentation on types
dotnet_diagnostic.MA0197.severity = error

# MA0198: Specify cref for ambiguous inheritdoc on types
dotnet_diagnostic.MA0198.severity = error

# MA0199: Do not use inheritdoc on types without inheritance source
dotnet_diagnostic.MA0199.severity = error
Original file line number Diff line number Diff line change
Expand Up @@ -584,5 +584,11 @@ 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
# MA0197: Add dedicated documentation on types
dotnet_diagnostic.MA0197.severity = suggestion

# MA0198: Specify cref for ambiguous inheritdoc on types
dotnet_diagnostic.MA0198.severity = suggestion

# MA0199: Do not use inheritdoc on types without inheritance source
dotnet_diagnostic.MA0199.severity = suggestion
Original file line number Diff line number Diff line change
Expand Up @@ -584,5 +584,11 @@ 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
# MA0197: Add dedicated documentation on types
dotnet_diagnostic.MA0197.severity = warning

# MA0198: Specify cref for ambiguous inheritdoc on types
dotnet_diagnostic.MA0198.severity = warning

# MA0199: Do not use inheritdoc on types without inheritance source
dotnet_diagnostic.MA0199.severity = warning
10 changes: 8 additions & 2 deletions src/Meziantou.Analyzer.Pack/configuration/default.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -584,5 +584,11 @@ 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
# MA0197: Add dedicated documentation on types
dotnet_diagnostic.MA0197.severity = suggestion

# MA0198: Specify cref for ambiguous inheritdoc on types
dotnet_diagnostic.MA0198.severity = warning

# MA0199: Do not use inheritdoc on types without inheritance source
dotnet_diagnostic.MA0199.severity = warning
Original file line number Diff line number Diff line change
Expand Up @@ -584,5 +584,11 @@ 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
# MA0197: Add dedicated documentation on types
dotnet_diagnostic.MA0197.severity = none

# MA0198: Specify cref for ambiguous inheritdoc on types
dotnet_diagnostic.MA0198.severity = none

# MA0199: Do not use inheritdoc on types without inheritance source
dotnet_diagnostic.MA0199.severity = none
2 changes: 2 additions & 0 deletions src/Meziantou.Analyzer/RuleIdentifiers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ internal static class RuleIdentifiers
public const string DoNotUseNotYetInitializedStaticField = "MA0195";
public const string InheritdocShouldBeUsedOnInheritingMember = "MA0196";
public const string InheritdocShouldNotBeUsedOnTypes = "MA0197";
public const string InheritdocShouldNotBeAmbiguousOnTypes = "MA0198";
public const string InheritdocShouldHaveSourceOnTypes = "MA0199";

public static string GetHelpUri(string identifier)
{
Expand Down
Loading
Loading