From 2354f1feb0d84e5c7565b24c1af0221ca9a9f4f1 Mon Sep 17 00:00:00 2001 From: Josef Pihrt Date: Mon, 8 Jan 2024 21:09:07 +0100 Subject: [PATCH] Fix RCS1014 (#1350) --- ChangeLog.md | 1 + ...lyOrImplicitlyTypedArrayCodeFixProvider.cs | 32 +++++++-- ...OrExplicitObjectCreationCodeFixProvider.cs | 65 +++++++++-------- .../ImplicitOrExplicitCreationAnalysis.cs | 9 ++- ...plicitOrExpressionArrayCreationAnalysis.cs | 23 ++++++ ...licitOrExpressionObjectCreationAnalysis.cs | 18 +++-- .../CSharp/Analysis/DiagnosticPropertyKeys.cs | 1 + .../RCS1014UseImplicitlyTypedArrayTests.cs | 71 +++++++++++++++++++ ...seImplicitOrExplicitObjectCreationTests.cs | 4 +- 9 files changed, 180 insertions(+), 44 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 68490ffe91..55f330dcb4 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Fix analyzer [RCS0034](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0034) ([PR](https://github.com/dotnet/roslynator/pull/1351)) - Fix analyzer [RCS0023](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0023) ([PR](https://github.com/dotnet/roslynator/pull/1352)) +- Fix analyzer [RCS1014](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1014) ([PR](https://github.com/dotnet/roslynator/pull/1350)) ## [4.8.0] - 2024-01-02 diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider.cs index da9be67985..53c21f1800 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider.cs @@ -100,7 +100,11 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) } else if (node is ImplicitArrayCreationExpressionSyntax implicitArrayCreation) { - if (diagnostic.Properties.ContainsKey(DiagnosticPropertyKeys.ImplicitToCollectionExpression)) + if (diagnostic.Properties.ContainsKey(DiagnosticPropertyKeys.VarToExplicit)) + { + return (ct => ConvertToExplicitAndUseVarAsync(document, implicitArrayCreation, ct), UseCollectionExpressionTitle); + } + else if (diagnostic.Properties.ContainsKey(DiagnosticPropertyKeys.ImplicitToCollectionExpression)) { return (ct => ConvertToCollectionExpressionAsync(document, implicitArrayCreation, ct), UseCollectionExpressionTitle); } @@ -130,6 +134,28 @@ private static async Task ConvertToExplicitAsync( Document document, ImplicitArrayCreationExpressionSyntax implicitArrayCreation, CancellationToken cancellationToken) + { + ArrayCreationExpressionSyntax newNode = await CreateArrayCreationAsync(document, implicitArrayCreation, cancellationToken).ConfigureAwait(false); + + return await document.ReplaceNodeAsync(implicitArrayCreation, newNode, cancellationToken).ConfigureAwait(false); + } + + private static async Task ConvertToExplicitAndUseVarAsync( + Document document, + ImplicitArrayCreationExpressionSyntax implicitArrayCreation, + CancellationToken cancellationToken) + { + ArrayCreationExpressionSyntax arrayCreation = await CreateArrayCreationAsync(document, implicitArrayCreation, cancellationToken).ConfigureAwait(false); + + VariableDeclarationSyntax variableDeclaration = implicitArrayCreation.FirstAncestor(); + + VariableDeclarationSyntax newVariableDeclaration = variableDeclaration.ReplaceNode(implicitArrayCreation, arrayCreation) + .WithType(CSharpFactory.VarType().WithTriviaFrom(variableDeclaration.Type)); + + return await document.ReplaceNodeAsync(variableDeclaration, newVariableDeclaration, cancellationToken).ConfigureAwait(false); + } + + private static async Task CreateArrayCreationAsync(Document document, ImplicitArrayCreationExpressionSyntax implicitArrayCreation, CancellationToken cancellationToken) { SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); ITypeSymbol typeSymbol = semanticModel.GetTypeSymbol(implicitArrayCreation, cancellationToken); @@ -146,14 +172,12 @@ private static async Task ConvertToExplicitAsync( initializer.Expressions, (node, _) => (node.IsKind(SyntaxKind.CastExpression)) ? node.WithSimplifierAnnotation() : node); - ArrayCreationExpressionSyntax newNode = ArrayCreationExpression( + return ArrayCreationExpression( newKeyword, arrayType .WithLeadingTrivia(implicitArrayCreation.OpenBracketToken.LeadingTrivia) .WithTrailingTrivia(implicitArrayCreation.CloseBracketToken.TrailingTrivia), newInitializer); - - return await document.ReplaceNodeAsync(implicitArrayCreation, newNode, cancellationToken).ConfigureAwait(false); } private static async Task ConvertToExplicitAsync( diff --git a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseImplicitOrExplicitObjectCreationCodeFixProvider.cs b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseImplicitOrExplicitObjectCreationCodeFixProvider.cs index 5386cfc958..41a4315479 100644 --- a/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseImplicitOrExplicitObjectCreationCodeFixProvider.cs +++ b/src/Analyzers.CodeFixes/CSharp/CodeFixes/UseImplicitOrExplicitObjectCreationCodeFixProvider.cs @@ -42,8 +42,7 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) predicate: f => f.IsKind( SyntaxKind.ObjectCreationExpression, SyntaxKind.ImplicitObjectCreationExpression, - SyntaxKind.CollectionExpression, - SyntaxKind.VariableDeclaration))) + SyntaxKind.CollectionExpression))) { return; } @@ -97,7 +96,37 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) } else if (node is ImplicitObjectCreationExpressionSyntax implicitObjectCreation) { - if (diagnostic.Properties.ContainsKey(DiagnosticPropertyKeys.ImplicitToCollectionExpression)) + if (diagnostic.Properties.ContainsKey(DiagnosticPropertyKeys.VarToExplicit)) + { + VariableDeclarationSyntax variableDeclaration = node.FirstAncestor(); + + CodeAction codeAction = CodeAction.Create( + UseExplicitObjectCreationTitle, + ct => + { + var implicitObjectCreation = (ImplicitObjectCreationExpressionSyntax)variableDeclaration.Variables.Single().Initializer.Value; + + SyntaxToken newKeyword = implicitObjectCreation.NewKeyword; + + if (!newKeyword.TrailingTrivia.Any()) + newKeyword = newKeyword.WithTrailingTrivia(ElasticSpace); + + ObjectCreationExpressionSyntax objectCreation = ObjectCreationExpression( + newKeyword, + variableDeclaration.Type.WithoutTrivia(), + implicitObjectCreation.ArgumentList, + implicitObjectCreation.Initializer); + + VariableDeclarationSyntax newVariableDeclaration = variableDeclaration.ReplaceNode(implicitObjectCreation, objectCreation) + .WithType(CSharpFactory.VarType().WithTriviaFrom(variableDeclaration.Type)); + + return document.ReplaceNodeAsync(variableDeclaration, newVariableDeclaration, ct); + }, + GetEquivalenceKey(diagnostic)); + + context.RegisterCodeFix(codeAction, diagnostic); + } + else if (diagnostic.Properties.ContainsKey(DiagnosticPropertyKeys.ImplicitToCollectionExpression)) { CodeAction codeAction = CodeAction.Create( UseCollectionExpressionTitle, @@ -199,35 +228,5 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) context.RegisterCodeFix(codeAction, diagnostic); } } - else - { - var variableDeclaration = (VariableDeclarationSyntax)node; - - CodeAction codeAction = CodeAction.Create( - UseExplicitObjectCreationTitle, - ct => - { - var implicitObjectCreation = (ImplicitObjectCreationExpressionSyntax)variableDeclaration.Variables.Single().Initializer.Value; - - SyntaxToken newKeyword = implicitObjectCreation.NewKeyword; - - if (!newKeyword.TrailingTrivia.Any()) - newKeyword = newKeyword.WithTrailingTrivia(ElasticSpace); - - ObjectCreationExpressionSyntax objectCreation = ObjectCreationExpression( - newKeyword, - variableDeclaration.Type.WithoutTrivia(), - implicitObjectCreation.ArgumentList, - implicitObjectCreation.Initializer); - - VariableDeclarationSyntax newVariableDeclaration = variableDeclaration.ReplaceNode(implicitObjectCreation, objectCreation) - .WithType(CSharpFactory.VarType().WithTriviaFrom(variableDeclaration.Type)); - - return document.ReplaceNodeAsync(variableDeclaration, newVariableDeclaration, ct); - }, - GetEquivalenceKey(diagnostic)); - - context.RegisterCodeFix(codeAction, diagnostic); - } } } diff --git a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitCreationAnalysis.cs b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitCreationAnalysis.cs index 2551acb951..bbd20e040a 100644 --- a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitCreationAnalysis.cs +++ b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExplicitCreationAnalysis.cs @@ -29,6 +29,11 @@ internal abstract class ImplicitOrExplicitCreationAnalysis new KeyValuePair(DiagnosticPropertyKeys.ExplicitToCollectionExpression, null) }); + protected static readonly ImmutableDictionary _varToExplicit = ImmutableDictionary.CreateRange(new[] + { + new KeyValuePair(DiagnosticPropertyKeys.VarToExplicit, null) + }); + public abstract TypeStyle GetTypeStyle(ref SyntaxNodeAnalysisContext context); protected abstract void ReportExplicitToImplicit(ref SyntaxNodeAnalysisContext context); @@ -37,6 +42,8 @@ internal abstract class ImplicitOrExplicitCreationAnalysis protected abstract void ReportImplicitToExplicit(ref SyntaxNodeAnalysisContext context); + protected abstract void ReportVarToExplicit(ref SyntaxNodeAnalysisContext context, TypeSyntax type); + protected abstract void ReportImplicitToCollectionExpression(ref SyntaxNodeAnalysisContext context); protected abstract void ReportCollectionExpressionToImplicit(ref SyntaxNodeAnalysisContext context); @@ -343,7 +350,7 @@ private void AnalyzeImplicit(ref SyntaxNodeAnalysisContext context) && !isVar && context.UseVarInsteadOfImplicitObjectCreation() == true) { - DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.UseImplicitOrExplicitObjectCreation, variableDeclaration, "explicit"); + ReportVarToExplicit(ref context, variableDeclaration.Type); } } } diff --git a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExpressionArrayCreationAnalysis.cs b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExpressionArrayCreationAnalysis.cs index 4cdbb4c77f..841e463a19 100644 --- a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExpressionArrayCreationAnalysis.cs +++ b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExpressionArrayCreationAnalysis.cs @@ -343,6 +343,29 @@ protected override void ReportImplicitToExplicit(ref SyntaxNodeAnalysisContext c "Use explicitly typed array"); } + protected override void ReportVarToExplicit(ref SyntaxNodeAnalysisContext context, TypeSyntax type) + { + if (context.Node.IsKind(SyntaxKind.CollectionExpression)) + return; + + ITypeSymbol typeSymbol = context.SemanticModel.GetTypeSymbol(type, context.CancellationToken); + + if (typeSymbol?.IsErrorType() != false) + return; + + ITypeSymbol expressionTypeSymbol = context.SemanticModel.GetTypeSymbol(context.Node, context.CancellationToken); + + if (!SymbolEqualityComparer.IncludeNullability.Equals(typeSymbol, expressionTypeSymbol)) + return; + + DiagnosticHelpers.ReportDiagnostic( + context, + DiagnosticRules.UseExplicitlyOrImplicitlyTypedArray, + GetLocationFromImplicit(ref context), + properties: _varToExplicit, + "Use explicitly typed array"); + } + protected override void ReportImplicitToCollectionExpression(ref SyntaxNodeAnalysisContext context) { DiagnosticHelpers.ReportDiagnostic( diff --git a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExpressionObjectCreationAnalysis.cs b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExpressionObjectCreationAnalysis.cs index 280b58756c..9e6ebc17cf 100644 --- a/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExpressionObjectCreationAnalysis.cs +++ b/src/Analyzers/CSharp/Analysis/ObjectCreation/ImplicitOrExpressionObjectCreationAnalysis.cs @@ -29,7 +29,7 @@ protected override void ReportExplicitToImplicit(ref SyntaxNodeAnalysisContext c context, DiagnosticRules.UseImplicitOrExplicitObjectCreation, objectCreation.Type.GetLocation(), - "Simplify array creation"); + "Simplify object creation"); } protected override void ReportExplicitToCollectionExpression(ref SyntaxNodeAnalysisContext context) @@ -41,7 +41,7 @@ protected override void ReportExplicitToCollectionExpression(ref SyntaxNodeAnaly DiagnosticRules.UseImplicitOrExplicitObjectCreation, objectCreation.Type.GetLocation(), properties: _explicitToCollectionExpression, - "Simplify array creation"); + "Simplify object creation"); } protected override void ReportImplicitToExplicit(ref SyntaxNodeAnalysisContext context) @@ -53,6 +53,16 @@ protected override void ReportImplicitToExplicit(ref SyntaxNodeAnalysisContext c "Use explicit object creation"); } + protected override void ReportVarToExplicit(ref SyntaxNodeAnalysisContext context, TypeSyntax type) + { + DiagnosticHelpers.ReportDiagnostic( + context, + DiagnosticRules.UseImplicitOrExplicitObjectCreation, + context.Node.GetLocation(), + properties: _varToExplicit, + "Use explicit object creation"); + } + protected override void ReportImplicitToCollectionExpression(ref SyntaxNodeAnalysisContext context) { DiagnosticHelpers.ReportDiagnostic( @@ -60,7 +70,7 @@ protected override void ReportImplicitToCollectionExpression(ref SyntaxNodeAnaly DiagnosticRules.UseImplicitOrExplicitObjectCreation, context.Node.GetLocation(), properties: _implicitToCollectionExpression, - "Simplify array creation"); + "Simplify object creation"); } protected override void ReportCollectionExpressionToImplicit(ref SyntaxNodeAnalysisContext context) @@ -70,6 +80,6 @@ protected override void ReportCollectionExpressionToImplicit(ref SyntaxNodeAnaly DiagnosticRules.UseImplicitOrExplicitObjectCreation, context.Node.GetLocation(), properties: _collectionExpressionToImplicit, - "Simplify array creation"); + "Simplify object creation"); } } diff --git a/src/Common/CSharp/Analysis/DiagnosticPropertyKeys.cs b/src/Common/CSharp/Analysis/DiagnosticPropertyKeys.cs index 836d2eadac..3f7addfdfe 100644 --- a/src/Common/CSharp/Analysis/DiagnosticPropertyKeys.cs +++ b/src/Common/CSharp/Analysis/DiagnosticPropertyKeys.cs @@ -7,4 +7,5 @@ internal static class DiagnosticPropertyKeys internal static readonly string ImplicitToCollectionExpression = nameof(ImplicitToCollectionExpression); internal static readonly string CollectionExpressionToImplicit = nameof(CollectionExpressionToImplicit); internal static readonly string ExplicitToCollectionExpression = nameof(ExplicitToCollectionExpression); + internal static readonly string VarToExplicit = nameof(VarToExplicit); } diff --git a/src/Tests/Analyzers.Tests/RCS1014UseImplicitlyTypedArrayTests.cs b/src/Tests/Analyzers.Tests/RCS1014UseImplicitlyTypedArrayTests.cs index 13a0436c70..787c2c3934 100644 --- a/src/Tests/Analyzers.Tests/RCS1014UseImplicitlyTypedArrayTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1014UseImplicitlyTypedArrayTests.cs @@ -196,4 +196,75 @@ void M() ", options: Options.AddConfigOption(ConfigOptionKeys.ArrayCreationTypeStyle, ConfigOptionValues.ArrayCreationTypeStyle_Implicit) .AddConfigOption(ConfigOptionKeys.UseVarInsteadOfImplicitObjectCreation, true)); } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseImplicitOrExplicitObjectCreation)] + public async Task Test_UseVarInsteadOfImplicitCreation() + { + await VerifyDiagnosticAndFixAsync(@" +using System.Collections.Generic; + +class C +{ + void M() + { + string[] addresses = [|new[]|] + { + ""address 1"", + ""address 2"" + }; + } +} +", @" +using System.Collections.Generic; + +class C +{ + void M() + { + var addresses = new string[] + { + ""address 1"", + ""address 2"" + }; + } +} +", options: Options.AddConfigOption(ConfigOptionKeys.ObjectCreationTypeStyle, ConfigOptionValues.ArrayCreationTypeStyle_Implicit) + .AddConfigOption(ConfigOptionKeys.UseVarInsteadOfImplicitObjectCreation, true)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseImplicitOrExplicitObjectCreation)] + public async Task TestNoDiagnostic_UseVarInsteadOfImplicitCreation() + { + await VerifyNoDiagnosticAsync(@" +using System.Collections.Generic; + +class C +{ + void M() + { + ICollection addresses = new[] + { + ""a"", + ""b"" + }; + } +} +", options: Options.AddConfigOption(ConfigOptionKeys.UseVarInsteadOfImplicitObjectCreation, true)); + } + + [Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseImplicitOrExplicitObjectCreation)] + public async Task TestNoDiagnostic_UseVarInsteadOfImplicitCreation_CollectionExpression() + { + await VerifyNoDiagnosticAsync(@" +using System.Collections.Generic; + +class C +{ + void M() + { + string[] addresses = [ ""a"", ""b"" ]; + } +} +", options: Options.AddConfigOption(ConfigOptionKeys.UseVarInsteadOfImplicitObjectCreation, true)); + } } diff --git a/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs b/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs index 6e8251facf..9dd53aa4af 100644 --- a/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs +++ b/src/Tests/Analyzers.Tests/RCS1250UseImplicitOrExplicitObjectCreationTests.cs @@ -1525,7 +1525,7 @@ class C { void M() { - [|string s = new(' ', 1)|]; + string s = [|new(' ', 1)|]; } } ", @" @@ -1563,7 +1563,7 @@ class C { void M() { - using ([|StringReader s = new("""")|]) + using (StringReader s = [|new("""")|]) { } }