Skip to content

Commit

Permalink
Use collection expression (RCS1014, RCS1250) (#1325)
Browse files Browse the repository at this point in the history
  • Loading branch information
josefpihrt authored Dec 9, 2023
1 parent 3c28eff commit 5b9e625
Show file tree
Hide file tree
Showing 39 changed files with 2,358 additions and 811 deletions.
3 changes: 3 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Make analyzer [RCS0014](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS0014) obsolete
- Add code fix "Declare as nullable" ([PR](https://github.com/dotnet/roslynator/pull/1333))
- Applicable to: `CS8600`, `CS8610`, `CS8765` and `CS8767`
- Add option `roslynator_use_collection_expression = true|false` ([PR](https://github.com/dotnet/roslynator/pull/1325))
- Applicable to [RCS1014](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1014) and [RCS1250](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1250)

### Changed

- Replace type declaration's empty braces with semicolon ([RCS1251](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1251) ([PR](https://github.com/dotnet/roslynator/pull/1323), [PR](https://github.com/dotnet/roslynator/pull/1327))
- [TestFramework] Bump `MSTest.TestFramerk` to `3.1.1` ([PR](https://github.com/dotnet/roslynator/pull/1332))
- [TestFramework] Bump `xunit.assert` to `2.6.2` ([PR](https://github.com/dotnet/roslynator/pull/1332))
- Bump Roslyn to 4.7.0 ([PR](https://github.com/dotnet/roslynator/pull/1325))

## [4.7.0] - 2023-12-03

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,20 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Roslynator.CodeFixes;
using Roslynator.CSharp.Analysis;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
using static Roslynator.CSharp.SyntaxRefactorings;

namespace Roslynator.CSharp.CodeFixes;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider))]
[Shared]
public sealed class UseExplicitlyOrImplicitlyTypedArrayCodeFixProvider : BaseCodeFixProvider
{
private const string UseExplicitlyTypedArrayTitle = "Use explicit type";
private const string UseImplicitlyTypedArrayTitle = "Use implicit type";
private const string UseCollectionExpressionTitle = "Use collection expression";

public override ImmutableArray<string> FixableDiagnosticIds
{
get { return ImmutableArray.Create(DiagnosticIdentifiers.UseExplicitlyOrImplicitlyTypedArray); }
Expand All @@ -28,79 +34,105 @@ public override ImmutableArray<string> FixableDiagnosticIds
public override FixAllProvider GetFixAllProvider()
{
return FixAllProvider.Create(async (context, document, diagnostics) => await FixAllAsync(document, diagnostics, context.CancellationToken).ConfigureAwait(false));
}

private static async Task<Document> FixAllAsync(Document document, ImmutableArray<Diagnostic> diagnostics, CancellationToken cancellationToken)
{
foreach (Diagnostic diagnostic in diagnostics.OrderByDescending(d => d.Location.SourceSpan.Start))
static async Task<Document> FixAllAsync(
Document document,
ImmutableArray<Diagnostic> diagnostics,
CancellationToken cancellationToken)
{
document = await ApplyFixToDocumentAsync(document, diagnostic, cancellationToken).ConfigureAwait(false);
}
foreach (Diagnostic diagnostic in diagnostics.OrderByDescending(d => d.Location.SourceSpan.Start))
{
(Func<CancellationToken, Task<Document>> CreateChangedDocument, string) result
= await GetChangedDocumentAsync(document, diagnostic, cancellationToken).ConfigureAwait(false);

return document;
document = await result.CreateChangedDocument(cancellationToken).ConfigureAwait(false);
}

return document;
}
}

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
SyntaxNode root = await context.GetSyntaxRootAsync().ConfigureAwait(false);

if (!TryFindFirstAncestorOrSelf(
root,
context.Span,
out SyntaxNode node,
predicate: f => f.IsKind(SyntaxKind.ImplicitArrayCreationExpression, SyntaxKind.ArrayCreationExpression)))
{
return;
}

Document document = context.Document;
Diagnostic diagnostic = context.Diagnostics[0];

string title = node switch
{
ImplicitArrayCreationExpressionSyntax => "Use explicitly typed array",
ArrayCreationExpressionSyntax => "Use implicitly typed array",
_ => throw new InvalidOperationException(),
};
(Func<CancellationToken, Task<Document>> CreateChangedDocument, string Title)
= await GetChangedDocumentAsync(document, diagnostic, context.CancellationToken).ConfigureAwait(false);

CodeAction codeAction = CodeAction.Create(
title,
ct => ApplyFixToDocumentAsync(document, diagnostic, ct),
GetEquivalenceKey(diagnostic));
Title,
ct => CreateChangedDocument(ct),
GetEquivalenceKey(diagnostic, (Title == UseCollectionExpressionTitle) ? "UseCollectionExpression" : null));

context.RegisterCodeFix(codeAction, diagnostic);
}

public static async Task<Document> ApplyFixToDocumentAsync(Document document, Diagnostic diag, CancellationToken cancellationToken)
private static async Task<(Func<CancellationToken, Task<Document>>, string)> GetChangedDocumentAsync(
Document document,
Diagnostic diagnostic,
CancellationToken cancellationToken)
{
SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);

if (!TryFindFirstAncestorOrSelf(
root,
diag.Location.SourceSpan,
diagnostic.Location.SourceSpan,
out SyntaxNode node,
predicate: f => f.IsKind(SyntaxKind.ImplicitArrayCreationExpression, SyntaxKind.ArrayCreationExpression)))
predicate: f => f.IsKind(
SyntaxKind.ImplicitArrayCreationExpression,
SyntaxKind.ArrayCreationExpression,
SyntaxKind.CollectionExpression)))
{
return null;
throw new InvalidOperationException();
}

return node switch
if (node is ArrayCreationExpressionSyntax arrayCreation)
{
if (diagnostic.Properties.ContainsKey(DiagnosticPropertyKeys.ExplicitToCollectionExpression))
{
return (ct => ConvertToCollectionExpressionAsync(document, arrayCreation, ct), UseCollectionExpressionTitle);
}
else
{
return (ct => ConvertToImplicitAsync(document, arrayCreation, ct), UseImplicitlyTypedArrayTitle);
}
}
else if (node is ImplicitArrayCreationExpressionSyntax implicitArrayCreation)
{
if (diagnostic.Properties.ContainsKey(DiagnosticPropertyKeys.ImplicitToCollectionExpression))
{
return (ct => ConvertToCollectionExpressionAsync(document, implicitArrayCreation, ct), UseCollectionExpressionTitle);
}
else
{
return (ct => ConvertToExplicitAsync(document, implicitArrayCreation, ct), UseExplicitlyTypedArrayTitle);
}
}
else if (node is CollectionExpressionSyntax collectionExpression)
{
if (diagnostic.Properties.ContainsKey(DiagnosticPropertyKeys.CollectionExpressionToImplicit))
{
return (ct => ConvertToImplicitAsync(document, collectionExpression, ct), UseImplicitlyTypedArrayTitle);
}
else
{
return (ct => ConvertToExplicitAsync(document, collectionExpression, ct), UseExplicitlyTypedArrayTitle);
}
}
else
{
ImplicitArrayCreationExpressionSyntax implicitArrayCreation => await ChangeArrayTypeToExplicitAsync(document, implicitArrayCreation, cancellationToken).ConfigureAwait(false),
ArrayCreationExpressionSyntax arrayCreation => await ChangeArrayTypeToImplicitAsync(document, arrayCreation, cancellationToken).ConfigureAwait(false),
_ => throw new InvalidOperationException()
};
throw new InvalidOperationException();
}
}

private static async Task<Document> ChangeArrayTypeToExplicitAsync(
private static async Task<Document> ConvertToExplicitAsync(
Document document,
ImplicitArrayCreationExpressionSyntax implicitArrayCreation,
CancellationToken cancellationToken)
{
SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);

ITypeSymbol typeSymbol = semanticModel.GetTypeSymbol(implicitArrayCreation, cancellationToken);

var arrayType = (ArrayTypeSyntax)typeSymbol.ToTypeSyntax().WithSimplifierAnnotation();

SyntaxToken newKeyword = implicitArrayCreation.NewKeyword;
Expand All @@ -124,7 +156,24 @@ private static async Task<Document> ChangeArrayTypeToExplicitAsync(
return await document.ReplaceNodeAsync(implicitArrayCreation, newNode, cancellationToken).ConfigureAwait(false);
}

private static async Task<Document> ChangeArrayTypeToImplicitAsync(
private static async Task<Document> ConvertToExplicitAsync(
Document document,
CollectionExpressionSyntax collectionExpression,
CancellationToken cancellationToken)
{
SemanticModel semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false);
ITypeSymbol typeSymbol = semanticModel.GetTypeInfo(collectionExpression, cancellationToken).ConvertedType;

ArrayCreationExpressionSyntax arrayCreation = ArrayCreationExpression(
Token(SyntaxKind.NewKeyword),
(ArrayTypeSyntax)typeSymbol.ToTypeSyntax().WithSimplifierAnnotation(),
ConvertCollectionExpressionToInitializer(collectionExpression, SyntaxKind.ArrayInitializerExpression))
.WithTriviaFrom(collectionExpression);

return await document.ReplaceNodeAsync(collectionExpression, arrayCreation, cancellationToken).ConfigureAwait(false);
}

private static async Task<Document> ConvertToImplicitAsync(
Document document,
ArrayCreationExpressionSyntax arrayCreation,
CancellationToken cancellationToken)
Expand Down Expand Up @@ -159,4 +208,41 @@ private static async Task<Document> ChangeArrayTypeToImplicitAsync(

return await document.ReplaceNodeAsync(arrayCreation, implicitArrayCreation, cancellationToken).ConfigureAwait(false);
}

private static async Task<Document> ConvertToImplicitAsync(
Document document,
CollectionExpressionSyntax collectionExpression,
CancellationToken cancellationToken)
{
InitializerExpressionSyntax initializer = ConvertCollectionExpressionToInitializer(collectionExpression, SyntaxKind.ArrayInitializerExpression);

ImplicitArrayCreationExpressionSyntax implicitArrayCreation = ImplicitArrayCreationExpression(initializer)
.WithTriviaFrom(collectionExpression);

return await document.ReplaceNodeAsync(collectionExpression, implicitArrayCreation, cancellationToken).ConfigureAwait(false);
}

private static async Task<Document> ConvertToCollectionExpressionAsync(
Document document,
ArrayCreationExpressionSyntax arrayCreation,
CancellationToken cancellationToken)
{
CollectionExpressionSyntax collectionExpression = ConvertInitializerToCollectionExpression(arrayCreation.Initializer)
.WithTriviaFrom(arrayCreation)
.WithFormatterAnnotation();

return await document.ReplaceNodeAsync(arrayCreation, collectionExpression, cancellationToken).ConfigureAwait(false);
}

private static async Task<Document> ConvertToCollectionExpressionAsync(
Document document,
ImplicitArrayCreationExpressionSyntax implicitArrayCreation,
CancellationToken cancellationToken)
{
CollectionExpressionSyntax collectionExpression = ConvertInitializerToCollectionExpression(implicitArrayCreation.Initializer)
.WithTriviaFrom(implicitArrayCreation)
.WithFormatterAnnotation();

return await document.ReplaceNodeAsync(implicitArrayCreation, collectionExpression, cancellationToken).ConfigureAwait(false);
}
}
Loading

0 comments on commit 5b9e625

Please sign in to comment.