diff --git a/docs/list-of-diagnostics.md b/docs/list-of-diagnostics.md index e2176891d66..a45d2b28f90 100644 --- a/docs/list-of-diagnostics.md +++ b/docs/list-of-diagnostics.md @@ -97,6 +97,7 @@ You may continue using obsolete APIs in your application, but we advise explorin | `LOGGEN036` | A value being logged doesn't have an effective way to be converted into a string | | `LOGGEN037` | Logging method contains malformed format strings | | `LOGGEN038` | Primary constructor parameter of type Microsoft.Extensions.Logging.ILogger is hidden by a field | +| `LOGGEN039` | Logging method parameters can't have the "params" or "scoped" modifier | # Metrics diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/DiagDescriptors.cs b/src/Generators/Microsoft.Gen.Logging/Parsing/DiagDescriptors.cs index 1763581307b..eabdd62b675 100644 --- a/src/Generators/Microsoft.Gen.Logging/Parsing/DiagDescriptors.cs +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/DiagDescriptors.cs @@ -258,4 +258,10 @@ internal sealed class DiagDescriptors : DiagDescriptorsBase messageFormat: Resources.PrimaryConstructorParameterLoggerHiddenMessage, category: Category, DiagnosticSeverity.Info); + + public static DiagnosticDescriptor LoggingMethodParameterParams { get; } = Make( + id: DiagnosticIds.LoggerMessage.LOGGEN039, + title: Resources.LoggingMethodParameterParamsTitle, + messageFormat: Resources.LoggingMethodParameterParamsMessage, + category: Category); } diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.cs b/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.cs index 94996b7b1b3..43792d1cdbc 100644 --- a/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.cs +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/Parser.cs @@ -540,11 +540,21 @@ private void CheckTagNamesAreUnique(LoggingMethod lm, Dictionary + /// Looks up a localized string similar to Logging method parameter "{0}" has an unsupported modifier ("params" or "scoped"). + /// + internal static string LoggingMethodParameterParamsMessage { + get { + return ResourceManager.GetString("LoggingMethodParameterParamsMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Logging method parameters can't have the "params" or "scoped" modifier. + /// + internal static string LoggingMethodParameterParamsTitle { + get { + return ResourceManager.GetString("LoggingMethodParameterParamsTitle", resourceCulture); + } + } + /// /// Looks up a localized string similar to Parameter "{0}" of logging method "{1}" has a sensitive field/property in its type. /// diff --git a/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.resx b/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.resx index 2ce7a16851a..5fdef3d523a 100644 --- a/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.resx +++ b/src/Generators/Microsoft.Gen.Logging/Parsing/Resources.resx @@ -351,4 +351,10 @@ Class '{0}' has a primary constructor parameter of type Microsoft.Extensions.Logging.ILogger that is hidden by a field in the class or a base class, preventing its use + + Logging method parameter "{0}" has an unsupported modifier ("params" or "scoped") + + + Logging method parameters can't have the "params" or "scoped" modifier + \ No newline at end of file diff --git a/src/Shared/DiagnosticIds/DiagnosticIds.cs b/src/Shared/DiagnosticIds/DiagnosticIds.cs index 0b11c260d10..36c12b81007 100644 --- a/src/Shared/DiagnosticIds/DiagnosticIds.cs +++ b/src/Shared/DiagnosticIds/DiagnosticIds.cs @@ -113,6 +113,7 @@ internal static class LoggerMessage internal const string LOGGEN036 = nameof(LOGGEN036); internal const string LOGGEN037 = nameof(LOGGEN037); internal const string LOGGEN038 = nameof(LOGGEN038); + internal const string LOGGEN039 = nameof(LOGGEN039); } internal static class Metrics diff --git a/test/Generators/Microsoft.Gen.Logging/Generated/LogMethodTests.cs b/test/Generators/Microsoft.Gen.Logging/Generated/LogMethodTests.cs index 12771c42767..5890b19d4ea 100644 --- a/test/Generators/Microsoft.Gen.Logging/Generated/LogMethodTests.cs +++ b/test/Generators/Microsoft.Gen.Logging/Generated/LogMethodTests.cs @@ -760,6 +760,18 @@ public void InParameterTests() Assert.Contains("Hello from S", collector.LatestRecord.Message); } + [Fact] + public void RefReadOnlyParameterTests() + { + using var logger = Utils.GetLogger(); + var collector = logger.FakeLogCollector; + + RefReadOnlyParameterTestExtensions.S s; + RefReadOnlyParameterTestExtensions.M0(logger, ref s); + Assert.Equal(1, collector.Count); + Assert.Contains("Hello from S", collector.LatestRecord.Message); + } + [Fact] public void AtSymbolsTest() { diff --git a/test/Generators/Microsoft.Gen.Logging/TestClasses/RefReadOnlyParameterTestExtensions.cs b/test/Generators/Microsoft.Gen.Logging/TestClasses/RefReadOnlyParameterTestExtensions.cs new file mode 100644 index 00000000000..aa6514fe773 --- /dev/null +++ b/test/Generators/Microsoft.Gen.Logging/TestClasses/RefReadOnlyParameterTestExtensions.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.Logging; + +namespace TestClasses +{ + internal static partial class RefReadOnlyParameterTestExtensions + { + internal struct S + { + public override readonly string ToString() => "Hello from S"; + } + + [LoggerMessage(0, LogLevel.Information, "M0 {s}")] + internal static partial void M0(ILogger logger, ref readonly S s); + } +} diff --git a/test/Generators/Microsoft.Gen.Logging/Unit/ParserTests.LogMethod.cs b/test/Generators/Microsoft.Gen.Logging/Unit/ParserTests.LogMethod.cs index 9cd3c8fd252..c944382a3bd 100644 --- a/test/Generators/Microsoft.Gen.Logging/Unit/ParserTests.LogMethod.cs +++ b/test/Generators/Microsoft.Gen.Logging/Unit/ParserTests.LogMethod.cs @@ -298,6 +298,45 @@ partial class C await RunGenerator(source, DiagDescriptors.LoggingMethodParameterRefKind); } + [Fact] + public async Task LogMethodParamsModifier() + { + const string Source = @" + partial class C + { + [LoggerMessage(0, LogLevel.Debug, ""Parameter"")] + static partial void M(ILogger logger, params int[] /*0+*/values/*-0*/); + }"; + + await RunGenerator(Source, DiagDescriptors.LoggingMethodParameterParams); + } + + [Fact] + public async Task LogMethodRefReadOnlyModifier() + { + const string Source = @" + partial class C + { + [LoggerMessage(0, LogLevel.Debug, ""Parameter {p1}"")] + static partial void M(ILogger logger, ref readonly int p1); + }"; + + await RunGenerator(Source); + } + + [Fact] + public async Task LogMethodScopedModifier() + { + const string Source = @" + partial class C + { + [LoggerMessage(0, LogLevel.Debug, ""Parameter {p1}"")] + static partial void M(ILogger logger, scoped ref readonly int /*0+*/p1/*-0*/); + }"; + + await RunGenerator(Source, DiagDescriptors.LoggingMethodParameterParams); + } + [Theory] [CombinatorialData] public async Task LogMethod_DetectsSensitiveMembersInRecord([CombinatorialRange(0, TotalSensitiveCases)] int positionNumber)