Skip to content

Commit d6bd197

Browse files
committed
Merge pull request #2245 from dlemstra/SA1413
SA1413 now also checks enum members.
2 parents e179221 + 763906e commit d6bd197

3 files changed

Lines changed: 114 additions & 65 deletions

File tree

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs

Lines changed: 5 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,15 @@
33

44
namespace StyleCop.Analyzers.MaintainabilityRules
55
{
6-
using System;
7-
using System.Collections.Generic;
86
using System.Collections.Immutable;
97
using System.Composition;
10-
using System.Linq;
118
using System.Threading;
129
using System.Threading.Tasks;
1310
using Helpers;
1411
using Microsoft.CodeAnalysis;
1512
using Microsoft.CodeAnalysis.CodeActions;
1613
using Microsoft.CodeAnalysis.CodeFixes;
17-
using Microsoft.CodeAnalysis.CSharp;
18-
using Microsoft.CodeAnalysis.CSharp.Syntax;
14+
using Microsoft.CodeAnalysis.Text;
1915

2016
/// <summary>
2117
/// Implements a code fix for <see cref="SA1413UseTrailingCommasInMultiLineInitializers"/>.
@@ -53,67 +49,11 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context)
5349
private static async Task<Document> GetTransformedDocumentAsync(Document document, Diagnostic diagnostic, CancellationToken cancellationToken)
5450
{
5551
var syntaxRoot = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
56-
var parent = syntaxRoot.FindNode(diagnostic.Location.SourceSpan).Parent;
52+
var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false);
53+
var syntaxNode = syntaxRoot.FindNode(diagnostic.Location.SourceSpan);
5754

58-
SyntaxNode newParent = null;
59-
switch (parent.Kind())
60-
{
61-
case SyntaxKind.ObjectInitializerExpression:
62-
case SyntaxKind.ArrayInitializerExpression:
63-
case SyntaxKind.CollectionInitializerExpression:
64-
newParent = RewriteInitializer((InitializerExpressionSyntax)parent);
65-
break;
66-
67-
case SyntaxKind.AnonymousObjectCreationExpression:
68-
newParent = RewriteAnonymousObjectInitializer((AnonymousObjectCreationExpressionSyntax)parent);
69-
break;
70-
71-
default:
72-
throw new InvalidOperationException("Unknown initializer type: " + parent.Kind());
73-
}
74-
75-
var newSyntaxRoot = syntaxRoot.ReplaceNode(parent, newParent);
76-
77-
var newDocument = document.WithSyntaxRoot(newSyntaxRoot.WithoutFormatting());
78-
return newDocument;
79-
}
80-
81-
private static SyntaxNode RewriteInitializer(InitializerExpressionSyntax initializer)
82-
{
83-
var existingItems = new List<ExpressionSyntax>(initializer.Expressions);
84-
var last = existingItems.Last();
85-
existingItems.Remove(last);
86-
existingItems.Add(last.WithoutTrailingTrivia());
87-
88-
var existingSeparators = initializer.Expressions.GetSeparators();
89-
var newSeparators = new List<SyntaxToken>(existingSeparators);
90-
newSeparators.Add(SyntaxFactory.Token(SyntaxKind.CommaToken).WithTrailingTrivia(last.GetTrailingTrivia()));
91-
92-
var newInitializerExpressions = SyntaxFactory.SeparatedList(
93-
existingItems,
94-
newSeparators);
95-
96-
var fixedInitializer = initializer.WithExpressions(newInitializerExpressions);
97-
return fixedInitializer;
98-
}
99-
100-
private static SyntaxNode RewriteAnonymousObjectInitializer(AnonymousObjectCreationExpressionSyntax initializer)
101-
{
102-
var existingItems = new List<AnonymousObjectMemberDeclaratorSyntax>(initializer.Initializers);
103-
var last = existingItems.Last();
104-
existingItems.Remove(last);
105-
existingItems.Add(last.WithoutTrailingTrivia());
106-
107-
var existingSeparators = initializer.Initializers.GetSeparators();
108-
var newSeparators = new List<SyntaxToken>(existingSeparators);
109-
newSeparators.Add(SyntaxFactory.Token(SyntaxKind.CommaToken).WithTrailingTrivia(last.GetTrailingTrivia()));
110-
111-
var newInitializerExpressions = SyntaxFactory.SeparatedList(
112-
existingItems,
113-
newSeparators);
114-
115-
var fixedInitializer = initializer.WithInitializers(newInitializerExpressions);
116-
return fixedInitializer;
55+
TextChange textChange = new TextChange(diagnostic.Location.SourceSpan, syntaxNode.ToString() + ",");
56+
return document.WithText(text.WithChanges(textChange));
11757
}
11858
}
11959
}

StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1413UnitTests.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,98 @@ void Foo()
425425
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
426426
}
427427

428+
/// <summary>
429+
/// Verifies that the last value of an empty enum does not produce a diagnostic.
430+
/// </summary>
431+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
432+
[Fact]
433+
public async Task VerifyEmptyEnumAsync()
434+
{
435+
var testCode = @"enum EmptyEnum
436+
{
437+
}
438+
";
439+
440+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
441+
}
442+
443+
/// <summary>
444+
/// Verifies that the last value of an enum with a trailing comma does not produce a diagnostic.
445+
/// </summary>
446+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
447+
[Fact]
448+
public async Task VerifyEnumWithTrailingCommaAsync()
449+
{
450+
var testCode = @"enum TestEnum
451+
{
452+
One,
453+
Two,
454+
}
455+
";
456+
457+
await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
458+
}
459+
460+
/// <summary>
461+
/// Verifies that the last value of an enum without a trailing comma produces a diagnostic.
462+
/// </summary>
463+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
464+
[Fact]
465+
public async Task VerifyEnumWithoutTrailingCommaAsync()
466+
{
467+
var testCode = @"enum TestEnum
468+
{
469+
One,
470+
Two
471+
}
472+
";
473+
474+
var fixedTestCode = @"enum TestEnum
475+
{
476+
One,
477+
Two,
478+
}
479+
";
480+
481+
DiagnosticResult[] expected =
482+
{
483+
this.CSharpDiagnostic().WithLocation(4, 5),
484+
};
485+
486+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
487+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
488+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
489+
}
490+
491+
/// <summary>
492+
/// Verifies that the last value of an enum without a trailing comma produces a diagnostic.
493+
/// </summary>
494+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
495+
[Fact]
496+
public async Task VerifyEnumWithValueWithoutTrailingCommaAsync()
497+
{
498+
var testCode = @"enum TestEnum
499+
{
500+
One = 2 /* test comment */
501+
}
502+
";
503+
504+
var fixedTestCode = @"enum TestEnum
505+
{
506+
One = 2, /* test comment */
507+
}
508+
";
509+
510+
DiagnosticResult[] expected =
511+
{
512+
this.CSharpDiagnostic().WithLocation(3, 5),
513+
};
514+
515+
await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
516+
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
517+
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
518+
}
519+
428520
/// <inheritdoc/>
429521
protected override CodeFixProvider GetCSharpCodeFixProvider()
430522
{

StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1413UseTrailingCommasInMultiLineInitializers.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.MaintainabilityRules
55
{
66
using System;
77
using System.Collections.Immutable;
8+
using System.Linq;
89
using Helpers;
910
using Microsoft.CodeAnalysis;
1011
using Microsoft.CodeAnalysis.CSharp;
@@ -73,6 +74,22 @@ public override void Initialize(AnalysisContext context)
7374

7475
context.RegisterSyntaxNodeAction(HandleObjectInitializerAction, ObjectInitializerKinds);
7576
context.RegisterSyntaxNodeAction(HandleAnonymousObjectInitializerAction, SyntaxKind.AnonymousObjectCreationExpression);
77+
context.RegisterSyntaxNodeAction(HandleEnumMemberDeclarationAction, SyntaxKind.EnumDeclaration);
78+
}
79+
80+
private static void HandleEnumMemberDeclarationAction(SyntaxNodeAnalysisContext context)
81+
{
82+
var initializer = (EnumDeclarationSyntax)context.Node;
83+
var lastMember = initializer.Members.LastOrDefault();
84+
if (lastMember == null)
85+
{
86+
return;
87+
}
88+
89+
if (initializer.Members.Count() != initializer.Members.SeparatorCount)
90+
{
91+
context.ReportDiagnostic(Diagnostic.Create(Descriptor, lastMember.GetLocation()));
92+
}
7693
}
7794

7895
private static void HandleObjectInitializer(SyntaxNodeAnalysisContext context)

0 commit comments

Comments
 (0)