diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs index f7d0a7930..5d31b5c35 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs @@ -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; /// /// Implements a code fix for . @@ -53,67 +49,11 @@ public override Task RegisterCodeFixesAsync(CodeFixContext context) private static async Task 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(initializer.Expressions); - var last = existingItems.Last(); - existingItems.Remove(last); - existingItems.Add(last.WithoutTrailingTrivia()); - - var existingSeparators = initializer.Expressions.GetSeparators(); - var newSeparators = new List(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(initializer.Initializers); - var last = existingItems.Last(); - existingItems.Remove(last); - existingItems.Add(last.WithoutTrailingTrivia()); - - var existingSeparators = initializer.Initializers.GetSeparators(); - var newSeparators = new List(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)); } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1413UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1413UnitTests.cs index 19de01f42..43dfbeec6 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1413UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1413UnitTests.cs @@ -425,6 +425,98 @@ void Foo() await this.VerifyCSharpFixAsync(testCode, fixedTestCode).ConfigureAwait(false); } + /// + /// Verifies that the last value of an empty enum does not produce a diagnostic. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task VerifyEmptyEnumAsync() + { + var testCode = @"enum EmptyEnum +{ +} +"; + + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that the last value of an enum with a trailing comma does not produce a diagnostic. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task VerifyEnumWithTrailingCommaAsync() + { + var testCode = @"enum TestEnum +{ + One, + Two, +} +"; + + await this.VerifyCSharpDiagnosticAsync(testCode, EmptyDiagnosticResults, CancellationToken.None).ConfigureAwait(false); + } + + /// + /// Verifies that the last value of an enum without a trailing comma produces a diagnostic. + /// + /// A representing the asynchronous unit test. + [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); + } + + /// + /// Verifies that the last value of an enum without a trailing comma produces a diagnostic. + /// + /// A representing the asynchronous unit test. + [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); + } + /// protected override CodeFixProvider GetCSharpCodeFixProvider() { diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1413UseTrailingCommasInMultiLineInitializers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1413UseTrailingCommasInMultiLineInitializers.cs index 251aeeaa0..1a2d131db 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1413UseTrailingCommasInMultiLineInitializers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1413UseTrailingCommasInMultiLineInitializers.cs @@ -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; @@ -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)