diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedPrivateMember.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedPrivateMember.cs index d6866fa7fd8..0c70ef038cc 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedPrivateMember.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedPrivateMember.cs @@ -190,10 +190,16 @@ private static HashSet GetUnusedSymbols(CSharpSymbolUsageCollector usag .Except(usageCollector.UsedSymbols) .Where(x => !IsMentionedInDebuggerDisplay(x, usageCollector) && !IsAccessorUsed(x, usageCollector) + && !IsDeconstructMethod(x) && !usageCollector.PrivateAttributes.Contains(x) && !IsUsedWithReflection(x, usageCollector.TypesUsedWithReflection)) .ToHashSet(); + private static bool IsDeconstructMethod(ISymbol symbol) => + symbol is IMethodSymbol { Name: "Deconstruct", Parameters.Length: > 0 } method + && method.ReturnType.Is(KnownType.Void) + && method.Parameters.All(x => x.RefKind == RefKind.Out); + private static bool IsAccessorUsed(ISymbol symbol, CSharpSymbolUsageCollector usageCollector) => symbol is IMethodSymbol { AssociatedSymbol: IPropertySymbol property } accessor && usageCollector.PropertyAccess.TryGetValue(property, out var access) diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/UnusedPrivateMember.CSharp7.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/UnusedPrivateMember.CSharp7.cs index 0a1e3fdba07..a4bad27cac1 100644 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/UnusedPrivateMember.CSharp7.cs +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/UnusedPrivateMember.CSharp7.cs @@ -60,32 +60,29 @@ private sealed class PublicDeconstructWithInnerType { public void Deconstruct(out object a, out InternalDeconstruct b) { a = b = null; } - // deconstructors must be public, internal or protected internal - private void Deconstruct(out object a, out object b) { a = b = null; } // Noncompliant + private void Deconstruct(out object a, out object b) { a = b = null; } // Compliant, Deconstruct methods are ignored } internal sealed class InternalDeconstruct { internal void Deconstruct(out object a, out object b) { a = b = null; } - // deconstructors must be public, internal or protected internal - private void Deconstruct(out object a, out string b, out string c) { a = b = c = null; } // Noncompliant + private void Deconstruct(out object a, out string b, out string c) { a = b = c = null; } // Compliant, Deconstruct methods are ignored } private class PublicDeconstruct { public void Deconstruct(out object a, out object b, out object c) { a = b = c = null; } - // deconstructors must be public, internal or protected internal - protected void Deconstruct(out string a, out string b, out string c) { a = b = c = null; } // Noncompliant - private void Deconstruct(out object a, out string b, out string c) { a = b = c = null; } // Noncompliant + protected void Deconstruct(out string a, out string b, out string c) { a = b = c = null; } // Compliant, Deconstruct methods are ignored + private void Deconstruct(out object a, out string b, out string c) { a = b = c = null; } // Compliant, Deconstruct methods are ignored } private sealed class MultipleDeconstructors { public void Deconstruct(out object a, out object b, out object c) { a = b = c = null; } - public void Deconstruct(out object a, out object b) // Noncompliant + public void Deconstruct(out object a, out object b) // Compliant, Deconstruct methods are ignored { a = b = null; } @@ -95,25 +92,37 @@ private class ProtectedInternalDeconstruct { protected internal void Deconstruct(out object a, out object b) { a = b = null; } - protected internal void Deconstruct(out object a, out object b, out object c) { a = b = c = null; } // Noncompliant + protected internal void Deconstruct(out object a, out object b, out object c) { a = b = c = null; } // Compliant, Deconstruct methods are ignored } private class Ambiguous { public void Deconstruct(out string a, out string b, out string c) { a = b = c = null; } - public void Deconstruct(out object a, out object b, out object c) { a = b = c = null; } // Noncompliant FP, actually the one above is not used + public void Deconstruct(out object a, out object b, out object c) { a = b = c = null; } // Compliant, Deconstruct methods are ignored } private class NotUsedDifferentArgumentCount { - public void Deconstruct(out string a, out string b, out string c) { a = b = c = null; } // Noncompliant - public void Deconstruct(out string a, out string b, out string c, out string d) { a = b = c = d = null; } // Noncompliant + public void Deconstruct(out string a, out string b, out string c) { a = b = c = null; } // Compliant, Deconstruct methods are ignored + public void Deconstruct(out string a, out string b, out string c, out string d) { a = b = c = d = null; } // Compliant, Deconstruct methods are ignored } private class NotUsedNotVisible { - protected void Deconstruct(out object a, out object b) { a = b = null; } // Noncompliant - private void Deconstruct(out string a, out string b) { a = b = null; } // Noncompliant + protected void Deconstruct(out object a, out object b) { a = b = null; } // Compliant, Deconstruct methods are ignored + private void Deconstruct(out string a, out string b) { a = b = null; } // Compliant, Deconstruct methods are ignored + } + + public class InvalidDeconstruct + { + private void Deconstruct(object a, out object b, out object c) { b = c = a; } // Noncompliant + private void Deconstruct() { } // Noncompliant + + private int Deconstruct(out object a, out object b, out object c) // Noncompliant + { + a = b = c = null; + return 42; + } } private ForMethod ReturnFromMethod() => null; diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/UnusedPrivateMember.CSharp8.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/UnusedPrivateMember.CSharp8.cs index 7f646832d0b..621e6383886 100644 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/UnusedPrivateMember.CSharp8.cs +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/UnusedPrivateMember.CSharp8.cs @@ -33,12 +33,12 @@ public void SomeMethod() private sealed class ForSwitchArm { - public void Deconstruct(out object a, out object b) { a = b = null; } // Noncompliant FP + public void Deconstruct(out object a, out object b) { a = b = null; } // Compliant, Deconstruct methods are ignored } private sealed class ForIsPattern { - public void Deconstruct(out string a, out string b) { a = b = null; } // Noncompliant FP + public void Deconstruct(out string a, out string b) { a = b = null; } // Compliant } } }