Skip to content

Commit

Permalink
Merge pull request #35 from sharwell/item-description
Browse files Browse the repository at this point in the history
Implement DOC201 (Item Should Have Description)
  • Loading branch information
sharwell authored Sep 18, 2018
2 parents 58d2cf5 + 74ed524 commit d467b1e
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT license. See LICENSE in the project root for license information.

namespace DocumentationAnalyzers.PortabilityRules
{
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using DocumentationAnalyzers.Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DOC201CodeFixProvider))]
[Shared]
internal class DOC201CodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(DOC201ItemShouldHaveDescription.DiagnosticId);

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

public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
if (!FixableDiagnosticIds.Contains(diagnostic.Id))
{
continue;
}

context.RegisterCodeFix(
CodeAction.Create(
PortabilityResources.DOC201CodeFix,
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
nameof(DOC201CodeFixProvider)),
diagnostic);
}

return SpecializedTasks.CompletedTask;
}

private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
{
SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
SyntaxToken token = root.FindToken(diagnostic.Location.SourceSpan.Start, findInsideTrivia: true);

var xmlElement = token.Parent.FirstAncestorOrSelf<XmlElementSyntax>();
var newXmlElement = xmlElement.WithContent(XmlSyntaxFactory.List(XmlSyntaxFactory.Element(XmlCommentHelper.DescriptionXmlTag, xmlElement.Content)));
return document.WithSyntaxRoot(root.ReplaceNode(xmlElement, newXmlElement));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT license. See LICENSE in the project root for license information.

namespace DocumentationAnalyzers.Test.CSharp7.PortabilityRules
{
using DocumentationAnalyzers.Test.PortabilityRules;

public class DOC201CSharp7UnitTests : DOC201UnitTests
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT license. See LICENSE in the project root for license information.

namespace DocumentationAnalyzers.Test.PortabilityRules
{
using System.Threading.Tasks;
using Xunit;
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<DocumentationAnalyzers.PortabilityRules.DOC201ItemShouldHaveDescription, DocumentationAnalyzers.PortabilityRules.DOC201CodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;

public class DOC201UnitTests
{
[Fact]
public async Task TestListItemsWithoutDescriptionAsync()
{
var testCode = @"
/// <remarks>
/// <list type=""number"">
/// <[|item|]>Item 1</item>
/// <[|item|]>Item 2</item>
/// </list>
/// </remarks>
class TestClass { }
";
var fixedCode = @"
/// <remarks>
/// <list type=""number"">
/// <item><description>Item 1</description></item>
/// <item><description>Item 2</description></item>
/// </list>
/// </remarks>
class TestClass { }
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestMultilineListItemsWithoutDescriptionAsync()
{
var testCode = @"
/// <remarks>
/// <list type=""number"">
/// <[|item|]>
/// Item 1
/// </item>
/// <[|item|]>
/// Item 2
/// </item>
/// </list>
/// </remarks>
class TestClass { }
";
var fixedCode = @"
/// <remarks>
/// <list type=""number"">
/// <item><description>
/// Item 1
/// </description></item>
/// <item><description>
/// Item 2
/// </description></item>
/// </list>
/// </remarks>
class TestClass { }
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestListItemsWithEmptyDescriptionAsync()
{
var testCode = @"
/// <remarks>
/// <list type=""number"">
/// <item><description></description></item>
/// <item><description/></item>
/// </list>
/// </remarks>
class TestClass { }
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestListItemsWithTermAsync()
{
var testCode = @"
/// <remarks>
/// <list type=""number"">
/// <item><term>Item 1</term></item>
/// <item><term>Item 2</term><description>Description</description></item>
/// </list>
/// </remarks>
class TestClass { }
";

await Verify.VerifyAnalyzerAsync(testCode);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
// Licensed under the MIT license. See LICENSE in the project root for license information.

namespace DocumentationAnalyzers.PortabilityRules
{
using System.Collections.Immutable;
using DocumentationAnalyzers.Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal class DOC201ItemShouldHaveDescription : DiagnosticAnalyzer
{
/// <summary>
/// The ID for diagnostics produced by the <see cref="DOC201ItemShouldHaveDescription"/> analyzer.
/// </summary>
public const string DiagnosticId = "DOC201";
private const string HelpLink = "https://github.com/DotNetAnalyzers/DocumentationAnalyzers/blob/master/docs/DOC201.md";

private static readonly LocalizableString Title = new LocalizableResourceString(nameof(PortabilityResources.DOC201Title), PortabilityResources.ResourceManager, typeof(PortabilityResources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(PortabilityResources.DOC201MessageFormat), PortabilityResources.ResourceManager, typeof(PortabilityResources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(PortabilityResources.DOC201Description), PortabilityResources.ResourceManager, typeof(PortabilityResources));

private static readonly DiagnosticDescriptor Descriptor =
new DiagnosticDescriptor(DiagnosticId, Title, MessageFormat, AnalyzerCategory.PortabilityRules, DiagnosticSeverity.Info, AnalyzerConstants.EnabledByDefault, Description, HelpLink);

/// <inheritdoc/>
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
= ImmutableArray.Create(Descriptor);

public override void Initialize(AnalysisContext context)
{
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();

context.RegisterSyntaxNodeAction(HandleXmlElementSyntax, SyntaxKind.XmlElement);
}

private static void HandleXmlElementSyntax(SyntaxNodeAnalysisContext context)
{
var xmlElementSyntax = (XmlElementSyntax)context.Node;
var name = xmlElementSyntax.StartTag?.Name;
if (name is null || name.Prefix != null)
{
return;
}

switch (name.LocalName.ValueText)
{
case XmlCommentHelper.ItemXmlTag:
break;

default:
return;
}

// check for a <term> or <description> child element
foreach (var node in xmlElementSyntax.Content)
{
var childName = node.GetName();
if (childName is null || childName.Prefix != null)
{
continue;
}

switch (childName.LocalName.ValueText)
{
case XmlCommentHelper.TermXmlTag:
case XmlCommentHelper.DescriptionXmlTag:
// Avoid analyzing <item> that already has <term> and/or <description>
return;

default:
break;
}
}

context.ReportDiagnostic(Diagnostic.Create(Descriptor, name.LocalName.GetLocation()));
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,16 @@
<data name="DOC200Title" xml:space="preserve">
<value>Use XML documentation syntax</value>
</data>
<data name="DOC201CodeFix" xml:space="preserve">
<value>Item should have description</value>
</data>
<data name="DOC201Description" xml:space="preserve">
<value>Item should have description</value>
</data>
<data name="DOC201MessageFormat" xml:space="preserve">
<value>Item should have description</value>
</data>
<data name="DOC201Title" xml:space="preserve">
<value>Item should have description</value>
</data>
</root>
49 changes: 49 additions & 0 deletions docs/DOC201.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# DOC201

<table>
<tr>
<td>TypeName</td>
<td>DOC201ItemShouldHaveDescription</td>
</tr>
<tr>
<td>CheckId</td>
<td>DOC201</td>
</tr>
<tr>
<td>Category</td>
<td>Portability Rules</td>
</tr>
</table>

## Cause

The documentation for an `<item>` within a `<list>` did not include the required `<term>` and/or `<description>`
elements.

## Rule description

According to the C# language specification, the `<item>` element within a documentation comment must have its content
wrapped in a `<description>` element. Not all documentation processing tools support omitting the `<description>`
element, so it should be included for consistent behavior.

See [dotnet/csharplang#1765](https://github.com/dotnet/csharplang/issues/1765) for a language proposal to natively
support lists with the `<description>` element removed.

## How to fix violations

To fix a violation of this rule, wrap the content of the `<item>` element in a `<description>` element.

## How to suppress violations

```csharp
#pragma warning disable DOC201 // Item should have description
/// <remarks>
/// <list type="bullet">
/// <item>This item has text not wrapped in a description element.</item>
/// </list>
/// </remarks>
public void SomeOperation()
#pragma warning restore DOC201 // Item should have description
{
}
```
1 change: 1 addition & 0 deletions docs/PortabilityRules.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ Rules related to the portability of documentation comments.
Identifier | Name | Description
-----------|------|-------------
[DOC200](DOC200.md) | UseXmlDocumentationSyntax | The documentation for the element an HTML element equivalent to a known XML documentation element.
[DOC201](DOC201.md) | ItemShouldHaveDescription | The documentation for an `<item>` within a `<list>` did not include the required `<term>` and/or `<description>` elements.

0 comments on commit d467b1e

Please sign in to comment.