From 32732ae51c09918a147bb378c9249e306e6c0953 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rald=20Barr=C3=A9?= Date: Tue, 29 Oct 2024 21:04:24 -0400 Subject: [PATCH] MA0151 handles backslashs in expressions --- ...teShouldContainValidExpressionsAnalyzer.cs | 52 ++++++++---- ...uldContainValidExpressionsAnalyzerTests.cs | 84 +++++++++++++++++++ 2 files changed, 122 insertions(+), 14 deletions(-) diff --git a/src/Meziantou.Analyzer/Rules/DebuggerDisplayAttributeShouldContainValidExpressionsAnalyzer.cs b/src/Meziantou.Analyzer/Rules/DebuggerDisplayAttributeShouldContainValidExpressionsAnalyzer.cs index d179f0196..8ad296aac 100644 --- a/src/Meziantou.Analyzer/Rules/DebuggerDisplayAttributeShouldContainValidExpressionsAnalyzer.cs +++ b/src/Meziantou.Analyzer/Rules/DebuggerDisplayAttributeShouldContainValidExpressionsAnalyzer.cs @@ -10,7 +10,7 @@ namespace Meziantou.Analyzer.Rules; [DiagnosticAnalyzer(LanguageNames.CSharp)] public sealed class DebuggerDisplayAttributeShouldContainValidExpressionsAnalyzer : DiagnosticAnalyzer { - private static readonly char[] MemberSeparators = [',', '(', '.', '[']; + private static readonly char[] MemberSeparators = [',', '(', '.', '[', ' ']; private static readonly DiagnosticDescriptor Rule = new( RuleIdentifiers.DebuggerDisplayAttributeShouldContainValidExpressions, @@ -80,29 +80,44 @@ static bool MemberExists(INamedTypeSymbol? symbol, string name) { List? result = null; + static int IndexOf(ReadOnlySpan value, char c) + { + var skipped = 0; + while (!value.IsEmpty) + { + var index = value.IndexOfAny(c, '\\'); + if (index < 0) + return -1; + + if (value[index] == c) + return index + skipped; + + if (index + 1 < value.Length) + { + skipped += index + 2; + value = value[(index + 2)..]; + } + else + { + return -1; + } + } + + return -1; + } + while (!value.IsEmpty) { - var startIndex = value.IndexOf('{'); + var startIndex = IndexOf(value, '{'); if (startIndex < 0) break; value = value[(startIndex + 1)..]; - var endIndex = value.IndexOf('}'); + var endIndex = IndexOf(value, '}'); if (endIndex < 0) break; var member = value[..endIndex]; - - - static string GetMemberName(ReadOnlySpan member) - { - var index = member.IndexOfAny(MemberSeparators); - if (index < 0) - return member.ToString(); - - return member[..index].ToString(); - } - result ??= []; result.Add(GetMemberName(member)); @@ -110,6 +125,15 @@ static string GetMemberName(ReadOnlySpan member) } return result; + + static string GetMemberName(ReadOnlySpan member) + { + var index = member.IndexOfAny(MemberSeparators); + if (index < 0) + return member.ToString(); + + return member[..index].ToString(); + } } static void ValidateValue(SymbolAnalysisContext context, INamedTypeSymbol symbol, AttributeData attribute, string value) diff --git a/tests/Meziantou.Analyzer.Test/Rules/DebuggerDisplayAttributeShouldContainValidExpressionsAnalyzerTests.cs b/tests/Meziantou.Analyzer.Test/Rules/DebuggerDisplayAttributeShouldContainValidExpressionsAnalyzerTests.cs index a1101a447..11748e6c9 100755 --- a/tests/Meziantou.Analyzer.Test/Rules/DebuggerDisplayAttributeShouldContainValidExpressionsAnalyzerTests.cs +++ b/tests/Meziantou.Analyzer.Test/Rules/DebuggerDisplayAttributeShouldContainValidExpressionsAnalyzerTests.cs @@ -206,4 +206,88 @@ await CreateProjectBuilder() .WithSourceCode(SourceCode) .ValidateAsync(); } + + [Fact] + public async Task SkipEscapedBraces1() + { + const string SourceCode = """ + using System.Diagnostics; + + [DebuggerDisplay(@"Person \{ Name = {Name} \}")] + public record Person(string Name); + """; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task SkipEscapedBraces2() + { + const string SourceCode = """ + using System.Diagnostics; + + [[|DebuggerDisplay(@"Person \\{NameInvalid}")|]] + public record Person(string Name); + """; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task SkipEscapedBraces3() + { + const string SourceCode = """ + using System.Diagnostics; + + [DebuggerDisplay(@"Person \\\{NameInvalid}")] + public record Person(string Name); + """; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task EscapeSingleChar() + { + const string SourceCode = """ + using System.Diagnostics; + + [DebuggerDisplay(@"\")] + public record Person(int Value); + """; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task Escape_IncompleteExpression() + { + const string SourceCode = """ + using System.Diagnostics; + + [DebuggerDisplay(@"{\")] + public record Person(int Value); + """; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } + + [Fact] + public async Task ExpressionLessThan() + { + const string SourceCode = """ + using System.Diagnostics; + + [DebuggerDisplay(@"{Value < 10}")] + public record Person(int Value); + """; + await CreateProjectBuilder() + .WithSourceCode(SourceCode) + .ValidateAsync(); + } }