diff --git a/docs/Rules/MA0028.md b/docs/Rules/MA0028.md index 053d4e2e2..a95853a1f 100644 --- a/docs/Rules/MA0028.md +++ b/docs/Rules/MA0028.md @@ -11,6 +11,8 @@ new StringBuilder().Append("a" + 10); new StringBuilder().Append("a").Append(10); ``` +The concatenation of constant strings and chars (e.g. `" " + ConstChar + " "`) is not reported, as the compiler merges it into a single string. + ```csharp new StringBuilder().Append("a"); diff --git a/src/Meziantou.Analyzer/Rules/OptimizeStringBuilderUsageAnalyzer.cs b/src/Meziantou.Analyzer/Rules/OptimizeStringBuilderUsageAnalyzer.cs index a2b00cd02..aeba701a7 100644 --- a/src/Meziantou.Analyzer/Rules/OptimizeStringBuilderUsageAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/OptimizeStringBuilderUsageAnalyzer.cs @@ -357,7 +357,16 @@ private static bool IsEquivalentAppendJoinOverload(IMethodSymbol joinMethod, IMe private static bool IsConstString(IOperation operation) { - return TryGetConstStringValue(operation, out _); + if (TryGetConstStringValue(operation, out _)) + return true; + + // The compiler merges the constant chars and strings of a concatenation into a single string (e.g. " " + 'c' + " ") + return operation switch + { + IConversionOperation { IsImplicit: true, Operand.ConstantValue: { HasValue: true, Value: char } } => true, + IBinaryOperation { OperatorKind: BinaryOperatorKind.Add } binaryOperation when binaryOperation.Type.IsString() => IsConstString(binaryOperation.LeftOperand) && IsConstString(binaryOperation.RightOperand), + _ => false, + }; } private static bool TryGetConstStringValue(IOperation operation, [NotNullWhen(true)] out string? value) diff --git a/tests/Meziantou.Analyzer.Test/Rules/OptimizeStringBuilderUsageAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/OptimizeStringBuilderUsageAnalyzerTests.cs index c42a1cbd6..ffee65508 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/OptimizeStringBuilderUsageAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/OptimizeStringBuilderUsageAnalyzerTests.cs @@ -264,6 +264,10 @@ void A() [InlineData(@"$""abc{""test""}""")] [InlineData(@"""abc"" + ""test""")] [InlineData(@"$""abc{""test""}"" + ""test""")] + [InlineData(@"""abc"" + 'd'")] + [InlineData(@"'a' + ""bc""")] + [InlineData(@""" "" + 'c' + "" """)] + [InlineData(@"""a"" + ('b' + ""c"")")] public Task Append_NoDiagnostic(string text) { var test = CreateTest(); @@ -281,10 +285,28 @@ void A() return test.RunAsync(); } + [Fact] + public Task Append_ConcatConstantChar_NoDiagnostic() + { + var test = CreateTest(); + test.TestCode = """ + using System.Text; + class Test + { + public const char Const = 'C'; + + string A() => new StringBuilder().Append(" " + Const + " ").AppendLine(" " + Const + " ").ToString(); + } + """; + + return test.RunAsync(); + } + [Theory] [InlineData(@"$""a{1}""")] [InlineData(@"""a"" + 10")] [InlineData(@"10 + 20 + ""a""")] + [InlineData(@"""a"" + 'b' + 10")] [InlineData(@"""""")] [InlineData(@""""" + """"")] [InlineData(@""""".Substring(0, 10)")]