diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems index 1166ed25ed1fd..a7c230bfafa91 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems @@ -34,6 +34,7 @@ + diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx index 2309710bdd916..59a1794da38b9 100644 --- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx +++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzersResources.resx @@ -383,4 +383,7 @@ Remove redundant nullable directive + + Missing trailing comma + \ No newline at end of file diff --git a/src/Analyzers/CSharp/Analyzers/CodeStyle/CSharpAnalyzerOptionsProvider.cs b/src/Analyzers/CSharp/Analyzers/CodeStyle/CSharpAnalyzerOptionsProvider.cs index ab7ce1d7cff07..750b73936918d 100644 --- a/src/Analyzers/CSharp/Analyzers/CodeStyle/CSharpAnalyzerOptionsProvider.cs +++ b/src/Analyzers/CSharp/Analyzers/CodeStyle/CSharpAnalyzerOptionsProvider.cs @@ -50,6 +50,7 @@ public CSharpAnalyzerOptionsProvider(AnalyzerConfigOptions options, AnalyzerOpti public CodeStyleOption2 AllowEmbeddedStatementsOnSameLine => GetOption(CSharpCodeStyleOptions.AllowEmbeddedStatementsOnSameLine, FallbackSimplifierOptions.AllowEmbeddedStatementsOnSameLine); public CodeStyleOption2 PreferThrowExpression => GetOption(CSharpCodeStyleOptions.PreferThrowExpression, FallbackSimplifierOptions.PreferThrowExpression); public CodeStyleOption2 PreferBraces => GetOption(CSharpCodeStyleOptions.PreferBraces, FallbackSimplifierOptions.PreferBraces); + public CodeStyleOption2 PreferTrailingComma => GetOption(CSharpCodeStyleOptions.PreferTrailingComma, FallbackSimplifierOptions.PreferTrailingComma); internal CSharpSimplifierOptions GetSimplifierOptions() => _options.GetCSharpSimplifierOptions(FallbackSimplifierOptions); diff --git a/src/Analyzers/CSharp/Analyzers/PreferTrailingComma/PreferTrailingCommaDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/PreferTrailingComma/PreferTrailingCommaDiagnosticAnalyzer.cs new file mode 100644 index 0000000000000..8e07fb55869e7 --- /dev/null +++ b/src/Analyzers/CSharp/Analyzers/PreferTrailingComma/PreferTrailingCommaDiagnosticAnalyzer.cs @@ -0,0 +1,95 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using Microsoft.CodeAnalysis.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.PreferTrailingComma +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + internal sealed class PreferTrailingCommaDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer + { + public PreferTrailingCommaDiagnosticAnalyzer() : base( + diagnosticId: IDEDiagnosticIds.PreferTrailingCommaDiagnosticId, + enforceOnBuild: EnforceOnBuildValues.PreferTrailingComma, + option: CSharpCodeStyleOptions.PreferTrailingComma, + language: LanguageNames.CSharp, + title: new LocalizableResourceString(nameof(CSharpAnalyzersResources.Missing_trailing_comma), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources))) + { + } + + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SyntaxTreeWithoutSemanticsAnalysis; + + protected override void InitializeWorker(AnalysisContext context) + { + context.RegisterSyntaxNodeAction(AnalyzeSyntaxNode, + SyntaxKind.EnumDeclaration, SyntaxKind.PropertyPatternClause, SyntaxKind.SwitchExpression, + SyntaxKind.ObjectInitializerExpression, SyntaxKind.CollectionInitializerExpression, SyntaxKind.WithInitializerExpression, + SyntaxKind.ArrayInitializerExpression, SyntaxKind.AnonymousObjectCreationExpression, SyntaxKind.ListPattern); + } + + private void AnalyzeSyntaxNode(SyntaxNodeAnalysisContext context) + { + var option = context.GetCSharpAnalyzerOptions().PreferTrailingComma; + if (!option.Value) + return; + + var nodesWithSeparators = GetNodesWithSeparators(context.Node); + if (nodesWithSeparators.Count < 1) + { + return; + } + + var lastNodeOrSeparator = nodesWithSeparators[^1]; + if (lastNodeOrSeparator.IsToken) + { + return; + } + + if (lastNodeOrSeparator.IsNode) + { + var lastNode = lastNodeOrSeparator.AsNode()!; + if (CommaWillBeLastTokenOnLine(lastNode, context.CancellationToken)) + context.ReportDiagnostic(DiagnosticHelper.Create(Descriptor, lastNode.GetLocation(), option.Notification.Severity, additionalLocations: null, properties: null)); + } + } + + private static bool CommaWillBeLastTokenOnLine(SyntaxNode node, CancellationToken cancellationToken) + { + var lines = node.SyntaxTree.GetText(cancellationToken).Lines; + var lastCurrentToken = node.DescendantTokens().Last(); + var nextToken = lastCurrentToken.GetNextToken(); + if (nextToken == default) + { + return true; + } + + var line1 = lines.GetLineFromPosition(lastCurrentToken.Span.End).LineNumber; + var line2 = lines.GetLineFromPosition(lastCurrentToken.GetNextToken().SpanStart).LineNumber; + return line1 != line2; + } + + internal static SyntaxNodeOrTokenList GetNodesWithSeparators(SyntaxNode node) + { + return node switch + { + EnumDeclarationSyntax enumDeclaration => enumDeclaration.Members.GetWithSeparators(), + PropertyPatternClauseSyntax propertyPattern => propertyPattern.Subpatterns.GetWithSeparators(), + SwitchExpressionSyntax switchExpression => switchExpression.Arms.GetWithSeparators(), + InitializerExpressionSyntax initializerExpression => initializerExpression.Expressions.GetWithSeparators(), + AnonymousObjectCreationExpressionSyntax anonymousObjectCreation => anonymousObjectCreation.Initializers.GetWithSeparators(), + ListPatternSyntax listPattern => listPattern.Patterns.GetWithSeparators(), + _ => throw ExceptionUtilities.Unreachable, + }; + } + } +} diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs index 74f40ff39fe92..d5a41302a68f8 100644 --- a/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs @@ -27,5 +27,10 @@ protected override bool AreCollectionInitializersSupported(Compilation compilati => compilation.LanguageVersion() >= LanguageVersion.CSharp3; protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance; + + protected override bool PreferTrailingComma(SyntaxNodeAnalysisContext context) + { + return context.GetCSharpAnalyzerOptions().PreferTrailingComma.Value; + } } } diff --git a/src/Analyzers/CSharp/Analyzers/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs index 935a904bc9aa7..e3802ee6de477 100644 --- a/src/Analyzers/CSharp/Analyzers/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/CSharp/Analyzers/UseObjectInitializer/CSharpUseObjectInitializerDiagnosticAnalyzer.cs @@ -61,5 +61,10 @@ protected override bool IsValidContainingStatement(StatementSyntax node) return node is not LocalDeclarationStatementSyntax localDecl || localDecl.UsingKeyword == default; } + + protected override bool PreferTrailingComma(SyntaxNodeAnalysisContext context) + { + return context.GetCSharpAnalyzerOptions().PreferTrailingComma.Value; + } } } diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf index af5366121bfdb..ad8a408b05cae 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.cs.xlf @@ -87,6 +87,11 @@ Nesprávné umístění direktivy using {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives Přesunout nesprávně umístěné direktivy using diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf index 339186f197b90..490557c995376 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.de.xlf @@ -87,6 +87,11 @@ Die using-Anweisung wurde falsch platziert. {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives Falsch platzierte using-Anweisungen verschieben diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf index f7c549b87d8f2..987d00f4298a8 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.es.xlf @@ -87,6 +87,11 @@ Directiva using mal colocada {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives Mover directivas using mal colocadas diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf index b3453f2d14ed9..b57955d0c08df 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.fr.xlf @@ -87,6 +87,11 @@ Directive using mal placée {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives Déplacer les directives using mal placées diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf index 1e39571ccaeb6..8adffed9054d3 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.it.xlf @@ -87,6 +87,11 @@ Direttiva using in posizione errata {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives Sposta le direttive using in posizione errata diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf index 813c1e6262195..fe194c6e70423 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ja.xlf @@ -87,6 +87,11 @@ using ディレクティブが正しく配置されていません {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives 誤って配置された using ディレクティブを移動します diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf index ade8ca96c9929..66e39114831f8 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ko.xlf @@ -87,6 +87,11 @@ 위치가 잘못된 using 지시문 {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives 위치가 잘못된 using 지시문 이동 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf index 5220a76631555..99bcbebeee8a5 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pl.xlf @@ -87,6 +87,11 @@ Nieprawidłowo umieszczona dyrektywa using {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives Przenieś nieprawidłowo umieszczone dyrektywy using diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf index 05a9c9fdc54bb..43029506852eb 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.pt-BR.xlf @@ -87,6 +87,11 @@ Diretiva using em local incorreto {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives Mover diretivas using no local incorreto diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf index 7ebe8349efe2d..a3b23cc4d5ca2 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.ru.xlf @@ -87,6 +87,11 @@ Неправильно расположенная директива using {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives Переместить неправильно расположенные директивы using diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf index 5abba358c15a5..c0157ed07cc4e 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.tr.xlf @@ -87,6 +87,11 @@ Yanlış yerleştirilmiş using yönergesi {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives Yanlış yerleştirilmiş using yönergelerini taşı diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf index 03643d9e6145f..a9daeaac9509e 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hans.xlf @@ -87,6 +87,11 @@ 错放了 using 指令 {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives 移动错放的 using 指令 diff --git a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf index 5b08f437ce5b6..717af1a3b8f6b 100644 --- a/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf +++ b/src/Analyzers/CSharp/Analyzers/xlf/CSharpAnalyzersResources.zh-Hant.xlf @@ -87,6 +87,11 @@ using 指示詞位置錯誤 {Locked="using"} "using" is a C# keyword and should not be localized. + + Missing trailing comma + Missing trailing comma + + Move misplaced using directives 移動位置錯誤的 using 指示詞 diff --git a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems index 1f71528cf56de..35947c37d91b5 100644 --- a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems +++ b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixes.projitems @@ -38,6 +38,7 @@ + diff --git a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixesResources.resx b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixesResources.resx index 7e37cbfd2dbbd..d193d776c4613 100644 --- a/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixesResources.resx +++ b/src/Analyzers/CSharp/CodeFixes/CSharpCodeFixesResources.resx @@ -187,4 +187,7 @@ Declare as nullable + + Add trailing comma + \ No newline at end of file diff --git a/src/Analyzers/CSharp/CodeFixes/PreferTrailingComma/PreferTrailingCommaCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/PreferTrailingComma/PreferTrailingCommaCodeFixProvider.cs new file mode 100644 index 0000000000000..69063d9422260 --- /dev/null +++ b/src/Analyzers/CSharp/CodeFixes/PreferTrailingComma/PreferTrailingCommaCodeFixProvider.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp.Extensions; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Shared.Extensions; +using Roslyn.Utilities; + +namespace Microsoft.CodeAnalysis.CSharp.PreferTrailingComma +{ + [ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.PreferTrailingComma), Shared] + internal sealed class PreferTrailingCommaCodeFixProvider : SyntaxEditorBasedCodeFixProvider + { + [ImportingConstructor] + [Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] + public PreferTrailingCommaCodeFixProvider() + { + } + + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(IDEDiagnosticIds.PreferTrailingCommaDiagnosticId); + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + foreach (var diagnostic in context.Diagnostics) + RegisterCodeFix(context, CSharpCodeFixesResources.Add_trailing_comma, nameof(CSharpCodeFixesResources.Add_trailing_comma), diagnostic); + + return Task.CompletedTask; + } + + protected override Task FixAllAsync(Document document, ImmutableArray diagnostics, SyntaxEditor editor, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken) + { + // Reverse order so that we fix inner diagnostics first when there are multiple nested diagnostics. + //diagnostics = diagnostics.Sort((d1, d2) => d2.Location.SourceSpan.Start - d1.Location.SourceSpan.Start); + + foreach (var diagnostic in diagnostics) + { + var node = diagnostic.Location.FindNode(cancellationToken).GetRequiredParent(); + editor.ReplaceNode(node, (n, g) => GetReplacement(n)); + } + + return Task.CompletedTask; + } + + private static SyntaxNode GetReplacement(SyntaxNode node) + { + var nodesAndTokens = PreferTrailingCommaDiagnosticAnalyzer.GetNodesWithSeparators(node); + var lastNode = nodesAndTokens[^1]; + nodesAndTokens = nodesAndTokens.ReplaceRange(lastNode, ImmutableArray.Create(lastNode.WithTrailingTrivia(), SyntaxFactory.Token(leading: default, SyntaxKind.CommaToken, trailing: lastNode.GetTrailingTrivia()))); + + return node switch + { + EnumDeclarationSyntax enumDeclaration => enumDeclaration.WithMembers(SyntaxFactory.SeparatedList(nodesAndTokens)), + PropertyPatternClauseSyntax propertyPattern => propertyPattern.WithSubpatterns(SyntaxFactory.SeparatedList(nodesAndTokens)), + SwitchExpressionSyntax switchExpression => switchExpression.WithArms(SyntaxFactory.SeparatedList(nodesAndTokens)), + InitializerExpressionSyntax initializerExpression => initializerExpression.WithExpressions(SyntaxFactory.SeparatedList(nodesAndTokens)), + AnonymousObjectCreationExpressionSyntax anonymousObjectCreation => anonymousObjectCreation.WithInitializers(SyntaxFactory.SeparatedList(nodesAndTokens)), + ListPatternSyntax listPattern => listPattern.WithPatterns(SyntaxFactory.SeparatedList(nodesAndTokens)), + _ => throw ExceptionUtilities.Unreachable, + }; + } + } +} diff --git a/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs index 931a70bb5063d..96f002a9c9f18 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs @@ -38,24 +38,27 @@ public CSharpUseCollectionInitializerCodeFixProvider() protected override StatementSyntax GetNewStatement( StatementSyntax statement, BaseObjectCreationExpressionSyntax objectCreation, - ImmutableArray matches) + ImmutableArray matches, + bool addTrailingComma) { return statement.ReplaceNode( objectCreation, - GetNewObjectCreation(objectCreation, matches)); + GetNewObjectCreation(objectCreation, matches, addTrailingComma)); } private static BaseObjectCreationExpressionSyntax GetNewObjectCreation( BaseObjectCreationExpressionSyntax objectCreation, - ImmutableArray matches) + ImmutableArray matches, + bool addTrailingComma) { return UseInitializerHelpers.GetNewObjectCreation( - objectCreation, CreateExpressions(objectCreation, matches)); + objectCreation, CreateExpressions(objectCreation, matches, addTrailingComma)); } private static SeparatedSyntaxList CreateExpressions( BaseObjectCreationExpressionSyntax objectCreation, - ImmutableArray matches) + ImmutableArray matches, + bool addTrailingComma) { using var _ = ArrayBuilder.GetInstance(out var nodesAndTokens); @@ -72,7 +75,7 @@ private static SeparatedSyntaxList CreateExpressions( .WithoutTrivia() .WithPrependedLeadingTrivia(newTrivia); - if (i < matches.Length - 1) + if (i < matches.Length - 1 || addTrailingComma) { nodesAndTokens.Add(newExpression); var commaToken = SyntaxFactory.Token(SyntaxKind.CommaToken) diff --git a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs index 8ae299d393040..1b4ff75823c17 100644 --- a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs +++ b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs @@ -32,24 +32,27 @@ public CSharpUseObjectInitializerCodeFixProvider() protected override StatementSyntax GetNewStatement( StatementSyntax statement, BaseObjectCreationExpressionSyntax objectCreation, - ImmutableArray> matches) + ImmutableArray> matches, + bool addTrailingComma) { return statement.ReplaceNode( objectCreation, - GetNewObjectCreation(objectCreation, matches)); + GetNewObjectCreation(objectCreation, matches, addTrailingComma)); } private static BaseObjectCreationExpressionSyntax GetNewObjectCreation( BaseObjectCreationExpressionSyntax objectCreation, - ImmutableArray> matches) + ImmutableArray> matches, + bool addTrailingComma) { return UseInitializerHelpers.GetNewObjectCreation( - objectCreation, CreateExpressions(objectCreation, matches)); + objectCreation, CreateExpressions(objectCreation, matches, addTrailingComma)); } private static SeparatedSyntaxList CreateExpressions( BaseObjectCreationExpressionSyntax objectCreation, - ImmutableArray> matches) + ImmutableArray> matches, + bool addTrailingComma) { using var _ = ArrayBuilder.GetInstance(out var nodesAndTokens); @@ -67,7 +70,7 @@ private static SeparatedSyntaxList CreateExpressions( var newAssignment = assignment.WithLeft( match.MemberAccessExpression.Name.WithLeadingTrivia(newTrivia)); - if (i < matches.Length - 1) + if (i < matches.Length - 1 || addTrailingComma) { nodesAndTokens.Add(newAssignment); var commaToken = SyntaxFactory.Token(SyntaxKind.CommaToken) diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.cs.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.cs.xlf index 4e992b7a672d2..15235582e312a 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.cs.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.cs.xlf @@ -17,6 +17,11 @@ Přidejte položku this. + + Add trailing comma + Add trailing comma + + Assign to '{0}' Přiřadit k {0} diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.de.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.de.xlf index 34626a07eb094..3ab3a6c53e980 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.de.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.de.xlf @@ -17,6 +17,11 @@ "this." hinzufügen + + Add trailing comma + Add trailing comma + + Assign to '{0}' "{0}" zuweisen diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.es.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.es.xlf index 8c30bd02a594d..3f9dc797a4b69 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.es.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.es.xlf @@ -17,6 +17,11 @@ Agregar "this." + + Add trailing comma + Add trailing comma + + Assign to '{0}' Asignar a "{0}" diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.fr.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.fr.xlf index 179a1a8e27920..5c2b17c50a010 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.fr.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.fr.xlf @@ -17,6 +17,11 @@ Ajouter 'this.' + + Add trailing comma + Add trailing comma + + Assign to '{0}' Affecter à '{0}' diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.it.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.it.xlf index 2254585338928..bce88a76e6636 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.it.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.it.xlf @@ -17,6 +17,11 @@ Aggiungi 'this.' + + Add trailing comma + Add trailing comma + + Assign to '{0}' Assegna a '{0}' diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ja.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ja.xlf index e86345bb4aecd..cf5b1a7d05468 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ja.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ja.xlf @@ -17,6 +17,11 @@ this' を追加します。 + + Add trailing comma + Add trailing comma + + Assign to '{0}' "{0}" に割り当て diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ko.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ko.xlf index 8c863dc730e46..591fe3c6160eb 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ko.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ko.xlf @@ -17,6 +17,11 @@ this'를 추가합니다. + + Add trailing comma + Add trailing comma + + Assign to '{0}' '{0}'에 할당 diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pl.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pl.xlf index b58e3ee562b61..b4b2c32368829 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pl.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pl.xlf @@ -17,6 +17,11 @@ Dodaj „this.” + + Add trailing comma + Add trailing comma + + Assign to '{0}' Przypisz do „{0}” diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pt-BR.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pt-BR.xlf index 929f1263cffc7..1c72fe6defb62 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pt-BR.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.pt-BR.xlf @@ -17,6 +17,11 @@ Adicionar 'isso.' + + Add trailing comma + Add trailing comma + + Assign to '{0}' Atribuir a '{0}' diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ru.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ru.xlf index 713b3835f0d50..b4bb476d4a1d6 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ru.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.ru.xlf @@ -17,6 +17,11 @@ Добавьте "this". + + Add trailing comma + Add trailing comma + + Assign to '{0}' Назначить "{0}" diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.tr.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.tr.xlf index 634d51f688cbb..730e2919fa30a 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.tr.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.tr.xlf @@ -17,6 +17,11 @@ this' ekleyin. + + Add trailing comma + Add trailing comma + + Assign to '{0}' '{0}' öğesine ata diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hans.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hans.xlf index 4b5146986cb1e..b2bbf4271524f 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hans.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hans.xlf @@ -17,6 +17,11 @@ 添加 "this." + + Add trailing comma + Add trailing comma + + Assign to '{0}' 赋值给 "{0}" diff --git a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hant.xlf b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hant.xlf index a1fda973b431e..1871621518045 100644 --- a/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hant.xlf +++ b/src/Analyzers/CSharp/CodeFixes/xlf/CSharpCodeFixesResources.zh-Hant.xlf @@ -17,6 +17,11 @@ 新增 'this.' + + Add trailing comma + Add trailing comma + + Assign to '{0}' 指派給 '{0}' diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems index a57f7926abb75..82f2078bc6cba 100644 --- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems +++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems @@ -29,6 +29,7 @@ + diff --git a/src/Analyzers/CSharp/Tests/PreferTrailingComma/PreferTrailingCommaTests.cs b/src/Analyzers/CSharp/Tests/PreferTrailingComma/PreferTrailingCommaTests.cs new file mode 100644 index 0000000000000..8d70192cc650c --- /dev/null +++ b/src/Analyzers/CSharp/Tests/PreferTrailingComma/PreferTrailingCommaTests.cs @@ -0,0 +1,664 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; +using Microsoft.CodeAnalysis.CSharp.PreferTrailingComma; +using Microsoft.CodeAnalysis.CSharp.Shared.Extensions; +using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; +using Microsoft.CodeAnalysis.Testing; +using Xunit; + +namespace Microsoft.CodeAnalysis.CSharp.Analyzers.UnitTests.PreferTrailingComma +{ + using VerifyCS = CSharpCodeFixVerifier; + + public class PreferTrailingCommaTests + { + [Fact] + public async Task TestOptionOff() + { + await new VerifyCS.Test + { + TestCode = @"enum A +{ + A, + B +}", + Options = + { + { CSharpCodeStyleOptions.PreferTrailingComma, false }, + }, + }.RunAsync(); + } + + [Fact] + public async Task TestOptionOn() + { + await new VerifyCS.Test + { + TestCode = @"enum A +{ + A, + [|B|] +} +", + FixedCode = @"enum A +{ + A, + B, +} +", + Options = + { + { CSharpCodeStyleOptions.PreferTrailingComma, true }, + }, + }.RunAsync(); + } + + [Fact] + public async Task TestTriviaOnEnumMember() + { + var code = @"enum A +{ + A, + // comment 1 + [|B|] // comment 2 +} +"; + var fixedCode = @"enum A +{ + A, + // comment 1 + B, // comment 2 +} +"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestEnumMemberOnSameLine() + { + var code = @"enum A +{ + A, [|B|] // comment +} +"; + var fixedCode = @"enum A +{ + A, B, // comment +} +"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestEnumSingleLine() + { + var code = "enum A { A, B }"; + await VerifyCS.VerifyCodeFixAsync(code, code); + } + + [Fact] + public async Task TestEmptyEnum() + { + var code = @"enum A +{ +} +"; + await VerifyCS.VerifyCodeFixAsync(code, code); + } + + [Fact] + public async Task TestNoNextToken() + { + var code = @"enum A +{ + A, + [|B|]{|CS1513:|}"; + var fixedCode = @"enum A +{ + A, + B,{|CS1513:|}"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestNoNextToken_SingleLine() + { + var code = "enum A { A, [|B|]{|CS1513:|}"; + var fixedCode = "enum A { A, B,{|CS1513:|}"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestPropertyPattern() + { + var code = @"class C +{ + void M(string s) + { + if (s is + { + Length: 0, + [|Length: 0|] + }) + { + } + } +} +"; + var fixedCode = @"class C +{ + void M(string s) + { + if (s is + { + Length: 0, + Length: 0, + }) + { + } + } +} +"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestTriviaOnPropertyPattern() + { + var code = @"class C +{ + void M(string s) + { + if (s is + { + Length: 0, + // comment 1 + [|Length: 0|] // comment 2 + }) + { + } + } +} +"; + var fixedCode = @"class C +{ + void M(string s) + { + if (s is + { + Length: 0, + // comment 1 + Length: 0, // comment 2 + }) + { + } + } +} +"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestPropertyPatternOnSameLine() + { + var code = @"class C +{ + void M(string s) + { + if (s is { Length: 0, Length: 0 }) + { + } + } +} +"; + await VerifyCS.VerifyCodeFixAsync(code, code); + } + + [Fact] + public async Task TestSwitchExpression() + { + var code = @"class C +{ + void M(object o) + { + _ = o switch + { + string s => 0, + [|_ => 1|] + }; + } +} +"; + var fixedCode = @"class C +{ + void M(object o) + { + _ = o switch + { + string s => 0, + _ => 1, + }; + } +} +"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestTriviaOnSwitchExpression() + { + var code = @"class C +{ + void M(object o) + { + _ = o switch + { + string s => 0, + // comment 1 + [|_ => 1|] // comment 2 + }; + } +} +"; + var fixedCode = @"class C +{ + void M(object o) + { + _ = o switch + { + string s => 0, + // comment 1 + _ => 1, // comment 2 + }; + } +} +"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestSwitchExpressionOnSameLine() + { + var code = @"class C +{ + void M(object o) + { + _ = o switch { string s => 0, _ => 1 }; + } +} +"; + await VerifyCS.VerifyCodeFixAsync(code, code); + } + + [Fact] + public async Task TestTriviaOnInitializerExpression() + { + var code = @"using System; +using System.Collections.Generic; + +record C +{ + public int X; + public int Y; + + void M() + { + C c1 = new() + { + X = 0, + // Comment 1 + [|Y = 1|] // Comment 2 + }; + + var c2 = new C() + { + X = 0, + // Comment 3 + [|Y = 1|] // Comment 4 + }; + + var c3 = c1 with + { + X = 0, + // Comment 5 + [|Y = 1|] // Comment 6 + }; + + var arr1 = new int[] + { + 0, + // Comment 7 + [|1|] // Comment 8 + }; + + var arr2 = new[] + { + 0, + // Comment 7 + [|1|] // Comment 8 + }; + + ReadOnlySpan arr3 = stackalloc int[2] + { + 0, + // Comment 9 + [|1|] // Comment 10 + }; + + ReadOnlySpan arr4 = stackalloc[] + { + 0, + // Comment 11 + [|1|] // Comment 12 + }; + + var list = new List + { + 0, + // Comment 13 + [|1|] // Comment 14 + }; + } +} +"; + var fixedCode = @"using System; +using System.Collections.Generic; + +record C +{ + public int X; + public int Y; + + void M() + { + C c1 = new() + { + X = 0, + // Comment 1 + Y = 1, // Comment 2 + }; + + var c2 = new C() + { + X = 0, + // Comment 3 + Y = 1, // Comment 4 + }; + + var c3 = c1 with + { + X = 0, + // Comment 5 + Y = 1, // Comment 6 + }; + + var arr1 = new int[] + { + 0, + // Comment 7 + 1, // Comment 8 + }; + + var arr2 = new[] + { + 0, + // Comment 7 + 1, // Comment 8 + }; + + ReadOnlySpan arr3 = stackalloc int[2] + { + 0, + // Comment 9 + 1, // Comment 10 + }; + + ReadOnlySpan arr4 = stackalloc[] + { + 0, + // Comment 11 + 1, // Comment 12 + }; + + var list = new List + { + 0, + // Comment 13 + 1, // Comment 14 + }; + } +} +"; + await new VerifyCS.Test + { + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersion.CSharp9, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + }.RunAsync(); + } + + [Fact] + public async Task TestInitializerExpressionOnSameLine() + { + var code = @"using System; +using System.Collections.Generic; + +record C +{ + public int X; + public int Y; + + void M() + { + C c1 = new() { X = 0, Y = 1 }; + + var c2 = new C() { X = 0, Y = 1 }; + + var c3 = c1 with { X = 0, Y = 1 }; + + var arr1 = new int[] { 0, 1 }; + + var arr2 = new[] { 0, 1 }; + + ReadOnlySpan arr3 = stackalloc int[2] { 0, 1 }; + + ReadOnlySpan arr4 = stackalloc[] { 0, 1 }; + + var list = new List { 0, 1 }; + } +} +"; + await new VerifyCS.Test + { + TestCode = code, + FixedCode = code, + LanguageVersion = LanguageVersion.CSharp9, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + }.RunAsync(); + } + + [Fact] + public async Task TestComplexElementInitializerExpression() + { + // At the time of writing this test, adding a comma after `2` (comment 5) doesn't compile. So ComplexElementInitializerExpression is not supported. + // If this changed in the future, the analyzer should support it. + var code = @"using System.Collections; + +class C : IEnumerable +{ + void M() + { + var c = new C() + { + // Comment 1 + 0, // Comment 2 + [|{ // Comment 3 + 1, // Comment 4 + 2 // Comment 5 + }|] // Comment 6 + // Comment 7 + }; + } + + public void Add(int x) { } + public void Add(int x, int y) { } + public IEnumerator GetEnumerator() => null; +} +"; + var fixedCode = @"using System.Collections; + +class C : IEnumerable +{ + void M() + { + var c = new C() + { + // Comment 1 + 0, // Comment 2 + { // Comment 3 + 1, // Comment 4 + 2 // Comment 5 + }, // Comment 6 + // Comment 7 + }; + } + + public void Add(int x) { } + public void Add(int x, int y) { } + public IEnumerator GetEnumerator() => null; +} +"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestAnonymousObjectExpression() + { + var code = @"class C +{ + void M() + { + var c = new + { + A = 0, + [|B = 1|] + }; + } +} +"; + var fixedCode = @"class C +{ + void M() + { + var c = new + { + A = 0, + B = 1, + }; + } +} +"; + await VerifyCS.VerifyCodeFixAsync(code, fixedCode); + } + + [Fact] + public async Task TestListPattern() + { + var code = @"class C +{ + void M(object[] arr) + { + if (arr is + [ + 1, + .., + [|2|] + ]) + { + } + } +} +"; + var fixedCode = @"class C +{ + void M(object[] arr) + { + if (arr is + [ + 1, + .., + 2, + ]) + { + } + } +} +"; + await new VerifyCS.Test + { + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersionExtensions.CSharpNext, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + }.RunAsync(); + } + + [Fact] + public async Task TestListPattern_Nested() + { + var code = @"class C +{ + void M(object[][] arr) + { + if (arr is + [ + [ + [|0|] + ], + .., + [|[ + [|0|] + ]|] + ]) + { + } + } +} +"; + var fixedCode = @"class C +{ + void M(object[][] arr) + { + if (arr is + [ + [ + 0, + ], + .., + [ + 0, + ], + ]) + { + } + } +} +"; + await new VerifyCS.Test + { + TestCode = code, + FixedCode = fixedCode, + LanguageVersion = LanguageVersionExtensions.CSharpNext, + ReferenceAssemblies = ReferenceAssemblies.Net.Net60, + }.RunAsync(); + } + } +} diff --git a/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests.cs b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests.cs index ffced1ca0a0f8..eea46b97a9097 100644 --- a/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests.cs +++ b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests.cs @@ -5,6 +5,7 @@ using System.Collections; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.UseCollectionInitializer; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; @@ -19,7 +20,7 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseCollectionInitialize public partial class UseCollectionInitializerTests { - private static async Task TestInRegularAndScriptAsync(string testCode, string fixedCode, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) + private static async Task TestInRegularAndScriptAsync(string testCode, string fixedCode, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool preferTrailingComma = false) { await new VerifyCS.Test { @@ -27,7 +28,8 @@ private static async Task TestInRegularAndScriptAsync(string testCode, string fi TestCode = testCode, FixedCode = fixedCode, LanguageVersion = LanguageVersion.Preview, - TestState = { OutputKind = outputKind } + TestState = { OutputKind = outputKind }, + Options = { { CSharpCodeStyleOptions.PreferTrailingComma, preferTrailingComma } }, }.RunAsync(); } @@ -45,6 +47,73 @@ private static async Task TestMissingInRegularAndScriptAsync(string testCode, La await test.RunAsync(); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCollectionInitializer)] + public async Task TestOnVariableDeclarator_PreferTrailingComma() + { + await TestInRegularAndScriptAsync( +@"using System.Collections.Generic; + +class C +{ + void M() + { + var c = [|new|] List(); + c.Add(1); + } +}", +@"using System.Collections.Generic; + +class C +{ + void M() + { + var c = new List + { + 1, + }; + } +}", preferTrailingComma: true); + } + + [Fact] + public async Task TestNestedCollectionInitializer_PreferTrailingComma() + { + await TestInRegularAndScriptAsync( +@"using System; +using System.Collections.Generic; + +class C +{ + void M() + { + var list1 = new List + { + () => + { + var list2 = [|new|] List(); + list2.Add(2); + } + }; + } +}", +@"using System; +using System.Collections.Generic; + +class C +{ + void M() + { + var list1 = new List + { + () => + { + var list2 = new List() { 2 }; + } + }; + } +}", preferTrailingComma: true); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseCollectionInitializer)] public async Task TestOnVariableDeclarator() { diff --git a/src/Analyzers/CSharp/Tests/UseObjectInitializer/UseObjectInitializerTests.cs b/src/Analyzers/CSharp/Tests/UseObjectInitializer/UseObjectInitializerTests.cs index 44c5784d08172..6e1c0cba48d6b 100644 --- a/src/Analyzers/CSharp/Tests/UseObjectInitializer/UseObjectInitializerTests.cs +++ b/src/Analyzers/CSharp/Tests/UseObjectInitializer/UseObjectInitializerTests.cs @@ -4,6 +4,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.UseObjectInitializer; using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions; using Microsoft.CodeAnalysis.Test.Utilities; @@ -18,14 +19,15 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseObjectInitializer public partial class UseObjectInitializerTests { - private static async Task TestInRegularAndScriptAsync(string testCode, string fixedCode, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) + private static async Task TestInRegularAndScriptAsync(string testCode, string fixedCode, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary, bool preferTrailingComma = false) { await new VerifyCS.Test { TestCode = testCode, FixedCode = fixedCode, LanguageVersion = LanguageVersion.Preview, - TestState = { OutputKind = outputKind } + TestState = { OutputKind = outputKind }, + Options = { { CSharpCodeStyleOptions.PreferTrailingComma, preferTrailingComma } } }.RunAsync(); } @@ -43,6 +45,34 @@ private static async Task TestMissingInRegularAndScriptAsync(string testCode, La await test.RunAsync(); } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseObjectInitializer)] + public async Task TestOnVariableDeclarator_PreferTrailingComma() + { + await TestInRegularAndScriptAsync( +@"class C +{ + int i; + + void M() + { + var c = [|new|] C(); + c.i = 1; + } +}", +@"class C +{ + int i; + + void M() + { + var c = new C + { + i = 1, + }; + } +}", preferTrailingComma: true); + } + [Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseObjectInitializer)] public async Task TestOnVariableDeclarator() { diff --git a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs index fadfccaa5e9f2..82065d2d0f9d9 100644 --- a/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs +++ b/src/Analyzers/Core/Analyzers/EnforceOnBuildValues.cs @@ -29,6 +29,7 @@ internal static class EnforceOnBuildValues public const EnforceOnBuild UseBlockScopedNamespace = /*IDE0160*/ EnforceOnBuild.HighlyRecommended; public const EnforceOnBuild UseFileScopedNamespace = /*IDE0161*/ EnforceOnBuild.HighlyRecommended; public const EnforceOnBuild UseTupleSwap = /*IDE0180*/ EnforceOnBuild.HighlyRecommended; + public const EnforceOnBuild PreferTrailingComma = /*IDE0250*/ EnforceOnBuild.HighlyRecommended; /* EnforceOnBuild.Recommended */ public const EnforceOnBuild UseThrowExpression = /*IDE0016*/ EnforceOnBuild.Recommended; diff --git a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs index a30b5a0d0b89a..6784bad887409 100644 --- a/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs +++ b/src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs @@ -146,6 +146,8 @@ internal static class IDEDiagnosticIds public const string UseNotPatternDiagnosticId = "IDE0083"; public const string UseIsNotExpressionDiagnosticId = "IDE0084"; + public const string PreferTrailingCommaDiagnosticId = "IDE0085"; + public const string UseImplicitObjectCreationDiagnosticId = "IDE0090"; public const string RemoveRedundantEqualityDiagnosticId = "IDE0100"; diff --git a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs index 99964ef9a0d60..daf3efca618b3 100644 --- a/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseCollectionInitializer/AbstractUseCollectionInitializerDiagnosticAnalyzer.cs @@ -31,6 +31,8 @@ internal abstract partial class AbstractUseCollectionInitializerDiagnosticAnalyz where TExpressionStatementSyntax : TStatementSyntax where TVariableDeclaratorSyntax : SyntaxNode { + internal const string PreferTrailingCommaKey = nameof(PreferTrailingCommaKey); + public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; @@ -45,6 +47,7 @@ protected AbstractUseCollectionInitializerDiagnosticAnalyzer() protected abstract ISyntaxFacts GetSyntaxFacts(); protected abstract bool AreCollectionInitializersSupported(Compilation compilation); + protected abstract bool PreferTrailingComma(SyntaxNodeAnalysisContext context); protected override void InitializeWorker(AnalysisContext context) => context.RegisterCompilationStartAction(OnCompilationStart); @@ -107,12 +110,18 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context, INamedTypeSymbol ien var locations = ImmutableArray.Create(objectCreationExpression.GetLocation()); + var properties = ImmutableDictionary.Empty; + if (PreferTrailingComma(context)) + { + properties = properties.Add(nameof(PreferTrailingCommaKey), ""); + } + context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, objectCreationExpression.GetFirstToken().GetLocation(), option.Notification.Severity, additionalLocations: locations, - properties: null)); + properties: properties)); FadeOutCode(context, matches.Value, locations); } diff --git a/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs b/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs index d7a659e87616c..45d78c8f53861 100644 --- a/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs +++ b/src/Analyzers/Core/Analyzers/UseObjectInitializer/AbstractUseObjectInitializerDiagnosticAnalyzer.cs @@ -29,6 +29,8 @@ internal abstract partial class AbstractUseObjectInitializerDiagnosticAnalyzer< where TAssignmentStatementSyntax : TStatementSyntax where TVariableDeclaratorSyntax : SyntaxNode { + internal const string PreferTrailingCommaKey = nameof(PreferTrailingCommaKey); + protected abstract bool FadeOutOperatorToken { get; } protected AbstractUseObjectInitializerDiagnosticAnalyzer() @@ -63,6 +65,8 @@ protected override void InitializeWorker(AnalysisContext context) protected abstract bool IsValidContainingStatement(TStatementSyntax node); + protected abstract bool PreferTrailingComma(SyntaxNodeAnalysisContext context); + private void AnalyzeNode(SyntaxNodeAnalysisContext context) { var objectCreationExpression = (TObjectCreationExpressionSyntax)context.Node; @@ -94,12 +98,18 @@ private void AnalyzeNode(SyntaxNodeAnalysisContext context) var locations = ImmutableArray.Create(objectCreationExpression.GetLocation()); + var properties = ImmutableDictionary.Empty; + if (PreferTrailingComma(context)) + { + properties = properties.Add(nameof(PreferTrailingCommaKey), ""); + } + context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, objectCreationExpression.GetFirstToken().GetLocation(), option.Notification.Severity, locations, - properties: null)); + properties: properties)); FadeOutCode(context, matches.Value, locations); } diff --git a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs index 1ccef400dfe3c..e6f177f161340 100644 --- a/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs +++ b/src/Analyzers/Core/CodeFixes/PredefinedCodeFixProviderNames.cs @@ -158,5 +158,6 @@ internal static class PredefinedCodeFixProviderNames public const string UseThrowExpression = nameof(UseThrowExpression); public const string UseTupleSwap = nameof(UseTupleSwap); public const string UseUtf8StringLiteral = nameof(UseUtf8StringLiteral); + public const string PreferTrailingComma = nameof(PreferTrailingComma); } } diff --git a/src/Analyzers/Core/CodeFixes/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs index 172b901fe390d..df99e8f2c60c2 100644 --- a/src/Analyzers/Core/CodeFixes/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseCollectionInitializer/AbstractUseCollectionInitializerCodeFixProvider.cs @@ -93,8 +93,8 @@ protected override async Task FixAllAsync( var statement = objectCreation.FirstAncestorOrSelf(); Contract.ThrowIfNull(statement); - - var newStatement = GetNewStatement(statement, objectCreation, matches.Value) + const string key = AbstractUseCollectionInitializerDiagnosticAnalyzer.PreferTrailingCommaKey; + var newStatement = GetNewStatement(statement, objectCreation, matches.Value, addTrailingComma: diagnostics[0].Properties.ContainsKey(key)) .WithAdditionalAnnotations(Formatter.Annotation); var subEditor = new SyntaxEditor(currentRoot, services); @@ -113,6 +113,7 @@ protected override async Task FixAllAsync( protected abstract TStatementSyntax GetNewStatement( TStatementSyntax statement, TObjectCreationExpressionSyntax objectCreation, - ImmutableArray matches); + ImmutableArray matches, + bool addTrailingComma); } } diff --git a/src/Analyzers/Core/CodeFixes/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs b/src/Analyzers/Core/CodeFixes/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs index ca7014e9c3c52..d3c75254b83d0 100644 --- a/src/Analyzers/Core/CodeFixes/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs +++ b/src/Analyzers/Core/CodeFixes/UseObjectInitializer/AbstractUseObjectInitializerCodeFixProvider.cs @@ -94,8 +94,8 @@ protected override async Task FixAllAsync( var statement = objectCreation.FirstAncestorOrSelf(); Contract.ThrowIfNull(statement); - - var newStatement = GetNewStatement(statement, objectCreation, matches.Value) + const string key = AbstractUseObjectInitializerDiagnosticAnalyzer.PreferTrailingCommaKey; + var newStatement = GetNewStatement(statement, objectCreation, matches.Value, addTrailingComma: diagnostics[0].Properties.ContainsKey(key)) .WithAdditionalAnnotations(Formatter.Annotation); var subEditor = new SyntaxEditor(currentRoot, services); @@ -116,6 +116,7 @@ protected override async Task FixAllAsync( protected abstract TStatementSyntax GetNewStatement( TStatementSyntax statement, TObjectCreationExpressionSyntax objectCreation, - ImmutableArray> matches); + ImmutableArray> matches, + bool addTrailingComma); } } diff --git a/src/Analyzers/VisualBasic/Analyzers/UseCollectionInitializer/VisualBasicUseCollectionInitializerDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/UseCollectionInitializer/VisualBasicUseCollectionInitializerDiagnosticAnalyzer.vb index 65c1409ee484f..d899c500ca60b 100644 --- a/src/Analyzers/VisualBasic/Analyzers/UseCollectionInitializer/VisualBasicUseCollectionInitializerDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/UseCollectionInitializer/VisualBasicUseCollectionInitializerDiagnosticAnalyzer.vb @@ -28,5 +28,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCollectionInitializer Protected Overrides Function GetSyntaxFacts() As ISyntaxFacts Return VisualBasicSyntaxFacts.Instance End Function + + Protected Overrides Function PreferTrailingComma(context As SyntaxNodeAnalysisContext) As Boolean + Return False + End Function End Class End Namespace diff --git a/src/Analyzers/VisualBasic/Analyzers/UseObjectInitializer/VisualBasicUseObjectInitializerDiagnosticAnalyzer.vb b/src/Analyzers/VisualBasic/Analyzers/UseObjectInitializer/VisualBasicUseObjectInitializerDiagnosticAnalyzer.vb index 73088cd3cec6d..36043aba20c45 100644 --- a/src/Analyzers/VisualBasic/Analyzers/UseObjectInitializer/VisualBasicUseObjectInitializerDiagnosticAnalyzer.vb +++ b/src/Analyzers/VisualBasic/Analyzers/UseObjectInitializer/VisualBasicUseObjectInitializerDiagnosticAnalyzer.vb @@ -38,5 +38,9 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseObjectInitializer Protected Overrides Function IsValidContainingStatement(node As StatementSyntax) As Boolean Return True End Function + + Protected Overrides Function PreferTrailingComma(context As SyntaxNodeAnalysisContext) As Boolean + Return False + End Function End Class End Namespace diff --git a/src/Analyzers/VisualBasic/CodeFixes/UseCollectionInitializer/VisualBasicUseCollectionInitializerCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/UseCollectionInitializer/VisualBasicUseCollectionInitializerCodeFixProvider.vb index 460742a47e92a..0d87827a3a17e 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/UseCollectionInitializer/VisualBasicUseCollectionInitializerCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/UseCollectionInitializer/VisualBasicUseCollectionInitializerCodeFixProvider.vb @@ -31,7 +31,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseCollectionInitializer Protected Overrides Function GetNewStatement( statement As StatementSyntax, objectCreation As ObjectCreationExpressionSyntax, - matches As ImmutableArray(Of ExpressionStatementSyntax)) As StatementSyntax + matches As ImmutableArray(Of ExpressionStatementSyntax), + addTrailingComma As Boolean) As StatementSyntax Dim newStatement = statement.ReplaceNode( objectCreation, GetNewObjectCreation(objectCreation, matches)) diff --git a/src/Analyzers/VisualBasic/CodeFixes/UseObjectInitializer/VisualBasicUseObjectInitializerCodeFixProvider.vb b/src/Analyzers/VisualBasic/CodeFixes/UseObjectInitializer/VisualBasicUseObjectInitializerCodeFixProvider.vb index 42246781a8558..7846d19d084f5 100644 --- a/src/Analyzers/VisualBasic/CodeFixes/UseObjectInitializer/VisualBasicUseObjectInitializerCodeFixProvider.vb +++ b/src/Analyzers/VisualBasic/CodeFixes/UseObjectInitializer/VisualBasicUseObjectInitializerCodeFixProvider.vb @@ -29,7 +29,8 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseObjectInitializer Protected Overrides Function GetNewStatement( statement As StatementSyntax, objectCreation As ObjectCreationExpressionSyntax, - matches As ImmutableArray(Of Match(Of ExpressionSyntax, StatementSyntax, MemberAccessExpressionSyntax, AssignmentStatementSyntax))) As StatementSyntax + matches As ImmutableArray(Of Match(Of ExpressionSyntax, StatementSyntax, MemberAccessExpressionSyntax, AssignmentStatementSyntax)), + addTrailingComma As Boolean) As StatementSyntax Dim newStatement = statement.ReplaceNode( objectCreation, GetNewObjectCreation(objectCreation, matches)) diff --git a/src/EditorFeatures/CSharp/Simplification/CSharpSimplifierOptionsStorage.cs b/src/EditorFeatures/CSharp/Simplification/CSharpSimplifierOptionsStorage.cs index e5dec6cf5fd97..5e361b9705886 100644 --- a/src/EditorFeatures/CSharp/Simplification/CSharpSimplifierOptionsStorage.cs +++ b/src/EditorFeatures/CSharp/Simplification/CSharpSimplifierOptionsStorage.cs @@ -38,5 +38,6 @@ public static CSharpSimplifierOptions GetCSharpSimplifierOptions(this IGlobalOpt AllowEmbeddedStatementsOnSameLine = globalOptions.GetOption(CSharpCodeStyleOptions.AllowEmbeddedStatementsOnSameLine), PreferBraces = globalOptions.GetOption(CSharpCodeStyleOptions.PreferBraces), PreferThrowExpression = globalOptions.GetOption(CSharpCodeStyleOptions.PreferThrowExpression), + PreferTrailingComma = globalOptions.GetOption(CSharpCodeStyleOptions.PreferTrailingComma), }; } diff --git a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateEnumMember/GenerateEnumMemberTests.cs b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateEnumMember/GenerateEnumMemberTests.cs index 6214dc3078c50..d422e2940f97f 100644 --- a/src/EditorFeatures/CSharpTest/Diagnostics/GenerateEnumMember/GenerateEnumMemberTests.cs +++ b/src/EditorFeatures/CSharpTest/Diagnostics/GenerateEnumMember/GenerateEnumMemberTests.cs @@ -50,7 +50,7 @@ void Main() enum Color { - Red + Red, }"); } @@ -81,7 +81,7 @@ void Main() enum Color { Red, - Blue + Blue, }"); } @@ -145,7 +145,7 @@ enum Color { Red, Blue, - Green + Green, }"); } @@ -176,7 +176,7 @@ void Main() enum Color { Red = 0, - Blue = 1 + Blue = 1, }"); } @@ -207,7 +207,7 @@ void Main() enum Color { Red = 1, - Blue = 2 + Blue = 2, }"); } @@ -238,7 +238,7 @@ void Main() enum Color { Red = 2, - Blue = 4 + Blue = 4, }"); } @@ -273,7 +273,7 @@ enum Color Red = 1, Yellow = 2, Green = 4, - Blue = 8 + Blue = 8, }"); } @@ -306,7 +306,7 @@ enum Color { Red = 1, Green = 2, - Blue = 3 + Blue = 3, }"); } @@ -341,7 +341,7 @@ enum Color Yellow = 0, Red = 1, Green = 2, - Blue = 3 + Blue = 3, }"); } @@ -372,7 +372,7 @@ void Main() enum Color { Green = 5, - Blue = 6 + Blue = 6, }"); } @@ -403,7 +403,7 @@ void Main() enum Color { Green = 1 << 0, - Blue = 1 << 1 + Blue = 1 << 1, }"); } @@ -434,7 +434,7 @@ void Main() enum Color { Green = 1 << 5, - Blue = 1 << 6 + Blue = 1 << 6, }"); } @@ -467,7 +467,7 @@ enum Color { Red = 2, Green = 1 << 5, - Blue = 33 + Blue = 33, }"); } @@ -498,7 +498,7 @@ void Main() enum Color { Red = 0b01, - Blue = 0b10 + Blue = 0b10, }"); } @@ -529,7 +529,7 @@ void Main() enum Color { Red = 0x1, - Blue = 0x2 + Blue = 0x2, }"); } @@ -560,7 +560,7 @@ void Main() enum Color { Red = 0x9, - Blue = 0xA + Blue = 0xA, }"); } @@ -591,7 +591,7 @@ void Main() enum Color { Red = 0xF, - Blue = 0x10 + Blue = 0x10, }"); } @@ -622,7 +622,7 @@ void Main() enum Color { Red = int.MaxValue, - Blue = int.MinValue + Blue = int.MinValue, }"); } @@ -653,7 +653,7 @@ void Main() enum Color : ushort { Red = 65535, - Blue = 0 + Blue = 0, }"); } @@ -684,7 +684,7 @@ void Main() enum Color : long { Red = long.MaxValue, - Blue = long.MinValue + Blue = long.MinValue, }"); } @@ -715,7 +715,7 @@ void Main() enum Color : long { Red = 0b0111111111111111111111111111111111111111111111111111111111111111, - Blue = 0b1000000000000000000000000000000000000000000000000000000000000000 + Blue = 0b1000000000000000000000000000000000000000000000000000000000000000, }"); } @@ -746,7 +746,7 @@ void Main() enum Color : long { Red = 0x7FFFFFFFFFFFFFFF, - Blue = 0x8000000000000000 + Blue = 0x8000000000000000, }"); } @@ -778,7 +778,7 @@ void Main() enum Color : long { Red = 0xFFFFFFFFFFFFFFFF, - Blue + Blue, }"); } @@ -812,7 +812,7 @@ enum Color : long { Red = 0xFFFFFFFFFFFFFFFF, Green = 0x0, - Blue = 0x1 + Blue = 0x1, }"); } @@ -843,7 +843,7 @@ void Main() enum Color : long { Red = 0x414 / 2, - Blue = 523 + Blue = 523, }"); } @@ -874,7 +874,7 @@ void Main() enum Color : ulong { Red = ulong.MaxValue, - Blue = 0 + Blue = 0, }"); } @@ -905,7 +905,7 @@ void Main() enum Color : long { Red = -10, - Blue = -9 + Blue = -9, }"); } @@ -940,7 +940,7 @@ enum Color Red, Green, Yellow = -1, - Blue = 2 + Blue = 2, }"); } @@ -975,7 +975,7 @@ enum Color Red, Green = 10, Yellow, - Blue + Blue, }"); } @@ -1007,7 +1007,7 @@ static void Main(string[] args) enum Color { Red, - Blue + Blue, //Blue }"); } @@ -1040,7 +1040,7 @@ static void Main(string[] args) enum Color { Red, - Blue + Blue, /*Blue*/ }"); } @@ -1072,7 +1072,7 @@ void Main() enum Color { Red = int.MinValue, - Blue = -2147483647 + Blue = -2147483647, }"); } @@ -1103,7 +1103,7 @@ void Main() enum Color { Red = int.MinValue + 100, - Blue = -2147483547 + Blue = -2147483547, }"); } @@ -1134,7 +1134,7 @@ void Main() enum Color : byte { Red = 255, - Blue = 0 + Blue = 0, }"); } @@ -1167,7 +1167,7 @@ enum Color { Red = 1 << 1, Green = 1 << 2, - Blue = 1 << 3 + Blue = 1 << 3, }"); } @@ -1198,7 +1198,7 @@ void Main() enum Color { Red = 2 >> 1, - Blue = 2 + Blue = 2, }"); } @@ -1231,7 +1231,7 @@ enum Color { Red = int.MinValue, Green = 1, - Blue = 2 + Blue = 2, }"); } @@ -1264,7 +1264,7 @@ enum Circular { A = B, B, - C + C, }"); } @@ -1295,7 +1295,7 @@ void Main() enum Circular : byte { A = -2, - B + B, }"); } @@ -1334,7 +1334,7 @@ void Main() public new enum BaseColor { Yellow = 3, - Blue = 4 + Blue = 4, } } @@ -1383,7 +1383,7 @@ void Main() public enum BaseColor { Yellow = 3, - Blue = 4 + Blue = 4, } } @@ -1431,7 +1431,7 @@ public enum BaseColor { Red = 1, Green = 2, - Blue = 3 + Blue = 3, } }"); } @@ -1467,7 +1467,7 @@ enum Color Red, Green, Yellow = Green, - Blue = 2 + Blue = 2, }"); } @@ -1504,7 +1504,7 @@ enum Color Red, Green = 10, Yellow, - Blue + Blue, }"); } @@ -1533,7 +1533,7 @@ static void Main(string[] args) enum Weekday { Monday, - Tuesday + Tuesday, }"); } @@ -1565,7 +1565,7 @@ static void Main(string[] args) enum Color { Red, - @enum + @enum, }"); } @@ -1682,7 +1682,7 @@ void Main() { A = 1, B, - C + C, } class Program @@ -1721,7 +1721,7 @@ void Main() enum Color : long { Green = 1L << 0, - Blue = 1L << 1 + Blue = 1L << 1, }"); } @@ -1753,7 +1753,7 @@ void Main() enum Color : uint { Green = 1u << 0, - Blue = 1u << 1 + Blue = 1u << 1, }"); } @@ -1785,7 +1785,7 @@ void Main() enum Color : ulong { Green = 1UL << 0, - Blue = 1UL << 1 + Blue = 1UL << 1, }"); } } diff --git a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs index b9a2c1a17f0e5..d6234ce98bc15 100644 --- a/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs +++ b/src/EditorFeatures/CSharpTest/Formatting/CodeCleanupTests.cs @@ -579,8 +579,8 @@ public void VerifyAllCodeStyleFixersAreSupportedByCodeCleanup(string language) var expectedNumberOfUnsupportedDiagnosticIds = language switch { - LanguageNames.CSharp => 36, - LanguageNames.VisualBasic => 72, + LanguageNames.CSharp => 37, + LanguageNames.VisualBasic => 73, _ => throw ExceptionUtilities.UnexpectedValue(language), }; diff --git a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs index 529e64816b069..9ff8bc88518f3 100644 --- a/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs +++ b/src/EditorFeatures/Test/CodeGeneration/CodeGenerationTests.CSharp.cs @@ -374,7 +374,7 @@ public async Task AddEnumWithValues() public enum E { F1 = 1, - F2 = 2 + F2 = 2, } }"; await TestAddNamedTypeAsync(input, expected, "E", diff --git a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs index de1bdc34c1c21..e4d3ed1960ec7 100644 --- a/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/IDEDiagnosticIDConfigurationTests.cs @@ -393,6 +393,9 @@ public void CSharp_VerifyIDEDiagnosticSeveritiesAreConfigurable() # IDE0083 dotnet_diagnostic.IDE0083.severity = %value% +# IDE0085 +dotnet_diagnostic.IDE0085.severity = %value% + # IDE0090 dotnet_diagnostic.IDE0090.severity = %value% @@ -1016,6 +1019,9 @@ No editorconfig based code style option # IDE0083, PreferNotPattern csharp_style_prefer_not_pattern = true +# IDE0085, PreferTrailingComma +csharp_style_prefer_trailing_comma = true + # IDE0090, ImplicitObjectCreationWhenTypeIsApparent csharp_style_implicit_object_creation_when_type_is_apparent = true diff --git a/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs b/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs index 472c8900a3db7..ee33e713414b4 100644 --- a/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs +++ b/src/VisualStudio/CSharp/Impl/EditorConfigSettings/DataProvider/CodeStyle/CSharpCodeStyleSettingsProvider.cs @@ -153,6 +153,7 @@ private IEnumerable GetExpressionCodeStyleOptions(AnalyzerConf yield return CodeStyleSetting.Create(CSharpCodeStyleOptions.ImplicitObjectCreationWhenTypeIsApparent, description: CSharpVSResources.Prefer_implicit_object_creation_when_type_is_apparent, editorConfigOptions, visualStudioOptions, updaterService, FileName); yield return CodeStyleSetting.Create(CSharpCodeStyleOptions.PreferTupleSwap, description: ServicesVSResources.Prefer_tuple_swap, editorConfigOptions, visualStudioOptions, updaterService, FileName); yield return CodeStyleSetting.Create(CSharpCodeStyleOptions.PreferUtf8StringLiterals, description: ServicesVSResources.Prefer_Utf8_string_literals, editorConfigOptions, visualStudioOptions, updaterService, FileName); + yield return CodeStyleSetting.Create(CSharpCodeStyleOptions.PreferTrailingComma, description: ServicesVSResources.Prefer_trailing_comma, editorConfigOptions, visualStudioOptions, updaterService, FileName); } private IEnumerable GetPatternMatchingCodeStyleOptions(AnalyzerConfigOptions editorConfigOptions, OptionSet visualStudioOptions, OptionUpdater updaterService) diff --git a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs index 3f05de4804405..12ad10908fdf2 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs +++ b/src/VisualStudio/CSharp/Impl/Options/AutomationObject/AutomationObject.Style.cs @@ -423,5 +423,11 @@ public string Style_NamespaceDeclarations get { return GetXmlOption(CSharpCodeStyleOptions.NamespaceDeclarations); } set { SetXmlOption(CSharpCodeStyleOptions.NamespaceDeclarations, value); } } + + public string Style_PreferTrailingComma + { + get { return GetXmlOption(CSharpCodeStyleOptions.PreferTrailingComma); } + set { SetXmlOption(CSharpCodeStyleOptions.PreferTrailingComma, value); } + } } } diff --git a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs index 259db40d70bbd..369ad5c7a975f 100644 --- a/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs +++ b/src/VisualStudio/CSharp/Impl/Options/Formatting/StyleViewModel.cs @@ -1157,6 +1157,26 @@ void M2(string[] args) //] }} }} +"; + + private static readonly string s_preferTrailingComma = $@" +//[ + // {ServicesVSResources.Prefer_colon} + enum E + {{ + A, + B, + }} +//] +//[ + // {ServicesVSResources.Over_colon} + enum E + {{ + A, + B + }} +//] +}} "; private static readonly string s_preferIsNullOverReferenceEquals = $@" @@ -2156,6 +2176,8 @@ internal StyleViewModel(OptionStore optionStore, IServiceProvider serviceProvide CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferTupleSwap, ServicesVSResources.Prefer_tuple_swap, s_preferTupleSwap, s_preferTupleSwap, this, optionStore, expressionPreferencesGroupTitle)); + CodeStyleItems.Add(new BooleanCodeStyleOptionViewModel(CSharpCodeStyleOptions.PreferTrailingComma, ServicesVSResources.Prefer_trailing_comma, s_preferTrailingComma, s_preferTrailingComma, this, optionStore, expressionPreferencesGroupTitle)); + AddExpressionBodyOptions(optionStore, expressionPreferencesGroupTitle); AddUnusedValueOptions(optionStore, expressionPreferencesGroupTitle); diff --git a/src/VisualStudio/Core/Def/ServicesVSResources.resx b/src/VisualStudio/Core/Def/ServicesVSResources.resx index fff9f7a5d389e..09af9e62cfd9f 100644 --- a/src/VisualStudio/Core/Def/ServicesVSResources.resx +++ b/src/VisualStudio/Core/Def/ServicesVSResources.resx @@ -1949,6 +1949,9 @@ Additional information: {1} Prefer top-level statements + + Prefer trailing comma + {0} ({1}) Used to show a symbol name, followed by the project name it came from @@ -1983,4 +1986,4 @@ Additional information: {1} Stack Trace - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf index fddf66a8dabba..dba5fc99ef44c 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.cs.xlf @@ -1122,6 +1122,11 @@ Preferovat statické místní funkce + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements Upřednostňovat příkazy nejvyšší úrovně @@ -3015,4 +3020,4 @@ Další informace: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf index 3f444cac72d59..082444f4f157a 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.de.xlf @@ -1122,6 +1122,11 @@ Statische lokale Funktionen bevorzugen + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements Anweisungen der obersten Ebene bevorzugen @@ -3015,4 +3020,4 @@ Zusätzliche Informationen: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf index 257279434a2a8..8cfc71922ad80 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.es.xlf @@ -1122,6 +1122,11 @@ Preferir funciones locales estáticas + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements Preferir instrucciones de nivel superior @@ -3015,4 +3020,4 @@ Información adicional: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf index fc47f38d81b0b..d7cc1a72c4fcf 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.fr.xlf @@ -1122,6 +1122,11 @@ Préférer les fonctions locales statiques + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements Préférer les instructions de niveau supérieur @@ -3015,4 +3020,4 @@ Informations supplémentaires : {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf index fc06647784ed0..678ea0eac196a 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.it.xlf @@ -1122,6 +1122,11 @@ Preferisci funzioni locali statiche + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements Preferisci istruzioni di primo livello @@ -3015,4 +3020,4 @@ Informazioni aggiuntive: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf index fe33d8ffe8a94..08122583f27b1 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ja.xlf @@ -1122,6 +1122,11 @@ 静的ローカル関数を優先する + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements 最上位レベルのステートメントを優先する @@ -3015,4 +3020,4 @@ Additional information: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf index fb30763eadfab..7bfa4764e2d24 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ko.xlf @@ -1122,6 +1122,11 @@ 정적 로컬 함수 선호 + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements 최상위 문장 선호 @@ -3015,4 +3020,4 @@ Additional information: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf index 0f1541856bbf8..c9abfcbe21db2 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pl.xlf @@ -1122,6 +1122,11 @@ Preferuj statyczne funkcje lokalne + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements Preferuj instrukcje najwyższego poziomu @@ -3015,4 +3020,4 @@ Dodatkowe informacje: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf index 4ed8a787403a7..3717d8854550e 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.pt-BR.xlf @@ -1122,6 +1122,11 @@ Preferir as funções locais estáticas + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements Preferir instruções de alto-nível @@ -3015,4 +3020,4 @@ Informações adicionais: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf index 483d93b6bbc09..a5a6c70ca439f 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.ru.xlf @@ -1122,6 +1122,11 @@ Предпочитать статические локальные функции + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements Предпочитать инструкции верхнего уровня @@ -3015,4 +3020,4 @@ Additional information: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf index b5d1659f4c2e0..2e6144a75bc5d 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.tr.xlf @@ -1122,6 +1122,11 @@ Statik yerel işlevleri tercih et + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements Üst düzey deyimleri tercih et @@ -3015,4 +3020,4 @@ Ek bilgiler: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf index 9d5b55101ab3b..00cb95273a25b 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hans.xlf @@ -1122,6 +1122,11 @@ 首选静态本地函数 + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements 首选顶级语句 @@ -3015,4 +3020,4 @@ Additional information: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf index d77f77774cd5d..5fd95897f8416 100644 --- a/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf +++ b/src/VisualStudio/Core/Def/xlf/ServicesVSResources.zh-Hant.xlf @@ -1122,6 +1122,11 @@ 優先使用靜態區域函式 + + Prefer trailing comma + Prefer trailing comma + + Prefer top-level statements 偏好最上層陳述式 @@ -3015,4 +3020,4 @@ Additional information: {1} - \ No newline at end of file + diff --git a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb index 3250dcaac36da..49f50df89a17f 100644 --- a/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb +++ b/src/VisualStudio/Core/Test/Options/CSharpEditorConfigGeneratorTests.vb @@ -140,6 +140,7 @@ csharp_style_prefer_index_operator = true csharp_style_prefer_local_over_anonymous_function = true csharp_style_prefer_null_check_over_type_check = true csharp_style_prefer_range_operator = true +csharp_style_prefer_trailing_comma = true csharp_style_prefer_tuple_swap = true csharp_style_prefer_utf8_string_literals = true csharp_style_throw_expression = true @@ -377,6 +378,7 @@ csharp_style_prefer_index_operator = true csharp_style_prefer_local_over_anonymous_function = true csharp_style_prefer_null_check_over_type_check = true csharp_style_prefer_range_operator = true +csharp_style_prefer_trailing_comma = true csharp_style_prefer_tuple_swap = true csharp_style_prefer_utf8_string_literals = true csharp_style_throw_expression = true diff --git a/src/Workspaces/CSharp/Portable/CodeGeneration/EnumMemberGenerator.cs b/src/Workspaces/CSharp/Portable/CodeGeneration/EnumMemberGenerator.cs index 6e6c99071b326..2e2cb52e804e3 100644 --- a/src/Workspaces/CSharp/Portable/CodeGeneration/EnumMemberGenerator.cs +++ b/src/Workspaces/CSharp/Portable/CodeGeneration/EnumMemberGenerator.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading; using Microsoft.CodeAnalysis.CodeGeneration; +using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Shared.Utilities; @@ -25,22 +26,19 @@ internal static EnumDeclarationSyntax AddEnumMemberTo(EnumDeclarationSyntax dest var member = GenerateEnumMemberDeclaration(enumMember, destination, info, cancellationToken); - if (members.Count == 0) - { - members.Add(member); - } - else if (members.LastOrDefault().Kind() == SyntaxKind.CommaToken) - { - members.Add(member); - members.Add(SyntaxFactory.Token(SyntaxKind.CommaToken)); - } - else + if (members.Count > 0 && !members[^1].IsKind(SyntaxKind.CommaToken)) { var lastMember = members.Last(); var trailingTrivia = lastMember.GetTrailingTrivia(); members[members.Count - 1] = lastMember.WithTrailingTrivia(); members.Add(SyntaxFactory.Token(SyntaxKind.CommaToken).WithTrailingTrivia(trailingTrivia)); - members.Add(member); + } + + members.Add(member); + + if (info.Options.PreferTrailingComma.Value) + { + members.Add(SyntaxFactory.Token(SyntaxKind.CommaToken)); } return destination.EnsureOpenAndCloseBraceTokens() diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeGeneration/CSharpCodeGenerationOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeGeneration/CSharpCodeGenerationOptions.cs index fec7b194448a8..472c46c491220 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeGeneration/CSharpCodeGenerationOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeGeneration/CSharpCodeGenerationOptions.cs @@ -46,6 +46,7 @@ internal sealed class CSharpCodeGenerationOptions : CodeGenerationOptions, IEqua [DataMember] public CodeStyleOption2 PreferExpressionBodiedLambdas { get; init; } = s_whenPossibleWithSilentEnforcement; [DataMember] public CodeStyleOption2 PreferStaticLocalFunction { get; init; } = s_trueWithSuggestionEnforcement; [DataMember] public CodeStyleOption2 NamespaceDeclarations { get; init; } = s_blockedScopedWithSilentEnforcement; + [DataMember] public CodeStyleOption2 PreferTrailingComma { get; init; } = s_trueWithSuggestionEnforcement; public override bool Equals(object? obj) => Equals(obj as CSharpCodeGenerationOptions); @@ -62,7 +63,8 @@ public bool Equals(CSharpCodeGenerationOptions? other) PreferExpressionBodiedLocalFunctions.Equals(other.PreferExpressionBodiedLocalFunctions) && PreferExpressionBodiedLambdas.Equals(other.PreferExpressionBodiedLambdas) && PreferStaticLocalFunction.Equals(other.PreferStaticLocalFunction) && - NamespaceDeclarations.Equals(other.NamespaceDeclarations); + NamespaceDeclarations.Equals(other.NamespaceDeclarations) && + PreferTrailingComma.Equals(other.PreferTrailingComma); public override int GetHashCode() => Hash.Combine(Common, @@ -75,7 +77,8 @@ public override int GetHashCode() Hash.Combine(PreferExpressionBodiedLocalFunctions, Hash.Combine(PreferExpressionBodiedLambdas, Hash.Combine(PreferStaticLocalFunction, - Hash.Combine(NamespaceDeclarations, 0))))))))))); + Hash.Combine(NamespaceDeclarations, + Hash.Combine(PreferTrailingComma, 0)))))))))))); #if !CODE_STYLE public override CodeGenerationContextInfo GetInfo(CodeGenerationContext context, ParseOptions parseOptions) @@ -101,7 +104,8 @@ public static CSharpCodeGenerationOptions GetCSharpCodeGenerationOptions(this An PreferExpressionBodiedLocalFunctions = options.GetEditorConfigOption(CSharpCodeStyleOptions.PreferExpressionBodiedLocalFunctions, fallbackOptions.PreferExpressionBodiedLocalFunctions), PreferExpressionBodiedLambdas = options.GetEditorConfigOption(CSharpCodeStyleOptions.PreferExpressionBodiedLambdas, fallbackOptions.PreferExpressionBodiedLambdas), PreferStaticLocalFunction = options.GetEditorConfigOption(CSharpCodeStyleOptions.PreferStaticLocalFunction, fallbackOptions.PreferStaticLocalFunction), - NamespaceDeclarations = options.GetEditorConfigOption(CSharpCodeStyleOptions.NamespaceDeclarations, fallbackOptions.NamespaceDeclarations) + NamespaceDeclarations = options.GetEditorConfigOption(CSharpCodeStyleOptions.NamespaceDeclarations, fallbackOptions.NamespaceDeclarations), + PreferTrailingComma = options.GetEditorConfigOption(CSharpCodeStyleOptions.PreferTrailingComma, fallbackOptions.PreferTrailingComma), }; } } diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs index a71d4d5bb9d59..7a3a042b6d877 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/CodeStyle/CSharpCodeStyleOptions.cs @@ -334,6 +334,12 @@ private static Option2> CreateN "csharp_style_prefer_method_group_conversion", "TextEditor.CSharp.Specific.PreferMethodGroupConversion"); + public static readonly Option2> PreferTrailingComma = CreateOption( + CSharpCodeStyleOptionGroups.ExpressionLevelPreferences, nameof(PreferTrailingComma), + defaultValue: s_trueWithSilentEnforcement, + "csharp_style_prefer_trailing_comma", + $"TextEditor.CSharp.Specific.{nameof(PreferTrailingComma)}"); + public static readonly Option2> PreferTopLevelStatements = CreateOption( CSharpCodeStyleOptionGroups.CodeBlockPreferences, nameof(PreferTopLevelStatements), CSharpSyntaxFormattingOptions.Default.PreferTopLevelStatements, diff --git a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/CSharpSimplifierOptions.cs b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/CSharpSimplifierOptions.cs index 5d726afe1e390..5852f39862d03 100644 --- a/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/CSharpSimplifierOptions.cs +++ b/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/CSharp/Simplification/CSharpSimplifierOptions.cs @@ -36,6 +36,7 @@ internal sealed class CSharpSimplifierOptions : SimplifierOptions, IEquatable AllowEmbeddedStatementsOnSameLine { get; init; } = s_trueWithSilentEnforcement; [DataMember] public CodeStyleOption2 PreferBraces { get; init; } = s_defaultPreferBraces; [DataMember] public CodeStyleOption2 PreferThrowExpression { get; init; } = s_trueWithSuggestionEnforcement; + [DataMember] public CodeStyleOption2 PreferTrailingComma { get; init; } = s_trueWithSuggestionEnforcement; public override bool Equals(object? obj) => Equals(obj as CSharpSimplifierOptions); @@ -50,7 +51,8 @@ public bool Equals([AllowNull] CSharpSimplifierOptions other) PreferParameterNullChecking.Equals(other.PreferParameterNullChecking) && AllowEmbeddedStatementsOnSameLine.Equals(other.AllowEmbeddedStatementsOnSameLine) && PreferBraces.Equals(other.PreferBraces) && - PreferThrowExpression.Equals(other.PreferThrowExpression); + PreferThrowExpression.Equals(other.PreferThrowExpression) && + PreferTrailingComma.Equals(other.PreferTrailingComma); public override int GetHashCode() => Hash.Combine(VarForBuiltInTypes, @@ -60,7 +62,8 @@ public override int GetHashCode() Hash.Combine(PreferParameterNullChecking, Hash.Combine(AllowEmbeddedStatementsOnSameLine, Hash.Combine(PreferBraces, - Hash.Combine(PreferThrowExpression, 0)))))))); + Hash.Combine(PreferThrowExpression, + Hash.Combine(PreferTrailingComma, 0))))))))); } internal static class CSharpSimplifierOptionsProviders @@ -78,7 +81,8 @@ public static CSharpSimplifierOptions GetCSharpSimplifierOptions(this AnalyzerCo PreferSimpleDefaultExpression = options.GetEditorConfigOption(CSharpCodeStyleOptions.PreferSimpleDefaultExpression, fallbackOptions.PreferSimpleDefaultExpression), AllowEmbeddedStatementsOnSameLine = options.GetEditorConfigOption(CSharpCodeStyleOptions.AllowEmbeddedStatementsOnSameLine, fallbackOptions.AllowEmbeddedStatementsOnSameLine), PreferBraces = options.GetEditorConfigOption(CSharpCodeStyleOptions.PreferBraces, fallbackOptions.PreferBraces), - PreferThrowExpression = options.GetEditorConfigOption(CSharpCodeStyleOptions.PreferThrowExpression, fallbackOptions.PreferThrowExpression) + PreferThrowExpression = options.GetEditorConfigOption(CSharpCodeStyleOptions.PreferThrowExpression, fallbackOptions.PreferThrowExpression), + PreferTrailingComma = options.GetEditorConfigOption(CSharpCodeStyleOptions.PreferTrailingComma, fallbackOptions.PreferTrailingComma), }; } }