Skip to content
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
12 changes: 11 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
- run: dotnet tool install -g GitVersion.Tool --version 5.11.1
- name: Resolve version
id: version
Expand Down Expand Up @@ -81,6 +86,11 @@ jobs:
working-directory: src
steps:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: |
8.0.x
9.0.x
- run: dotnet restore Roslynator.CoreAndTesting.slnf
- run: dotnet build Roslynator.CoreAndTesting.slnf --no-restore
- run: dotnet pack Roslynator.CoreAndTesting.slnf --no-build -o _nupkg
Expand Down Expand Up @@ -263,7 +273,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-dotnet@v4
with:
dotnet-version: 9.0.101
dotnet-version: 9.0.x
- run: dotnet restore
- run: dotnet build --no-restore
- run: dotnet pack --no-build
Expand Down
4 changes: 4 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Fixed

- Fix analyzer [RCS1246](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1246) ([PR](https://github.com/dotnet/roslynator/pull/1676))

## [4.14.0] - 2025-07-26

### Added
Expand Down
6 changes: 2 additions & 4 deletions src/Analyzers/CSharp/Analysis/OptimizeMethodCallAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,14 +183,12 @@ public static void OptimizeStringJoin(SyntaxNodeAnalysisContext context, in Simp
return;

if (!parameters[1].IsParameterArrayOf(SpecialType.System_String, SpecialType.System_Object)
&& !parameters[1].Type.OriginalDefinition.IsIEnumerableOfT())
&& !parameters[1].Type.OriginalDefinition.IsIEnumerableOfT()
&& !parameters[1].Type.OriginalDefinition.HasMetadataName(MetadataNames.System_ReadOnlySpan_T))
{
return;
}

if (firstArgument.Expression is null)
return;

if (!CSharpUtility.IsEmptyStringExpression(firstArgument.Expression, semanticModel, cancellationToken))
return;

Expand Down
15 changes: 9 additions & 6 deletions src/Common/CSharp/Analysis/UseElementAccessAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ public static bool IsFixableElementAt(
if (invocationExpression.IsParentKind(SyntaxKind.ExpressionStatement))
return false;

IMethodSymbol methodSymbol = semanticModel.GetReducedExtensionMethodInfo(invocationExpression, cancellationToken).Symbol;
ExtensionMethodSymbolInfo reducedExtensionMethodInfo = semanticModel.GetReducedExtensionMethodInfo(invocationExpression, cancellationToken);
IMethodSymbol methodSymbol = reducedExtensionMethodInfo.Symbol;

if (methodSymbol is null)
return false;
Expand All @@ -31,7 +32,7 @@ public static bool IsFixableElementAt(

ITypeSymbol typeSymbol = semanticModel.GetTypeSymbol(invocationInfo.Expression, cancellationToken);

if (!HasAccessibleIndexer(typeSymbol, semanticModel, invocationExpression.SpanStart))
if (!HasAccessibleIndexer(typeSymbol, reducedExtensionMethodInfo.ReducedSymbolOrSymbol.ReturnType, semanticModel, invocationExpression.SpanStart))
return false;

ElementAccessExpressionSyntax elementAccess = SyntaxFactory.ElementAccessExpression(
Expand All @@ -53,7 +54,8 @@ public static bool IsFixableFirst(
if (invocationInfo.InvocationExpression.IsParentKind(SyntaxKind.ExpressionStatement))
return false;

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

if (methodSymbol is null)
return false;
Expand All @@ -63,7 +65,7 @@ public static bool IsFixableFirst(

ITypeSymbol typeSymbol = semanticModel.GetTypeSymbol(invocationInfo.Expression, cancellationToken);

return HasAccessibleIndexer(typeSymbol, semanticModel, invocationInfo.InvocationExpression.SpanStart);
return HasAccessibleIndexer(typeSymbol, reducedExtensionMethodInfo.ReducedSymbolOrSymbol.ReturnType, semanticModel, invocationInfo.InvocationExpression.SpanStart);
}

public static bool IsFixableLast(
Expand All @@ -80,7 +82,8 @@ public static bool IsFixableLast(
if (semanticModel.Compilation.GetTypeByMetadataName("System.Index")?.DeclaredAccessibility != Accessibility.Public)
return false;

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

if (methodSymbol is null)
return false;
Expand All @@ -90,7 +93,7 @@ public static bool IsFixableLast(

ITypeSymbol typeSymbol = semanticModel.GetTypeSymbol(invocationInfo.Expression, cancellationToken);

return HasAccessibleIndexer(typeSymbol, semanticModel, invocationInfo.InvocationExpression.SpanStart);
return HasAccessibleIndexer(typeSymbol, reducedExtensionMethodInfo.ReducedSymbolOrSymbol.ReturnType, semanticModel, invocationInfo.InvocationExpression.SpanStart);
}

private static bool CheckInfiniteRecursion(
Expand Down
7 changes: 6 additions & 1 deletion src/Core/SymbolUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public static bool IsEventHandlerMethod(IMethodSymbol methodSymbol)

public static bool HasAccessibleIndexer(
ITypeSymbol typeSymbol,
ITypeSymbol returnType,
SemanticModel semanticModel,
int position)
{
Expand Down Expand Up @@ -135,8 +136,12 @@ public static bool HasAccessibleIndexer(

foreach (ISymbol symbol in typeSymbol.GetMembers("this[]"))
{
if (semanticModel.IsAccessible(position, symbol))
if (semanticModel.IsAccessible(position, symbol)
&& symbol is IPropertySymbol indexerSymbol
&& SymbolEqualityComparer.IncludeNullability.Equals(indexerSymbol.Type, returnType))
{
return true;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,9 @@ public static async Task ComputeRefactoringsAsync(RefactoringContext context, Fo
semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false);

ITypeSymbol typeSymbol = semanticModel.GetTypeSymbol(forEachStatement.Expression, context.CancellationToken);
ITypeSymbol elementTypeSymbol = semanticModel.GetTypeSymbol(forEachStatement.Type, context.CancellationToken);

if (SymbolUtility.HasAccessibleIndexer(typeSymbol, semanticModel, forEachStatement.SpanStart))
if (SymbolUtility.HasAccessibleIndexer(typeSymbol, elementTypeSymbol, semanticModel, forEachStatement.SpanStart))
{
if (context.IsRefactoringEnabled(RefactoringDescriptors.ConvertForEachToFor))
{
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/Analyzers.Tests/Analyzers.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
21 changes: 21 additions & 0 deletions src/Tests/Analyzers.Tests/RCS1246UseElementAccessTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,27 @@ class C : IReadOnlyList<int>

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
");
}

[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseElementAccess)]
public async Task TestNoDiagnostic_UseElementAccessInsteadOf_OrderedDictionary()
{
await VerifyNoDiagnosticAsync(@"
using System.Collections.Generic;
using System.Linq;

class C
{
void M()
{
OrderedDictionary<string, string> dic = new OrderedDictionary<string, string>();

KeyValuePair<string, string> first = dic.First();
KeyValuePair<string, string> last = dic.Last();
KeyValuePair<string, string> elementAt = dic.ElementAt(1);
}
}
");
}
}
2 changes: 1 addition & 1 deletion src/Tests/CSharp.Tests/CSharp.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/CSharp.Tests/SyntaxKindTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static void DetectNewSyntaxKinds()
{
List<SyntaxKind> unknownKinds = null;

foreach (SyntaxKind value in Enum.GetValues(typeof(SyntaxKind)))
foreach (SyntaxKind value in Enum.GetValues<SyntaxKind>())
{
switch (value)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/CodeFixes.Tests/CodeFixes.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/Core.Tests/Core.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
67 changes: 67 additions & 0 deletions src/Tests/Refactorings.Tests/RR0129ConvertForEachToForTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.Threading.Tasks;
using Roslynator.Testing.CSharp;
using Xunit;

namespace Roslynator.CSharp.Refactorings.Tests;

public class RR0129ConvertForEachToForTests : AbstractCSharpRefactoringVerifier
{
public override string RefactoringId { get; } = RefactoringIdentifiers.ConvertForEachToFor;

[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.ConvertForEachToFor)]
public async Task Test()
{
await VerifyRefactoringAsync(@"
using System.Collections.Generic;

class C
{
void M()
{
List<string> items = new List<string>();

f[||]oreach (string item in items)
{
var x = item;
}
}
}
", @"
using System.Collections.Generic;

class C
{
void M()
{
List<string> items = new List<string>();

for (int i = 0; i < items.Count; i++)
{
var x = items[i];
}
}
}
", equivalenceKey: EquivalenceKey.Create(RefactoringId));
}

[Fact, Trait(Traits.Refactoring, RefactoringIdentifiers.ConvertForEachToFor)]
public async Task TestNoRefactoring_OrderedDictionary()
{
await VerifyNoRefactoringAsync(@"
using System.Collections.Generic;

class C
{
void M()
{
OrderedDictionary<int, string> dic = new OrderedDictionary<int, string>();

f[||]oreach (KeyValuePair<int, string> item in dic)
{
KeyValuePair<int, string> x = item;
}
}
}
", equivalenceKey: EquivalenceKey.Create(RefactoringId));
}
}
2 changes: 1 addition & 1 deletion src/Tests/Refactorings.Tests/Refactorings.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/TestConsole/TestConsole.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Tests/TestLibrary/TestLibrary.csproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down
2 changes: 1 addition & 1 deletion src/Tools/CodeGeneration/Markdown/MarkdownGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ public static string CreateAnalyzerMarkdown(AnalyzerMetadata analyzer, Immutable

static IEnumerable<MElement> CreateSamples(AnalyzerMetadata analyzer)
{
IReadOnlyList<SampleMetadata> samples = analyzer.Samples;
List<SampleMetadata> samples = analyzer.Samples;

if (samples.Count > 0)
{
Expand Down
Loading