-
-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New rule: MA0160 Use ContainsKey instead of TryGetValue when the seco…
…nd arg is a discard (#743)
- Loading branch information
Showing
10 changed files
with
193 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# MA0160 - Use ContainsKey instead of TryGetValue | ||
|
||
````c# | ||
Dictionary<string, string> dict; | ||
dict.TryGetValue("dummy", out _); // non-compliant | ||
dict.TryGetValue("dummy", out var a); // ok | ||
dict.ContainsKey("dummy"); // ok | ||
```` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
{ | ||
"sdk": { | ||
"version": "8.0.302", | ||
"version": "8.0.303", | ||
"rollForward": "latestMajor" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
src/Meziantou.Analyzer/Rules/UseContainsKeyInsteadOfTryGetValueAnalyzer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
using System; | ||
using System.Collections.Immutable; | ||
using System.Linq; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.Diagnostics; | ||
using Microsoft.CodeAnalysis.Operations; | ||
|
||
namespace Meziantou.Analyzer.Rules; | ||
|
||
[DiagnosticAnalyzer(LanguageNames.CSharp)] | ||
public sealed class UseContainsKeyInsteadOfTryGetValueAnalyzer : DiagnosticAnalyzer | ||
{ | ||
private static readonly DiagnosticDescriptor Rule = new( | ||
RuleIdentifiers.UseContainsKeyInsteadOfTryGetValue, | ||
title: "Use ContainsKey instead of TryGetValue", | ||
messageFormat: "Use ContainsKey instead of TryGetValue", | ||
RuleCategories.Performance, | ||
DiagnosticSeverity.Info, | ||
isEnabledByDefault: true, | ||
description: "", | ||
helpLinkUri: RuleIdentifiers.GetHelpUri(RuleIdentifiers.UseContainsKeyInsteadOfTryGetValue)); | ||
|
||
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule); | ||
|
||
public override void Initialize(AnalysisContext context) | ||
{ | ||
context.EnableConcurrentExecution(); | ||
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.ReportDiagnostics); | ||
|
||
context.RegisterCompilationStartAction(ctx => | ||
{ | ||
var analyzerContext = new AnalyzerContext(ctx.Compilation); | ||
ctx.RegisterOperationAction(analyzerContext.AnalyzeInvocation, OperationKind.Invocation); | ||
}); | ||
} | ||
|
||
private sealed class AnalyzerContext(Compilation compilation) | ||
{ | ||
private INamedTypeSymbol? IReadOnlyDictionary { get; } = compilation.GetBestTypeByMetadataName("System.Collections.Generic.IReadOnlyDictionary`2"); | ||
private INamedTypeSymbol? IDictionary { get; } = compilation.GetBestTypeByMetadataName("System.Collections.Generic.IDictionary`2"); | ||
|
||
public void AnalyzeInvocation(OperationAnalysisContext context) | ||
{ | ||
var operation = (IInvocationOperation)context.Operation; | ||
|
||
if (operation is { TargetMethod: { Name: "TryGetValue", Parameters.Length: 2, ContainingType: not null }, Arguments: [_, { Value: IDiscardOperation }] }) | ||
{ | ||
foreach (var symbol in (ReadOnlySpan<INamedTypeSymbol?>)[IReadOnlyDictionary, IDictionary]) | ||
{ | ||
if (symbol is not null) | ||
{ | ||
var iface = operation.TargetMethod.ContainingType.OriginalDefinition.IsEqualTo(symbol) ? operation.TargetMethod.ContainingType : operation.TargetMethod.ContainingType.AllInterfaces.FirstOrDefault(i => SymbolEqualityComparer.Default.Equals(i.OriginalDefinition, symbol)); | ||
if (iface is not null) | ||
{ | ||
if (iface.GetMembers("TryGetValue").FirstOrDefault() is IMethodSymbol member) | ||
{ | ||
var implementation = operation.TargetMethod.IsEqualTo(member) ? member : operation.TargetMethod.ContainingType.FindImplementationForInterfaceMember(member); | ||
if (SymbolEqualityComparer.Default.Equals(operation.TargetMethod, implementation)) | ||
{ | ||
context.ReportDiagnostic(Rule, operation); | ||
return; | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
98 changes: 98 additions & 0 deletions
98
tests/Meziantou.Analyzer.Test/Rules/UseContainsKeyInsteadOfTryGetValueAnalyzerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
using System.Threading.Tasks; | ||
using Meziantou.Analyzer.Rules; | ||
using TestHelper; | ||
using Xunit; | ||
|
||
namespace Meziantou.Analyzer.Test.Rules; | ||
public sealed class UseContainsKeyInsteadOfTryGetValueAnalyzerTests | ||
{ | ||
private static ProjectBuilder CreateProjectBuilder() | ||
{ | ||
return new ProjectBuilder() | ||
.WithAnalyzer<UseContainsKeyInsteadOfTryGetValueAnalyzer>(); | ||
} | ||
|
||
[Fact] | ||
public async Task IDictionary_TryGetValue_Value() | ||
{ | ||
await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
class ClassTest | ||
{ | ||
void Test(System.Collections.Generic.IDictionary<string, string> dict) | ||
{ | ||
dict.TryGetValue("", out var a); | ||
} | ||
} | ||
""") | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task IDictionary_TryGetValue_Discard() | ||
{ | ||
await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
class ClassTest | ||
{ | ||
void Test(System.Collections.Generic.IDictionary<string, string> dict) | ||
{ | ||
[||]dict.TryGetValue("", out _); | ||
} | ||
} | ||
""") | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task IReadOnlyDictionary_TryGetValue_Discard() | ||
{ | ||
await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
class ClassTest | ||
{ | ||
void Test(System.Collections.Generic.IReadOnlyDictionary<string, string> dict) | ||
{ | ||
[||]dict.TryGetValue("", out _); | ||
} | ||
} | ||
""") | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task Dictionary_TryGetValue_Discard() | ||
{ | ||
await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
class ClassTest | ||
{ | ||
void Test(System.Collections.Generic.Dictionary<string, string> dict) | ||
{ | ||
[||]dict.TryGetValue("", out _); | ||
} | ||
} | ||
""") | ||
.ValidateAsync(); | ||
} | ||
|
||
[Fact] | ||
public async Task CustomDictionary_TryGetValue_Discard() | ||
{ | ||
await CreateProjectBuilder() | ||
.WithSourceCode(""" | ||
class ClassTest | ||
{ | ||
void Test(SampleDictionary dict) | ||
{ | ||
[||]dict.TryGetValue("", out _); | ||
} | ||
} | ||
class SampleDictionary : System.Collections.Generic.Dictionary<string, string> | ||
{ | ||
} | ||
""") | ||
.ValidateAsync(); | ||
} | ||
} |