diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedStringBuilder.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedStringBuilder.cs index 8a53d1f0eb1..6bdc2c77c0c 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedStringBuilder.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/UnusedStringBuilder.cs @@ -60,6 +60,9 @@ public sealed class UnusedStringBuilder : UnusedStringBuilderBase interpolations, ISymbol variableSymbol, SemanticModel semanticModel) => interpolations.Any(x => IsSameVariable(x.Expression, variableSymbol, semanticModel)); + protected override bool IsPropertyReferenced(VariableDeclaratorSyntax declaration, IList invocations, ISymbol variableSymbol, SemanticModel semanticModel) => + GetElementAccessExpressions(declaration).Any(x => IsSameVariable(x.Expression, variableSymbol, semanticModel)); + private static bool IsStringBuilderObjectCreation(ExpressionSyntax expression, SemanticModel semanticModel) => (expression is ObjectCreationExpressionSyntax || ImplicitObjectCreationExpressionSyntaxWrapper.IsInstance(expression)) && ObjectCreationFactory.Create(expression).IsKnownType(KnownType.System_Text_StringBuilder, semanticModel); @@ -72,6 +75,11 @@ public sealed class UnusedStringBuilder : UnusedStringBuilderBase variableSymbol.Equals(semanticModel.GetSymbolInfo(identifier).Symbol); + private static IList GetElementAccessExpressions(VariableDeclaratorSyntax declaration) => + declaration.IsTopLevel() + ? GetTopLevelStatementSyntax(declaration) + : declaration.Parent.Parent.Parent.DescendantNodes().OfType().ToList(); + private static IList GetTopLevelStatementSyntax(VariableDeclaratorSyntax declaration) { List list = new(); diff --git a/analyzers/src/SonarAnalyzer.Common/Rules/UnusedStringBuilderBase.cs b/analyzers/src/SonarAnalyzer.Common/Rules/UnusedStringBuilderBase.cs index c0db0e9905e..b96c0eeaa92 100644 --- a/analyzers/src/SonarAnalyzer.Common/Rules/UnusedStringBuilderBase.cs +++ b/analyzers/src/SonarAnalyzer.Common/Rules/UnusedStringBuilderBase.cs @@ -41,6 +41,7 @@ public abstract class UnusedStringBuilderBase invocations, ISymbol variableSymbol, SemanticModel semanticModel); protected abstract bool IsReturned(IList returnStatements, ISymbol variableSymbol, SemanticModel semanticModel); protected abstract bool IsWithinInterpolatedString(IList interpolations, ISymbol variableSymbol, SemanticModel semanticModel); + protected abstract bool IsPropertyReferenced(TVariableDeclarator declaration, IList invocations, ISymbol variableSymbol, SemanticModel semanticModel); protected UnusedStringBuilderBase() : base(DiagnosticId) { } @@ -57,7 +58,8 @@ public abstract class UnusedStringBuilderBase interpolations, ISymbol variableSymbol, SemanticModel semanticModel) => interpolations.Any(x => IsSameVariable(x.Expression, variableSymbol, semanticModel)); + protected override bool IsPropertyReferenced(VariableDeclaratorSyntax declaration, IList invocations, ISymbol variableSymbol, SemanticModel semanticModel) => + invocations.Any(x => IsSameVariable(x.Expression, variableSymbol, semanticModel) && semanticModel.GetOperation(x).Kind is OperationKindEx.PropertyReference); + private static bool IsSameVariable(ExpressionSyntax expression, ISymbol variableSymbol, SemanticModel semanticModel) => expression.DescendantNodesAndSelf().OfType().Any(p => IsSameVariable(p, variableSymbol, semanticModel)) || (expression.Ancestors().OfType().Any() && expression.Ancestors().OfType().First() diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/UnusedStringBuilderTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/UnusedStringBuilderTest.cs index bbd9e0cf81d..30bab62e502 100644 --- a/analyzers/tests/SonarAnalyzer.UnitTest/Rules/UnusedStringBuilderTest.cs +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Rules/UnusedStringBuilderTest.cs @@ -39,7 +39,7 @@ public class UnusedStringBuilderTest [DataRow("""var a = sb.Append("").Append("").Append("").Append("").ToString().ToLower();""", true)] [DataRow("sb.CopyTo(0, new char[1], 0, 1);", true)] [DataRow("sb.GetChunks();", true)] - [DataRow("var a = sb[0];", false)] // FP + [DataRow("var a = sb[0];", true)] [DataRow("""sb?.Append("").ToString().ToLower();""", true)] [DataRow("""@sb.Append("").ToString();""", true)] [DataRow("sb.Remove(sb.Length - 1, 1);", true)] @@ -83,7 +83,7 @@ public void MyMethod() [DataRow("""var a = sb.Append("").Append("").Append("").Append("").ToString().ToLower();""", true)] [DataRow("sb.CopyTo(0, new char[1], 0, 1);", true)] [DataRow("sb.GetChunks();", true)] - [DataRow("var a = sb[0];", false)] // FP + [DataRow("var a = sb[0];", true)] [DataRow("""sb?.Append("").ToString().ToLower();""", true)] [DataRow("""@sb.Append("").ToString();""", true)] [DataRow("sb.Remove(sb.Length - 1, 1);", true)] @@ -115,7 +115,7 @@ public void UnusedStringBuilder_TopLevelStatements(string expression, bool compl [DataRow("""Dim a = sb.Append("").Append("").Append("").Append("").ToString().ToLower()""", true)] [DataRow("sb.CopyTo(0, New Char(0) {}, 0, 1)", true)] [DataRow("sb.GetChunks()", true)] - [DataRow("Dim a = sb(0)", false)] // FP + [DataRow("Dim a = sb(0)", true)] [DataRow("""sb?.Append("").ToString().ToLower()""", true)] [DataRow("""sb.Append("").ToString()""", true)] [DataRow("sb.Remove(sb.Length - 1, 1)", true)]