Skip to content

Commit

Permalink
RCS1246 - update, added Last (#1436)
Browse files Browse the repository at this point in the history
  • Loading branch information
jakubreznak committed Apr 15, 2024
1 parent 38481fb commit 429fddf
Show file tree
Hide file tree
Showing 8 changed files with 92 additions and 12 deletions.
1 change: 1 addition & 0 deletions ChangeLog.md
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- [CLI] Bump Roslyn to 4.9.2 ([PR](https://github.com/dotnet/roslynator/pull/1441))
- Convert `Last()` to `[]` ([RCS1246](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1246)) ([PR](https://github.com/dotnet/roslynator/pull/1436))

### Fixed

Expand Down
Expand Up @@ -257,6 +257,16 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context)
ct => UseElementAccessInsteadOfEnumerableMethodRefactoring.UseElementAccessInsteadOfElementAtAsync(document, invocation, ct),
GetEquivalenceKey(diagnostic, "UseElementAccessInsteadOfElementAt"));

context.RegisterCodeFix(codeAction, diagnostic);
return;
}
case "Last":
{
CodeAction codeAction = CodeAction.Create(
"Use [] instead of calling 'Last'",
ct => UseElementAccessInsteadOfEnumerableMethodRefactoring.UseElementAccessInsteadOfLastAsync(document, invocation, ct),
GetEquivalenceKey(diagnostic, "UseElementAccessInsteadOfLast"));

context.RegisterCodeFix(codeAction, diagnostic);
return;
}
Expand Down
5 changes: 5 additions & 0 deletions src/Analyzers.xml
Expand Up @@ -7277,6 +7277,7 @@ public class C
<Title>Use element access</Title>
<DefaultSeverity>Info</DefaultSeverity>
<IsEnabledByDefault>true</IsEnabledByDefault>
<MinLanguageVersion>8.0 for [^1]</MinLanguageVersion>
<Samples>
<Sample>
<Before><![CDATA[list.First()]]></Before>
Expand All @@ -7286,6 +7287,10 @@ public class C
<Before><![CDATA[list.ElementAt(1)]]></Before>
<After><![CDATA[list[1]]]></After>
</Sample>
<Sample>
<Before><![CDATA[list.Last()]]></Before>
<After><![CDATA[list[^1]]]></After>
</Sample>
</Samples>
<Options>
<Option Identifier="DoNotUseElementAccessWhenExpressionIsInvocation">
Expand Down
17 changes: 17 additions & 0 deletions src/Analyzers/CSharp/Analysis/InvocationExpressionAnalyzer.cs
Expand Up @@ -192,6 +192,23 @@ private static void AnalyzeInvocationExpression(SyntaxNodeAnalysisContext contex
break;
}
case "Last":
{
if (DiagnosticRules.OptimizeLinqMethodCall.IsEffective(context))
{
if (!invocationInfo.Expression.IsKind(SyntaxKind.ElementAccessExpression, SyntaxKind.InvocationExpression)
&& UseElementAccessAnalysis.IsFixableLast(invocationInfo, context.SemanticModel, context.CancellationToken))
{
DiagnosticHelpers.ReportDiagnostic(
context,
DiagnosticRules.UseElementAccess,
Location.Create(invocation.SyntaxTree, TextSpan.FromBounds(invocationInfo.Name.SpanStart, invocationInfo.ArgumentList.Span.End)));
}

OptimizeLinqMethodCallAnalysis.AnalyzeWhere(context, invocationInfo);
}

break;
}
case "LastOrDefault":
case "LongCount":
case "Single":
Expand Down
3 changes: 3 additions & 0 deletions src/Common/CSharp/Analysis/UseElementAccessAnalysis.cs
Expand Up @@ -74,6 +74,9 @@ internal static class UseElementAccessAnalysis
if (invocationInfo.InvocationExpression.IsParentKind(SyntaxKind.ExpressionStatement))
return false;

if (((CSharpCompilation)semanticModel.Compilation).LanguageVersion < LanguageVersion.CSharp8)
return false;

IMethodSymbol methodSymbol = semanticModel.GetReducedExtensionMethodInfo(invocationInfo.InvocationExpression, cancellationToken).Symbol;

if (methodSymbol is null)
Expand Down
Expand Up @@ -50,14 +50,9 @@ public static async Task ComputeRefactoringsAsync(RefactoringContext context, In
if (!UseElementAccessAnalysis.IsFixableLast(invocationInfo, semanticModel, context.CancellationToken))
break;

string propertyName = CSharpUtility.GetCountOrLengthPropertyName(invocationInfo.Expression, semanticModel, context.CancellationToken);

if (propertyName is null)
break;

context.RegisterRefactoring(
"Use [] instead of calling 'Last'",
ct => UseElementAccessInsteadOfEnumerableMethodRefactoring.UseElementAccessInsteadOfLastAsync(context.Document, invocation, propertyName, ct),
ct => UseElementAccessInsteadOfEnumerableMethodRefactoring.UseElementAccessInsteadOfLastAsync(context.Document, invocation, ct),
RefactoringDescriptors.UseElementAccessInsteadOfLinqMethod);

break;
Expand Down
54 changes: 54 additions & 0 deletions src/Tests/Analyzers.Tests/RCS1246UseElementAccessTests.cs
Expand Up @@ -94,6 +94,60 @@ void M()
");
}

[Theory, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseElementAccess)]
[InlineData("((List<object>)x).[|Last()|]", "((List<object>)x)[^1]")]
[InlineData("((IList<object>)x).[|Last()|]", "((IList<object>)x)[^1]")]
[InlineData("((IReadOnlyList<object>)x).[|Last()|]", "((IReadOnlyList<object>)x)[^1]")]
[InlineData("((Collection<object>)x).[|Last()|]", "((Collection<object>)x)[^1]")]
[InlineData("((ImmutableArray<object>)x).[|Last()|]", "((ImmutableArray<object>)x)[^1]")]
[InlineData("((object[])x).[|Last()|]", "((object[])x)[^1]")]
[InlineData("((string)x).[|Last()|]", "((string)x)[^1]")]
public async Task Test_UseElementAccessInsteadOfLast(string source, string expected)
{
await VerifyDiagnosticAndFixAsync(@"
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
class C
{
void M()
{
object x = null;
var y = [||];
}
}
", source, expected);
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseElementAccess)]
public async Task TestNoDiagnostic_UseElementAccessInsteadOfLast()
{
await VerifyNoDiagnosticAsync(@"
using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
class C
{
void M()
{
object x = null;
x = ((ICollection<object>)x).Last();
x = ((IReadOnlyCollection<object>)x).Last();
x = ((IEnumerable<object>)x).Last();
x = ((Dictionary<object, object>)x).Last();
}
}
");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseElementAccess)]
public async Task TestNoDiagnostic_UseElementAccessInsteadOfFirst()
{
Expand Down
Expand Up @@ -85,7 +85,6 @@ internal static class UseElementAccessInsteadOfEnumerableMethodRefactoring
public static Task<Document> UseElementAccessInsteadOfLastAsync(
Document document,
InvocationExpressionSyntax invocation,
string propertyName,
CancellationToken cancellationToken = default)
{
ArgumentListSyntax argumentList = invocation.ArgumentList;
Expand All @@ -105,11 +104,7 @@ internal static class UseElementAccessInsteadOfEnumerableMethodRefactoring
expression = expression.WithTrailingTrivia(trivia);
}

ExpressionSyntax argumentExpression = SubtractExpression(
SimpleMemberAccessExpression(
expression,
IdentifierName(propertyName)),
NumericLiteralExpression(1));
ExpressionSyntax argumentExpression = ParseExpression("^1");

ElementAccessExpressionSyntax elementAccess = ElementAccessExpression(
expression,
Expand Down

0 comments on commit 429fddf

Please sign in to comment.