Skip to content

Commit f7482c1

Browse files
committed
Implement a code fix + fix all provider for SA1130
1 parent 7a2006a commit f7482c1

6 files changed

Lines changed: 368 additions & 3 deletions

File tree

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

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@
128128
<Compile Include="ReadabilityRules\SA1131CodeFixProvider.cs" />
129129
<Compile Include="ReadabilityRules\SA1132CodeFixProvider.cs" />
130130
<Compile Include="ReadabilityRules\SA1133CodeFixProvider.cs" />
131+
<Compile Include="ReadabilityRules\SA1130CodeFixProvider.cs" />
131132
<Compile Include="ReadabilityRules\SA1134CodeFixProvider.cs" />
132133
<Compile Include="ReadabilityRules\SX1101CodeFixProvider.cs" />
133134
<Compile Include="Settings\SettingsFileCodeFixProvider.cs" />

0 commit comments

Comments
 (0)