Skip to content

Commit

Permalink
Add initial implementation of DOC206 (Synchronize documentation)
Browse files Browse the repository at this point in the history
  • Loading branch information
sharwell committed Oct 9, 2018
1 parent 37a0d98 commit ec94d4f
Show file tree
Hide file tree
Showing 11 changed files with 457 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen

var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var documentedSymbol = semanticModel.GetDeclaredSymbol(xmlNode.FirstAncestorOrSelf<SyntaxNode>(SyntaxNodeExtensionsEx.IsSymbolDeclaration), cancellationToken);
var candidateSymbol = GetCandidateSymbol(documentedSymbol);
var candidateSymbol = InheritdocHelper.GetCandidateSymbol(documentedSymbol);
var candidateDocumentation = candidateSymbol.GetDocumentationCommentXml(expandIncludes: false, cancellationToken: cancellationToken);

var xmlDocumentation = XElement.Parse(candidateDocumentation);
Expand All @@ -63,13 +63,13 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
var content = new List<XmlNodeSyntax>();
content.AddRange(xmlDocumentation.Elements().Select(element => XmlSyntaxFactory.Node(newLineText, element)));

var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, "autoinheritdoc", oldStartToken.TrailingTrivia);
var newStartToken = SyntaxFactory.Identifier(oldStartToken.LeadingTrivia, XmlCommentHelper.AutoinheritdocXmlTag, 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, "autoinheritdoc", oldEndToken.TrailingTrivia);
var newEndToken = SyntaxFactory.Identifier(oldEndToken.LeadingTrivia, XmlCommentHelper.AutoinheritdocXmlTag, oldEndToken.TrailingTrivia);
newXmlNode = newXmlNode.ReplaceToken(oldEndToken, newEndToken);
}

Expand All @@ -78,78 +78,5 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen

return document.WithSyntaxRoot(root.ReplaceNode(xmlNode, content));
}

private static ISymbol GetCandidateSymbol(ISymbol memberSymbol)
{
if (memberSymbol is IMethodSymbol methodSymbol)
{
if (methodSymbol.MethodKind == MethodKind.Constructor || methodSymbol.MethodKind == MethodKind.StaticConstructor)
{
var baseType = memberSymbol.ContainingType.BaseType;
return baseType.Constructors.Where(c => IsSameSignature(methodSymbol, c)).FirstOrDefault();
}
else if (!methodSymbol.ExplicitInterfaceImplementations.IsEmpty)
{
// prototype(inheritdoc): do we need 'OrDefault'?
return methodSymbol.ExplicitInterfaceImplementations.FirstOrDefault();
}
else if (methodSymbol.IsOverride)
{
return methodSymbol.OverriddenMethod;
}
else
{
// prototype(inheritdoc): check for implicit interface
return null;
}
}
else if (memberSymbol is INamedTypeSymbol typeSymbol)
{
if (typeSymbol.TypeKind == TypeKind.Class)
{
// prototype(inheritdoc): when does base class take precedence over interface?
return typeSymbol.BaseType;
}
else if (typeSymbol.TypeKind == TypeKind.Interface)
{
return typeSymbol.Interfaces.FirstOrDefault();
}
else
{
// This includes structs, enums, and delegates as mentioned in the inheritdoc spec
return null;
}
}

return null;
}

private static bool IsSameSignature(IMethodSymbol left, IMethodSymbol right)
{
if (left.Parameters.Length != right.Parameters.Length)
{
return false;
}

if (left.IsStatic != right.IsStatic)
{
return false;
}

if (!left.ReturnType.Equals(right.ReturnType))
{
return false;
}

for (int i = 0; i < left.Parameters.Length; i++)
{
if (!left.Parameters[i].Type.Equals(right.Parameters[i].Type))
{
return false;
}
}

return true;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
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(DOC206CodeFixProvider))]
[Shared]
internal class DOC206CodeFixProvider : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; }
= ImmutableArray.Create(DOC206SynchronizeDocumentation.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(
PortabilityResources.DOC206CodeFix,
token => GetTransformedDocumentAsync(context.Document, diagnostic, token),
nameof(DOC206CodeFixProvider)),
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 xmlNode = (XmlNodeSyntax)root.FindNode(diagnostic.Location.SourceSpan, findInsideTrivia: true, getInnermostNodeForTie: true);
var oldStartToken = xmlNode.GetName().LocalName;

var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var documentedSymbol = semanticModel.GetDeclaredSymbol(xmlNode.FirstAncestorOrSelf<SyntaxNode>(SyntaxNodeExtensionsEx.IsSymbolDeclaration), cancellationToken);
var candidateSymbol = InheritdocHelper.GetCandidateSymbol(documentedSymbol);
var candidateDocumentation = candidateSymbol.GetDocumentationCommentXml(expandIncludes: false, cancellationToken: cancellationToken);

var xmlDocumentation = XElement.Parse(candidateDocumentation);
var newLineText = Environment.NewLine;

var content = new List<XmlNodeSyntax>();
content.AddRange(xmlDocumentation.Elements().Select(element => XmlSyntaxFactory.Node(newLineText, element)));
content.Add(XmlSyntaxFactory.NewLine(newLineText));
content.Add(xmlNode);

return document.WithSyntaxRoot(root.ReplaceNode(
xmlNode.FirstAncestorOrSelf<DocumentationCommentTriviaSyntax>(),
XmlSyntaxFactory.DocumentationComment(newLineText, content.ToArray())));
}
}
}
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 DOC206CSharp7UnitTests : DOC206UnitTests
{
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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 Microsoft.CodeAnalysis.CSharp.Testing;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<DocumentationAnalyzers.PortabilityRules.DOC206SynchronizeDocumentation, DocumentationAnalyzers.PortabilityRules.DOC206CodeFixProvider, Microsoft.CodeAnalysis.Testing.Verifiers.XUnitVerifier>;

public class DOC206UnitTests
{
[Fact]
public async Task TestInheritSummaryAsync()
{
var testCode = @"
/// <summary>
/// Summary text.
/// </summary>
/// <autoinheritdoc/>
class TestClass : BaseClass
{
}
/// <summary>
/// Summary text.
/// </summary>
class BaseClass
{
}
";

await Verify.VerifyAnalyzerAsync(testCode);
}

[Fact]
public async Task TestIncorrectSummaryAsync()
{
var testCode = @"
/// <summary>
/// Incorrect summary text.
/// </summary>
/// [|<autoinheritdoc/>|]
class TestClass : BaseClass
{
}
/// <summary>
/// Summary text.
/// </summary>
class BaseClass
{
}
";
var fixedCode = @"
/// <summary>
/// Summary text.
/// </summary>
/// <autoinheritdoc/>
class TestClass : BaseClass
{
}
/// <summary>
/// Summary text.
/// </summary>
class BaseClass
{
}
";

await Verify.VerifyCodeFixAsync(testCode, fixedCode);
}

[Fact]
public async Task TestMissingSummaryAsync()
{
var testCode = @"
/// [|<autoinheritdoc/>|]
class TestClass : BaseClass
{
}
/// <summary>
/// Summary text.
/// </summary>
class BaseClass
{
}
";
var fixedCode = @"
/// <summary>
/// Summary text.
/// </summary>
/// <autoinheritdoc/>
class TestClass : BaseClass
{
}
/// <summary>
/// Summary text.
/// </summary>
class BaseClass
{
}
";

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

0 comments on commit ec94d4f

Please sign in to comment.