AbstractRemoveDocCommentNodeCodeFixProvider.cs 4.0 KB
Newer Older
A
Abraham Hosch 已提交
1 2 3
// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using System;
C
CyrusNajmabadi 已提交
4
using System.Collections.Generic;
A
Abraham Hosch 已提交
5 6 7 8 9 10 11 12 13 14
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.DiagnosticComments.CodeFixes
{
15 16
    internal abstract class AbstractRemoveDocCommentNodeCodeFixProvider<TXmlElementSyntax> : CodeFixProvider
        where TXmlElementSyntax : SyntaxNode
A
Abraham Hosch 已提交
17 18 19 20
    {
        public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

        public abstract override ImmutableArray<string> FixableDiagnosticIds { get; }
C
CyrusNajmabadi 已提交
21

A
Abraham Hosch 已提交
22 23 24 25
        protected abstract string DocCommentSignifierToken { get; }

        protected abstract SyntaxTriviaList GetRevisedDocCommentTrivia(string docCommentText);

A
Abraham Hosch 已提交
26
        public async sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
A
Abraham Hosch 已提交
27
        {
28
            var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
A
Abraham Hosch 已提交
29

A
Abraham Hosch 已提交
30 31 32 33
            if (GetParamNode(root, context.Span) != null)
            {
                context.RegisterCodeFix(
                    new MyCodeAction(
34
                        c => RemoveDuplicateParamTagAsync(context.Document, context.Span, c)),
A
Abraham Hosch 已提交
35 36
                    context.Diagnostics);
            }
A
Abraham Hosch 已提交
37 38
        }

39
        private TXmlElementSyntax GetParamNode(SyntaxNode root, TextSpan span, CancellationToken cancellationToken = default(CancellationToken))
A
Abraham Hosch 已提交
40
        {
41 42 43 44
            // First, we get the node the diagnostic fired on
            // Then, we climb the tree to the first parent that is of the type XMLElement
            // This is to correctly handle XML nodes that are nested in other XML nodes, so we only
            // remove the node the diagnostic fired on and its children, but no parent nodes
45
            var paramNode = root.FindNode(span, findInsideTrivia: true);
C
CyrusNajmabadi 已提交
46
            return paramNode.FirstAncestorOrSelf<TXmlElementSyntax>();
A
Abraham Hosch 已提交
47 48
        }

49 50
        private async Task<Document> RemoveDuplicateParamTagAsync(
            Document document, TextSpan span, CancellationToken cancellationToken)
A
Abraham Hosch 已提交
51
        {
52
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
A
Abraham Hosch 已提交
53
            var paramNode = GetParamNode(root, span, cancellationToken);
54

A
Abraham Hosch 已提交
55
            var removedNodes = new List<SyntaxNode> { paramNode };
56
            var paramNodeSiblings = paramNode.Parent.ChildNodes().ToList();
A
Abraham Hosch 已提交
57

58 59
            // This should not cause a crash because the diagnostics are only thrown in
            // doc comment XML nodes, which, by definition, start with `///` (C#) or `'''` (VB.NET)
60 61 62 63
            // If, perhaps, this specific node is not directly preceded by the comment marker node,
            // it will be preceded by another XML node
            var paramNodeIndex = paramNodeSiblings.IndexOf(paramNode);
            var previousNodeTextTrimmed = paramNodeSiblings[paramNodeIndex - 1].ToFullString().Trim();
A
Abraham Hosch 已提交
64 65 66

            if (previousNodeTextTrimmed == string.Empty || previousNodeTextTrimmed == DocCommentSignifierToken)
            {
67
                removedNodes.Add(paramNodeSiblings[paramNodeIndex - 1]);
A
Abraham Hosch 已提交
68 69
            }

A
Abraham Hosch 已提交
70
            // Remove all trivia attached to the nodes I am removing.
71 72
            // Really, any option should work here because the leading/trailing text
            // around these nodes are not attached to them as trivia.
73
            var newRoot = root.RemoveNodes(removedNodes, SyntaxRemoveOptions.KeepNoTrivia);
A
Abraham Hosch 已提交
74 75 76 77 78
            return document.WithSyntaxRoot(newRoot);
        }

        private class MyCodeAction : CodeAction.DocumentChangeAction
        {
C
CyrusNajmabadi 已提交
79 80
            public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
                : base(FeaturesResources.Remove_tag, createChangedDocument)
A
Abraham Hosch 已提交
81 82 83 84 85
            {
            }
        }
    }
}