Skip to content

Commit 914b232

Browse files
authored
Add analyzer 'Add/remove trailing comma' (#931)
1 parent 971754b commit 914b232

File tree

19 files changed

+829
-41
lines changed

19 files changed

+829
-41
lines changed

.editorconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ roslynator_doc_comment_summary_style = multi_line
4545
roslynator_enum_flag_value_style = shift_operator
4646
roslynator_blank_line_after_file_scoped_namespace_declaration = true
4747
roslynator_null_check_style = pattern_matching
48+
roslynator_trailing_comma_style = omit_when_single_line
4849

4950
dotnet_diagnostic.RCS0001.severity = suggestion
5051
dotnet_diagnostic.RCS0003.severity = suggestion
@@ -146,6 +147,7 @@ dotnet_diagnostic.RCS1252.severity = suggestion
146147
dotnet_diagnostic.RCS1253.severity = suggestion
147148
dotnet_diagnostic.RCS1254.severity = suggestion
148149
dotnet_diagnostic.RCS1255.severity = none
150+
dotnet_diagnostic.RCS1260.severity = suggestion
149151

150152
dotnet_diagnostic.IDE0007.severity = none
151153
dotnet_diagnostic.IDE0007WithoutSuggestion.severity = none

ChangeLog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2727
- Remove empty region directive ([RCS1091](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1091))
2828
- Remove empty destructor ([RCS1106](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1106))
2929
- [CLI] Add glob pattern matching (`--include` or/and `--exclude`) ([#1178](https://github.com/josefpihrt/roslynator/pull/1178), [#1183](https://github.com/josefpihrt/roslynator/pull/1183)).
30+
- Add analyzer "Include/omit trailing comma" ([RCS1256](https://github.com/JosefPihrt/Roslynator/blob/main/docs/analyzers/RCS1256.md)) ([#931](https://github.com/JosefPihrt/Roslynator/pull/931)).
31+
- Required option: `roslynator_trailing_comma_style = include|omit|omit_when_single_line`
32+
- Not enabled by default
3033

3134
### Changed
3235

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
2+
3+
using System.Collections.Immutable;
4+
using System.Composition;
5+
using System.Threading;
6+
using System.Threading.Tasks;
7+
using Microsoft.CodeAnalysis;
8+
using Microsoft.CodeAnalysis.CodeActions;
9+
using Microsoft.CodeAnalysis.CodeFixes;
10+
using Microsoft.CodeAnalysis.CSharp;
11+
using Microsoft.CodeAnalysis.CSharp.Syntax;
12+
using Microsoft.CodeAnalysis.Text;
13+
using Roslynator.CodeFixes;
14+
15+
namespace Roslynator.CSharp.CodeFixes;
16+
17+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AddOrRemoveTrailingCommaCodeFixProvider))]
18+
[Shared]
19+
public sealed class AddOrRemoveTrailingCommaCodeFixProvider : BaseCodeFixProvider
20+
{
21+
public override ImmutableArray<string> FixableDiagnosticIds
22+
{
23+
get { return ImmutableArray.Create(DiagnosticIdentifiers.AddOrRemoveTrailingComma); }
24+
}
25+
26+
public override async Task RegisterCodeFixesAsync(CodeFixContext context)
27+
{
28+
SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false);
29+
30+
if (!TryFindFirstAncestorOrSelf(
31+
root,
32+
context.Span,
33+
out SyntaxNode node,
34+
predicate: f => f.IsKind(
35+
SyntaxKind.ArrayInitializerExpression,
36+
SyntaxKind.ObjectInitializerExpression,
37+
SyntaxKind.CollectionInitializerExpression,
38+
SyntaxKind.EnumDeclaration,
39+
SyntaxKind.AnonymousObjectCreationExpression)))
40+
{
41+
return;
42+
}
43+
44+
Diagnostic diagnostic = context.Diagnostics[0];
45+
Document document = context.Document;
46+
47+
if (node is InitializerExpressionSyntax initializer)
48+
{
49+
SeparatedSyntaxList<ExpressionSyntax> expressions = initializer.Expressions;
50+
51+
int count = expressions.Count;
52+
53+
if (count == expressions.SeparatorCount)
54+
{
55+
CodeAction codeAction = CodeAction.Create(
56+
"Remove comma",
57+
ct => RemoveTrailingComma(document, expressions.GetSeparator(count - 1), ct),
58+
GetEquivalenceKey(diagnostic));
59+
60+
context.RegisterCodeFix(codeAction, diagnostic);
61+
}
62+
else
63+
{
64+
CodeAction codeAction = CodeAction.Create(
65+
"Add comma",
66+
ct => AddTrailingComma(document, expressions.Last(), ct),
67+
GetEquivalenceKey(diagnostic));
68+
69+
context.RegisterCodeFix(codeAction, diagnostic);
70+
}
71+
}
72+
else if (node is AnonymousObjectCreationExpressionSyntax objectCreation)
73+
{
74+
SeparatedSyntaxList<AnonymousObjectMemberDeclaratorSyntax> initializers = objectCreation.Initializers;
75+
76+
int count = initializers.Count;
77+
78+
if (count == initializers.SeparatorCount)
79+
{
80+
CodeAction codeAction = CodeAction.Create(
81+
"Remove comma",
82+
ct => RemoveTrailingComma(document, initializers.GetSeparator(count - 1), ct),
83+
GetEquivalenceKey(diagnostic));
84+
85+
context.RegisterCodeFix(codeAction, diagnostic);
86+
}
87+
else
88+
{
89+
CodeAction codeAction = CodeAction.Create(
90+
"Add comma",
91+
ct => AddTrailingComma(document, initializers.Last(), ct),
92+
GetEquivalenceKey(diagnostic));
93+
94+
context.RegisterCodeFix(codeAction, diagnostic);
95+
}
96+
}
97+
else if (node is EnumDeclarationSyntax enumDeclaration)
98+
{
99+
SeparatedSyntaxList<EnumMemberDeclarationSyntax> members = enumDeclaration.Members;
100+
101+
int count = members.Count;
102+
103+
if (count == members.SeparatorCount)
104+
{
105+
CodeAction codeAction = CodeAction.Create(
106+
"Remove comma",
107+
ct => RemoveTrailingComma(document, members.GetSeparator(count - 1), ct),
108+
GetEquivalenceKey(diagnostic));
109+
110+
context.RegisterCodeFix(codeAction, diagnostic);
111+
}
112+
else
113+
{
114+
CodeAction codeAction = CodeAction.Create(
115+
"Add comma",
116+
ct => AddTrailingComma(document, members.Last(), ct),
117+
GetEquivalenceKey(diagnostic));
118+
119+
context.RegisterCodeFix(codeAction, diagnostic);
120+
}
121+
}
122+
}
123+
124+
private static Task<Document> RemoveTrailingComma(
125+
Document document,
126+
SyntaxToken comma,
127+
CancellationToken cancellationToken)
128+
{
129+
return document.WithTextChangeAsync(new TextChange(comma.Span, ""), cancellationToken);
130+
}
131+
132+
private static Task<Document> AddTrailingComma(
133+
Document document,
134+
SyntaxNode lastNode,
135+
CancellationToken cancellationToken)
136+
{
137+
return document.WithTextChangeAsync(new TextChange(new TextSpan(lastNode.Span.End, 0), ","), cancellationToken);
138+
}
139+
}

src/Analyzers.xml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2336,10 +2336,13 @@ if (f)
23362336
<Analyzer>
23372337
<Id>RCS1035</Id>
23382338
<Identifier>RemoveRedundantCommaInInitializer</Identifier>
2339+
<Status>Obsolete</Status>
2340+
<ObsoleteMessage>Use RCS1260 instead</ObsoleteMessage>
23392341
<Title>Remove redundant comma in initializer.</Title>
23402342
<DefaultSeverity>Hidden</DefaultSeverity>
23412343
<IsEnabledByDefault>false</IsEnabledByDefault>
23422344
<SupportsFadeOut>true</SupportsFadeOut>
2345+
<Tags>HideFromConfiguration</Tags>
23432346
<Samples>
23442347
<Sample>
23452348
<Before><![CDATA[public void Foo()
@@ -7421,6 +7424,43 @@ void M()
74217424
* empty region directive
74227425
</Summary>
74237426
</Analyzer>
7427+
<Analyzer Identifier="AddOrRemoveTrailingComma">
7428+
<Id>RCS1260</Id>
7429+
<Title>Add/remove trailing comma.</Title>
7430+
<MessageFormat>{0} trailing comma.</MessageFormat>
7431+
<Category>General</Category>
7432+
<DefaultSeverity>Info</DefaultSeverity>
7433+
<IsEnabledByDefault>false</IsEnabledByDefault>
7434+
<ConfigOptions>
7435+
<Option Key="trailing_comma_style" IsRequired="true" />
7436+
</ConfigOptions>
7437+
<Samples>
7438+
<Sample>
7439+
<ConfigOptions>
7440+
<Option Key="trailing_comma_style" Value="include" />
7441+
</ConfigOptions>
7442+
<Before><![CDATA[public enum Foo
7443+
{
7444+
A,
7445+
B,
7446+
C
7447+
}]]></Before>
7448+
<After><![CDATA[public enum Foo
7449+
{
7450+
A,
7451+
B,
7452+
C,
7453+
}]]></After>
7454+
</Sample>
7455+
<Sample>
7456+
<ConfigOptions>
7457+
<Option Key="trailing_comma_style" Value="omit_when_single_line" />
7458+
</ConfigOptions>
7459+
<Before><![CDATA[public enum Foo { A, B, C, }]]></Before>
7460+
<After><![CDATA[public enum Foo { A, B, C }]]></After>
7461+
</Sample>
7462+
</Samples>
7463+
</Analyzer>
74247464
<Analyzer>
74257465
<Id>RCS9001</Id>
74267466
<Identifier>UsePatternMatching</Identifier>

0 commit comments

Comments
 (0)