Skip to content

Commit 65fbf93

Browse files
authored
Merge pull request #2562 from vweijsters/implement-764
Implemented SA1600 codefix
2 parents ac10b44 + 93eaade commit 65fbf93

9 files changed

Lines changed: 693 additions & 78 deletions

File tree

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
// Copyright (c) Tunnel Vision Laboratories, LLC. All Rights Reserved.
2+
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
3+
4+
namespace StyleCop.Analyzers.DocumentationRules
5+
{
6+
using System;
7+
using System.Collections.Generic;
8+
using System.Collections.Immutable;
9+
using System.Composition;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
using Helpers;
13+
using Microsoft.CodeAnalysis;
14+
using Microsoft.CodeAnalysis.CodeActions;
15+
using Microsoft.CodeAnalysis.CodeFixes;
16+
using Microsoft.CodeAnalysis.CSharp;
17+
using Microsoft.CodeAnalysis.CSharp.Syntax;
18+
using Microsoft.CodeAnalysis.Formatting;
19+
using Microsoft.CodeAnalysis.Simplification;
20+
21+
/// <summary>
22+
/// Implements a code fix that will generate a documentation comment comprised of an empty
23+
/// <c>&lt;inheritdoc/&gt;</c> element.
24+
/// </summary>
25+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1600CodeFixProvider))]
26+
[Shared]
27+
internal class SA1600CodeFixProvider : CodeFixProvider
28+
{
29+
/// <inheritdoc/>
30+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
31+
ImmutableArray.Create(
32+
"CS1591",
33+
SA1600ElementsMustBeDocumented.DiagnosticId);
34+
35+
/// <inheritdoc/>
36+
public override FixAllProvider GetFixAllProvider()
37+
{
38+
return CustomFixAllProviders.BatchFixer;
39+
}
40+
41+
/// <inheritdoc/>
42+
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
43+
{
44+
SyntaxNode root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
45+
SemanticModel semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
46+
47+
foreach (var diagnostic in context.Diagnostics)
48+
{
49+
SyntaxToken identifierToken = root.FindToken(diagnostic.Location.SourceSpan.Start);
50+
if (identifierToken.IsMissingOrDefault())
51+
{
52+
continue;
53+
}
54+
55+
switch (identifierToken.Parent.Kind())
56+
{
57+
case SyntaxKind.ConstructorDeclaration:
58+
case SyntaxKind.DestructorDeclaration:
59+
context.RegisterCodeFix(
60+
CodeAction.Create(
61+
DocumentationResources.ConstructorDocumentationCodeFix,
62+
cancellationToken => GetConstructorOrDestructorDocumentationTransformedDocumentAsync(context.Document, diagnostic, root, (BaseMethodDeclarationSyntax)identifierToken.Parent, cancellationToken),
63+
nameof(SA1600CodeFixProvider)),
64+
diagnostic);
65+
break;
66+
67+
case SyntaxKind.MethodDeclaration:
68+
MethodDeclarationSyntax methodDeclaration = (MethodDeclarationSyntax)identifierToken.Parent;
69+
if (TaskHelper.IsTaskReturningMethod(semanticModel, methodDeclaration, context.CancellationToken) &&
70+
!IsCoveredByInheritDoc(semanticModel, methodDeclaration, context.CancellationToken))
71+
{
72+
context.RegisterCodeFix(
73+
CodeAction.Create(
74+
DocumentationResources.MethodDocumentationCodeFix,
75+
cancellationToken => GetMethodDocumentationTransformedDocumentAsync(context.Document, diagnostic, root, semanticModel, (MethodDeclarationSyntax)identifierToken.Parent, cancellationToken),
76+
nameof(SA1600CodeFixProvider)),
77+
diagnostic);
78+
}
79+
80+
break;
81+
}
82+
}
83+
}
84+
85+
private static bool IsCoveredByInheritDoc(SemanticModel semanticModel, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken)
86+
{
87+
if (methodDeclaration.ExplicitInterfaceSpecifier != null)
88+
{
89+
return true;
90+
}
91+
92+
if (methodDeclaration.Modifiers.Any(SyntaxKind.OverrideKeyword))
93+
{
94+
return true;
95+
}
96+
97+
ISymbol declaredSymbol = semanticModel.GetDeclaredSymbol(methodDeclaration, cancellationToken);
98+
return (declaredSymbol != null) && NamedTypeHelpers.IsImplementingAnInterfaceMember(declaredSymbol);
99+
}
100+
101+
private static Task<Document> GetConstructorOrDestructorDocumentationTransformedDocumentAsync(Document document, Diagnostic diagnostic, SyntaxNode root, BaseMethodDeclarationSyntax declaration, CancellationToken cancellationToken)
102+
{
103+
SyntaxTriviaList leadingTrivia = declaration.GetLeadingTrivia();
104+
int insertionIndex = GetInsertionIndex(ref leadingTrivia);
105+
106+
string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp);
107+
108+
var documentationNodes = new List<XmlNodeSyntax>();
109+
110+
var typeDeclaration = declaration.FirstAncestorOrSelf<BaseTypeDeclarationSyntax>();
111+
var standardText = SA1642SA1643CodeFixProvider.GenerateStandardText(document, declaration, typeDeclaration, cancellationToken);
112+
var standardTextSyntaxList = SA1642SA1643CodeFixProvider.BuildStandardTextSyntaxList(typeDeclaration, newLineText, standardText[0], standardText[1]);
113+
114+
// Remove the empty line generated by build standard text, as this is not needed with constructing a new summary element.
115+
standardTextSyntaxList = standardTextSyntaxList.RemoveAt(0);
116+
117+
documentationNodes.Add(XmlSyntaxFactory.SummaryElement(newLineText, standardTextSyntaxList));
118+
119+
if (declaration.ParameterList != null)
120+
{
121+
foreach (var parameter in declaration.ParameterList.Parameters)
122+
{
123+
documentationNodes.Add(XmlSyntaxFactory.NewLine(newLineText));
124+
documentationNodes.Add(XmlSyntaxFactory.ParamElement(parameter.Identifier.ValueText));
125+
}
126+
}
127+
128+
var documentationComment =
129+
XmlSyntaxFactory.DocumentationComment(
130+
newLineText,
131+
documentationNodes.ToArray());
132+
var trivia = SyntaxFactory.Trivia(documentationComment);
133+
134+
SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(insertionIndex, trivia);
135+
SyntaxNode newElement = declaration.WithLeadingTrivia(newLeadingTrivia);
136+
return Task.FromResult(document.WithSyntaxRoot(root.ReplaceNode(declaration, newElement)));
137+
}
138+
139+
private static Task<Document> GetMethodDocumentationTransformedDocumentAsync(Document document, Diagnostic diagnostic, SyntaxNode root, SemanticModel semanticModel, MethodDeclarationSyntax methodDeclaration, CancellationToken cancellationToken)
140+
{
141+
SyntaxTriviaList leadingTrivia = methodDeclaration.GetLeadingTrivia();
142+
int insertionIndex = GetInsertionIndex(ref leadingTrivia);
143+
144+
string newLineText = document.Project.Solution.Workspace.Options.GetOption(FormattingOptions.NewLine, LanguageNames.CSharp);
145+
146+
var documentationNodes = new List<XmlNodeSyntax>();
147+
148+
documentationNodes.Add(XmlSyntaxFactory.SummaryElement(newLineText));
149+
150+
if (methodDeclaration.TypeParameterList != null)
151+
{
152+
foreach (var typeParameter in methodDeclaration.TypeParameterList.Parameters)
153+
{
154+
documentationNodes.Add(XmlSyntaxFactory.NewLine(newLineText));
155+
documentationNodes.Add(XmlSyntaxFactory.TypeParamElement(typeParameter.Identifier.ValueText));
156+
}
157+
}
158+
159+
if (methodDeclaration.ParameterList != null)
160+
{
161+
foreach (var parameter in methodDeclaration.ParameterList.Parameters)
162+
{
163+
documentationNodes.Add(XmlSyntaxFactory.NewLine(newLineText));
164+
documentationNodes.Add(XmlSyntaxFactory.ParamElement(parameter.Identifier.ValueText));
165+
}
166+
}
167+
168+
TypeSyntax typeName;
169+
170+
var typeSymbol = semanticModel.GetSymbolInfo(methodDeclaration.ReturnType, cancellationToken).Symbol as INamedTypeSymbol;
171+
if (typeSymbol.IsGenericType)
172+
{
173+
typeName = SyntaxFactory.ParseTypeName("global::System.Threading.Tasks.Task<TResult>");
174+
}
175+
else
176+
{
177+
typeName = SyntaxFactory.ParseTypeName("global::System.Threading.Tasks.Task");
178+
}
179+
180+
XmlNodeSyntax[] returnContent =
181+
{
182+
XmlSyntaxFactory.Text(DocumentationResources.TaskReturnElementFirstPart),
183+
XmlSyntaxFactory.SeeElement(SyntaxFactory.TypeCref(typeName)).WithAdditionalAnnotations(Simplifier.Annotation),
184+
XmlSyntaxFactory.Text(DocumentationResources.TaskReturnElementSecondPart),
185+
};
186+
187+
documentationNodes.Add(XmlSyntaxFactory.NewLine(newLineText));
188+
documentationNodes.Add(XmlSyntaxFactory.ReturnsElement(returnContent));
189+
190+
var documentationComment =
191+
XmlSyntaxFactory.DocumentationComment(
192+
newLineText,
193+
documentationNodes.ToArray());
194+
var trivia = SyntaxFactory.Trivia(documentationComment);
195+
196+
SyntaxTriviaList newLeadingTrivia = leadingTrivia.Insert(insertionIndex, trivia);
197+
SyntaxNode newElement = methodDeclaration.WithLeadingTrivia(newLeadingTrivia);
198+
return Task.FromResult(document.WithSyntaxRoot(root.ReplaceNode(methodDeclaration, newElement)));
199+
}
200+
201+
private static int GetInsertionIndex(ref SyntaxTriviaList leadingTrivia)
202+
{
203+
int insertionIndex = leadingTrivia.Count;
204+
while (insertionIndex > 0 && !leadingTrivia[insertionIndex - 1].HasBuiltinEndLine())
205+
{
206+
insertionIndex--;
207+
}
208+
209+
return insertionIndex;
210+
}
211+
}
212+
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1615SA1616CodeFixProvider.cs

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
9191
bool isAsynchronousTestMethod;
9292
if (methodDeclarationSyntax != null)
9393
{
94-
isTask = IsTaskReturningMethod(semanticModel, methodDeclarationSyntax, cancellationToken);
94+
isTask = TaskHelper.IsTaskReturningMethod(semanticModel, methodDeclarationSyntax, cancellationToken);
9595
isAsynchronousTestMethod = isTask && IsAsynchronousTestMethod(semanticModel, methodDeclarationSyntax, cancellationToken);
9696
}
9797
else
9898
{
99-
isTask = IsTaskReturningMethod(semanticModel, delegateDeclarationSyntax, cancellationToken);
99+
isTask = TaskHelper.IsTaskReturningMethod(semanticModel, delegateDeclarationSyntax, cancellationToken);
100100
isAsynchronousTestMethod = false;
101101
}
102102

@@ -168,38 +168,6 @@ private static async Task<Document> GetTransformedDocumentAsync(Document documen
168168
return document.WithSyntaxRoot(newRoot);
169169
}
170170

171-
private static bool IsTaskReturningMethod(SemanticModel semanticModel, MethodDeclarationSyntax methodDeclarationSyntax, CancellationToken cancellationToken)
172-
{
173-
return IsTaskType(semanticModel, methodDeclarationSyntax.ReturnType, cancellationToken);
174-
}
175-
176-
private static bool IsTaskReturningMethod(SemanticModel semanticModel, DelegateDeclarationSyntax delegateDeclarationSyntax, CancellationToken cancellationToken)
177-
{
178-
return IsTaskType(semanticModel, delegateDeclarationSyntax.ReturnType, cancellationToken);
179-
}
180-
181-
private static bool IsTaskType(SemanticModel semanticModel, TypeSyntax typeSyntax, CancellationToken cancellationToken)
182-
{
183-
SymbolInfo symbolInfo = semanticModel.GetSymbolInfo(typeSyntax, cancellationToken);
184-
INamedTypeSymbol namedTypeSymbol = symbolInfo.Symbol as INamedTypeSymbol;
185-
if (namedTypeSymbol == null)
186-
{
187-
return false;
188-
}
189-
190-
if (!string.Equals(nameof(Task), namedTypeSymbol.Name, StringComparison.Ordinal))
191-
{
192-
return false;
193-
}
194-
195-
if (!string.Equals(typeof(Task).Namespace, namedTypeSymbol.ContainingNamespace?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat.WithGlobalNamespaceStyle(SymbolDisplayGlobalNamespaceStyle.Omitted)), StringComparison.Ordinal))
196-
{
197-
return false;
198-
}
199-
200-
return true;
201-
}
202-
203171
private static bool IsAsynchronousTestMethod(SemanticModel semanticModel, MethodDeclarationSyntax methodDeclarationSyntax, CancellationToken cancellationToken)
204172
{
205173
foreach (AttributeListSyntax attributeList in methodDeclarationSyntax.AttributeLists)

0 commit comments

Comments
 (0)