Skip to content

Commit d5bd61e

Browse files
committed
Merge pull request #2063 from pdelvo/SA1130CF
Implement a code fix + fix all provider for SA1130
2 parents d6a733d + 8e4e5ab commit d5bd61e

5 files changed

Lines changed: 368 additions & 1 deletion

File tree

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
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.ReadabilityRules
5+
{
6+
using System.Collections.Generic;
7+
using System.Collections.Immutable;
8+
using System.Composition;
9+
using System.Linq;
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+
20+
/// <summary>
21+
/// Implements a code fix for <see cref="SA1130UseLambdaSyntax"/>.
22+
/// </summary>
23+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1130CodeFixProvider))]
24+
[Shared]
25+
internal class SA1130CodeFixProvider : CodeFixProvider
26+
{
27+
/// <inheritdoc/>
28+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
29+
ImmutableArray.Create(SA1130UseLambdaSyntax.DiagnosticId);
30+
31+
/// <inheritdoc/>
32+
public override FixAllProvider GetFixAllProvider()
33+
{
34+
return FixAll.Instance;
35+
}
36+
37+
/// <inheritdoc/>
38+
public override Task RegisterCodeFixesAsync(CodeFixContext context)
39+
{
40+
foreach (var diagnostic in context.Diagnostics)
41+
{
42+
context.RegisterCodeFix(
43+
CodeAction.Create(
44+
ReadabilityResources.SA1130CodeFix,
45+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
46+
nameof(SA1134CodeFixProvider)),
47+
diagnostic);
48+
}
49+
50+
return SpecializedTasks.CompletedTask;
51+
}
52+
53+
private static SyntaxNode ReplaceWithLambda(AnonymousMethodExpressionSyntax anonymousMethod)
54+
{
55+
var parameterList = anonymousMethod.ParameterList;
56+
SyntaxNode lambdaExpression;
57+
58+
if (parameterList == null)
59+
{
60+
parameterList = SyntaxFactory.ParameterList()
61+
.WithLeadingTrivia(anonymousMethod.DelegateKeyword.LeadingTrivia)
62+
.WithTrailingTrivia(anonymousMethod.DelegateKeyword.TrailingTrivia);
63+
}
64+
else
65+
{
66+
parameterList = parameterList.WithLeadingTrivia(anonymousMethod.DelegateKeyword.TrailingTrivia);
67+
}
68+
69+
foreach (var parameter in parameterList.Parameters)
70+
{
71+
if (!IsValid(parameter))
72+
{
73+
return anonymousMethod;
74+
}
75+
}
76+
77+
var arrowToken = SyntaxFactory.Token(SyntaxKind.EqualsGreaterThanToken)
78+
.WithTrailingTrivia(SyntaxFactory.ElasticSpace);
79+
80+
if (parameterList.Parameters.Count == 1)
81+
{
82+
var parameterSyntax = RemoveType(parameterList.Parameters[0]);
83+
84+
var trailingTrivia = parameterSyntax.GetTrailingTrivia()
85+
.Concat(parameterList.CloseParenToken.LeadingTrivia)
86+
.Concat(parameterList.CloseParenToken.TrailingTrivia.WithoutTrailingWhitespace())
87+
.Concat(new[] { SyntaxFactory.ElasticSpace });
88+
var leadingTrivia = parameterList.OpenParenToken.LeadingTrivia
89+
.Concat(parameterList.OpenParenToken.TrailingTrivia)
90+
.Concat(parameterSyntax.GetLeadingTrivia());
91+
92+
parameterSyntax = parameterSyntax
93+
.WithLeadingTrivia(leadingTrivia)
94+
.WithTrailingTrivia(trailingTrivia);
95+
96+
lambdaExpression = SyntaxFactory.SimpleLambdaExpression(anonymousMethod.AsyncKeyword, parameterSyntax, arrowToken, anonymousMethod.Body);
97+
}
98+
else
99+
{
100+
var parameterListSyntax = RemoveType(parameterList)
101+
.WithTrailingTrivia(parameterList.GetTrailingTrivia().WithoutTrailingWhitespace().Add(SyntaxFactory.ElasticSpace));
102+
lambdaExpression = SyntaxFactory.ParenthesizedLambdaExpression(anonymousMethod.AsyncKeyword, parameterListSyntax, arrowToken, anonymousMethod.Body);
103+
}
104+
105+
return lambdaExpression
106+
.WithAdditionalAnnotations(Formatter.Annotation);
107+
}
108+
109+
private static ParameterListSyntax RemoveType(ParameterListSyntax parameterList)
110+
{
111+
return parameterList.WithParameters(SyntaxFactory.SeparatedList(parameterList.Parameters.Select(x => RemoveType(x)), parameterList.Parameters.GetSeparators()));
112+
}
113+
114+
private static ParameterSyntax RemoveType(ParameterSyntax parameterSyntax)
115+
{
116+
var syntax = parameterSyntax.WithType(null)
117+
.WithLeadingTrivia(parameterSyntax.Type.GetLeadingTrivia().Concat(parameterSyntax.Type.GetTrailingTrivia()));
118+
return syntax.WithTrailingTrivia(syntax.GetTrailingTrivia().WithoutTrailingWhitespace())
119+
.WithLeadingTrivia(syntax.GetLeadingTrivia().WithoutWhitespace());
120+
}
121+
122+
private static bool IsValid(ParameterSyntax parameterSyntax)
123+
{
124+
// If one of the following conditions is false the code won't compile, but we want to check for it anyway and not make it worse by applying this code fix.
125+
return parameterSyntax.AttributeLists.Count == 0
126+
&& parameterSyntax.Default == null
127+
&& parameterSyntax.Modifiers.Count == 0
128+
&& !parameterSyntax.Identifier.IsKind(SyntaxKind.ArgListKeyword);
129+
}
130+
131+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
132+
{
133+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
134+
135+
var anonymousMethod = (AnonymousMethodExpressionSyntax)syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
136+
137+
var newSyntaxRoot = syntaxRoot.ReplaceNode(anonymousMethod, ReplaceWithLambda(anonymousMethod));
138+
var newDocument = document.WithSyntaxRoot(newSyntaxRoot.WithoutFormatting());
139+
140+
return newDocument;
141+
}
142+
143+
private class FixAll : DocumentBasedFixAllProvider
144+
{
145+
public static FixAllProvider Instance { get; } =
146+
new FixAll();
147+
148+
protected override string CodeActionTitle => ReadabilityResources.SA1130CodeFix;
149+
150+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
151+
{
152+
var syntaxRoot = await document.GetSyntaxRootAsync(fixAllContext.CancellationToken).ConfigureAwait(false);
153+
154+
var nodes = new List<AnonymousMethodExpressionSyntax>();
155+
156+
foreach (var diagnostic in diagnostics)
157+
{
158+
var node = (AnonymousMethodExpressionSyntax)syntaxRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
159+
160+
nodes.Add(node);
161+
}
162+
163+
return syntaxRoot.ReplaceNodes(nodes, (originalNode, rewrittenNode) => ReplaceWithLambda(rewrittenNode));
164+
}
165+
}
166+
}
167+
}

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/StyleCop.Analyzers.CodeFixes.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
<Compile Include="ReadabilityRules\SA1127CodeFixProvider.cs" />
128128
<Compile Include="ReadabilityRules\SA1128CodeFixProvider.cs" />
129129
<Compile Include="ReadabilityRules\SA1129CodeFixProvider.cs" />
130+
<Compile Include="ReadabilityRules\SA1130CodeFixProvider.cs" />
130131
<Compile Include="ReadabilityRules\SA1131CodeFixProvider.cs" />
131132
<Compile Include="ReadabilityRules\SA1132CodeFixProvider.cs" />
132133
<Compile Include="ReadabilityRules\SA1133CodeFixProvider.cs" />

0 commit comments

Comments
 (0)