From b0e5f48a118d32bb3dba9465dbb8db5e1274529c Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Tue, 6 Dec 2016 23:01:28 +0100 Subject: [PATCH 1/2] SA1413 now also checks enum members. --- .../SA1413CodeFixProvider.cs | 12 +++ .../MaintainabilityRules/SA1413UnitTests.cs | 92 +++++++++++++++++++ ...seTrailingCommasInMultiLineInitializers.cs | 18 ++++ 3 files changed, 122 insertions(+) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs index f7d0a7930..3b03aa2ed 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs @@ -16,6 +16,7 @@ namespace StyleCop.Analyzers.MaintainabilityRules using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Text; /// /// Implements a code fix for . @@ -68,6 +69,11 @@ private static async Task GetTransformedDocumentAsync(Document documen newParent = RewriteAnonymousObjectInitializer((AnonymousObjectCreationExpressionSyntax)parent); break; + case SyntaxKind.EnumDeclaration: + var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); + TextChange textChange = GetEnumMemberTextChange(diagnostic, syntaxRoot); + return document.WithText(text.WithChanges(textChange)); + default: throw new InvalidOperationException("Unknown initializer type: " + parent.Kind()); } @@ -115,5 +121,11 @@ private static SyntaxNode RewriteAnonymousObjectInitializer(AnonymousObjectCreat var fixedInitializer = initializer.WithInitializers(newInitializerExpressions); return fixedInitializer; } + + private static TextChange GetEnumMemberTextChange(Diagnostic diagnostic, SyntaxNode syntaxRoot) + { + var member = (EnumMemberDeclarationSyntax)syntaxRoot.FindNode(diagnostic.Location.SourceSpan); + return new TextChange(diagnostic.Location.SourceSpan, member.ToString() + ","); + } } } diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1413UnitTests.cs b/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1413UnitTests.cs index 19de01f42..7281a3074 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 +} +"; + + var fixedTestCode = @"enum TestEnum +{ + One = 2, +} +"; + + 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..f218c0cf3 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,23 @@ 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; + } + + var commas = initializer.ChildNodesAndTokens().OfType().Where(token => token.IsKind(SyntaxKind.CommaToken)); + if (initializer.Members.Count() != commas.Count()) + { + context.ReportDiagnostic(Diagnostic.Create(Descriptor, lastMember.GetLocation())); + } } private static void HandleObjectInitializer(SyntaxNodeAnalysisContext context) From 763906e06f1cdb9fb25c2a006f837d3bf1396e2c Mon Sep 17 00:00:00 2001 From: Dirk Lemstra Date: Thu, 8 Dec 2016 23:16:56 +0100 Subject: [PATCH 2/2] Changes due to review comments. --- .../SA1413CodeFixProvider.cs | 80 +------------------ .../MaintainabilityRules/SA1413UnitTests.cs | 4 +- ...seTrailingCommasInMultiLineInitializers.cs | 3 +- 3 files changed, 7 insertions(+), 80 deletions(-) diff --git a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs index 3b03aa2ed..5d31b5c35 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/MaintainabilityRules/SA1413CodeFixProvider.cs @@ -3,19 +3,14 @@ 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; /// @@ -54,78 +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; - - case SyntaxKind.EnumDeclaration: - var text = await document.GetTextAsync(cancellationToken).ConfigureAwait(false); - TextChange textChange = GetEnumMemberTextChange(diagnostic, syntaxRoot); - return document.WithText(text.WithChanges(textChange)); - - 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; - } - - private static TextChange GetEnumMemberTextChange(Diagnostic diagnostic, SyntaxNode syntaxRoot) - { - var member = (EnumMemberDeclarationSyntax)syntaxRoot.FindNode(diagnostic.Location.SourceSpan); - return new TextChange(diagnostic.Location.SourceSpan, member.ToString() + ","); + 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 7281a3074..43dfbeec6 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1413UnitTests.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers.Test/MaintainabilityRules/SA1413UnitTests.cs @@ -497,13 +497,13 @@ public async Task VerifyEnumWithValueWithoutTrailingCommaAsync() { var testCode = @"enum TestEnum { - One = 2 + One = 2 /* test comment */ } "; var fixedTestCode = @"enum TestEnum { - One = 2, + One = 2, /* test comment */ } "; diff --git a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1413UseTrailingCommasInMultiLineInitializers.cs b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1413UseTrailingCommasInMultiLineInitializers.cs index f218c0cf3..1a2d131db 100644 --- a/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1413UseTrailingCommasInMultiLineInitializers.cs +++ b/StyleCop.Analyzers/StyleCop.Analyzers/MaintainabilityRules/SA1413UseTrailingCommasInMultiLineInitializers.cs @@ -86,8 +86,7 @@ private static void HandleEnumMemberDeclarationAction(SyntaxNodeAnalysisContext return; } - var commas = initializer.ChildNodesAndTokens().OfType().Where(token => token.IsKind(SyntaxKind.CommaToken)); - if (initializer.Members.Count() != commas.Count()) + if (initializer.Members.Count() != initializer.Members.SeparatorCount) { context.ReportDiagnostic(Diagnostic.Create(Descriptor, lastMember.GetLocation())); }