Skip to content

Commit

Permalink
Add analyzer 'Add/remove trailing comma' (#931)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt committed Aug 25, 2023
1 parent 971754b commit 914b232
Show file tree
Hide file tree
Showing 19 changed files with 829 additions and 41 deletions.
2 changes: 2 additions & 0 deletions .editorconfig
Expand Up @@ -45,6 +45,7 @@ roslynator_doc_comment_summary_style = multi_line
roslynator_enum_flag_value_style = shift_operator
roslynator_blank_line_after_file_scoped_namespace_declaration = true
roslynator_null_check_style = pattern_matching
roslynator_trailing_comma_style = omit_when_single_line

dotnet_diagnostic.RCS0001.severity = suggestion
dotnet_diagnostic.RCS0003.severity = suggestion
Expand Down Expand Up @@ -146,6 +147,7 @@ dotnet_diagnostic.RCS1252.severity = suggestion
dotnet_diagnostic.RCS1253.severity = suggestion
dotnet_diagnostic.RCS1254.severity = suggestion
dotnet_diagnostic.RCS1255.severity = none
dotnet_diagnostic.RCS1260.severity = suggestion

dotnet_diagnostic.IDE0007.severity = none
dotnet_diagnostic.IDE0007WithoutSuggestion.severity = none
Expand Down
3 changes: 3 additions & 0 deletions ChangeLog.md
Expand Up @@ -27,6 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Remove empty region directive ([RCS1091](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1091))
- Remove empty destructor ([RCS1106](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1106))
- [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)).
- 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)).
- Required option: `roslynator_trailing_comma_style = include|omit|omit_when_single_line`
- Not enabled by default

### Changed

Expand Down
@@ -0,0 +1,139 @@
// Copyright (c) Josef Pihrt and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Roslynator.CodeFixes;

namespace Roslynator.CSharp.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AddOrRemoveTrailingCommaCodeFixProvider))]
[Shared]
public sealed class AddOrRemoveTrailingCommaCodeFixProvider : BaseCodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(DiagnosticIdentifiers.AddOrRemoveTrailingComma); }
}

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false);

if (!TryFindFirstAncestorOrSelf(
root,
context.Span,
out SyntaxNode node,
predicate: f => f.IsKind(
SyntaxKind.ArrayInitializerExpression,
SyntaxKind.ObjectInitializerExpression,
SyntaxKind.CollectionInitializerExpression,
SyntaxKind.EnumDeclaration,
SyntaxKind.AnonymousObjectCreationExpression)))
{
return;
}

Diagnostic diagnostic = context.Diagnostics[0];
Document document = context.Document;

if (node is InitializerExpressionSyntax initializer)
{
SeparatedSyntaxList<ExpressionSyntax> expressions = initializer.Expressions;

int count = expressions.Count;

if (count == expressions.SeparatorCount)
{
CodeAction codeAction = CodeAction.Create(
"Remove comma",
ct => RemoveTrailingComma(document, expressions.GetSeparator(count - 1), ct),
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
}
else
{
CodeAction codeAction = CodeAction.Create(
"Add comma",
ct => AddTrailingComma(document, expressions.Last(), ct),
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
}
}
else if (node is AnonymousObjectCreationExpressionSyntax objectCreation)
{
SeparatedSyntaxList<AnonymousObjectMemberDeclaratorSyntax> initializers = objectCreation.Initializers;

int count = initializers.Count;

if (count == initializers.SeparatorCount)
{
CodeAction codeAction = CodeAction.Create(
"Remove comma",
ct => RemoveTrailingComma(document, initializers.GetSeparator(count - 1), ct),
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
}
else
{
CodeAction codeAction = CodeAction.Create(
"Add comma",
ct => AddTrailingComma(document, initializers.Last(), ct),
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
}
}
else if (node is EnumDeclarationSyntax enumDeclaration)
{
SeparatedSyntaxList<EnumMemberDeclarationSyntax> members = enumDeclaration.Members;

int count = members.Count;

if (count == members.SeparatorCount)
{
CodeAction codeAction = CodeAction.Create(
"Remove comma",
ct => RemoveTrailingComma(document, members.GetSeparator(count - 1), ct),
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
}
else
{
CodeAction codeAction = CodeAction.Create(
"Add comma",
ct => AddTrailingComma(document, members.Last(), ct),
GetEquivalenceKey(diagnostic));

context.RegisterCodeFix(codeAction, diagnostic);
}
}
}

private static Task<Document> RemoveTrailingComma(
Document document,
SyntaxToken comma,
CancellationToken cancellationToken)
{
return document.WithTextChangeAsync(new TextChange(comma.Span, ""), cancellationToken);
}

private static Task<Document> AddTrailingComma(
Document document,
SyntaxNode lastNode,
CancellationToken cancellationToken)
{
return document.WithTextChangeAsync(new TextChange(new TextSpan(lastNode.Span.End, 0), ","), cancellationToken);
}
}
40 changes: 40 additions & 0 deletions src/Analyzers.xml
Expand Up @@ -2336,10 +2336,13 @@ if (f)
<Analyzer>
<Id>RCS1035</Id>
<Identifier>RemoveRedundantCommaInInitializer</Identifier>
<Status>Obsolete</Status>
<ObsoleteMessage>Use RCS1260 instead</ObsoleteMessage>
<Title>Remove redundant comma in initializer.</Title>
<DefaultSeverity>Hidden</DefaultSeverity>
<IsEnabledByDefault>false</IsEnabledByDefault>
<SupportsFadeOut>true</SupportsFadeOut>
<Tags>HideFromConfiguration</Tags>
<Samples>
<Sample>
<Before><![CDATA[public void Foo()
Expand Down Expand Up @@ -7421,6 +7424,43 @@ void M()
* empty region directive
</Summary>
</Analyzer>
<Analyzer Identifier="AddOrRemoveTrailingComma">
<Id>RCS1260</Id>
<Title>Add/remove trailing comma.</Title>
<MessageFormat>{0} trailing comma.</MessageFormat>
<Category>General</Category>
<DefaultSeverity>Info</DefaultSeverity>
<IsEnabledByDefault>false</IsEnabledByDefault>
<ConfigOptions>
<Option Key="trailing_comma_style" IsRequired="true" />
</ConfigOptions>
<Samples>
<Sample>
<ConfigOptions>
<Option Key="trailing_comma_style" Value="include" />
</ConfigOptions>
<Before><![CDATA[public enum Foo
{
A,
B,
C
}]]></Before>
<After><![CDATA[public enum Foo
{
A,
B,
C,
}]]></After>
</Sample>
<Sample>
<ConfigOptions>
<Option Key="trailing_comma_style" Value="omit_when_single_line" />
</ConfigOptions>
<Before><![CDATA[public enum Foo { A, B, C, }]]></Before>
<After><![CDATA[public enum Foo { A, B, C }]]></After>
</Sample>
</Samples>
</Analyzer>
<Analyzer>
<Id>RCS9001</Id>
<Identifier>UsePatternMatching</Identifier>
Expand Down

0 comments on commit 914b232

Please sign in to comment.