diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs index 8dd2d75539734..ecbc1d78b09f2 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/gen/LoggerMessageGenerator.Parser.cs @@ -637,68 +637,76 @@ private static bool ExtractTemplates(string? message, IDictionary + /// Searches for the next brace index in the message. + /// + /// The search skips any sequences of {{ or }}. + /// {{prefix{{{Argument}}}suffix}} + /// The zero-based index position of the first occurrence of the searched brace; -1 if the searched brace was not found; -2 if the wrong brace was found. + private static int FindBraceIndex(string message, char searchedBrace, int startIndex, int endIndex) { - Debug.Assert(brace is '{' or '}'); + Debug.Assert(searchedBrace is '{' or '}'); - // Example: {{prefix{{{Argument}}}suffix}}. int braceIndex = -1; int scanIndex = startIndex; - int braceOccurrenceCount = 0; while (scanIndex < endIndex) { - if (braceOccurrenceCount > 0 && message[scanIndex] != brace) + char current = message[scanIndex]; + + if (current is '{' or '}') { - if (braceOccurrenceCount % 2 == 0) + char currentBrace = current; + + int scanIndexBeforeSkip = scanIndex; + while (current == currentBrace && ++scanIndex < endIndex) { - // Even number of '{' or '}' found. Proceed search with next occurrence of '{' or '}'. - braceOccurrenceCount = 0; - braceIndex = endIndex; + current = message[scanIndex]; } - else + + int bracesCount = scanIndex - scanIndexBeforeSkip; + if (bracesCount % 2 != 0) // if it is an even number of braces, just skip them, otherwise, we found an unescaped brace { - // An unescaped '{' or '}' found. + if (currentBrace == searchedBrace) + { + if (currentBrace == '{') + { + braceIndex = scanIndex - 1; // For '{' pick the last occurrence. + } + else + { + braceIndex = scanIndexBeforeSkip; // For '}' pick the first occurrence. + } + } + else + { + braceIndex = -2; // wrong brace found + } + break; } } - else if (message[scanIndex] == '{') + else { - if (brace != '{') - { - return -2; // not expected - } - - // For '{' pick the last occurrence. - braceIndex = scanIndex; - braceOccurrenceCount++; + scanIndex++; } - else if (message[scanIndex] == '}') - { - if (brace != '}') - { - return -2; // not expected - } - - if (braceOccurrenceCount == 0) - { - // For '}' pick the first occurrence. - braceIndex = scanIndex; - } - braceOccurrenceCount++; - } - - scanIndex++; } return braceIndex; diff --git a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs index 39c4d76ee59b8..4adc5eb25310f 100644 --- a/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs +++ b/src/libraries/Microsoft.Extensions.Logging.Abstractions/tests/Microsoft.Extensions.Logging.Generators.Tests/LoggerMessageGeneratorParserTests.cs @@ -670,7 +670,7 @@ public async Task MalformedFormatString() IReadOnlyList diagnostics = await RunGenerator(@" partial class C { - [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1 {A} M1 { M3"")] + [LoggerMessage(EventId = 1, Level = LogLevel.Debug, Message = ""M1 {A} M1 { M1"")] static partial void M1(ILogger logger); [LoggerMessage(EventId = 2, Level = LogLevel.Debug, Message = ""M2 {A} M2 } M2"")] @@ -691,15 +691,21 @@ partial class C [LoggerMessage(EventId = 7, Level = LogLevel.Debug, Message = ""{M7{"")] static partial void M7(ILogger logger); - [LoggerMessage(EventId = 8, Level = LogLevel.Debug, Message = ""M8 {{arg1}}"")] + [LoggerMessage(EventId = 8, Level = LogLevel.Debug, Message = ""{{{arg1 M8"")] static partial void M8(ILogger logger); - [LoggerMessage(EventId = 9, Level = LogLevel.Debug, Message = ""}M9{arg1}{arg2}"")] + [LoggerMessage(EventId = 9, Level = LogLevel.Debug, Message = ""arg1}}} M9"")] static partial void M9(ILogger logger); + + [LoggerMessage(EventId = 10, Level = LogLevel.Debug, Message = ""{} M10"")] + static partial void M10(ILogger logger); + + [LoggerMessage(EventId = 11, Level = LogLevel.Debug, Message = ""{ } M11"")] + static partial void M11(ILogger logger); } "); - Assert.Equal(9, diagnostics.Count); + Assert.Equal(11, diagnostics.Count); foreach (var diagnostic in diagnostics) { Assert.Equal(DiagnosticDescriptors.MalformedFormatStrings.Id, diagnostic.Id); @@ -707,7 +713,7 @@ partial class C } [Fact] - public async Task Templates() + public async Task ValidTemplates() { IReadOnlyList diagnostics = await RunGenerator(@" partial class C @@ -727,14 +733,14 @@ partial class C [LoggerMessage(EventId = 5, Level = LogLevel.Debug, Message = ""{arg1} M5"")] static partial void M5(ILogger logger, int arg1); - [LoggerMessage(EventId = 6, Level = LogLevel.Debug, Message = ""{arg1}{arg2}"")] + [LoggerMessage(EventId = 6, Level = LogLevel.Debug, Message = ""M6{arg1}M6{arg2}M6"")] static partial void M6(ILogger logger, string arg1, string arg2); - [LoggerMessage(EventId = 7, Level = LogLevel.Debug, Message = ""M7 {arg1} {arg2}"")] - static partial void M7(ILogger logger, string arg1, string arg2); + [LoggerMessage(EventId = 7, Level = LogLevel.Debug, Message = ""M7 {{const}}"")] + static partial void M7(ILogger logger); - [LoggerMessage(EventId = 8, Level = LogLevel.Debug, Message = ""{arg1} M8 {arg2} "")] - static partial void M8(ILogger logger, string arg1, string arg2); + [LoggerMessage(EventId = 8, Level = LogLevel.Debug, Message = ""{{prefix{{{arg1}}}suffix}}"")] + static partial void M8(ILogger logger, string arg1); } ");