diff --git a/ChangeLog.md b/ChangeLog.md index 64305fad4f..0b03d8dace 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,8 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS0049](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0049) ([PR](https://github.com/dotnet/roslynator/pull/1386)) - Fix analyzer [RCS1159](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1159) ([PR](https://github.com/dotnet/roslynator/pull/1390)) - Fix analyzer [RCS1019](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1019) ([PR](https://github.com/dotnet/roslynator/pull/1402)) -- Fix analyzer [RCS1250](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1250) ([PR](https://github.com/dotnet/roslynator/pull/1403)) -- Fix analyzer [RCS1060](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1060) ([PR](https://github.com/dotnet/roslynator/pull/1401)) +- Fix analyzer [RCS1250](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1250) ([PR](https://github.com/dotnet/roslynator/pull/1403), [PR](https://github.com/dotnet/roslynator/pull/1404)) - Fix code fix for [CS8600](https://josefpihrt.github.io/docs/roslynator/fixes/CS8600) changing the wrong type when casts or `var` are involved ([PR](https://github.com/dotnet/roslynator/pull/1393) by @jroessel) ## [4.10.0] - 2024-01-24 diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider.cs index bad8dd0a3a..d6e01e0ded 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider.cs @@ -237,15 +237,25 @@ private static async Task ConvertToExplicitAsync( CancellationToken cancellationToken) { SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); - ITypeSymbol typeSymbol = semanticModel.GetTypeInfo(collectionExpression, cancellationToken).ConvertedType; + var typeSymbol = (IArrayTypeSymbol)semanticModel.GetTypeInfo(collectionExpression, cancellationToken).ConvertedType; - ArrayCreationExpressionSyntax arrayCreation = ArrayCreationExpression( - Token(SyntaxKind.NewKeyword), - (ArrayTypeSyntax)typeSymbol.ToTypeSyntax().WithSimplifierAnnotation(), - ConvertCollectionExpressionToInitializer(collectionExpression, SyntaxKind.ArrayInitializerExpression)) - .WithTriviaFrom(collectionExpression); + InitializerExpressionSyntax initializer = ConvertCollectionExpressionToInitializer(collectionExpression, SyntaxKind.ArrayInitializerExpression); - return await document.ReplaceNodeAsync(collectionExpression, arrayCreation, cancellationToken).ConfigureAwait(false); + SyntaxNode newNode; + if (initializer is not null) + { + newNode = ArrayCreationExpression( + Token(SyntaxKind.NewKeyword), + (ArrayTypeSyntax)typeSymbol.ToTypeSyntax().WithSimplifierAnnotation(), + initializer) + .WithTriviaFrom(collectionExpression); + } + else + { + newNode = CreateArrayEmpty(typeSymbol); + } + + return await document.ReplaceNodeAsync(collectionExpression, newNode, cancellationToken).ConfigureAwait(false); } private static async Task ConvertToImplicitAsync( @@ -255,10 +265,31 @@ private static async Task ConvertToImplicitAsync( { InitializerExpressionSyntax initializer = ConvertCollectionExpressionToInitializer(collectionExpression, SyntaxKind.ArrayInitializerExpression); - ImplicitArrayCreationExpressionSyntax implicitArrayCreation = ImplicitArrayCreationExpression(initializer) - .WithTriviaFrom(collectionExpression); + SyntaxNode newNode; + if (initializer is not null) + { + newNode = ImplicitArrayCreationExpression(initializer); + } + else + { + SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + + var typeSymbol = (IArrayTypeSymbol)semanticModel.GetTypeInfo(collectionExpression, cancellationToken).ConvertedType; + + newNode = CreateArrayEmpty(typeSymbol); + } - return await document.ReplaceNodeAsync(collectionExpression, implicitArrayCreation, cancellationToken).ConfigureAwait(false); + newNode = newNode.WithTriviaFrom(collectionExpression); + + return await document.ReplaceNodeAsync(collectionExpression, newNode, cancellationToken).ConfigureAwait(false); + } + + private static InvocationExpressionSyntax CreateArrayEmpty(IArrayTypeSymbol typeSymbol) + { + return CSharpFactory.SimpleMemberInvocationExpression( + ParseExpression("global::System.Array").WithSimplifierAnnotation(), + (SimpleNameSyntax)ParseName($"Empty<{typeSymbol.ElementType.ToTypeSyntax().WithSimplifierAnnotation()}>"), + ArgumentList()); } private static async Task ConvertToCollectionExpressionAsync( diff --git a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitArrayCreationAnalysis.cs b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitArrayCreationAnalysis.cs index a7d052952c..5bf7c44c28 100644 --- a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitArrayCreationAnalysis.cs +++ b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitArrayCreationAnalysis.cs @@ -13,6 +13,12 @@ internal class ImplicitOrExplicitArrayCreationAnalysis : ImplicitOrExplicitCreat { public static ImplicitOrExplicitArrayCreationAnalysis Instance { get; } = new(); + public override void AnalyzeCollectionExpression(ref SyntaxNodeAnalysisContext context) + { + if (context.SemanticModel.GetTypeInfo(context.Node, context.CancellationToken).ConvertedType?.TypeKind == TypeKind.Array) + AnalyzeImplicit(ref context); + } + public override void AnalyzeExplicitCreation(ref SyntaxNodeAnalysisContext context) { if (context.Node.ContainsDiagnostics) diff --git a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitCreationAnalysis.cs b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitCreationAnalysis.cs index 1292585a5c..38519b2ba1 100644 --- a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitCreationAnalysis.cs +++ b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitCreationAnalysis.cs @@ -50,6 +50,8 @@ internal abstract class ImplicitOrExplicitCreationAnalysis protected abstract void ReportCollectionExpressionToImplicit(ref SyntaxNodeAnalysisContext context); #endif + public abstract void AnalyzeCollectionExpression(ref SyntaxNodeAnalysisContext context); + protected virtual bool IsInitializerObvious(ref SyntaxNodeAnalysisContext context) => false; public virtual void AnalyzeExplicitCreation(ref SyntaxNodeAnalysisContext context) @@ -301,12 +303,7 @@ public virtual void AnalyzeImplicitCreation(ref SyntaxNodeAnalysisContext contex AnalyzeImplicit(ref context); } - public virtual void AnalyzeCollectionExpression(ref SyntaxNodeAnalysisContext context) - { - AnalyzeImplicit(ref context); - } - - private void AnalyzeImplicit(ref SyntaxNodeAnalysisContext context) + protected void AnalyzeImplicit(ref SyntaxNodeAnalysisContext context) { if (context.Node.ContainsDiagnostics) return; diff --git a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitObjectCreationAnalysis.cs b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitObjectCreationAnalysis.cs index 67b269d8b9..80b90bee1c 100644 --- a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitObjectCreationAnalysis.cs +++ b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitObjectCreationAnalysis.cs @@ -17,6 +17,12 @@ public override TypeStyle GetTypeStyle(ref SyntaxNodeAnalysisContext context) return context.GetObjectCreationTypeStyle(); } + public override void AnalyzeCollectionExpression(ref SyntaxNodeAnalysisContext context) + { + if (context.SemanticModel.GetTypeInfo(context.Node, context.CancellationToken).ConvertedType?.TypeKind != TypeKind.Array) + AnalyzeImplicit(ref context); + } + protected override void ReportExplicitToImplicit(ref SyntaxNodeAnalysisContext context) { var objectCreation = (ObjectCreationExpressionSyntax)context.Node; diff --git a/src/Tests/Analyzers.Tests/RCS1014UseExplicitlyTypedArrayOrViceVersaTests.cs b/src/Tests/Analyzers.Tests/RCS1014UseExplicitlyTypedArrayOrViceVersaTests.cs index 8543630200..fbe1a3a561 100644 --- a/src/Tests/Analyzers.Tests/RCS1014UseExplicitlyTypedArrayOrViceVersaTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1014UseExplicitlyTypedArrayOrViceVersaTests.cs @@ -503,4 +503,78 @@ void M(List x) ", options: Options.AddConfigOption(ConfigOptionKeys.ArrayCreationTypeStyle, ConfigOptionValues.ArrayCreationTypeStyle_Implicit) .AddConfigOption(ConfigOptionKeys.UseCollectionExpression, false)); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseExplicitlyOrImplicitlyTypedArray)] + public async Task Test_CollectionExpressionToImplicit_EmptyArray() + { + await VerifyDiagnosticAndFixAsync(""" +using System; + +class C +{ + void M1() + { + string[] values = [|[]|]; + } + + void M2() + { + string[] values = [|["a", "b", "c"]|]; + } +} +""", """ +using System; + +class C +{ + void M1() + { + string[] values = Array.Empty(); + } + + void M2() + { + string[] values = new[] { "a", "b", "c" }; + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.ArrayCreationTypeStyle, ConfigOptionValues.ArrayCreationTypeStyle_Implicit) + .AddConfigOption(ConfigOptionKeys.UseCollectionExpression, false)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseExplicitlyOrImplicitlyTypedArray)] + public async Task Test_CollectionExpressionToExplicit_EmptyArray() + { + await VerifyDiagnosticAndFixAsync(""" +using System; + +class C +{ + void M1() + { + string[] values = [|[]|]; + } + + void M2() + { + string[] values = [|["a", "b", "c"]|]; + } +} +""", """ +using System; + +class C +{ + void M1() + { + string[] values = Array.Empty(); + } + + void M2() + { + string[] values = new string[] { "a", "b", "c" }; + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.ArrayCreationTypeStyle, ConfigOptionValues.ArrayCreationTypeStyle_Explicit) + .AddConfigOption(ConfigOptionKeys.UseCollectionExpression, false)); + } } diff --git a/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs b/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs index d0a84f8985..2f6d681556 100644 --- a/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs @@ -1881,4 +1881,26 @@ static C M() """, options: Options.AddConfigOption(ConfigOptionKeys.ObjectCreationTypeStyle, ConfigOptionValues.ObjectCreationTypeStyle_Implicit) .AddConfigOption(ConfigOptionKeys.UseCollectionExpression, true)); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseImplicitOrExplicitObjectCreation)] + public async Task TestNoDiagnostic_Array() + { + await VerifyNoDiagnosticAsync(""" +using System; + +class C +{ + void M1() + { + string[] values = []; + } + + void M2() + { + string[] values = ["a", "b", "c"]; + } +} +""", options: Options.AddConfigOption(ConfigOptionKeys.ObjectCreationTypeStyle, ConfigOptionValues.ObjectCreationTypeStyle_Implicit) + .AddConfigOption(ConfigOptionKeys.UseCollectionExpression, false)); + } }