Skip to content

Commit

Permalink
Implement element usage analyzers
Browse files Browse the repository at this point in the history
Add analyzers and fixes for incorrect placement of section, block,
and/or inline elements.

Closes #30
Closes #33
Closes #34
  • Loading branch information
sharwell committed Sep 20, 2018
1 parent 059d451 commit 125ee0a
Show file tree
Hide file tree
Showing 18 changed files with 1,219 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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;
using Microsoft.CodeAnalysis.CSharp.Syntax;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DOC202CodeFixProvider))]
[Shared]
internal class DOC202CodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(DOC202UseSectionElementsCorrectly.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.DOC202CodeFix,
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
nameof(DOC202CodeFixProvider)),
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 xmlNode = token.Parent.FirstAncestorOrSelf<XmlNodeSyntax>();
var oldStartToken = xmlNode.GetName().LocalName;

string newIdentifier;
switch (oldStartToken.ValueText)
{
case XmlCommentHelper.ParamXmlTag:
newIdentifier = XmlCommentHelper.ParamRefXmlTag;
break;

case XmlCommentHelper.TypeParamXmlTag:
newIdentifier = XmlCommentHelper.TypeParamRefXmlTag;
break;

default:
// Not handled
return document;
}

var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, newIdentifier, oldStartToken.TrailingTrivia);
var newXmlNode = xmlNode.ReplaceToken(oldStartToken, newStartToken);

if (newXmlNode is XmlElementSyntax newXmlElement)
{
var oldEndToken = newXmlElement.EndTag.Name.LocalName;
var newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, newIdentifier, oldEndToken.TrailingTrivia);
newXmlNode = newXmlNode.ReplaceToken(oldEndToken, newEndToken);
}

return document.WithSyntaxRoot(root.ReplaceNode(xmlNode, newXmlNode));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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;
using Microsoft.CodeAnalysis.CSharp.Syntax;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DOC203CodeFixProvider))]
[Shared]
internal class DOC203CodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(DOC203UseBlockElementsCorrectly.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.DOC203CodeFix,
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
nameof(DOC203CodeFixProvider)),
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 xmlNode = token.Parent.FirstAncestorOrSelf<XmlNodeSyntax>();
var oldStartToken = xmlNode.GetName().LocalName;

string newIdentifier;
switch (oldStartToken.ValueText)
{
case XmlCommentHelper.CodeXmlTag:
newIdentifier = XmlCommentHelper.CXmlTag;
break;

default:
// Not handled
return document;
}

var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, newIdentifier, oldStartToken.TrailingTrivia);
var newXmlNode = xmlNode.ReplaceToken(oldStartToken, newStartToken);

if (newXmlNode is XmlElementSyntax newXmlElement)
{
var oldEndToken = newXmlElement.EndTag.Name.LocalName;
var newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, newIdentifier, oldEndToken.TrailingTrivia);
newXmlNode = newXmlNode.ReplaceToken(oldEndToken, newEndToken);
}

return document.WithSyntaxRoot(root.ReplaceNode(xmlNode, newXmlNode));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// 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;
using Microsoft.CodeAnalysis.CSharp.Syntax;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(DOC204CodeFixProvider))]
[Shared]
internal class DOC204CodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(DOC204UseInlineElementsCorrectly.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.DOC204CodeFix,
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
nameof(DOC204CodeFixProvider)),
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 xmlNode = token.Parent.FirstAncestorOrSelf<XmlNodeSyntax>();
var oldStartToken = xmlNode.GetName().LocalName;

string newIdentifier;
switch (oldStartToken.ValueText)
{
case XmlCommentHelper.ParamRefXmlTag:
newIdentifier = XmlCommentHelper.ParamXmlTag;
break;

case XmlCommentHelper.TypeParamRefXmlTag:
newIdentifier = XmlCommentHelper.TypeParamXmlTag;
break;

default:
// Not handled
return document;
}

var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, newIdentifier, oldStartToken.TrailingTrivia);
var newXmlNode = xmlNode.ReplaceToken(oldStartToken, newStartToken);

if (newXmlNode is XmlElementSyntax newXmlElement)
{
var oldEndToken = newXmlElement.EndTag.Name.LocalName;
var newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, newIdentifier, oldEndToken.TrailingTrivia);
newXmlNode = newXmlNode.ReplaceToken(oldEndToken, newEndToken);
}

return document.WithSyntaxRoot(root.ReplaceNode(xmlNode, newXmlNode));
}
}
}
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 DOC202CSharp7UnitTests : DOC202UnitTests
{
}
}
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 DOC203CSharp7UnitTests : DOC203UnitTests
{
}
}
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 DOC204CSharp7UnitTests : DOC204UnitTests
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
// 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.DOC202UseSectionElementsCorrectly, DocumentationAnalyzers.PortabilityRules.DOC202CodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;

public class DOC202UnitTests
{
[Fact]
public async Task TestElementsUsedCorrectlyAsync()
{
var testCode = @"
class TestClass
{
/// <summary>
/// Pass in a value.
/// </summary>
/// <typeparam name=""T"">The type of value</typeparam>
/// <param name=""value"">The value</param>
void Method<T>(int value)
{
}
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestParamUsedAsParamRefAsync()
{
var testCode = @"
class TestClass
{
/// <summary>
/// Pass in a <$$param name=""value""/>.
/// </summary>
void Method(int value)
{
}
}
";
var fixedCode = @"
class TestClass
{
/// <summary>
/// Pass in a <paramref name=""value""/>.
/// </summary>
void Method(int value)
{
}
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestTypeParamUsedAsTypeParamRefAsync()
{
var testCode = @"
class TestClass
{
/// <summary>
/// Pass in a <$$typeparam name=""T""/>.
/// </summary>
void Method<T>()
{
}
}
";
var fixedCode = @"
class TestClass
{
/// <summary>
/// Pass in a <typeparamref name=""T""/>.
/// </summary>
void Method<T>()
{
}
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}
}
}
Loading

0 comments on commit 125ee0a

Please sign in to comment.