Skip to content

Commit

Permalink
Merge pull request #55 from sharwell/empty-para
Browse files Browse the repository at this point in the history
Implement DOC108 (Avoid Empty Paragraphs)
  • Loading branch information
sharwell authored Sep 21, 2018
2 parents 3d145af + 67dac52 commit d4ca1c6
Show file tree
Hide file tree
Showing 7 changed files with 396 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// 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.StyleRules
{
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
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(DOC108CodeFixProvider))]
[Shared]
internal class DOC108CodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(DOC108AvoidEmptyParagraphs.DiagnosticId);

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

public override Task RegisterCodeFixesAsync(CodeFixContext context)
{
foreach (var diagnostic in context.Diagnostics)
{
Debug.Assert(FixableDiagnosticIds.Contains(diagnostic.Id), "Assertion failed: FixableDiagnosticIds.Contains(diagnostic.Id)");

context.RegisterCodeFix(
CodeAction.Create(
StyleResources.DOC108CodeFix,
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
nameof(DOC108CodeFixProvider)),
diagnostic);
}

return SpecializedTasks.CompletedTask;
}

private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
{
SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var xmlEmptyElement = (XmlEmptyElementSyntax)root.FindNode(diagnostic.Location.SourceSpan, findInsideTrivia: true, getInnermostNodeForTie: true);
return document.WithSyntaxRoot(root.RemoveNode(xmlEmptyElement, SyntaxRemoveOptions.KeepExteriorTrivia));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// 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.StyleRules
{
using System.Threading.Tasks;
using DocumentationAnalyzers.StyleRules;
using Xunit;
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<DocumentationAnalyzers.StyleRules.DOC108AvoidEmptyParagraphs, DocumentationAnalyzers.StyleRules.DOC108CodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;

/// <summary>
/// This class contains unit tests for <see cref="DOC108AvoidEmptyParagraphs"/>.
/// </summary>
public class DOC108UnitTests
{
[Fact]
public async Task TestEmptyParagraphElementSeparatesParagraphsAsync()
{
var testCode = @"
/// <summary>
/// Summary 1
/// $$<para/>
/// Summary 2
/// </summary>
class TestClass
{
}
";
var fixedCode = @"
/// <summary>
/// Summary 1
///
/// Summary 2
/// </summary>
class TestClass
{
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestEmptySeeElementSeparatesParagraphsAsync()
{
var testCode = @"
/// <summary>
/// Summary 1
/// <see cref=""TestClass""/>
/// Summary 2
/// </summary>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestPrefixedParagraphElementSeparatesParagraphsAsync()
{
var testCode = @"
/// <summary>
/// Summary 1
/// <html:p/>
/// Summary 2
/// </summary>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestEmptyElementWithMissingNameAsync()
{
var testCode = @"
/// <summary>
/// <
/// </summary>
class TestClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestEmptyHtmlParagraphElementSeparatesParagraphsAsync()
{
var testCode = @"
/// <summary>
/// Summary 1
/// $$<p/>
/// Summary 2
/// </summary>
class TestClass
{
}
";
var fixedCode = @"
/// <summary>
/// Summary 1
///
/// Summary 2
/// </summary>
class TestClass
{
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestEmptyParagraphBeforeFullParagraphAsync()
{
var testCode = @"
/// <summary>
/// Summary 1
/// $$<para/>
/// <para>Summary 2</para>
/// </summary>
class TestClass
{
}
";
var fixedCode = @"
/// <summary>
/// Summary 1
///
/// <para>Summary 2</para>
/// </summary>
class TestClass
{
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestEmptyHtmlParagraphBeforeFullHtmlParagraphAsync()
{
var testCode = @"
/// <summary>
/// Summary 1
/// $$<p/>
/// <p>Summary 2</p>
/// </summary>
class TestClass
{
}
";
var fixedCode = @"
/// <summary>
/// Summary 1
///
/// <p>Summary 2</p>
/// </summary>
class TestClass
{
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// 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.StyleRules
{
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

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

private static readonly LocalizableString Title = new LocalizableResourceString(nameof(StyleResources.DOC108Title), StyleResources.ResourceManager, typeof(StyleResources));
private static readonly LocalizableString MessageFormat = new LocalizableResourceString(nameof(StyleResources.DOC108MessageFormat), StyleResources.ResourceManager, typeof(StyleResources));
private static readonly LocalizableString Description = new LocalizableResourceString(nameof(StyleResources.DOC108Description), StyleResources.ResourceManager, typeof(StyleResources));

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(HandleXmlEmptyElementSyntax, SyntaxKind.XmlEmptyElement);
}

private static void HandleXmlEmptyElementSyntax(SyntaxNodeAnalysisContext context)
{
var xmlEmptyElement = (XmlEmptyElementSyntax)context.Node;
var name = xmlEmptyElement.Name;
if (name.Prefix != null)
{
return;
}

switch (name.LocalName.ValueText)
{
case "para":
case "p":
break;

default:
return;
}

context.ReportDiagnostic(Diagnostic.Create(Descriptor, xmlEmptyElement.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 @@ -147,4 +147,16 @@
<data name="DOC102Title" xml:space="preserve">
<value>Use child blocks consistently across elements of the same kind</value>
</data>
<data name="DOC108CodeFix" xml:space="preserve">
<value>Wrap text in paragraph element</value>
</data>
<data name="DOC108Description" xml:space="preserve">
<value>Avoid empty paragraphs</value>
</data>
<data name="DOC108MessageFormat" xml:space="preserve">
<value>Avoid empty paragraphs</value>
</data>
<data name="DOC108Title" xml:space="preserve">
<value>Avoid empty paragraphs</value>
</data>
</root>
Loading

0 comments on commit d4ca1c6

Please sign in to comment.