Skip to content

Commit a3b471c

Browse files
authored
Fix analyzer RCS1246 - handle generic OrderedDictionary (#1676)
1 parent d90d40b commit a3b471c

File tree

20 files changed

+134
-25
lines changed

20 files changed

+134
-25
lines changed

.github/workflows/build.yml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ jobs:
2828
- uses: actions/checkout@v4
2929
with:
3030
fetch-depth: 0
31+
- uses: actions/setup-dotnet@v4
32+
with:
33+
dotnet-version: |
34+
8.0.x
35+
9.0.x
3136
- run: dotnet tool install -g GitVersion.Tool --version 5.11.1
3237
- name: Resolve version
3338
id: version
@@ -81,6 +86,11 @@ jobs:
8186
working-directory: src
8287
steps:
8388
- uses: actions/checkout@v4
89+
- uses: actions/setup-dotnet@v4
90+
with:
91+
dotnet-version: |
92+
8.0.x
93+
9.0.x
8494
- run: dotnet restore Roslynator.CoreAndTesting.slnf
8595
- run: dotnet build Roslynator.CoreAndTesting.slnf --no-restore
8696
- run: dotnet pack Roslynator.CoreAndTesting.slnf --no-build -o _nupkg
@@ -263,7 +273,7 @@ jobs:
263273
- uses: actions/checkout@v4
264274
- uses: actions/setup-dotnet@v4
265275
with:
266-
dotnet-version: 9.0.101
276+
dotnet-version: 9.0.x
267277
- run: dotnet restore
268278
- run: dotnet build --no-restore
269279
- run: dotnet pack --no-build

ChangeLog.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Fixed
11+
12+
- Fix analyzer [RCS1246](https://josefpihrt.github.io/docs/roslynator/analyzers/RCS1246) ([PR](https://github.com/dotnet/roslynator/pull/1676))
13+
1014
## [4.14.0] - 2025-07-26
1115

1216
### Added

src/Analyzers/CSharp/Analysis/OptimizeMethodCallAnalysis.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,14 +183,12 @@ public static void OptimizeStringJoin(SyntaxNodeAnalysisContext context, in Simp
183183
return;
184184

185185
if (!parameters[1].IsParameterArrayOf(SpecialType.System_String, SpecialType.System_Object)
186-
&& !parameters[1].Type.OriginalDefinition.IsIEnumerableOfT())
186+
&& !parameters[1].Type.OriginalDefinition.IsIEnumerableOfT()
187+
&& !parameters[1].Type.OriginalDefinition.HasMetadataName(MetadataNames.System_ReadOnlySpan_T))
187188
{
188189
return;
189190
}
190191

191-
if (firstArgument.Expression is null)
192-
return;
193-
194192
if (!CSharpUtility.IsEmptyStringExpression(firstArgument.Expression, semanticModel, cancellationToken))
195193
return;
196194

src/Common/CSharp/Analysis/UseElementAccessAnalysis.cs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ public static bool IsFixableElementAt(
2121
if (invocationExpression.IsParentKind(SyntaxKind.ExpressionStatement))
2222
return false;
2323

24-
IMethodSymbol methodSymbol = semanticModel.GetReducedExtensionMethodInfo(invocationExpression, cancellationToken).Symbol;
24+
ExtensionMethodSymbolInfo reducedExtensionMethodInfo = semanticModel.GetReducedExtensionMethodInfo(invocationExpression, cancellationToken);
25+
IMethodSymbol methodSymbol = reducedExtensionMethodInfo.Symbol;
2526

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

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

34-
if (!HasAccessibleIndexer(typeSymbol, semanticModel, invocationExpression.SpanStart))
35+
if (!HasAccessibleIndexer(typeSymbol, reducedExtensionMethodInfo.ReducedSymbolOrSymbol.ReturnType, semanticModel, invocationExpression.SpanStart))
3536
return false;
3637

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

56-
IMethodSymbol methodSymbol = semanticModel.GetReducedExtensionMethodInfo(invocationInfo.InvocationExpression, cancellationToken).Symbol;
57+
ExtensionMethodSymbolInfo reducedExtensionMethodInfo = semanticModel.GetReducedExtensionMethodInfo(invocationInfo.InvocationExpression, cancellationToken);
58+
IMethodSymbol methodSymbol = reducedExtensionMethodInfo.Symbol;
5759

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

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

66-
return HasAccessibleIndexer(typeSymbol, semanticModel, invocationInfo.InvocationExpression.SpanStart);
68+
return HasAccessibleIndexer(typeSymbol, reducedExtensionMethodInfo.ReducedSymbolOrSymbol.ReturnType, semanticModel, invocationInfo.InvocationExpression.SpanStart);
6769
}
6870

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

83-
IMethodSymbol methodSymbol = semanticModel.GetReducedExtensionMethodInfo(invocationInfo.InvocationExpression, cancellationToken).Symbol;
85+
ExtensionMethodSymbolInfo reducedExtensionMethodInfo = semanticModel.GetReducedExtensionMethodInfo(invocationInfo.InvocationExpression, cancellationToken);
86+
IMethodSymbol methodSymbol = reducedExtensionMethodInfo.Symbol;
8487

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

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

93-
return HasAccessibleIndexer(typeSymbol, semanticModel, invocationInfo.InvocationExpression.SpanStart);
96+
return HasAccessibleIndexer(typeSymbol, reducedExtensionMethodInfo.ReducedSymbolOrSymbol.ReturnType, semanticModel, invocationInfo.InvocationExpression.SpanStart);
9497
}
9598

9699
private static bool CheckInfiniteRecursion(

src/Core/SymbolUtility.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ public static bool IsEventHandlerMethod(IMethodSymbol methodSymbol)
9393

9494
public static bool HasAccessibleIndexer(
9595
ITypeSymbol typeSymbol,
96+
ITypeSymbol returnType,
9697
SemanticModel semanticModel,
9798
int position)
9899
{
@@ -135,8 +136,12 @@ public static bool HasAccessibleIndexer(
135136

136137
foreach (ISymbol symbol in typeSymbol.GetMembers("this[]"))
137138
{
138-
if (semanticModel.IsAccessible(position, symbol))
139+
if (semanticModel.IsAccessible(position, symbol)
140+
&& symbol is IPropertySymbol indexerSymbol
141+
&& SymbolEqualityComparer.IncludeNullability.Equals(indexerSymbol.Type, returnType))
142+
{
139143
return true;
144+
}
140145
}
141146
}
142147

src/Refactorings/CSharp/Refactorings/ForEachStatementRefactoring.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,9 @@ public static async Task ComputeRefactoringsAsync(RefactoringContext context, Fo
3232
semanticModel = await context.GetSemanticModelAsync().ConfigureAwait(false);
3333

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

36-
if (SymbolUtility.HasAccessibleIndexer(typeSymbol, semanticModel, forEachStatement.SpanStart))
37+
if (SymbolUtility.HasAccessibleIndexer(typeSymbol, elementTypeSymbol, semanticModel, forEachStatement.SpanStart))
3738
{
3839
if (context.IsRefactoringEnabled(RefactoringDescriptors.ConvertForEachToFor))
3940
{

src/Tests/Analyzers.Tests/Analyzers.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
4+
<TargetFramework>net9.0</TargetFramework>
55
</PropertyGroup>
66

77
<PropertyGroup>

src/Tests/Analyzers.Tests/RCS1246UseElementAccessTests.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,27 @@ class C : IReadOnlyList<int>
313313
314314
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
315315
}
316+
");
317+
}
318+
319+
[Fact, Trait(Traits.Analyzer, DiagnosticIdentifiers.UseElementAccess)]
320+
public async Task TestNoDiagnostic_UseElementAccessInsteadOf_OrderedDictionary()
321+
{
322+
await VerifyNoDiagnosticAsync(@"
323+
using System.Collections.Generic;
324+
using System.Linq;
325+
326+
class C
327+
{
328+
void M()
329+
{
330+
OrderedDictionary<string, string> dic = new OrderedDictionary<string, string>();
331+
332+
KeyValuePair<string, string> first = dic.First();
333+
KeyValuePair<string, string> last = dic.Last();
334+
KeyValuePair<string, string> elementAt = dic.ElementAt(1);
335+
}
336+
}
316337
");
317338
}
318339
}

src/Tests/CSharp.Tests/CSharp.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFramework>net8.0</TargetFramework>
4+
<TargetFramework>net9.0</TargetFramework>
55
</PropertyGroup>
66

77
<PropertyGroup>

src/Tests/CSharp.Tests/SyntaxKindTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static void DetectNewSyntaxKinds()
1515
{
1616
List<SyntaxKind> unknownKinds = null;
1717

18-
foreach (SyntaxKind value in Enum.GetValues(typeof(SyntaxKind)))
18+
foreach (SyntaxKind value in Enum.GetValues<SyntaxKind>())
1919
{
2020
switch (value)
2121
{

0 commit comments

Comments
 (0)