Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use collection expression (RCS1014, RCS1250) #1325

Merged
merged 30 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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