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}