Skip to content

Commit

Permalink
Merge pull request #2245 from dlemstra/SA1413
Browse files Browse the repository at this point in the history
SA1413 now also checks enum members.
  • Loading branch information
sharwell committed Dec 24, 2016
2 parents e179221 + 763906e commit d6bd197
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,15 @@

namespace StyleCop.Analyzers.MaintainabilityRules
{
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

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

SyntaxNode newParent = null;
switch (parent.Kind())
{
case SyntaxKind.ObjectInitializerExpression:
case SyntaxKind.ArrayInitializerExpression:
case SyntaxKind.CollectionInitializerExpression:
newParent = RewriteInitializer((InitializerExpressionSyntax)parent);
break;

case SyntaxKind.AnonymousObjectCreationExpression:
newParent = RewriteAnonymousObjectInitializer((AnonymousObjectCreationExpressionSyntax)parent);
break;

default:
throw new InvalidOperationException("Unknown initializer type: " + parent.Kind());
}

var newSyntaxRoot = syntaxRoot.ReplaceNode(parent, newParent);

var newDocument = document.WithSyntaxRoot(newSyntaxRoot.WithoutFormatting());
return newDocument;
}

private static SyntaxNode RewriteInitializer(InitializerExpressionSyntax initializer)
{
var existingItems = new List<ExpressionSyntax>(initializer.Expressions);
var last = existingItems.Last();
existingItems.Remove(last);
existingItems.Add(last.WithoutTrailingTrivia());

var existingSeparators = initializer.Expressions.GetSeparators();
var newSeparators = new List<SyntaxToken>(existingSeparators);
newSeparators.Add(SyntaxFactory.Token(SyntaxKind.CommaToken).WithTrailingTrivia(last.GetTrailingTrivia()));

var newInitializerExpressions = SyntaxFactory.SeparatedList(
existingItems,
newSeparators);

var fixedInitializer = initializer.WithExpressions(newInitializerExpressions);
return fixedInitializer;
}

private static SyntaxNode RewriteAnonymousObjectInitializer(AnonymousObjectCreationExpressionSyntax initializer)
{
var existingItems = new List<AnonymousObjectMemberDeclaratorSyntax>(initializer.Initializers);
var last = existingItems.Last();
existingItems.Remove(last);
existingItems.Add(last.WithoutTrailingTrivia());

var existingSeparators = initializer.Initializers.GetSeparators();
var newSeparators = new List<SyntaxToken>(existingSeparators);
newSeparators.Add(SyntaxFactory.Token(SyntaxKind.CommaToken).WithTrailingTrivia(last.GetTrailingTrivia()));

var newInitializerExpressions = SyntaxFactory.SeparatedList(
existingItems,
newSeparators);

var fixedInitializer = initializer.WithInitializers(newInitializerExpressions);
return fixedInitializer;
TextChange textChange = new TextChange(diagnostic.Location.SourceSpan, syntaxNode.ToString() + ",");
return document.WithText(text.WithChanges(textChange));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,98 @@ void Foo()
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
}

/// <summary>
/// Verifies that the last value of an empty enum does not produce a diagnostic.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task VerifyEmptyEnumAsync()
{
var testCode = @"enum EmptyEnum
{
}
";

await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Verifies that the last value of an enum with a trailing comma does not produce a diagnostic.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task VerifyEnumWithTrailingCommaAsync()
{
var testCode = @"enum TestEnum
{
One,
Two,
}
";

await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
}

/// <summary>
/// Verifies that the last value of an enum without a trailing comma produces a diagnostic.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task VerifyEnumWithoutTrailingCommaAsync()
{
var testCode = @"enum TestEnum
{
One,
Two
}
";

var fixedTestCode = @"enum TestEnum
{
One,
Two,
}
";

DiagnosticResult[] expected =
{
this.CSharpDiagnostic().WithLocation(4, 5),
};

await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
}

/// <summary>
/// Verifies that the last value of an enum without a trailing comma produces a diagnostic.
/// </summary>
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
[Fact]
public async Task VerifyEnumWithValueWithoutTrailingCommaAsync()
{
var testCode = @"enum TestEnum
{
One = 2 /* test comment */
}
";

var fixedTestCode = @"enum TestEnum
{
One = 2, /* test comment */
}
";

DiagnosticResult[] expected =
{
this.CSharpDiagnostic().WithLocation(3, 5),
};

await this.VerifyCSharpDiagnosticAsync(testCode, expected, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpDiagnosticAsync(fixedTestCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false);
await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false);
}

/// <inheritdoc/>
protected override CodeFixProvider GetCSharpCodeFixProvider()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace StyleCop.Analyzers.MaintainabilityRules
{
using System;
using System.Collections.Immutable;
using System.Linq;
using Helpers;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
Expand Down Expand Up @@ -73,6 +74,22 @@ public override void Initialize(AnalysisContext context)

context.RegisterSyntaxNodeAction(HandleObjectInitializerAction, ObjectInitializerKinds);
context.RegisterSyntaxNodeAction(HandleAnonymousObjectInitializerAction, SyntaxKind.AnonymousObjectCreationExpression);
context.RegisterSyntaxNodeAction(HandleEnumMemberDeclarationAction, SyntaxKind.EnumDeclaration);
}

private static void HandleEnumMemberDeclarationAction(SyntaxNodeAnalysisContext context)
{
var initializer = (EnumDeclarationSyntax)context.Node;
var lastMember = initializer.Members.LastOrDefault();
if (lastMember == null)
{
return;
}

if (initializer.Members.Count() != initializer.Members.SeparatorCount)
{
context.ReportDiagnostic(Diagnostic.Create(Descriptor, lastMember.GetLocation()));
}
}

private static void HandleObjectInitializer(SyntaxNodeAnalysisContext context)
Expand Down

0 comments on commit d6bd197

Please sign in to comment.