提交 c3bdaf60 编写于 作者: C CyrusNajmabadi 提交者: GitHub

Merge pull request #12997 from Hosch250/master

Implement code fix for CS1573
......@@ -205,6 +205,7 @@
<Compile Include="Diagnostics\UseImplicitOrExplicitType\UseExplicitTypeTests_FixAllTests.cs" />
<Compile Include="Diagnostics\UseImplicitOrExplicitType\UseImplicitTypeTests_FixAllTests.cs" />
<Compile Include="Diagnostics\UseImplicitOrExplicitType\UseImplicitTypeTests.cs" />
<Compile Include="DocumentationComments\CodeFixes\AddDocCommentNodesCodeFixProviderTests.cs" />
<Compile Include="DocumentationComments\CodeFixes\RemoveDocCommentNodeCodeFixProviderTests.cs" />
<Compile Include="GoToAdjacentMember\CSharpGoToAdjacentMemberTests.cs" />
<Compile Include="Diagnostics\AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest.cs" />
......
// 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;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.DiagnosticComments.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.Diagnostics;
using Roslyn.Test.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.DocumentationComments.CodeFixes
{
public class AddDocCommentNodesCodesFixProviderTests : AbstractCSharpDiagnosticProviderBasedUserDiagnosticTest
{
internal override Tuple<DiagnosticAnalyzer, CodeFixProvider> CreateDiagnosticProviderAndFixer(Workspace workspace)
{
return new Tuple<DiagnosticAnalyzer, CodeFixProvider>(null, new CSharpAddDocCommentNodesCodeFixProvider());
}
private async Task TestAsync(string initial, string expected)
{
var parseOptions = Options.Regular.WithDocumentationMode(DocumentationMode.Diagnose);
await TestAsync(initial, expected, parseOptions: parseOptions, compareTokens: false);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_NoNodesBefore()
{
var initial =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
public void Fizz(int [|i|], int j, int k) {}
}
";
var expected =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_NoNodesAfter()
{
var initial =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
public void Fizz(int i, int j, int [|k|]) {}
}
";
var expected =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_NodesBeforeAndAfter()
{
var initial =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""k""></param>
public void Fizz(int i, int [|j|], int k) {}
}
";
var expected =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_NodesBeforeAndAfter_RawTextInComment()
{
var initial =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// text
/// <param name=""k""></param>
public void Fizz(int i, int [|j|], int k) {}
}
";
var expected =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// text
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_NodesBeforeAndAfter_WithContent()
{
var initial =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i"">Parameter `i` does something</param>
/// <param name=""k"">Parameter `k` does something else</param>
public void Fizz(int i, int [|j|], int k) {}
}
";
var expected =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i"">Parameter `i` does something</param>
/// <param name=""j""></param>
/// <param name=""k"">Parameter `k` does something else</param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_NestedInSummaryTag()
{
var initial =
@"class Program
{
/// <summary>
/// <param name=""j""></param>
/// </summary>
public void Fizz(int i, int j, int [|k|]) {}
}
";
var expected =
@"class Program
{
/// <summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
/// </summary>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_BeforeNode_EverythingOnOneLine()
{
var initial =
@"class Program
{
/// <summary></summary> <param name=""j""></param>
public void Fizz(int [|i|], int j, int k) {}
}
";
var expected =
@"class Program
{
/// <summary></summary>
/// <param name=""i""></param> <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_AfterNode_EverythingOnOneLine()
{
var initial =
@"class Program
{
/// <summary></summary> <param name=""j""></param>
public void Fizz(int i, int j, int [|k|]) {}
}
";
var expected =
@"class Program
{
/// <summary></summary>
/// <param name=""i""></param> <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_BeforeNode_JustParamNode()
{
var initial =
@"class Program
{
/// <param name=""j""></param>
public void Fizz(int [|i|], int j, int k) {}
}
";
var expected =
@"class Program
{
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_AfterNode_JustParamNode()
{
var initial =
@"class Program
{
/// <param name=""j""></param>
public void Fizz(int i, int j, int [|k|]) {}
}
";
var expected =
@"class Program
{
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_MultipleDocComments()
{
var initial =
@"class Program
{
/// <summary></summary>
// ...
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
public void Fizz(int [|i|], int j, int k) {}
}
";
var expected =
@"class Program
{
/// <summary></summary>
// ...
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_Ctor()
{
var initial =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
public Program(int [|i|], int j, int k) {}
}
";
var expected =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public Program(int i, int j, int k) {}
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_Delegate()
{
var initial =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
public delegate int Foo(int [|i|], int j, int k);
}
";
var expected =
@"class Program
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public delegate int Foo(int [|i|], int j, int k);
}
";
await TestAsync(initial, expected);
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
public async Task AddsParamTag_Operator()
{
var initial =
@"public struct MyStruct
{
public int Val { get; }
public MyStruct(int val)
{
Val = val;
}
/// <summary>
///
/// </summary>
/// <param name=""s1""></param>
/// <returns></returns>
public static MyStruct operator +(MyStruct s1, MyStruct [|s2|])
{
return new MyStruct(s1.Val + s2.Val);
}
}
";
var expected =
@"public struct MyStruct
{
public int Val { get; }
public MyStruct(int val)
{
Val = val;
}
/// <summary>
///
/// </summary>
/// <param name=""s1""></param>
/// <param name=""s2""></param>
/// <returns></returns>
public static MyStruct operator +(MyStruct s1, MyStruct s2)
{
return new MyStruct(s1.Val + s2.Val);
}
}
";
await TestAsync(initial, expected);
}
[Fact]
[Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
[Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
public async Task TestFixAllInDocument_MultipleParamNodesInVariousPlaces()
{
var initial =
@"class Program
{
/// <summary>
/// <param name=""i""></param>
/// </summary>
/// <param name=""k""></param>
public void Fizz(int i, int {|FixAllInDocument:j|}, int k, int l) {}
}";
var expected =
@"class Program
{
/// <summary>
/// <param name=""i""></param>
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
/// <param name=""l""></param>
public void Fizz(int i, int j, int k, int l) {}
}";
await TestAsync(initial, expected);
}
[Fact]
[Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
[Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
public async Task TestFixAllInDocument()
{
var initial = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program1
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
public void Fizz(int i, int j, {|FixAllInDocument:int k|}) {}
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
/// <param name=""k""></param>
/// <returns></returns>
public int Buzz(int i, int j, int k) { returns 0; }
}]]>
</Document>
<Document>
<![CDATA[
class Program2
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
</Project>
<Project Language=""C#"" AssemblyName=""Assembly2"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program3
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program1
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
/// <returns></returns>
public int Buzz(int i, int j, int k) { returns 0; }
}]]>
</Document>
<Document>
<![CDATA[
class Program2
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
</Project>
<Project Language=""C#"" AssemblyName=""Assembly2"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program3
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
</Project>
</Workspace>";
await TestAsync(initial, expected);
}
[Fact]
[Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
[Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
public async Task TestFixAllInProject()
{
var initial = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program1
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
public void Fizz(int i, int j, {|FixAllInProject:int k|}) {}
}]]>
</Document>
<Document>
<![CDATA[
class Program2
{
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
/// <param name=""k""></param>
/// <returns></returns>
public int Buzz(int i, int j, int k) { returns 0; }
}]]>
</Document>
</Project>
<Project Language=""C#"" AssemblyName=""Assembly2"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program3
{
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
/// <param name=""k""></param>
/// <returns></returns>
public int Buzz(int i, int j, int k) { returns 0; }
}]]>
</Document>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"">
<Document>
<![CDATA[
class Program1
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
<Document>
<![CDATA[
class Program2
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
/// <returns></returns>
public int Buzz(int i, int j, int k) { returns 0; }
}]]>
</Document>
</Project>
<Project Language=""C#"" AssemblyName=""Assembly2"" CommonReferences=""true"">
<Document>
<![CDATA[
class Program3
{
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
/// <param name=""k""></param>
/// <returns></returns>
public int Buzz(int i, int j, int k) { returns 0; }
}]]>
</Document>
</Project>
</Workspace>";
await TestAsync(initial, expected);
}
[Fact]
[Trait(Traits.Feature, Traits.Features.CodeActionsAddDocCommentNodes)]
[Trait(Traits.Feature, Traits.Features.CodeActionsFixAllOccurrences)]
public async Task TestFixAllInSolution()
{
var initial = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program1
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
public void Fizz(int i, int j, {|FixAllInSolution:int k|}) {}
}]]>
</Document>
<Document>
<![CDATA[
class Program2
{
/// <summary>
///
/// </summary>
/// <param name=""j""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
</Project>
<Project Language=""C#"" AssemblyName=""Assembly2"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program3
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
</Project>
</Workspace>";
var expected = @"
<Workspace>
<Project Language=""C#"" AssemblyName=""Assembly1"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program1
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
<Document>
<![CDATA[
class Program2
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
</Project>
<Project Language=""C#"" AssemblyName=""Assembly2"" CommonReferences=""true"" DocumentationMode=""Diagnose"">
<Document>
<![CDATA[
class Program3
{
/// <summary>
///
/// </summary>
/// <param name=""i""></param>
/// <param name=""j""></param>
/// <param name=""k""></param>
public void Fizz(int i, int j, int k) {}
}]]>
</Document>
</Project>
</Workspace>";
await TestAsync(initial, expected);
}
}
}
......@@ -28,6 +28,7 @@ public static class Features
public const string Classification = nameof(Classification);
public const string ClassView = nameof(ClassView);
public const string CodeActionsAddConstructorParameters = "CodeActions.AddConstructorParameters";
public const string CodeActionsAddDocCommentNodes = "CodeActions.AddDocCommentParamNodes";
public const string CodeActionsAddAsync = "CodeActions.AddAsync";
public const string CodeActionsAddAwait = "CodeActions.AddAwait";
public const string CodeActionsAddBraces = "CodeActions.AddBraces";
......
......@@ -271,6 +271,7 @@
<Compile Include="Diagnostics\Analyzers\CSharpUseExplicitTypeDiagnosticAnalyzer.cs" />
<Compile Include="Diagnostics\Analyzers\CSharpUseImplicitTypeDiagnosticAnalyzer.cs" />
<Compile Include="Diagnostics\CSharpAnalyzerDriverService.cs" />
<Compile Include="DocumentationComments\CodeFixes\CSharpAddDocCommentNodesCodeFixProvider.cs" />
<Compile Include="DocumentationComments\CodeFixes\CSharpRemoveDocCommentNodeCodeFixProvider.cs" />
<Compile Include="DocumentationComments\CSharpDocumentationCommentFormattingService.cs" />
<Compile Include="DocumentationComments\DocumentationCommentUtilities.cs" />
......
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.CodeAnalysis.DiagnosticComments.CodeFixes
{
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.AddDocCommentNodes), Shared]
[ExtensionOrder(After = PredefinedCodeFixProviderNames.ImplementInterface)]
internal 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; } = ImmutableArray.Create(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 List<string> GetParameterNames(MemberDeclarationSyntax member)
{
var parameterList = (ParameterListSyntax)member.DescendantNodes(descendIntoChildren: _ => true, descendIntoTrivia: false)
.FirstOrDefault(f => f is ParameterListSyntax);
return parameterList != null
? parameterList.Parameters.Select(s => s.Identifier.ValueText).ToList()
: new List<string>();
}
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()));
}
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ namespace Microsoft.CodeAnalysis.CodeFixes
{
internal static class PredefinedCodeFixProviderNames
{
public const string AddDocCommentNodes = "Add Doc Comment Nodes";
public const string AddAwait = "Add Await For Expression";
public const string AddAsync = "Add Async To Member";
public const string ApplyNamingStyle = "Apply Naming Style";
......
// 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;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.DiagnosticComments.CodeFixes
{
internal abstract class AbstractAddDocCommentNodesCodeFixProvider
<TXmlElementSyntax, TXmlNameAttributeSyntax, TXmlTextSyntax, TMemberDeclarationSyntax> : CodeFixProvider
where TXmlElementSyntax : SyntaxNode
where TXmlNameAttributeSyntax : SyntaxNode
where TXmlTextSyntax : SyntaxNode
where TMemberDeclarationSyntax : SyntaxNode
{
public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;
public async sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var parentMethod = root.FindNode(context.Span).FirstAncestorOrSelf<TMemberDeclarationSyntax>();
if (parentMethod != null && TryGetDocCommentNode(parentMethod.GetLeadingTrivia()) != null)
{
context.RegisterCodeFix(
new MyCodeAction(
c => AddParamTagAsync(context.Document, context.Span, c)),
context.Diagnostics);
}
}
protected abstract string NodeName { get; }
protected abstract List<TXmlNameAttributeSyntax> GetNameAttributes(TXmlElementSyntax node);
protected abstract string GetValueFromNameAttribute(TXmlNameAttributeSyntax attribute);
protected abstract SyntaxNode TryGetDocCommentNode(SyntaxTriviaList parameter);
protected abstract string GetXmlElementLocalName(TXmlElementSyntax element);
protected abstract List<string> GetParameterNames(TMemberDeclarationSyntax method);
protected abstract TXmlElementSyntax GetNewNode(string parameterName, bool isFirstNodeInComment);
protected async Task<Document> AddParamTagAsync(
Document document, TextSpan span, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var parentMethod = root.FindNode(span).FirstAncestorOrSelf<TMemberDeclarationSyntax>();
var docCommentNode = TryGetDocCommentNode(parentMethod.GetLeadingTrivia());
var newDocComment = docCommentNode;
var parameterNames = GetParameterNames(parentMethod);
for (var index = 0; index < parameterNames.Count; index++)
{
var parameterName = parameterNames[index];
var paramNodes = GetElementNodes(newDocComment, NodeName);
if (NodeExists(paramNodes, parameterName))
{
continue;
}
var paramsBeforeCurrentParam = parameterNames.TakeWhile(t => t != parameterName).ToList();
var paramsAfterCurrentParam = parameterNames.Except(paramsBeforeCurrentParam).ToList();
paramsAfterCurrentParam.Remove(parameterName);
// If the index is not `0`, there is a node before the current one for sure
// If the index is `0`, try to add the node after the `summary` node,
// only if any existing nodes are at the top level--this route will not
// be taken if the existing node is nested in another node
var summaryNode = GetElementNodes(newDocComment, "summary").FirstOrDefault();
if (index != 0 || (!paramNodes.Any() && summaryNode != null))
{
// First, try to get the node before the param node so we know where to insert the new node
TXmlElementSyntax nodeBeforeNewParamNode = null;
if (index > 0)
{
nodeBeforeNewParamNode = GetParamNodeForParamName(paramNodes, parameterNames[index - 1]);
}
// This will be hit in the index is `0`, in which case the previous node is the summary node
if (nodeBeforeNewParamNode == null)
{
nodeBeforeNewParamNode = summaryNode;
}
newDocComment = newDocComment.InsertNodesAfter(nodeBeforeNewParamNode,
new[] { GetNewNode(parameterName, isFirstNodeInComment: false) });
continue;
}
// At this point, the node has to go at the beginning of the comment
var nodeAfterNewParamNode = paramNodes.FirstOrDefault() ?? newDocComment.ChildNodes().First();
// Adjust for doc comment marker before the node
if (nodeAfterNewParamNode != null)
{
var paramNodeSiblings = nodeAfterNewParamNode.Parent.ChildNodes().ToList();
var indexOfNode = paramNodeSiblings.IndexOf(nodeAfterNewParamNode);
// set insert node to be the doc comment signifier of the closest param before the new node
if (indexOfNode > 0 && paramNodeSiblings[indexOfNode - 1] is TXmlTextSyntax)
{
nodeAfterNewParamNode = paramNodeSiblings[indexOfNode - 1];
}
}
var newNodeList = new[]
{
// the last value will almost always be true, unless the node is embedded in another doc comment node
GetNewNode(parameterName, nodeAfterNewParamNode == newDocComment.ChildNodes().First())
};
newDocComment = newDocComment.InsertNodesBefore(nodeAfterNewParamNode, newNodeList);
}
var newRoot = root.ReplaceNode(docCommentNode, newDocComment.WithAdditionalAnnotations(Formatter.Annotation));
return document.WithSyntaxRoot(newRoot);
}
private List<TXmlElementSyntax> GetElementNodes(SyntaxNode docComment, string nodeName)
{
var nodes = docComment.ChildNodes().OfType<TXmlElementSyntax>()
.Where(w => GetXmlElementLocalName(w) == nodeName)
.ToList();
// Prefer to return element nodes that are the top-level children of the DocComment.
// If we don't find any, then fallback to the first element node at any depth with the requested name.
if (!nodes.Any())
{
nodes = docComment.DescendantNodes(descendIntoChildren: _ => true)
.OfType<TXmlElementSyntax>()
.Where(w => GetXmlElementLocalName(w) == nodeName)
.ToList();
}
return nodes;
}
private bool NodeExists(IEnumerable<TXmlElementSyntax> paramNodes, string name)
{
return paramNodes.Select(GetNameAttributes)
.Where(nameAttributes => nameAttributes.Count == 1)
.Any(nameAttributes => nameAttributes.Select(GetValueFromNameAttribute).Contains(name));
}
protected TXmlElementSyntax GetParamNodeForParamName(
IEnumerable<TXmlElementSyntax> paramNodeList,
string name)
{
foreach (var paramNode in paramNodeList)
{
var paramNameAttributesForNode = GetNameAttributes(paramNode);
// param node is missing `name` attribute or there are multiple `name` attributes
if (paramNameAttributesForNode.Count != 1)
{
continue;
}
if (GetValueFromNameAttribute(paramNameAttributesForNode.Single()) == name)
{
return paramNode;
}
}
return null;
}
private class MyCodeAction : CodeAction.DocumentChangeAction
{
public MyCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument)
: base(FeaturesResources.Add_missing_param_nodes, createChangedDocument)
{
}
}
}
}
\ No newline at end of file
......@@ -25,7 +25,7 @@ internal abstract class AbstractRemoveDocCommentNodeCodeFixProvider<TXmlElementS
public async sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetSyntaxRootAsync().ConfigureAwait(false);
var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
if (GetParamNode(root, context.Span) != null)
{
......
......@@ -237,6 +237,7 @@
<Compile Include="Diagnostics\Analyzers\PreferFrameworkTypeDiagnosticAnalyzerBase.cs" />
<Compile Include="Diagnostics\Analyzers\QualifyMemberAccessDiagnosticAnalyzerBase.cs" />
<Compile Include="Diagnostics\Analyzers\NamingStyles\Capitalization.cs" />
<Compile Include="DocumentationComments\CodeFixes\AbstractAddDocCommentNodesCodeFixProvider.cs" />
<Compile Include="DocumentationComments\CodeFixes\AbstractRemoveDocCommentNodeCodeFixProvider.cs" />
<Compile Include="DocumentSpan.cs" />
<Compile Include="FindReferences\DefinitionItem.cs" />
......
......@@ -143,6 +143,15 @@ internal class FeaturesResources {
}
}
/// <summary>
/// Looks up a localized string similar to Add missing param nodes.
/// </summary>
internal static string Add_missing_param_nodes {
get {
return ResourceManager.GetString("Add_missing_param_nodes", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add optional parameters to &apos;{0}({1})&apos;.
/// </summary>
......
......@@ -1046,6 +1046,9 @@ This version used in: {2}</value>
<data name="Remove_tag" xml:space="preserve">
<value>Remove tag</value>
</data>
<data name="Add_missing_param_nodes" xml:space="preserve">
<value>Add missing param nodes</value>
</data>
<data name="Make_containing_scope_async" xml:space="preserve">
<value>Make containing scope async</value>
</data>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册