diff --git a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
index 9c3b89c6fed3c..52f82c2892dfb 100644
--- a/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
+++ b/src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems
@@ -81,6 +81,7 @@
+
diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForArrayDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForArrayDiagnosticAnalyzer.cs
index eedf9668b1e97..41c1aeec94769 100644
--- a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForArrayDiagnosticAnalyzer.cs
+++ b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/CSharpUseCollectionExpressionForArrayDiagnosticAnalyzer.cs
@@ -4,29 +4,22 @@
using System.Collections.Immutable;
using System.Diagnostics;
-using System.Linq;
-using System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
+using Microsoft.CodeAnalysis.CSharp.Analyzers.UseCollectionExpression;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
-using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Options;
-using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.CSharp.UseCollectionExpression;
-using static SyntaxFactory;
-
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed partial class CSharpUseCollectionExpressionForArrayDiagnosticAnalyzer
: AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
- private static readonly CollectionExpressionSyntax s_emptyCollectionExpression = CollectionExpression();
-
public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
@@ -56,7 +49,7 @@ protected override void InitializeWorker(AnalysisContext context)
private void OnCompilationStart(CompilationStartAnalysisContext context)
{
- if (!context.Compilation.LanguageVersion().IsCSharp12OrAbove())
+ if (!context.Compilation.LanguageVersion().SupportsCollectionExpressions())
return;
// We wrap the SyntaxNodeAction within a CodeBlockStartAction, which allows us to
@@ -72,101 +65,6 @@ private void OnCompilationStart(CompilationStartAnalysisContext context)
});
}
- private static bool IsInTargetTypedLocation(SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken)
- {
- var topExpression = expression.WalkUpParentheses();
- var parent = topExpression.Parent;
- return parent switch
- {
- EqualsValueClauseSyntax equalsValue => IsInTargetTypedEqualsValueClause(equalsValue),
- CastExpressionSyntax castExpression => IsInTargetTypedCastExpression(castExpression),
- // a ? [1, 2, 3] : ... is target typed if either the other side is *not* a collection,
- // or the entire ternary is target typed itself.
- ConditionalExpressionSyntax conditionalExpression => IsInTargetTypedConditionalExpression(conditionalExpression, topExpression),
- // Similar rules for switches.
- SwitchExpressionArmSyntax switchExpressionArm => IsInTargetTypedSwitchExpressionArm(switchExpressionArm),
- InitializerExpressionSyntax initializerExpression => IsInTargetTypedInitializerExpression(initializerExpression, topExpression),
- AssignmentExpressionSyntax assignmentExpression => IsInTargetTypedAssignmentExpression(assignmentExpression, topExpression),
- BinaryExpressionSyntax binaryExpression => IsInTargetTypedBinaryExpression(binaryExpression, topExpression),
- ArgumentSyntax or AttributeArgumentSyntax => true,
- ReturnStatementSyntax => true,
- _ => false,
- };
-
- bool HasType(ExpressionSyntax expression)
- => semanticModel.GetTypeInfo(expression, cancellationToken).Type != null;
-
- static bool IsInTargetTypedEqualsValueClause(EqualsValueClauseSyntax equalsValue)
- // If we're after an `x = ...` and it's not `var x`, this is target typed.
- => equalsValue.Parent is not VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type.IsVar: true } };
-
- static bool IsInTargetTypedCastExpression(CastExpressionSyntax castExpression)
- // (X[])[1, 2, 3] is target typed. `(X)[1, 2, 3]` is currently not (because it looks like indexing into an expr).
- => castExpression.Type is not IdentifierNameSyntax;
-
- bool IsInTargetTypedConditionalExpression(ConditionalExpressionSyntax conditionalExpression, ExpressionSyntax expression)
- {
- if (conditionalExpression.WhenTrue == expression)
- return HasType(conditionalExpression.WhenFalse) || IsInTargetTypedLocation(semanticModel, conditionalExpression, cancellationToken);
- else if (conditionalExpression.WhenFalse == expression)
- return HasType(conditionalExpression.WhenTrue) || IsInTargetTypedLocation(semanticModel, conditionalExpression, cancellationToken);
- else
- return false;
- }
-
- bool IsInTargetTypedSwitchExpressionArm(SwitchExpressionArmSyntax switchExpressionArm)
- {
- var switchExpression = (SwitchExpressionSyntax)switchExpressionArm.GetRequiredParent();
-
- // check if any other arm has a type that this would be target typed against.
- foreach (var arm in switchExpression.Arms)
- {
- if (arm != switchExpressionArm && HasType(arm.Expression))
- return true;
- }
-
- // All arms do not have a type, this is target typed if the switch itself is target typed.
- return IsInTargetTypedLocation(semanticModel, switchExpression, cancellationToken);
- }
-
- bool IsInTargetTypedInitializerExpression(InitializerExpressionSyntax initializerExpression, ExpressionSyntax expression)
- {
- // new X[] { [1, 2, 3] }. Elements are target typed by array type.
- if (initializerExpression.Parent is ArrayCreationExpressionSyntax)
- return true;
-
- // new [] { [1, 2, 3], ... }. Elements are target typed if there's another element with real type.
- if (initializerExpression.Parent is ImplicitArrayCreationExpressionSyntax)
- {
- foreach (var sibling in initializerExpression.Expressions)
- {
- if (sibling != expression && HasType(sibling))
- return true;
- }
- }
-
- // TODO: Handle these.
- if (initializerExpression.Parent is StackAllocArrayCreationExpressionSyntax or ImplicitStackAllocArrayCreationExpressionSyntax)
- return false;
-
- // T[] x = [1, 2, 3];
- if (initializerExpression.Parent is EqualsValueClauseSyntax)
- return true;
-
- return false;
- }
-
- bool IsInTargetTypedAssignmentExpression(AssignmentExpressionSyntax assignmentExpression, ExpressionSyntax expression)
- {
- return expression == assignmentExpression.Right && HasType(assignmentExpression.Left);
- }
-
- bool IsInTargetTypedBinaryExpression(BinaryExpressionSyntax binaryExpression, ExpressionSyntax expression)
- {
- return binaryExpression.Kind() == SyntaxKind.CoalesceExpression && binaryExpression.Right == expression && HasType(binaryExpression.Left);
- }
- }
-
private static void AnalyzeArrayInitializer(SyntaxNodeAnalysisContext context)
{
var semanticModel = context.SemanticModel;
@@ -179,74 +77,38 @@ private static void AnalyzeArrayInitializer(SyntaxNodeAnalysisContext context)
if (!option.Value)
return;
- if (initializer.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error))
- return;
-
- var parent = initializer.GetRequiredParent();
- var topmostExpression = parent is ExpressionSyntax parentExpression
- ? parentExpression.WalkUpParentheses()
- : initializer.WalkUpParentheses();
+ var isConcreteOrImplicitArrayCreation = initializer.Parent is ArrayCreationExpressionSyntax or ImplicitArrayCreationExpressionSyntax;
- if (!IsInTargetTypedLocation(semanticModel, topmostExpression, cancellationToken))
+ // a naked `{ ... }` can only be converted to a collection expression when in the exact form `x = { ... }`
+ if (!isConcreteOrImplicitArrayCreation && initializer.Parent is not EqualsValueClauseSyntax)
return;
- var isConcreteOrImplicitArrayCreation = parent is ArrayCreationExpressionSyntax or ImplicitArrayCreationExpressionSyntax;
- if (isConcreteOrImplicitArrayCreation)
- {
- // X[] = new Y[] { 1, 2, 3 }
- //
- // First, we don't change things if X and Y are different. That could lead to something observable at
- // runtime in the case of something like: object[] x = new string[] ...
-
- var typeInfo = semanticModel.GetTypeInfo(parent, cancellationToken);
- if (typeInfo.Type is null or IErrorTypeSymbol ||
- typeInfo.ConvertedType is null or IErrorTypeSymbol)
- {
- return;
- }
+ var arrayCreationExpression = isConcreteOrImplicitArrayCreation
+ ? (ExpressionSyntax)initializer.GetRequiredParent()
+ : initializer;
- if (!typeInfo.Type.Equals(typeInfo.ConvertedType))
- return;
- }
- else if (parent is not EqualsValueClauseSyntax)
+ if (!UseCollectionExpressionHelpers.CanReplaceWithCollectionExpression(
+ semanticModel, arrayCreationExpression, cancellationToken))
{
return;
}
- // Looks good as something to replace. Now check the semantics of making the replacement to see if there would
- // any issues. To keep things simple, all we do is replace the existing expression with the `[]` literal. This
- // will tell us if we have problems assigning a collection expression to teh target type.
- //
- // Note: this does mean certain unambiguous cases with overloads (like `Goo(int[] values)` vs `Goo(string[]
- // values)`) will not get simplification. We can revisit this in the future to see if that warrants a more
- // expensive check that involves checking the consitutuent elements of the literal.
- var speculationAnalyzer = new SpeculationAnalyzer(
- topmostExpression,
- s_emptyCollectionExpression,
- semanticModel,
- cancellationToken,
- skipVerificationForReplacedNode: false,
- failOnOverloadResolutionFailuresInOriginalCode: true);
-
- if (speculationAnalyzer.ReplacementChangesSemantics())
- return;
-
if (isConcreteOrImplicitArrayCreation)
{
var locations = ImmutableArray.Create(initializer.GetLocation());
context.ReportDiagnostic(DiagnosticHelper.Create(
s_descriptor,
- parent.GetFirstToken().GetLocation(),
+ arrayCreationExpression.GetFirstToken().GetLocation(),
option.Notification.Severity,
additionalLocations: locations,
properties: null));
var additionalUnnecessaryLocations = ImmutableArray.Create(
syntaxTree.GetLocation(TextSpan.FromBounds(
- parent.SpanStart,
- parent is ArrayCreationExpressionSyntax arrayCreation
+ arrayCreationExpression.SpanStart,
+ arrayCreationExpression is ArrayCreationExpressionSyntax arrayCreation
? arrayCreation.Type.Span.End
- : ((ImplicitArrayCreationExpressionSyntax)parent).CloseBracketToken.Span.End)));
+ : ((ImplicitArrayCreationExpressionSyntax)arrayCreationExpression).CloseBracketToken.Span.End)));
context.ReportDiagnostic(DiagnosticHelper.CreateWithLocationTags(
s_unnecessaryCodeDescriptor,
@@ -257,7 +119,7 @@ parent is ArrayCreationExpressionSyntax arrayCreation
}
else
{
- Debug.Assert(parent is EqualsValueClauseSyntax);
+ Debug.Assert(initializer.Parent is EqualsValueClauseSyntax);
// int[] = { 1, 2, 3 };
//
// In this case, we always have a target type, so it should always be valid to convert this to a collection expression.
diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs
new file mode 100644
index 0000000000000..3614cdf460a0e
--- /dev/null
+++ b/src/Analyzers/CSharp/Analyzers/UseCollectionExpression/UseCollectionExpressionHelpers.cs
@@ -0,0 +1,162 @@
+// 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.Linq;
+using System.Threading;
+using Microsoft.CodeAnalysis.CSharp.Extensions;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+using Microsoft.CodeAnalysis.CSharp.Utilities;
+using Microsoft.CodeAnalysis.Shared.Extensions;
+
+namespace Microsoft.CodeAnalysis.CSharp.Analyzers.UseCollectionExpression;
+
+using static SyntaxFactory;
+
+internal static class UseCollectionExpressionHelpers
+{
+ private static readonly LiteralExpressionSyntax s_nullLiteralExpression = LiteralExpression(SyntaxKind.NullLiteralExpression);
+
+ public static bool CanReplaceWithCollectionExpression(
+ SemanticModel semanticModel,
+ ExpressionSyntax expression,
+ CancellationToken cancellationToken)
+ {
+ var topMostExpression = expression.WalkUpParentheses();
+ if (topMostExpression.GetDiagnostics().Any(d => d.Severity == DiagnosticSeverity.Error))
+ return false;
+
+ var parent = topMostExpression.GetRequiredParent();
+
+ if (!IsInTargetTypedLocation(semanticModel, topMostExpression, cancellationToken))
+ return false;
+
+ // X[] = new Y[] { 1, 2, 3 }
+ //
+ // First, we don't change things if X and Y are different. That could lead to something observable at
+ // runtime in the case of something like: object[] x = new string[] ...
+
+ var typeInfo = semanticModel.GetTypeInfo(topMostExpression, cancellationToken);
+ if (typeInfo.Type is IErrorTypeSymbol)
+ return false;
+
+ if (typeInfo.ConvertedType is null or IErrorTypeSymbol)
+ return false;
+
+ if (typeInfo.Type != null && !typeInfo.Type.Equals(typeInfo.ConvertedType))
+ return false;
+
+ // Looks good as something to replace. Now check the semantics of making the replacement to see if there would
+ // any issues. To keep things simple, all we do is replace the existing expression with the `null` literal.
+ // This is a similarly 'untyped' literal (like a collection-expression is), so it tells us if the new code will
+ // have any issues moving to something untyped. This will also tell us if we have any ambiguities (because
+ // there are multiple destination types that could accept the collection expression).
+ var speculationAnalyzer = new SpeculationAnalyzer(
+ topMostExpression,
+ s_nullLiteralExpression,
+ semanticModel,
+ cancellationToken,
+ skipVerificationForReplacedNode: true,
+ failOnOverloadResolutionFailuresInOriginalCode: true);
+
+ if (speculationAnalyzer.ReplacementChangesSemantics())
+ return false;
+
+ return true;
+ }
+
+ private static bool IsInTargetTypedLocation(SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken)
+ {
+ var topExpression = expression.WalkUpParentheses();
+ var parent = topExpression.Parent;
+ return parent switch
+ {
+ EqualsValueClauseSyntax equalsValue => IsInTargetTypedEqualsValueClause(equalsValue),
+ CastExpressionSyntax castExpression => IsInTargetTypedCastExpression(castExpression),
+ // a ? [1, 2, 3] : ... is target typed if either the other side is *not* a collection,
+ // or the entire ternary is target typed itself.
+ ConditionalExpressionSyntax conditionalExpression => IsInTargetTypedConditionalExpression(conditionalExpression, topExpression),
+ // Similar rules for switches.
+ SwitchExpressionArmSyntax switchExpressionArm => IsInTargetTypedSwitchExpressionArm(switchExpressionArm),
+ InitializerExpressionSyntax initializerExpression => IsInTargetTypedInitializerExpression(initializerExpression, topExpression),
+ AssignmentExpressionSyntax assignmentExpression => IsInTargetTypedAssignmentExpression(assignmentExpression, topExpression),
+ BinaryExpressionSyntax binaryExpression => IsInTargetTypedBinaryExpression(binaryExpression, topExpression),
+ ArgumentSyntax or AttributeArgumentSyntax => true,
+ ReturnStatementSyntax => true,
+ _ => false,
+ };
+
+ bool HasType(ExpressionSyntax expression)
+ => semanticModel.GetTypeInfo(expression, cancellationToken).Type != null;
+
+ static bool IsInTargetTypedEqualsValueClause(EqualsValueClauseSyntax equalsValue)
+ // If we're after an `x = ...` and it's not `var x`, this is target typed.
+ => equalsValue.Parent is not VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Type.IsVar: true } };
+
+ static bool IsInTargetTypedCastExpression(CastExpressionSyntax castExpression)
+ // (X[])[1, 2, 3] is target typed. `(X)[1, 2, 3]` is currently not (because it looks like indexing into an expr).
+ => castExpression.Type is not IdentifierNameSyntax;
+
+ bool IsInTargetTypedConditionalExpression(ConditionalExpressionSyntax conditionalExpression, ExpressionSyntax expression)
+ {
+ if (conditionalExpression.WhenTrue == expression)
+ return HasType(conditionalExpression.WhenFalse) || IsInTargetTypedLocation(semanticModel, conditionalExpression, cancellationToken);
+ else if (conditionalExpression.WhenFalse == expression)
+ return HasType(conditionalExpression.WhenTrue) || IsInTargetTypedLocation(semanticModel, conditionalExpression, cancellationToken);
+ else
+ return false;
+ }
+
+ bool IsInTargetTypedSwitchExpressionArm(SwitchExpressionArmSyntax switchExpressionArm)
+ {
+ var switchExpression = (SwitchExpressionSyntax)switchExpressionArm.GetRequiredParent();
+
+ // check if any other arm has a type that this would be target typed against.
+ foreach (var arm in switchExpression.Arms)
+ {
+ if (arm != switchExpressionArm && HasType(arm.Expression))
+ return true;
+ }
+
+ // All arms do not have a type, this is target typed if the switch itself is target typed.
+ return IsInTargetTypedLocation(semanticModel, switchExpression, cancellationToken);
+ }
+
+ bool IsInTargetTypedInitializerExpression(InitializerExpressionSyntax initializerExpression, ExpressionSyntax expression)
+ {
+ // new X[] { [1, 2, 3] }. Elements are target typed by array type.
+ if (initializerExpression.Parent is ArrayCreationExpressionSyntax)
+ return true;
+
+ // new [] { [1, 2, 3], ... }. Elements are target typed if there's another element with real type.
+ if (initializerExpression.Parent is ImplicitArrayCreationExpressionSyntax)
+ {
+ foreach (var sibling in initializerExpression.Expressions)
+ {
+ if (sibling != expression && HasType(sibling))
+ return true;
+ }
+ }
+
+ // TODO: Handle these.
+ if (initializerExpression.Parent is StackAllocArrayCreationExpressionSyntax or ImplicitStackAllocArrayCreationExpressionSyntax)
+ return false;
+
+ // T[] x = [1, 2, 3];
+ if (initializerExpression.Parent is EqualsValueClauseSyntax)
+ return true;
+
+ return false;
+ }
+
+ bool IsInTargetTypedAssignmentExpression(AssignmentExpressionSyntax assignmentExpression, ExpressionSyntax expression)
+ {
+ return expression == assignmentExpression.Right && HasType(assignmentExpression.Left);
+ }
+
+ bool IsInTargetTypedBinaryExpression(BinaryExpressionSyntax binaryExpression, ExpressionSyntax expression)
+ {
+ return binaryExpression.Kind() == SyntaxKind.CoalesceExpression && binaryExpression.Right == expression && HasType(binaryExpression.Left);
+ }
+ }
+}
diff --git a/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs
index 8a3cdd0f30d61..c77375394ff8f 100644
--- a/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs
+++ b/src/Analyzers/CSharp/Analyzers/UseCollectionInitializer/CSharpUseCollectionInitializerDiagnosticAnalyzer.cs
@@ -2,8 +2,11 @@
// 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.Threading;
+using Microsoft.CodeAnalysis.CSharp.Analyzers.UseCollectionExpression;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
+using Microsoft.CodeAnalysis.CSharp.Shared.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageService;
@@ -21,11 +24,21 @@ internal class CSharpUseCollectionInitializerDiagnosticAnalyzer :
MemberAccessExpressionSyntax,
InvocationExpressionSyntax,
ExpressionStatementSyntax,
+ ForEachStatementSyntax,
VariableDeclaratorSyntax>
{
protected override bool AreCollectionInitializersSupported(Compilation compilation)
=> compilation.LanguageVersion() >= LanguageVersion.CSharp3;
+ protected override bool AreCollectionExpressionsSupported(Compilation compilation)
+ => compilation.LanguageVersion().SupportsCollectionExpressions();
+
protected override ISyntaxFacts GetSyntaxFacts() => CSharpSyntaxFacts.Instance;
+
+ protected override bool CanUseCollectionExpression
+ (SemanticModel semanticModel, BaseObjectCreationExpressionSyntax objectCreationExpression, CancellationToken cancellationToken)
+ {
+ return UseCollectionExpressionHelpers.CanReplaceWithCollectionExpression(semanticModel, objectCreationExpression, cancellationToken);
+ }
}
}
diff --git a/src/Analyzers/CSharp/Analyzers/UsePrimaryConstructor/CSharpUsePrimaryConstructorDiagnosticAnalyzer.cs b/src/Analyzers/CSharp/Analyzers/UsePrimaryConstructor/CSharpUsePrimaryConstructorDiagnosticAnalyzer.cs
index 919a4862761c9..64ad93cb23a7e 100644
--- a/src/Analyzers/CSharp/Analyzers/UsePrimaryConstructor/CSharpUsePrimaryConstructorDiagnosticAnalyzer.cs
+++ b/src/Analyzers/CSharp/Analyzers/UsePrimaryConstructor/CSharpUsePrimaryConstructorDiagnosticAnalyzer.cs
@@ -73,9 +73,7 @@ protected override void InitializeWorker(AnalysisContext context)
{
context.RegisterCompilationStartAction(context =>
{
- // "x is not Type y" is only available in C# 9.0 and above. Don't offer this refactoring
- // in projects targeting a lesser version.
- if (!context.Compilation.LanguageVersion().IsCSharp12OrAbove())
+ if (!context.Compilation.LanguageVersion().SupportsPrimaryConstructors())
return;
// Mapping from a named type to a particular analyzer we have created for it. Needed because nested
diff --git a/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs
index 931a70bb5063d..469fb3530944a 100644
--- a/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs
+++ b/src/Analyzers/CSharp/CodeFixes/UseCollectionInitializer/CSharpUseCollectionInitializerCodeFixProvider.cs
@@ -7,16 +7,21 @@
using System.Composition;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.UseObjectInitializer;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
+using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UseCollectionInitializer;
+using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp.UseCollectionInitializer
{
+ using static SyntaxFactory;
+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseCollectionInitializer), Shared]
internal class CSharpUseCollectionInitializerCodeFixProvider :
AbstractUseCollectionInitializerCodeFixProvider<
@@ -27,6 +32,7 @@ internal class CSharpUseCollectionInitializerCodeFixProvider :
MemberAccessExpressionSyntax,
InvocationExpressionSyntax,
ExpressionStatementSyntax,
+ ForEachStatementSyntax,
VariableDeclaratorSyntax>
{
[ImportingConstructor]
@@ -36,80 +42,67 @@ public CSharpUseCollectionInitializerCodeFixProvider()
}
protected override StatementSyntax GetNewStatement(
+ SourceText sourceText,
StatementSyntax statement,
BaseObjectCreationExpressionSyntax objectCreation,
- ImmutableArray matches)
+ int wrappingLength,
+ bool useCollectionExpression,
+ ImmutableArray> matches)
{
return statement.ReplaceNode(
objectCreation,
- GetNewObjectCreation(objectCreation, matches));
+ GetNewObjectCreation(sourceText, objectCreation, wrappingLength, useCollectionExpression, matches));
}
- private static BaseObjectCreationExpressionSyntax GetNewObjectCreation(
+ private static ExpressionSyntax GetNewObjectCreation(
+ SourceText sourceText,
BaseObjectCreationExpressionSyntax objectCreation,
- ImmutableArray matches)
+ int wrappingLength,
+ bool useCollectionExpression,
+ ImmutableArray> matches)
{
- return UseInitializerHelpers.GetNewObjectCreation(
- objectCreation, CreateExpressions(objectCreation, matches));
+ return useCollectionExpression
+ ? CreateCollectionExpression(objectCreation, matches, MakeMultiLine(sourceText, objectCreation, matches, wrappingLength))
+ : CreateObjectInitializerExpression(objectCreation, matches);
}
- private static SeparatedSyntaxList CreateExpressions(
+ private static BaseObjectCreationExpressionSyntax CreateObjectInitializerExpression(
BaseObjectCreationExpressionSyntax objectCreation,
- ImmutableArray matches)
+ ImmutableArray> matches)
{
- using var _ = ArrayBuilder.GetInstance(out var nodesAndTokens);
-
- UseInitializerHelpers.AddExistingItems(objectCreation, nodesAndTokens);
-
- for (var i = 0; i < matches.Length; i++)
- {
- var expressionStatement = matches[i];
- var trivia = expressionStatement.GetLeadingTrivia();
-
- var newTrivia = i == 0 ? trivia.WithoutLeadingBlankLines() : trivia;
-
- var newExpression = ConvertExpression(expressionStatement.Expression)
- .WithoutTrivia()
- .WithPrependedLeadingTrivia(newTrivia);
+ var expressions = CreateElements(objectCreation, matches, static (_, e) => e);
+ var withLineBreaks = AddLineBreaks(expressions, includeFinalLineBreak: true);
+ return UseInitializerHelpers.GetNewObjectCreation(objectCreation, withLineBreaks);
+ }
- if (i < matches.Length - 1)
- {
- nodesAndTokens.Add(newExpression);
- var commaToken = SyntaxFactory.Token(SyntaxKind.CommaToken)
- .WithTriviaFrom(expressionStatement.SemicolonToken);
+ private static CollectionExpressionSyntax CreateCollectionExpression(
+ BaseObjectCreationExpressionSyntax objectCreation,
+ ImmutableArray> matches,
+ bool makeMultiLine)
+ {
+ var elements = CreateElements(
+ objectCreation, matches,
+ static (match, expression) => match?.UseSpread is true ? SpreadElement(expression) : ExpressionElement(expression));
- nodesAndTokens.Add(commaToken);
- }
- else
- {
- newExpression = newExpression.WithTrailingTrivia(
- expressionStatement.GetTrailingTrivia());
- nodesAndTokens.Add(newExpression);
- }
- }
+ if (makeMultiLine)
+ elements = AddLineBreaks(elements, includeFinalLineBreak: false);
- return SyntaxFactory.SeparatedList(nodesAndTokens);
+ return CollectionExpression(elements).WithTriviaFrom(objectCreation);
}
private static ExpressionSyntax ConvertExpression(ExpressionSyntax expression)
- {
- if (expression is InvocationExpressionSyntax invocation)
+ => expression switch
{
- return ConvertInvocation(invocation);
- }
- else if (expression is AssignmentExpressionSyntax assignment)
- {
- return ConvertAssignment(assignment);
- }
-
- throw new InvalidOperationException();
- }
+ InvocationExpressionSyntax invocation => ConvertInvocation(invocation),
+ AssignmentExpressionSyntax assignment => ConvertAssignment(assignment),
+ _ => throw new InvalidOperationException(),
+ };
- private static ExpressionSyntax ConvertAssignment(AssignmentExpressionSyntax assignment)
+ private static AssignmentExpressionSyntax ConvertAssignment(AssignmentExpressionSyntax assignment)
{
var elementAccess = (ElementAccessExpressionSyntax)assignment.Left;
return assignment.WithLeft(
- SyntaxFactory.ImplicitElementAccess(elementAccess.ArgumentList));
+ ImplicitElementAccess(elementAccess.ArgumentList));
}
private static ExpressionSyntax ConvertInvocation(InvocationExpressionSyntax invocation)
@@ -124,17 +117,137 @@ private static ExpressionSyntax ConvertInvocation(InvocationExpressionSyntax inv
// avoid the ambiguity.
var expression = arguments[0].Expression;
return SyntaxFacts.IsAssignmentExpression(expression.Kind())
- ? SyntaxFactory.ParenthesizedExpression(expression)
+ ? ParenthesizedExpression(expression)
: expression;
}
- return SyntaxFactory.InitializerExpression(
+ return InitializerExpression(
SyntaxKind.ComplexElementInitializerExpression,
- SyntaxFactory.Token(SyntaxKind.OpenBraceToken).WithoutTrivia(),
- SyntaxFactory.SeparatedList(
+ Token(SyntaxKind.OpenBraceToken).WithoutTrivia(),
+ SeparatedList(
arguments.Select(a => a.Expression),
arguments.GetSeparators()),
- SyntaxFactory.Token(SyntaxKind.CloseBraceToken).WithoutTrivia());
+ Token(SyntaxKind.CloseBraceToken).WithoutTrivia());
+ }
+
+ private static bool MakeMultiLine(
+ SourceText sourceText,
+ BaseObjectCreationExpressionSyntax objectCreation,
+ ImmutableArray> matches,
+ int wrappingLength)
+ {
+ // If it's already multiline, keep it that way.
+ if (!sourceText.AreOnSameLine(objectCreation.GetFirstToken(), objectCreation.GetLastToken()))
+ return true;
+
+ foreach (var match in matches)
+ {
+ var expression = GetExpression(match);
+
+ // If we have anything like: `new Dictionary { { A, B }, { C, D } }` then always make multiline.
+ // Similarly, if we have `new Dictionary { [A] = B }`.
+ if (expression is InitializerExpressionSyntax or AssignmentExpressionSyntax)
+ return true;
+
+ // if any of the expressions we're adding are multiline, then make things multiline.
+ if (!sourceText.AreOnSameLine(expression.GetFirstToken(), expression.GetLastToken()))
+ return true;
+ }
+
+ var totalLength = "{}".Length;
+ foreach (var match in matches)
+ {
+ var expression = GetExpression(match);
+ totalLength += expression.Span.Length;
+ totalLength += ", ".Length;
+
+ if (totalLength > wrappingLength)
+ return true;
+ }
+
+ return false;
+
+ static ExpressionSyntax GetExpression(Match match)
+ => match.Statement switch
+ {
+ ExpressionStatementSyntax expressionStatement => expressionStatement.Expression,
+ ForEachStatementSyntax foreachStatement => foreachStatement.Expression,
+ _ => throw ExceptionUtilities.Unreachable(),
+ };
+ }
+
+ public static SeparatedSyntaxList AddLineBreaks(
+ SeparatedSyntaxList nodes, bool includeFinalLineBreak)
+ where TNode : SyntaxNode
+ {
+ using var _ = ArrayBuilder.GetInstance(out var nodesAndTokens);
+
+ var nodeOrTokenList = nodes.GetWithSeparators();
+ foreach (var item in nodeOrTokenList)
+ {
+ var addLineBreak = item.IsToken || (includeFinalLineBreak && item == nodeOrTokenList.Last());
+ if (addLineBreak && item.GetTrailingTrivia() is not [.., (kind: SyntaxKind.EndOfLineTrivia)])
+ {
+ nodesAndTokens.Add(item.WithAppendedTrailingTrivia(ElasticCarriageReturnLineFeed));
+ }
+ else
+ {
+ nodesAndTokens.Add(item);
+ }
+ }
+
+ return SeparatedList(nodesAndTokens);
+ }
+
+ private static SeparatedSyntaxList CreateElements(
+ BaseObjectCreationExpressionSyntax objectCreation,
+ ImmutableArray> matches,
+ Func?, ExpressionSyntax, TElement> createElement)
+ where TElement : SyntaxNode
+ {
+ using var _ = ArrayBuilder.GetInstance(out var nodesAndTokens);
+
+ UseInitializerHelpers.AddExistingItems(objectCreation, nodesAndTokens, createElement);
+
+ for (var i = 0; i < matches.Length; i++)
+ {
+ var match = matches[i];
+ var statement = match.Statement;
+
+ if (statement is ExpressionStatementSyntax expressionStatement)
+ {
+ var trivia = statement.GetLeadingTrivia();
+ var leadingTrivia = i == 0 ? trivia.WithoutLeadingBlankLines() : trivia;
+
+ var semicolon = expressionStatement.SemicolonToken;
+ var trailingTrivia = semicolon.TrailingTrivia.Contains(static t => t.IsSingleOrMultiLineComment())
+ ? semicolon.TrailingTrivia
+ : default;
+
+ var expression = createElement(match, ConvertExpression(expressionStatement.Expression).WithoutTrivia()).WithLeadingTrivia(leadingTrivia);
+ if (i < matches.Length - 1)
+ {
+ nodesAndTokens.Add(expression);
+ nodesAndTokens.Add(Token(SyntaxKind.CommaToken).WithTrailingTrivia(trailingTrivia));
+ }
+ else
+ {
+ nodesAndTokens.Add(expression.WithTrailingTrivia(trailingTrivia));
+ }
+ }
+ else if (statement is ForEachStatementSyntax foreachStatement)
+ {
+ nodesAndTokens.Add(createElement(match, foreachStatement.Expression.WithoutTrivia()));
+ if (i < matches.Length - 1)
+ nodesAndTokens.Add(Token(SyntaxKind.CommaToken));
+ }
+ else
+ {
+ throw ExceptionUtilities.Unreachable();
+ }
+ }
+
+ return SeparatedList(nodesAndTokens);
}
}
}
diff --git a/src/Analyzers/CSharp/CodeFixes/UseConditionalExpression/MultiLineConditionalExpressionFormattingRule.cs b/src/Analyzers/CSharp/CodeFixes/UseConditionalExpression/MultiLineConditionalExpressionFormattingRule.cs
index 4c687000ffcb8..12f3a88dccbe9 100644
--- a/src/Analyzers/CSharp/CodeFixes/UseConditionalExpression/MultiLineConditionalExpressionFormattingRule.cs
+++ b/src/Analyzers/CSharp/CodeFixes/UseConditionalExpression/MultiLineConditionalExpressionFormattingRule.cs
@@ -32,15 +32,7 @@ private MultiLineConditionalExpressionFormattingRule()
}
private static bool IsQuestionOrColonOfNewConditional(SyntaxToken token)
- {
- if (token.Kind() is SyntaxKind.QuestionToken or
- SyntaxKind.ColonToken)
- {
- return token.Parent.HasAnnotation(SpecializedFormattingAnnotation);
- }
-
- return false;
- }
+ => token.Kind() is SyntaxKind.QuestionToken or SyntaxKind.ColonToken && token.Parent.HasAnnotation(SpecializedFormattingAnnotation);
public override AdjustNewLinesOperation GetAdjustNewLinesOperation(
in SyntaxToken previousToken, in SyntaxToken currentToken, in NextGetAdjustNewLinesOperation nextOperation)
diff --git a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs
index 8ae299d393040..7a2364acc67c5 100644
--- a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs
+++ b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/CSharpUseObjectInitializerCodeFixProvider.cs
@@ -13,6 +13,8 @@
namespace Microsoft.CodeAnalysis.CSharp.UseObjectInitializer
{
+ using ObjectInitializerMatch = Match;
+
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.UseObjectInitializer), Shared]
internal class CSharpUseObjectInitializerCodeFixProvider :
AbstractUseObjectInitializerCodeFixProvider<
@@ -32,7 +34,7 @@ public CSharpUseObjectInitializerCodeFixProvider()
protected override StatementSyntax GetNewStatement(
StatementSyntax statement, BaseObjectCreationExpressionSyntax objectCreation,
- ImmutableArray> matches)
+ ImmutableArray matches)
{
return statement.ReplaceNode(
objectCreation,
@@ -41,19 +43,20 @@ protected override StatementSyntax GetNewStatement(
private static BaseObjectCreationExpressionSyntax GetNewObjectCreation(
BaseObjectCreationExpressionSyntax objectCreation,
- ImmutableArray> matches)
+ ImmutableArray matches)
{
return UseInitializerHelpers.GetNewObjectCreation(
objectCreation, CreateExpressions(objectCreation, matches));
}
private static SeparatedSyntaxList CreateExpressions(
- BaseObjectCreationExpressionSyntax objectCreation,
- ImmutableArray> matches)
+ BaseObjectCreationExpressionSyntax objectCreation,
+ ImmutableArray matches)
{
using var _ = ArrayBuilder.GetInstance(out var nodesAndTokens);
- UseInitializerHelpers.AddExistingItems(objectCreation, nodesAndTokens);
+ UseInitializerHelpers.AddExistingItems(
+ objectCreation, nodesAndTokens, static (_, e) => e);
for (var i = 0; i < matches.Length; i++)
{
diff --git a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/UseInitializerHelpers.cs b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/UseInitializerHelpers.cs
index 6e0cef2918db2..727f661e6c596 100644
--- a/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/UseInitializerHelpers.cs
+++ b/src/Analyzers/CSharp/CodeFixes/UseObjectInitializer/UseInitializerHelpers.cs
@@ -2,6 +2,7 @@
// 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 Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
@@ -15,8 +16,7 @@ public static BaseObjectCreationExpressionSyntax GetNewObjectCreation(
BaseObjectCreationExpressionSyntax baseObjectCreation,
SeparatedSyntaxList expressions)
{
- if (baseObjectCreation is ObjectCreationExpressionSyntax objectCreation &&
- objectCreation.ArgumentList?.Arguments.Count == 0)
+ if (baseObjectCreation is ObjectCreationExpressionSyntax { ArgumentList.Arguments.Count: 0 } objectCreation)
{
baseObjectCreation = objectCreation
.WithType(objectCreation.Type.WithTrailingTrivia(objectCreation.ArgumentList.GetTrailingTrivia()))
@@ -31,10 +31,23 @@ public static BaseObjectCreationExpressionSyntax GetNewObjectCreation(
return baseObjectCreation.WithInitializer(InitializerExpression(initializerKind, expressions));
}
- public static void AddExistingItems(BaseObjectCreationExpressionSyntax objectCreation, ArrayBuilder nodesAndTokens)
+ public static void AddExistingItems(
+ BaseObjectCreationExpressionSyntax objectCreation,
+ ArrayBuilder nodesAndTokens,
+ Func createElement)
+ where TMatch : struct
+ where TElementSyntax : SyntaxNode
{
if (objectCreation.Initializer != null)
- nodesAndTokens.AddRange(objectCreation.Initializer.Expressions.GetWithSeparators());
+ {
+ foreach (var nodeOrToken in objectCreation.Initializer.Expressions.GetWithSeparators())
+ {
+ if (nodeOrToken.IsToken)
+ nodesAndTokens.Add(nodeOrToken.AsToken());
+ else
+ nodesAndTokens.Add(createElement(null, (ExpressionSyntax)nodeOrToken.AsNode()!));
+ }
+ }
// If we have an odd number of elements already, add a comma at the end so that we can add the rest of the
// items afterwards without a syntax issue.
diff --git a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
index 45695b2a2dba2..a3b353293227f 100644
--- a/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
+++ b/src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems
@@ -99,6 +99,7 @@
+
diff --git a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs
index 21f3e1cfcfb93..4ff0621a3174c 100644
--- a/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs
+++ b/src/Analyzers/CSharp/Tests/UseCollectionExpression/UseCollectionExpressionForArrayTests.cs
@@ -483,13 +483,6 @@ void M()
var i = new int[] { 1 }.AsSpan();
}
}
-
- //internal static class Extensions
- //{
- // public static ReadOnlySpan AsSpan(this T[] values) => default;
- //}
-
- //internal readonly struct ReadOnlySpan { }
""",
LanguageVersion = LanguageVersionExtensions.CSharpNext,
ReferenceAssemblies = ReferenceAssemblies.Net.Net70,
@@ -508,11 +501,11 @@ class C
}
""",
FixedCode = """
- class C
- {
- private int[] X = [1];
- }
- """,
+ class C
+ {
+ private int[] X = [1];
+ }
+ """,
LanguageVersion = LanguageVersionExtensions.CSharpNext,
}.RunAsync();
}
@@ -529,11 +522,11 @@ class C
}
""",
FixedCode = """
- class C
- {
- private int[] X { get; } = [1];
- }
- """,
+ class C
+ {
+ private int[] X { get; } = [1];
+ }
+ """,
LanguageVersion = LanguageVersionExtensions.CSharpNext,
}.RunAsync();
}
@@ -553,14 +546,14 @@ void M()
}
""",
FixedCode = """
- class C
- {
- void M()
+ class C
{
- var c = (int[])[1];
+ void M()
+ {
+ var c = (int[])[1];
+ }
}
- }
- """,
+ """,
LanguageVersion = LanguageVersionExtensions.CSharpNext,
}.RunAsync();
}
@@ -667,7 +660,7 @@ void M(int[] x)
}
[Fact]
- public async Task TestTargetTypedInConditional4()
+ public async Task TestNotTargetTypedInConditional4()
{
await new VerifyCS.Test
{
@@ -766,7 +759,7 @@ void M(int[] x, bool b)
}
[Fact]
- public async Task TestTargetTypedInSwitchExpressionArm4()
+ public async Task TestNotTargetTypedInSwitchExpressionArm4()
{
await new VerifyCS.Test
{
@@ -784,7 +777,7 @@ void M(int[] x, bool b)
}
[Fact]
- public async Task TestTargetTypedInitializer1()
+ public async Task TestNotTargetTypedInitializer1()
{
await new VerifyCS.Test
{
@@ -932,7 +925,7 @@ void X(int[] x) { }
}
[Fact]
- public async Task TestTargetTypedArgument2()
+ public async Task TestNotTargetTypedArgument2()
{
await new VerifyCS.Test
{
@@ -1112,7 +1105,7 @@ void M(int[] x)
}
[Fact]
- public async Task TestLinqLet()
+ public async Task TestNotWithLinqLet()
{
await new VerifyCS.Test
{
diff --git a/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests.cs b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests.cs
index 3903e99d70a67..a96d2931126fa 100644
--- a/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests.cs
+++ b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests.cs
@@ -4,6 +4,7 @@
using System.Collections;
using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.UseCollectionInitializer;
using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
@@ -20,16 +21,21 @@ namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseCollectionInitialize
[Trait(Traits.Feature, Traits.Features.CodeActionsUseCollectionInitializer)]
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)
{
- await new VerifyCS.Test
+ var test = new VerifyCS.Test
{
ReferenceAssemblies = Testing.ReferenceAssemblies.NetCore.NetCoreApp31,
TestCode = testCode,
FixedCode = fixedCode,
- LanguageVersion = LanguageVersion.Preview,
- TestState = { OutputKind = outputKind }
- }.RunAsync();
+ LanguageVersion = LanguageVersion.CSharp11,
+ TestState = { OutputKind = outputKind },
+ };
+
+ await test.RunAsync();
}
private static async Task TestMissingInRegularAndScriptAsync(string testCode, LanguageVersion? languageVersion = null)
@@ -78,6 +84,76 @@ void M()
""");
}
+ [Fact]
+ public async Task TestOnVariableDeclarator_AddRange()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ var c = [|new|] List();
+ [|c.Add(|]1);
+ c.AddRange(x);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ var c = new List
+ {
+ 1
+ };
+ c.AddRange(x);
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_Foreach()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ var c = [|new|] List();
+ [|c.Add(|]1);
+ foreach (var v in x)
+ c.Add(v);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ var c = new List
+ {
+ 1
+ };
+ foreach (var v in x)
+ c.Add(v);
+ }
+ }
+ """);
+ }
+
[Fact]
public async Task TestIndexAccess1()
{
@@ -1437,7 +1513,10 @@ await TestInRegularAndScriptAsync(
"""
using System.Collections.Generic;
- var list = new List { 1 };
+ var list = new List
+ {
+ 1
+ };
""", OutputKind.ConsoleApplication);
}
diff --git a/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs
new file mode 100644
index 0000000000000..d856a6b4e1933
--- /dev/null
+++ b/src/Analyzers/CSharp/Tests/UseCollectionInitializer/UseCollectionInitializerTests_CollectionExpression.cs
@@ -0,0 +1,1805 @@
+// 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.Collections;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.CSharp;
+using Microsoft.CodeAnalysis.CSharp.UseCollectionInitializer;
+using Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions;
+using Microsoft.CodeAnalysis.Test.Utilities;
+using Roslyn.Test.Utilities;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.UseCollectionInitializer;
+
+using VerifyCS = CSharpCodeFixVerifier<
+ CSharpUseCollectionInitializerDiagnosticAnalyzer,
+ CSharpUseCollectionInitializerCodeFixProvider>;
+
+[Trait(Traits.Feature, Traits.Features.CodeActionsUseCollectionInitializer)]
+public partial class UseCollectionInitializerTests_CollectionExpression
+{
+ private static async Task TestInRegularAndScriptAsync(string testCode, string fixedCode, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary)
+ {
+ await new VerifyCS.Test
+ {
+ ReferenceAssemblies = Testing.ReferenceAssemblies.NetCore.NetCoreApp31,
+ TestCode = testCode,
+ FixedCode = fixedCode,
+ LanguageVersion = LanguageVersion.Preview,
+ TestState = { OutputKind = outputKind }
+ }.RunAsync();
+ }
+
+ private static Task TestMissingInRegularAndScriptAsync(string testCode)
+ => TestInRegularAndScriptAsync(testCode, testCode);
+
+ [Fact]
+ public async Task TestNotOnVarVariableDeclarator()
+ {
+ 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
+ };
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M()
+ {
+ List c = [|new|] List();
+ [|c.Add(|]1);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M()
+ {
+ List c = [1];
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclaratorDifferentType()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M()
+ {
+ IList c = [|new|] List();
+ [|c.Add(|]1);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M()
+ {
+ IList c = new List
+ {
+ 1
+ };
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_Foreach1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = [|new|] List();
+ [|c.Add(|]1);
+ [|foreach (var v in |]x)
+ c.Add(v);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = [1, .. x];
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_Foreach1_A()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = [|new|] List();
+ [|c.Add(|]1);
+ [|foreach (var v in |]x)
+ {
+ c.Add(v);
+ }
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = [1, .. x];
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_Foreach1_B()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = [|new|] List();
+ [|c.Add(|]1);
+ foreach (var v in x)
+ {
+ c.Add(0);
+ }
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = [1];
+ foreach (var v in x)
+ {
+ c.Add(0);
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_Foreach1_C()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int z)
+ {
+ List c = [|new|] List();
+ [|c.Add(|]1);
+ foreach (var v in x)
+ {
+ c.Add(z);
+ }
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int z)
+ {
+ List c = [1];
+ foreach (var v in x)
+ {
+ c.Add(z);
+ }
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_Foreach2()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int[] y)
+ {
+ List c = [|new|] List();
+ [|c.Add(|]1);
+ [|foreach (var v in |]x)
+ c.Add(v);
+ [|foreach (var v in |]y)
+ c.Add(v);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int[] y)
+ {
+ List c = [1, .. x, .. y];
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_Foreach3()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int[] y)
+ {
+ List c = [|new|] List();
+ [|foreach (var v in |]x)
+ c.Add(v);
+ [|c.Add(|]1);
+ [|foreach (var v in |]y)
+ c.Add(v);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int[] y)
+ {
+ List c = [.. x, 1, .. y];
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_Foreach4()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int[] y)
+ {
+ List c = [|new|] List();
+ [|foreach (var v in |]x)
+ c.Add(v);
+ [|foreach (var v in |]y)
+ c.Add(v);
+ [|c.Add(|]1);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int[] y)
+ {
+ List c = [.. x, .. y, 1];
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_AddRange1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = [|new|] List();
+ [|c.Add(|]1);
+ [|c.AddRange(|]x);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = [1, .. x];
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestOnVariableDeclarator_AddRangeAndForeach1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int[] y)
+ {
+ List c = [|new|] List();
+ [|c.Add(|]1);
+ [|foreach (var v in |]x)
+ c.Add(v);
+ [|c.AddRange(|]y);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class C
+ {
+ void M(int[] x, int[] y)
+ {
+ List c = [1, .. x, .. y];
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestIndexAccess1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+ class C
+ {
+ void M()
+ {
+ List c = [|new|] List();
+ c[1] = 2;
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+ class C
+ {
+ void M()
+ {
+ List c = new List
+ {
+ [1] = 2
+ };
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestIndexAccess1_Foreach()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = [|new|] List();
+ c[1] = 2;
+ foreach (var v in x)
+ c.Add(v);
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+ class C
+ {
+ void M(int[] x)
+ {
+ List c = new List
+ {
+ [1] = 2
+ };
+ foreach (var v in x)
+ c.Add(v);
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestComplexIndexAccess1()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+
+ class A
+ {
+ public B b;
+ }
+
+ class B
+ {
+ public List c;
+ }
+
+ class C
+ {
+ void M(A a)
+ {
+ a.b.c = [|new|] List();
+ a.b.c[1] = 2;
+ }
+ }
+ """,
+ """
+ using System.Collections.Generic;
+
+ class A
+ {
+ public B b;
+ }
+
+ class B
+ {
+ public List c;
+ }
+
+ class C
+ {
+ void M(A a)
+ {
+ a.b.c = new List
+ {
+ [1] = 2
+ };
+ }
+ }
+ """);
+ }
+
+ [Fact]
+ public async Task TestIndexAccess2()
+ {
+ await TestInRegularAndScriptAsync(
+ """
+ using System.Collections.Generic;
+ class C
+ {
+ void M()
+ {
+ List