Skip to content

Commit 26e26b2

Browse files
committed
Implement SA1653 (incl. tests, code fix)
1 parent 3e23b10 commit 26e26b2

10 files changed

Lines changed: 668 additions & 0 deletions

File tree

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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.MaintainabilityRules
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.Editing;
20+
21+
/// <summary>
22+
/// Implements a code fix for <see cref="SA1653UseTrailingCommasInMultiLineInitializers"/>.
23+
/// </summary>
24+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(SA1653CodeFixProvider))]
25+
[Shared]
26+
internal class SA1653CodeFixProvider : CodeFixProvider
27+
{
28+
/// <inheritdoc/>
29+
public override ImmutableArray<string> FixableDiagnosticIds { get; } =
30+
ImmutableArray.Create(SA1653UseTrailingCommasInMultiLineInitializers.DiagnosticId);
31+
32+
/// <inheritdoc/>
33+
public override FixAllProvider GetFixAllProvider()
34+
{
35+
return CustomFixAllProviders.BatchFixer;
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+
MaintainabilityResources.SA1653CodeFix,
46+
cancellationToken => GetTransformedDocumentAsync(context.Document, diagnostic, cancellationToken),
47+
nameof(SA1653CodeFixProvider)),
48+
diagnostic);
49+
}
50+
51+
return SpecializedTasks.CompletedTask;
52+
}
53+
54+
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
55+
{
56+
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
57+
var parent = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).Parent;
58+
59+
SyntaxNode newParent = null;
60+
switch (parent.Kind())
61+
{
62+
case SyntaxKind.ObjectInitializerExpression:
63+
case SyntaxKind.ArrayInitializerExpression:
64+
case SyntaxKind.CollectionInitializerExpression:
65+
newParent = RewriteInitializer((InitializerExpressionSyntax)parent);
66+
break;
67+
68+
case SyntaxKind.AnonymousObjectCreationExpression:
69+
newParent = RewriteAnonymousObjectInitializer((AnonymousObjectCreationExpressionSyntax)parent);
70+
break;
71+
72+
default:
73+
throw new InvalidOperationException("Unknown initializer type: " + parent.Kind());
74+
}
75+
76+
var newSyntaxRoot = syntaxRoot.ReplaceNode(parent, newParent);
77+
78+
var newDocument = document.WithSyntaxRoot(newSyntaxRoot.WithoutFormatting());
79+
return newDocument;
80+
}
81+
82+
private static SyntaxNode RewriteInitializer(InitializerExpressionSyntax initializer)
83+
{
84+
var existingItems = new List<ExpressionSyntax>(initializer.Expressions);
85+
var last = existingItems.Last();
86+
existingItems.Remove(last);
87+
existingItems.Add(last.WithTrailingTrivia(last.GetTrailingTrivia().WithoutTrailingWhitespace()));
88+
89+
var existingSeparators = initializer.Expressions.GetSeparators();
90+
var newSeparators = new List<SyntaxToken>(existingSeparators);
91+
newSeparators.Add(SyntaxFactory.Token(SyntaxKind.CommaToken).WithTrailingTrivia(last.GetTrailingTrivia()));
92+
93+
var newInitializerExpressions = SyntaxFactory.SeparatedList(
94+
existingItems,
95+
newSeparators);
96+
97+
var newInitializer = SyntaxFactory.InitializerExpression(
98+
initializer.Kind(),
99+
initializer.OpenBraceToken,
100+
newInitializerExpressions,
101+
initializer.CloseBraceToken);
102+
103+
return newInitializer;
104+
}
105+
106+
private static SyntaxNode RewriteAnonymousObjectInitializer(AnonymousObjectCreationExpressionSyntax initializer)
107+
{
108+
var existingItems = new List<AnonymousObjectMemberDeclaratorSyntax>(initializer.Initializers);
109+
var last = existingItems.Last();
110+
existingItems.Remove(last);
111+
existingItems.Add(last.WithTrailingTrivia(last.GetTrailingTrivia().WithoutTrailingWhitespace()));
112+
113+
var existingSeparators = initializer.Initializers.GetSeparators();
114+
var newSeparators = new List<SyntaxToken>(existingSeparators);
115+
newSeparators.Add(SyntaxFactory.Token(SyntaxKind.CommaToken).WithTrailingTrivia(last.GetTrailingTrivia()));
116+
117+
var newInitializerExpressions = SyntaxFactory.SeparatedList(
118+
existingItems,
119+
newSeparators);
120+
121+
var newInitializer = SyntaxFactory.AnonymousObjectCreationExpression(
122+
initializer.NewKeyword,
123+
initializer.OpenBraceToken,
124+
newInitializerExpressions,
125+
initializer.CloseBraceToken);
126+
return newInitializer;
127+
}
128+
}
129+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
<Compile Include="MaintainabilityRules\SA1410SA1411CodeFixProvider.cs" />
9696
<Compile Include="MaintainabilityRules\SA1412CodeFixProvider.cs" />
9797
<Compile Include="MaintainabilityRules\SA1412FixAllProvider.cs" />
98+
<Compile Include="MaintainabilityRules\SA1653CodeFixProvider.cs" />
9899
<Compile Include="NamingRules\RenameToLowerCaseCodeFixProvider.cs" />
99100
<Compile Include="NamingRules\RenameToUpperCaseCodeFixProvider.cs" />
100101
<Compile Include="NamingRules\SA1302CodeFixProvider.cs" />

0 commit comments

Comments
 (0)