File: src\Analyzers\CSharp\CodeFixes\DocumentationComments\CSharpAddDocCommentNodesCodeFixProvider.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.DocumentationComments;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.DocumentationComments;
 
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AddDocCommentNodes), Shared]
[ExtensionOrder(After = PredefinedCodeFixProviderNames.ImplementInterface)]
[method: ImportingConstructor]
[method: SuppressMessage("RoslynDiagnosticsReliability", "RS0033:Importing constructor should be [Obsolete]", Justification = "Used in test code: https://github.com/dotnet/roslyn/issues/42814")]
internal sealed class CSharpAddDocCommentNodesCodeFixProvider()
    : AbstractAddDocCommentNodesCodeFixProvider<XmlElementSyntax, XmlNameAttributeSyntax, XmlTextSyntax, MemberDeclarationSyntax>
{
    /// <summary>
    /// Parameter has no matching param tag in XML comment
    /// </summary>
    private const string CS1573 = nameof(CS1573);
 
    public override ImmutableArray<string> FixableDiagnosticIds { get; } = [CS1573];
 
    protected override string NodeName { get; } = "param";
 
    protected override List<XmlNameAttributeSyntax> GetNameAttributes(XmlElementSyntax node)
        => node.StartTag.Attributes.OfType<XmlNameAttributeSyntax>().ToList();
 
    protected override string GetValueFromNameAttribute(XmlNameAttributeSyntax attribute)
        => attribute.Identifier.Identifier.ValueText;
 
    protected override SyntaxNode? TryGetDocCommentNode(SyntaxTriviaList leadingTrivia)
    {
        var docCommentNodes = leadingTrivia.Where(f => f.IsKind(SyntaxKind.SingleLineDocumentationCommentTrivia));
 
        foreach (var node in docCommentNodes)
        {
            var nodeStructure = node.GetStructure()!;
            var descendentXmlElements = nodeStructure.DescendantNodes().OfType<XmlElementSyntax>();
 
            if (descendentXmlElements.Any(element => GetXmlElementLocalName(element) == NodeName))
                return nodeStructure;
        }
 
        return null;
    }
 
    protected override string GetXmlElementLocalName(XmlElementSyntax element)
        => element.StartTag.Name.LocalName.ValueText;
 
    protected override ImmutableArray<string> GetParameterNames(MemberDeclarationSyntax member)
    {
        var parameterList = (ParameterListSyntax?)member
            .DescendantNodes(descendIntoChildren: _ => true, descendIntoTrivia: false)
            .FirstOrDefault(f => f is ParameterListSyntax);
 
        return parameterList == null
            ? []
            : parameterList.Parameters.SelectAsArray(s => s.Identifier.ValueText);
    }
 
    protected override XmlElementSyntax GetNewNode(string parameterName, bool isFirstNodeInComment)
    {
        // This is the simplest way of getting the XML node with the correct leading trivia
        // However, trying to add a `DocumentationCommentTriviaSyntax` to the node in the abstract
        // implementation causes an exception, so we have to add an XmlElementSyntax
        var newDocCommentNode = SyntaxFactory.DocumentationComment(SyntaxFactory.XmlParamElement(parameterName));
        var elementNode = (XmlElementSyntax)newDocCommentNode.ChildNodes().ElementAt(0);
 
        // return node on new line
        if (isFirstNodeInComment)
        {
            return elementNode.WithTrailingTrivia(SyntaxFactory.ParseTrailingTrivia(Environment.NewLine));
        }
 
        return elementNode.WithLeadingTrivia(
            SyntaxFactory.ParseLeadingTrivia(Environment.NewLine)
                .AddRange(elementNode.GetLeadingTrivia()));
    }
}