From ec39a35e873d1911f827492aade046c28915845a Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Sun, 14 Apr 2024 18:36:29 +0200 Subject: [PATCH] Add CA1873: Avoid potentially expensive logging This analyzer detects calls to 'ILogger.Log', extension methods in 'Microsoft.Extensions.Logging.LoggerExtensions' and methods decorated with '[LoggerMessage]'. It then checks if they evaluate expensive arguments without checking if logging is enabled with 'ILogger.IsEnabled'. --- .../Core/AnalyzerReleases.Unshipped.md | 1 + .../MicrosoftNetCoreAnalyzersResources.resx | 9 + ...voidPotentiallyExpensiveCallWhenLogging.cs | 333 ++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.de.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.es.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.it.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 15 + ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 15 + .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 + .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 20 + src/NetAnalyzers/RulesMissingDocumentation.md | 3 +- ...otentiallyExpensiveCallWhenLoggingTests.cs | 3532 +++++++++++++++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- src/Utilities/Compiler/WellKnownTypeNames.cs | 1 + 22 files changed, 4105 insertions(+), 3 deletions(-) create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 6e16bd95c7..07d2e22fc1 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -8,6 +8,7 @@ CA1514 | Maintainability | Info | AvoidLengthCheckWhenSlicingToEndAnalyzer, [Doc CA1515 | Maintainability | Disabled | MakeTypesInternal, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515) CA1871 | Performance | Info | DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1871) CA1872 | Performance | Info | PreferConvertToHexStringOverBitConverterAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1872) +CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873) CA2022 | Reliability | Warning | AvoidUnreliableStreamReadAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2022) CA2262 | Usage | Info | ProvideHttpClientHandlerMaxResponseHeaderLengthValueCorrectly, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2262) CA2263 | Usage | Info | PreferGenericOverloadsAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2263) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 3c1daafb7a..face2b18a6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -2123,6 +2123,15 @@ Widening and user defined conversions are not supported with generic types. Use char overload + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + Avoid potentially expensive logging + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs new file mode 100644 index 0000000000..f4278374a6 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs @@ -0,0 +1,333 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA1873: + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer : DiagnosticAnalyzer + { + private const string RuleId = "CA1873"; + + private const string Level = nameof(Level); + private const string LogLevel = nameof(LogLevel); + + private const string Log = nameof(Log); + private const string IsEnabled = nameof(IsEnabled); + private const string LogTrace = nameof(LogTrace); + private const string LogDebug = nameof(LogDebug); + private const string LogInformation = nameof(LogInformation); + private const string LogWarning = nameof(LogWarning); + private const string LogError = nameof(LogError); + private const string LogCritical = nameof(LogCritical); + + private const int LogLevelTrace = 0; + private const int LogLevelDebug = 1; + private const int LogLevelInformation = 2; + private const int LogLevelWarning = 3; + private const int LogLevelError = 4; + private const int LogLevelCritical = 5; + private const int LogLevelPassedAsParameter = int.MinValue; + + private static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(AvoidPotentiallyExpensiveCallWhenLoggingTitle)), + CreateLocalizableResourceString(nameof(AvoidPotentiallyExpensiveCallWhenLoggingMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(AvoidPotentiallyExpensiveCallWhenLoggingDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public sealed override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public sealed override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private void OnCompilationStart(CompilationStartAnalysisContext context) + { + if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols)) + { + return; + } + + context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation); + + void AnalyzeInvocation(OperationAnalysisContext context) + { + var invocation = (IInvocationOperation)context.Operation; + + // Check if invocation is a logging invocation and capture the log level (either as IOperation or as int, depending if it is dynamic or not). + // Use these to check if the logging invocation is guarded by 'ILogger.IsEnabled' and bail out if it is. + if (!symbols.TryGetLogLevel(invocation, out var logLevelArgumentOperation, out var logLevel) || + symbols.IsGuardedByIsEnabled(currentOperation: invocation, logInvocation: invocation, logLevel, logLevelArgumentOperation)) + { + return; + } + + var arguments = invocation.Arguments.Skip(invocation.IsExtensionMethodAndHasNoInstance() ? 1 : 0); + + // At this stage we have a logging invocation that is not guarded. + // Check each argument if it is potentially expensive. + foreach (var argument in arguments) + { + // Check the argument value after conversions to prevent noise (e.g. implicit conversions from null or from int to EventId). + if (IsPotentiallyExpensive(argument.Value.WalkDownConversion())) + { + // Filter out implicit operations in the case of params arguments. + // If we would report the diagnostic on the implicit argument operation, it would flag the whole invocation. + var explicitDescendants = argument.Value.GetTopmostExplicitDescendants(); + + if (!explicitDescendants.IsEmpty) + { + context.ReportDiagnostic(explicitDescendants[0].CreateDiagnostic(Rule)); + } + } + } + } + } + + private static bool IsPotentiallyExpensive(IOperation? operation) + { + if (operation is null + // Implicit params array creation is treated as not expensive. This would otherwise cause a lot of noise. + or IArrayCreationOperation { IsImplicit: true, Initializer.ElementValues.IsEmpty: true } + or IInstanceReferenceOperation + or IConditionalAccessInstanceOperation + or ILiteralOperation + or ILocalReferenceOperation + or IParameterReferenceOperation) + { + return false; + } + + if (operation is IPropertyReferenceOperation { Arguments.IsEmpty: false } indexerReference) + { + return IsPotentiallyExpensive(indexerReference.Instance) || + indexerReference.Arguments.Any(a => IsPotentiallyExpensive(a.Value)); + } + + if (operation is IArrayElementReferenceOperation arrayElementReference) + { + return IsPotentiallyExpensive(arrayElementReference.ArrayReference) || + arrayElementReference.Indices.Any(IsPotentiallyExpensive); + } + + if (operation is IConditionalAccessOperation conditionalAccess) + { + return IsPotentiallyExpensive(conditionalAccess.WhenNotNull); + } + + if (operation is IMemberReferenceOperation memberReference) + { + return IsPotentiallyExpensive(memberReference.Instance); + } + + return true; + } + + internal sealed class RequiredSymbols + { + private RequiredSymbols( + IMethodSymbol logMethod, + IMethodSymbol isEnabledMethod, + ImmutableDictionary logExtensionsMethodsAndLevel, + INamedTypeSymbol? loggerMessageAttributeType) + { + _logMethod = logMethod; + _isEnabledMethod = isEnabledMethod; + _logExtensionsMethodsAndLevel = logExtensionsMethodsAndLevel; + _loggerMessageAttributeType = loggerMessageAttributeType; + } + + public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] out RequiredSymbols? symbols) + { + symbols = default; + + var iLoggerType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingILogger); + + if (iLoggerType is null) + { + return false; + } + + var logMethod = iLoggerType.GetMembers(Log) + .OfType() + .FirstOrDefault(); + + var isEnabledMethod = iLoggerType.GetMembers(IsEnabled) + .OfType() + .FirstOrDefault(); + + if (logMethod is null || isEnabledMethod is null) + { + return false; + } + + var loggerExtensionsType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingLoggerExtensions); + var logExtensionsMethodsBuilder = ImmutableDictionary.CreateBuilder(SymbolEqualityComparer.Default); + AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogTrace).OfType(), LogLevelTrace); + AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogDebug).OfType(), LogLevelDebug); + AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogInformation).OfType(), LogLevelInformation); + AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogWarning).OfType(), LogLevelWarning); + AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogError).OfType(), LogLevelError); + AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(LogCritical).OfType(), LogLevelCritical); + AddRangeIfNotNull(logExtensionsMethodsBuilder, loggerExtensionsType?.GetMembers(Log).OfType(), LogLevelPassedAsParameter); + + var loggerMessageAttributeType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.MicrosoftExtensionsLoggingLoggerMessageAttribute); + + symbols = new RequiredSymbols(logMethod, isEnabledMethod, logExtensionsMethodsBuilder.ToImmutable(), loggerMessageAttributeType); + + return true; + + void AddRangeIfNotNull(ImmutableDictionary.Builder builder, IEnumerable? range, int value) + { + if (range is not null) + { + builder.AddRange(range.Select(s => new KeyValuePair(s, value))); + } + } + } + + public bool TryGetLogLevel(IInvocationOperation invocation, out IArgumentOperation? logLevelArgumentOperation, out int logLevel) + { + logLevelArgumentOperation = default; + logLevel = LogLevelPassedAsParameter; + + var method = invocation.TargetMethod.ReducedFrom ?? invocation.TargetMethod; + + // ILogger.Log + if (SymbolEqualityComparer.Default.Equals(method.ConstructedFrom, _logMethod) || + method.ConstructedFrom.IsOverrideOrImplementationOfInterfaceMember(_logMethod)) + { + logLevelArgumentOperation = invocation.Arguments.GetArgumentForParameterAtIndex(0); + + return true; + } + + // LoggerExtensions.Log and named variants (e.g. LoggerExtensions.LogInformation) + if (_logExtensionsMethodsAndLevel.TryGetValue(method, out logLevel)) + { + // LoggerExtensions.Log + if (logLevel == LogLevelPassedAsParameter) + { + logLevelArgumentOperation = invocation.Arguments.GetArgumentForParameterAtIndex(invocation.IsExtensionMethodAndHasNoInstance() ? 1 : 0); + } + + return true; + } + + var loggerMessageAttribute = method.GetAttribute(_loggerMessageAttributeType); + + if (loggerMessageAttribute is null) + { + return false; + } + + // Try to get the log level from the attribute arguments. + logLevel = loggerMessageAttribute.NamedArguments + .FirstOrDefault(p => p.Key.Equals(Level, StringComparison.Ordinal)) + .Value.Value as int? + ?? LogLevelPassedAsParameter; + + if (logLevel == LogLevelPassedAsParameter) + { + logLevelArgumentOperation = invocation.Arguments + .FirstOrDefault(a => a.Value.Type?.Name.Equals(LogLevel, StringComparison.Ordinal) ?? false); + + if (logLevelArgumentOperation is null) + { + return false; + } + } + + return true; + } + + public bool IsGuardedByIsEnabled(IOperation currentOperation, IInvocationOperation logInvocation, int logLevel, IArgumentOperation? logLevelArgumentOperation) + { + var conditional = currentOperation.GetAncestor(OperationKind.Conditional); + + // This is the base case where no ancestor conditional is found. + if (conditional is null) + { + return false; + } + + var conditionInvocations = conditional.Condition + .DescendantsAndSelf() + .OfType(); + + // Check each invocation in the condition to see if it is a valid guard invocation, i.e. same instance and same log level. + // This is not perfect (e.g. 'if (logger.IsEnabled(LogLevel.Debug) || true)' is treated as guarded), but should be good enough to prevent false positives. + if (conditionInvocations.Any(IsValidIsEnabledGuardInvocation)) + { + return true; + } + + // Recursively check the next conditional. + return IsGuardedByIsEnabled(conditional, logInvocation, logLevel, logLevelArgumentOperation); + + bool IsValidIsEnabledGuardInvocation(IInvocationOperation invocation) + { + if (!SymbolEqualityComparer.Default.Equals(_isEnabledMethod, invocation.TargetMethod) && + !invocation.TargetMethod.IsOverrideOrImplementationOfInterfaceMember(_isEnabledMethod)) + { + return false; + } + + return AreInvocationsOnSameInstance(logInvocation, invocation) && IsSameLogLevel(invocation.Arguments[0]); + } + + static bool AreInvocationsOnSameInstance(IInvocationOperation invocation1, IInvocationOperation invocation2) + { + return (invocation1.GetInstance()?.WalkDownConversion(), invocation2.GetInstance()?.WalkDownConversion()) switch + { + (IFieldReferenceOperation fieldRef1, IFieldReferenceOperation fieldRef2) => fieldRef1.Member == fieldRef2.Member, + (IPropertyReferenceOperation propRef1, IPropertyReferenceOperation propRef2) => propRef1.Member == propRef2.Member, + (IParameterReferenceOperation paramRef1, IParameterReferenceOperation paramRef2) => paramRef1.Parameter == paramRef2.Parameter, + (ILocalReferenceOperation localRef1, ILocalReferenceOperation localRef2) => localRef1.Local == localRef2.Local, + _ => false, + }; + } + + bool IsSameLogLevel(IArgumentOperation isEnabledArgument) + { + if (isEnabledArgument.Value.ConstantValue.HasValue) + { + int isEnabledLogLevel = (int)isEnabledArgument.Value.ConstantValue.Value!; + + return logLevel == LogLevelPassedAsParameter + ? logLevelArgumentOperation?.Value.HasConstantValue(isEnabledLogLevel) ?? false + : isEnabledLogLevel == logLevel; + } + + return isEnabledArgument.Value.GetReferencedMemberOrLocalOrParameter() == logLevelArgumentOperation?.Value.GetReferencedMemberOrLocalOrParameter(); + } + } + + private readonly IMethodSymbol _logMethod; + private readonly IMethodSymbol _isEnabledMethod; + private readonly ImmutableDictionary _logExtensionsMethodsAndLevel; + private readonly INamedTypeSymbol? _loggerMessageAttributeType; + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index cf3c53f482..7958b1f206 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -67,6 +67,21 @@ Vyhněte se konstantním polím jako argumentům + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. Vyhněte se vytvoření nové instance JsonSerializerOptions pro každou operaci serializace. Místo toho ukládejte instance do mezipaměti a znovu je používejte. Instance JsonSerializerOptions pro jedno použití můžou výrazně snížit výkon vaší aplikace. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index de59d2fbe4..4dc0c4bc5a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -67,6 +67,21 @@ Konstantenmatrizen als Argumente vermeiden + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. Erstellen Sie für Serialisierungsvorgänge nicht jeweils eine neue JsonSerializerOptions-Instanz. Speichern Sie stattdessen Instanzen zwischen, und verwenden Sie sie wieder. Die einmalige Verwendung von JsonSerializerOptions-Instanzen kann die Leistung Ihrer Anwendung erheblich beeinträchtigen. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 0ec6aa48e5..4c0db21d59 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -67,6 +67,21 @@ Evitar matrices constantes como argumentos + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. Evite crear una nueva instancia de "JsonSerializerOptions" para cada operación de serialización. En su lugar, almacene en caché y reutilice instancias. Las instancias "JsonSerializerOptions" de uso único pueden degradar considerablemente el rendimiento de la aplicación. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index ea1fb007b5..056243131a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -67,6 +67,21 @@ Éviter les tableaux constants en tant qu’arguments + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. Évitez de créer une instance « JsonSerializerOptions » pour chaque opération de sérialisation. Mettez en cache et réutilisez les instances à la place. Les instances « JsonSerializerOptions » à usage unique peuvent considérablement dégrader les performances de votre application. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index a0cecdb587..ac42b37e69 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -67,6 +67,21 @@ Evitare matrici costanti come argomenti + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. Evitare di creare una nuova istanza di 'JsonSerializerOptions' per ogni operazione di serializzazione. Memorizzare nella cache le istanze e riutilizzarle. Le istanze monouso di 'JsonSerializerOptions' possono ridurre notevolmente le prestazioni dell'applicazione. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 8c9ee9a227..a798262b04 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -67,6 +67,21 @@ 引数として定数配列を使用しない + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. シリアル化操作ごとに新しい 'JsonSerializerOptions' インスタンスを作成しないでください。代わりにインスタンスをキャッシュして再利用します。'JsonSerializerOptions' インスタンスの単独使用では、アプリケーションのパフォーマンスが大幅に低下する可能性があります。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index b452dcb1b8..58aba402dd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -67,6 +67,21 @@ 상수 배열을 인수로 사용하지 않습니다. + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. 모든 직렬화 작업에 대해 새 'JsonSerializerOptions' 인스턴스를 만들지 마세요. 대신 인스턴스를 캐시하고 다시 사용합니다. 단일 사용 'JsonSerializerOptions' 인스턴스는 애플리케이션의 성능을 크게 저하시킬 수 있습니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 3583b201b6..9f673e1493 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -67,6 +67,21 @@ Unikaj tablic stałych jako argumentów + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. Unikaj tworzenia nowego wystąpienia „JsonSerializerOptions” dla każdej operacji serializacji. Zamiast tego buforuj i ponownie wykorzystuj wystąpienia. Jednorazowe użycie wystąpienia „JsonSerializerOptions” może znacznie obniżyć wydajność aplikacji. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 7846c9ceab..d4d9b191ea 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -67,6 +67,21 @@ Evite matrizes constantes como argumentos + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. Evite criar uma nova instância 'JsonSerializerOptions' para cada operação de serialização. Em vez disso, armazene em cache e reutilize instâncias. Instâncias 'JsonSerializerOptions' de uso único podem degradar substancialmente o desempenho de seu aplicativo. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 997f93cee2..14637b5ef3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -67,6 +67,21 @@ Избегайте использования константных массивов в качестве аргументов + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. Избегайте создания нового экземпляра "JsonSerializerOptions" для каждой операции сериализации. Выполняйте кэширование и повторно используйте экземпляры. Однократное использование экземпляров "JsonSerializerOptions" может значительно снизить производительность приложения. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 4373b25f18..ee004dc6fb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -67,6 +67,21 @@ Sabit dizileri bağımsız değişkenler olarak kullanmaktan sakının + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. Her serileştirme işlemi için yeni bir 'JsonSerializerOptions' örneği oluşturmaktan kaçının. Bunun yerine örnekleri önbelleğe alıp yeniden kullanın. Tek kullanımlık 'JsonSerializerOptions' örnekleri uygulamanızın performansını önemli ölçüde düşürebilir. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index de4a764cc0..713c2b3f62 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -67,6 +67,21 @@ 不要将常量数组作为参数 + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. 避免为每个序列化操作创建新的“JsonSerializerOptions”实例。请改为缓存和重用实例。仅使用“JsonSerializerOptions”实例可能会显著降低应用程序的性能。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 5f5fcd7826..3a945ba69b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -67,6 +67,21 @@ 避免常數陣列作為引數 + + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + + + + Evaluation of this argument may be expensive and unnecessary if logging is disabled + Evaluation of this argument may be expensive and unnecessary if logging is disabled + + + + Avoid potentially expensive logging + Avoid potentially expensive logging + + Avoid creating a new 'JsonSerializerOptions' instance for every serialization operation. Cache and reuse instances instead. Single use 'JsonSerializerOptions' instances can substantially degrade the performance of your application. 避免為每個序列化作業建立新的 'JsonSerializerOptions' 執行個體。改為快取並重新使用執行個體。單一使用 'JsonSerializerOptions' 執行個體可能會大幅降低應用程式的效能。 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index b3a6bc05d4..68100f1dae 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1860,6 +1860,18 @@ Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a |CodeFix|True| --- +## [CA1873](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873): Avoid potentially expensive logging + +In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|False| +--- + ## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 176cbcbd86..8f20065d49 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3428,6 +3428,26 @@ ] } }, + "CA1873": { + "id": "CA1873", + "shortDescription": "Avoid potentially expensive logging", + "fullDescription": "In many situations, logging is disabled or set to a log level that results in an unnecessary evaluation for this argument.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index f0385ea23d..e1107d820d 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,7 +2,6 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| -CA1871 | | Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull' | +CA1873 | | Avoid potentially expensive logging | CA2022 | | Avoid inexact read with 'Stream.Read' | -CA2264 | | Do not pass a non-nullable value to 'ArgumentNullException.ThrowIfNull' | CA2265 | | Do not compare Span\ to 'null' or 'default' | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs new file mode 100644 index 0000000000..00ea3c7f19 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs @@ -0,0 +1,3532 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, + Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class AvoidPotentiallyExpensiveCallWhenLoggingTests + { + public static readonly TheoryData LogLevels = new() + { + "Trace", + "Debug", + "Information", + "Warning", + "Error", + "Critical" + }; + + [Fact] + public async Task LiteralInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Trace, eventId, "literal", exception, formatter); + + logger.Log(LogLevel.Debug, "literal"); + logger.Log(LogLevel.Information, eventId, "literal"); + logger.Log(LogLevel.Warning, exception, "literal"); + logger.Log(LogLevel.Error, eventId, exception, "literal"); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task LiteralInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception) + { + logger.Log{{logLevel}}("literal"); + logger.Log{{logLevel}}(eventId, "literal"); + logger.Log{{logLevel}}(exception, "literal"); + logger.Log{{logLevel}}(eventId, exception, "literal"); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task LiteralInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + logger.StaticLogLevel("literal"); + logger.DynamicLogLevel(LogLevel.Debug, "literal"); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task LocalInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + string local = "local"; + + logger.Log(LogLevel.Trace, eventId, local, exception, formatter); + + logger.Log(LogLevel.Debug, local); + logger.Log(LogLevel.Information, eventId, local); + logger.Log(LogLevel.Warning, exception, local); + logger.Log(LogLevel.Error, eventId, exception, local); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task LocalInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception) + { + string local = "local"; + + logger.Log{{logLevel}}(local); + logger.Log{{logLevel}}(eventId, local); + logger.Log{{logLevel}}(exception, local); + logger.Log{{logLevel}}(eventId, exception, local); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task LocalInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + string local = "local"; + + logger.StaticLogLevel(local); + logger.DynamicLogLevel(LogLevel.Debug, local); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task FieldInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + private string _field; + + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Trace, eventId, _field, exception, formatter); + + logger.Log(LogLevel.Debug, _field); + logger.Log(LogLevel.Information, eventId, _field); + logger.Log(LogLevel.Warning, exception, _field); + logger.Log(LogLevel.Error, eventId, exception, _field); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task FieldInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + private string _field; + + void M(ILogger logger, EventId eventId, Exception exception) + { + logger.Log{{logLevel}}(_field); + logger.Log{{logLevel}}(eventId, _field); + logger.Log{{logLevel}}(exception, _field); + logger.Log{{logLevel}}(eventId, exception, _field); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task FieldInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + private static string _field; + + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + logger.StaticLogLevel(_field); + logger.DynamicLogLevel(LogLevel.Debug, _field); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task PropertyInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + public string Property { get; set; } + + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Trace, eventId, Property, exception, formatter); + + logger.Log(LogLevel.Debug, Property); + logger.Log(LogLevel.Information, eventId, Property); + logger.Log(LogLevel.Warning, exception, Property); + logger.Log(LogLevel.Error, eventId, exception, Property); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task PropertyInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + public string Property { get; set; } + + void M(ILogger logger, EventId eventId, Exception exception) + { + logger.Log{{logLevel}}(Property); + logger.Log{{logLevel}}(eventId, Property); + logger.Log{{logLevel}}(exception, Property); + logger.Log{{logLevel}}(eventId, exception, Property); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task PropertyInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + public static string Property { get; set; } + + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + logger.StaticLogLevel(Property); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task IndexerInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using System.Collections.Generic; + using Microsoft.Extensions.Logging; + + class C + { + private Dictionary _messages; + + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Trace, eventId, _messages[LogLevel.Trace], exception, formatter); + + logger.Log(LogLevel.Debug, _messages[LogLevel.Debug]); + logger.Log(LogLevel.Information, eventId, _messages[LogLevel.Information]); + logger.Log(LogLevel.Warning, exception, _messages[LogLevel.Warning]); + logger.Log(LogLevel.Error, eventId, exception, _messages[LogLevel.Error]); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task IndexerInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using System.Collections.Generic; + using Microsoft.Extensions.Logging; + + class C + { + private Dictionary _messages; + + void M(ILogger logger, EventId eventId, Exception exception) + { + logger.Log{{logLevel}}(_messages[LogLevel.{{logLevel}}]); + logger.Log{{logLevel}}(eventId, _messages[LogLevel.{{logLevel}}]); + logger.Log{{logLevel}}(exception, _messages[LogLevel.{{logLevel}}]); + logger.Log{{logLevel}}(eventId, exception, _messages[LogLevel.{{logLevel}}]); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task IndexerInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using System.Collections.Generic; + using Microsoft.Extensions.Logging; + + static partial class C + { + private static Dictionary _messages; + + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + logger.StaticLogLevel(_messages[LogLevel.Information]); + logger.DynamicLogLevel(LogLevel.Debug, _messages[LogLevel.Debug]); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task ArrayIndexerInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + private string[] _messages; + + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Trace, eventId, _messages[0], exception, formatter); + + logger.Log(LogLevel.Debug, _messages[0]); + logger.Log(LogLevel.Information, eventId, _messages[0]); + logger.Log(LogLevel.Warning, exception, _messages[0]); + logger.Log(LogLevel.Error, eventId, exception, _messages[0]); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task ArrayIndexerInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + private string[] _messages; + + void M(ILogger logger, EventId eventId, Exception exception) + { + logger.Log{{logLevel}}(_messages[0]); + logger.Log{{logLevel}}(eventId, _messages[0]); + logger.Log{{logLevel}}(exception, _messages[0]); + logger.Log{{logLevel}}(eventId, exception, _messages[0]); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task ArrayIndexerInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + private static string[] _messages; + + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + logger.StaticLogLevel(_messages[0]); + logger.DynamicLogLevel(LogLevel.Debug, _messages[0]); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task ConditionalAccessInLog_NoDiagnostic_CS() + { + string source = """ + #nullable enable + + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception? exception, Func formatter) + { + logger.Log(LogLevel.Trace, eventId, exception?.Message, exception, formatter); + + logger.Log(LogLevel.Debug, exception?.Message); + logger.Log(LogLevel.Information, eventId, exception?.Message); + logger.Log(LogLevel.Warning, exception, exception?.Message); + logger.Log(LogLevel.Error, eventId, exception, exception?.Message); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task ConditionalAccessInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + #nullable enable + + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception? exception) + { + logger.Log{{logLevel}}(exception?.Message); + logger.Log{{logLevel}}(eventId, exception?.Message); + logger.Log{{logLevel}}(exception, exception?.Message); + logger.Log{{logLevel}}(eventId, exception, exception?.Message); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task ConditionalAccessInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + #nullable enable + + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string? message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string? message); + + static void M(ILogger logger, Exception? exception) + { + logger.StaticLogLevel(exception?.Message); + logger.DynamicLogLevel(LogLevel.Debug, exception?.Message); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task OtherILoggerMethodCalled_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger) + { + logger.BeginScope(ExpensiveMethodCall()); + logger.BeginScope("Processing calculation result {CalculationResult}", ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + // Tests for operations that get flagged. + + [Fact] + public async Task AnonymousObjectCreationOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|new { Test = "42" }|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task ArrayCreationOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|new int[10]|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task AwaitOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using System.Threading.Tasks; + using Microsoft.Extensions.Logging; + + class C + { + async void M(ILogger logger, EventId eventId, Exception exception, Func formatter, Task task) + { + logger.Log(LogLevel.Debug, eventId, [|await task|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task BinaryOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|4 + 2|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task CoalesceOperation_ReportsDiagnostic_CS() + { + string source = """ + #nullable enable + + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception? exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|exception ?? new Exception()|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task CollectionExpressionOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|[4, 2]|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source, CodeAnalysis.CSharp.LanguageVersion.CSharp12); + } + + [Fact] + public async Task DefaultValueOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|default|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task IncrementOrDecrementOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, int input) + { + logger.Log(LogLevel.Debug, eventId, [|input++|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task InterpolatedStringOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, int input) + { + logger.Log(LogLevel.Debug, eventId, [|$"{input}"|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task InvocationOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|exception.ToString()|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task IsPatternOperation_ReportsDiagnostic_CS() + { + string source = """ + #nullable enable + + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception? exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|exception is not null|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task IsTypeOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, object input) + { + logger.Log(LogLevel.Debug, eventId, [|input is Exception|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task NameOfOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|nameof(logger)|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task ObjectCreationOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|new Exception()|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task SizeOfOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|sizeof(int)|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task TypeOfOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|typeof(int)|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task UnaryOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, bool input) + { + logger.Log(LogLevel.Debug, eventId, [|!input|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task WithOperation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + record Point(int X, int Y); + + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, Point input) + { + logger.Log(LogLevel.Debug, eventId, [|input with { Y = 42 }|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + // Tests for work done in indexers, array element references or instances of member references. + + [Fact] + public async Task WorkInIndexerInstance_ReportsDiagnostic_CS() + { + string source = """ + using System; + using System.Collections.Generic; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|ExpensiveMethodCall()[LogLevel.Debug]|], exception, formatter); + } + + Dictionary ExpensiveMethodCall() + { + return default; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInIndexerArgument_ReportsDiagnostic_CS() + { + string source = """ + using System; + using System.Collections.Generic; + using Microsoft.Extensions.Logging; + + class C + { + private Dictionary _messages; + + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|_messages[ExpensiveMethodCall()]|], exception, formatter); + } + + LogLevel ExpensiveMethodCall() + { + return LogLevel.Debug; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInConditionalAccess_ReportsDiagnostic_CS() + { + string source = """ + #nullable enable + + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception? exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|exception?.ToString()|], exception, formatter); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInFieldInstance_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + private int _field; + + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|ExpensiveMethodCall()._field|], exception, formatter); + } + + C ExpensiveMethodCall() + { + return new C(); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInPropertyInstance_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + logger.Log(LogLevel.Debug, eventId, [|ExpensiveMethodCall().Length|], exception, formatter); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInArrayReference_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + private int _field; + + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, int[] input) + { + logger.Log(LogLevel.Debug, eventId, [|input[ExpensiveMethodCall()]|], exception, formatter); + } + + int ExpensiveMethodCall() + { + return 0; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + // Tests when log call is guarded. + + [Fact] + public async Task GuardedWorkInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + if (logger.IsEnabled(LogLevel.Trace)) + logger.Log(LogLevel.Trace, eventId, ExpensiveMethodCall(), exception, formatter); + + if (logger.IsEnabled(LogLevel.Debug)) + logger.Log(LogLevel.Debug, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.Information)) + logger.Log(LogLevel.Information, eventId, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.Warning)) + logger.Log(LogLevel.Warning, exception, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.Error)) + logger.Log(LogLevel.Error, eventId, exception, ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task GuardedWorkInLogWithDynamicLogLevel_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, LogLevel level) + { + if (logger.IsEnabled(level)) + logger.Log(level, eventId, ExpensiveMethodCall(), exception, formatter); + + if (logger.IsEnabled(level)) + logger.Log(level, ExpensiveMethodCall()); + + if (logger.IsEnabled(level)) + logger.Log(level, eventId, ExpensiveMethodCall()); + + if (logger.IsEnabled(level)) + logger.Log(level, exception, ExpensiveMethodCall()); + + if (logger.IsEnabled(level)) + logger.Log(level, eventId, exception, ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception) + { + if (logger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(eventId, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(exception, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(eventId, exception, ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkInLoggerMessage_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.{{logLevel}}, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + if (logger.IsEnabled(LogLevel.{{logLevel}})) + { + logger.StaticLogLevel(ExpensiveMethodCall()); + } + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + { + logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()); + } + } + + static string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task NestedGuardedWorkInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + if (exception is not null) + { + logger.Log(LogLevel.Debug, eventId, ExpensiveMethodCall(), exception, formatter); + logger.Log(LogLevel.Debug, ExpensiveMethodCall()); + logger.Log(LogLevel.Debug, eventId, ExpensiveMethodCall()); + logger.Log(LogLevel.Debug, exception, ExpensiveMethodCall()); + logger.Log(LogLevel.Debug, eventId, exception, ExpensiveMethodCall()); + } + } + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task NestedGuardedWorkInLogWithDynamicLogLevel_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, LogLevel level) + { + if (logger.IsEnabled(level)) + { + if (exception is not null) + { + logger.Log(level, eventId, ExpensiveMethodCall(), exception, formatter); + logger.Log(level, ExpensiveMethodCall()); + logger.Log(level, eventId, ExpensiveMethodCall()); + logger.Log(level, exception, ExpensiveMethodCall()); + logger.Log(level, eventId, exception, ExpensiveMethodCall()); + } + } + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception) + { + if (logger.IsEnabled(LogLevel.{{logLevel}})) + if (exception is not null) + logger.Log{{logLevel}}(ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + if (exception is not null) + logger.Log{{logLevel}}(eventId, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + if (exception is not null) + logger.Log{{logLevel}}(exception, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + if (exception is not null) + logger.Log{{logLevel}}(eventId, exception, ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkInLoggerMessage_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + static bool IsExpensiveComputationEnabled { get; set; } + + [LoggerMessage(EventId = 0, Level = LogLevel.{{logLevel}}, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + if (logger.IsEnabled(LogLevel.{{logLevel}})) + { + if (IsExpensiveComputationEnabled) + { + logger.StaticLogLevel(ExpensiveMethodCall()); + } + } + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + { + if (IsExpensiveComputationEnabled) + { + logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()); + } + } + } + + static string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task CustomLoggerGuardedWorkInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class CustomLogger : ILogger + { + public IDisposable BeginScope(TState state) { return default; } + public bool IsEnabled(LogLevel logLevel) { return true; } + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } + } + + class C + { + void M(CustomLogger logger, EventId eventId, Exception exception, Func formatter) + { + if (logger.IsEnabled(LogLevel.Trace)) + logger.Log(LogLevel.Trace, eventId, ExpensiveMethodCall(), exception, formatter); + + if (logger.IsEnabled(LogLevel.Debug)) + logger.Log(LogLevel.Debug, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.Information)) + logger.Log(LogLevel.Information, eventId, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.Warning)) + logger.Log(LogLevel.Warning, exception, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.Error)) + logger.Log(LogLevel.Error, eventId, exception, ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task CustomLoggerGuardedWorkInLogWithDynamicLogLevel_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class CustomLogger : ILogger + { + public IDisposable BeginScope(TState state) { return default; } + public bool IsEnabled(LogLevel logLevel) { return true; } + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } + } + + class C + { + void M(CustomLogger logger, EventId eventId, Exception exception, Func formatter, LogLevel level) + { + if (logger.IsEnabled(level)) + logger.Log(level, eventId, ExpensiveMethodCall(), exception, formatter); + + if (logger.IsEnabled(level)) + logger.Log(level, ExpensiveMethodCall()); + + if (logger.IsEnabled(level)) + logger.Log(level, eventId, ExpensiveMethodCall()); + + if (logger.IsEnabled(level)) + logger.Log(level, exception, ExpensiveMethodCall()); + + if (logger.IsEnabled(level)) + logger.Log(level, eventId, exception, ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task CustomLoggerGuardedWorkInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class CustomLogger : ILogger + { + public IDisposable BeginScope(TState state) { return default; } + public bool IsEnabled(LogLevel logLevel) { return true; } + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } + } + + class C + { + void M(CustomLogger logger, EventId eventId, Exception exception) + { + if (logger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(eventId, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(exception, ExpensiveMethodCall()); + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(eventId, exception, ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task CustomLoggerGuardedWorkInLoggerMessage_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class CustomLogger : ILogger + { + public IDisposable BeginScope(TState state) { return default; } + public bool IsEnabled(LogLevel logLevel) { return true; } + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) { } + } + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.{{logLevel}}, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(CustomLogger logger) + { + if (logger.IsEnabled(LogLevel.{{logLevel}})) + { + logger.StaticLogLevel(ExpensiveMethodCall()); + } + + if (logger.IsEnabled(LogLevel.{{logLevel}})) + { + logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()); + } + } + + static string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task WrongLogLevelGuardedWorkInLog_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + if (logger.IsEnabled(LogLevel.Critical)) + logger.Log(LogLevel.Trace, eventId, [|ExpensiveMethodCall()|], exception, formatter); + + if (logger.IsEnabled(LogLevel.Critical)) + logger.Log(LogLevel.Debug, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(LogLevel.Critical)) + logger.Log(LogLevel.Information, eventId, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(LogLevel.Critical)) + logger.Log(LogLevel.Warning, exception, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(LogLevel.Critical)) + logger.Log(LogLevel.Error, eventId, exception, [|ExpensiveMethodCall()|]); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongLogLevelGuardedWorkInLogNamed_ReportsDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception) + { + if (logger.IsEnabled(LogLevel.None)) + logger.Log{{logLevel}}([|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(LogLevel.None)) + logger.Log{{logLevel}}(eventId, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(LogLevel.None)) + logger.Log{{logLevel}}(exception, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(LogLevel.None)) + logger.Log{{logLevel}}(eventId, exception, [|ExpensiveMethodCall()|]); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongLogLevelGuardedWorkInLoggerMessage_ReportsDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.{{logLevel}}, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + if (logger.IsEnabled(LogLevel.None)) + { + logger.StaticLogLevel([|ExpensiveMethodCall()|]); + } + + if (logger.IsEnabled(LogLevel.None)) + { + logger.DynamicLogLevel(LogLevel.{{logLevel}}, [|ExpensiveMethodCall()|]); + } + } + + static string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task WrongDynamicLogLevelGuardedWorkInLog_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, LogLevel level) + { + if (logger.IsEnabled(level)) + logger.Log(LogLevel.Trace, eventId, [|ExpensiveMethodCall()|], exception, formatter); + + if (logger.IsEnabled(level)) + logger.Log(LogLevel.Debug, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(level)) + logger.Log(LogLevel.Information, eventId, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(level)) + logger.Log(LogLevel.Warning, exception, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(level)) + logger.Log(LogLevel.Error, eventId, exception, [|ExpensiveMethodCall()|]); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongDynamicLogLevelGuardedWorkInLogNamed_ReportsDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, LogLevel level) + { + if (logger.IsEnabled(level)) + logger.Log{{logLevel}}([|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(level)) + logger.Log{{logLevel}}(eventId, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(level)) + logger.Log{{logLevel}}(exception, [|ExpensiveMethodCall()|]); + + if (logger.IsEnabled(level)) + logger.Log{{logLevel}}(eventId, exception, [|ExpensiveMethodCall()|]); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongDynamicLogLevelGuardedWorkInLoggerMessage_ReportsDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.{{logLevel}}, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger, LogLevel level) + { + if (logger.IsEnabled(level)) + { + logger.StaticLogLevel([|ExpensiveMethodCall()|]); + } + + if (logger.IsEnabled(level)) + { + logger.DynamicLogLevel(LogLevel.{{logLevel}}, [|ExpensiveMethodCall()|]); + } + } + + static string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task WrongInstanceGuardedWorkInLog_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + private ILogger _otherLogger; + + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + if (_otherLogger.IsEnabled(LogLevel.Trace)) + logger.Log(LogLevel.Trace, eventId, [|ExpensiveMethodCall()|], exception, formatter); + + if (_otherLogger.IsEnabled(LogLevel.Debug)) + logger.Log(LogLevel.Debug, [|ExpensiveMethodCall()|]); + + if (_otherLogger.IsEnabled(LogLevel.Information)) + logger.Log(LogLevel.Information, eventId, [|ExpensiveMethodCall()|]); + + if (_otherLogger.IsEnabled(LogLevel.Warning)) + logger.Log(LogLevel.Warning, exception, [|ExpensiveMethodCall()|]); + + if (_otherLogger.IsEnabled(LogLevel.Error)) + logger.Log(LogLevel.Error, eventId, exception, [|ExpensiveMethodCall()|]); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongInstanceGuardedWorkInLogNamed_ReportsDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + private ILogger _otherLogger; + + void M(ILogger logger, EventId eventId, Exception exception) + { + if (_otherLogger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}([|ExpensiveMethodCall()|]); + + if (_otherLogger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(eventId, [|ExpensiveMethodCall()|]); + + if (_otherLogger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(exception, [|ExpensiveMethodCall()|]); + + if (_otherLogger.IsEnabled(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(eventId, exception, [|ExpensiveMethodCall()|]); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongInstanceGuardedWorkInLoggerMessage_ReportsDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + private static ILogger _otherLogger; + + [LoggerMessage(EventId = 0, Level = LogLevel.{{logLevel}}, Message = "Static log level `{message}`")] + static partial void StaticLogLevel(this ILogger logger, string message); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{message}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string message); + + static void M(ILogger logger) + { + if (_otherLogger.IsEnabled(LogLevel.{{logLevel}})) + { + logger.StaticLogLevel([|ExpensiveMethodCall()|]); + } + } + + static string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + // VB tests + + [Fact] + public async Task LiteralInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log(LogLevel.Trace, eventId, "literal", exception, formatter) + logger.Log(LogLevel.Debug, "literal") + logger.Log(LogLevel.Information, eventId, "literal") + logger.Log(LogLevel.Warning, exception, "literal") + logger.Log(LogLevel.[Error], eventId, exception, "literal") + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task LiteralInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log{{logLevel}}("literal") + logger.Log{{logLevel}}(eventId, "literal") + logger.Log{{logLevel}}(exception, "literal") + logger.Log{{logLevel}}(eventId, exception, "literal") + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task LiteralInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel("literal") + logger.DynamicLogLevel(LogLevel.Debug, "literal") + End Sub + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task LocalInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + Dim local As String = "local" + + logger.Log(LogLevel.Trace, eventId, local, exception, formatter) + logger.Log(LogLevel.Debug, local) + logger.Log(LogLevel.Information, eventId, local) + logger.Log(LogLevel.Warning, exception, local) + logger.Log(LogLevel.[Error], eventId, exception, local) + End Sub + End Class + + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task LocalInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + Dim local As String = "local" + + logger.Log{{logLevel}}("literal") + logger.Log{{logLevel}}(eventId, "literal") + logger.Log{{logLevel}}(exception, "literal") + logger.Log{{logLevel}}(eventId, exception, "literal") + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task LocalInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + Dim local As String = "local" + + logger.StaticLogLevel(local) + logger.DynamicLogLevel(LogLevel.Debug, local) + End Sub + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task FieldInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Private _field As String + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log(LogLevel.Trace, eventId, _field, exception, formatter) + logger.Log(LogLevel.Debug, _field) + logger.Log(LogLevel.Information, eventId, _field) + logger.Log(LogLevel.Warning, exception, _field) + logger.Log(LogLevel.[Error], eventId, exception, _field) + End Sub + End Class + + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task FieldInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Private _field As String + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log{{logLevel}}(_field) + logger.Log{{logLevel}}(eventId, _field) + logger.Log{{logLevel}}(exception, _field) + logger.Log{{logLevel}}(eventId, exception, _field) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task FieldInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + Private _field As String + + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel(_field) + logger.DynamicLogLevel(LogLevel.Debug, _field) + End Sub + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task PropertyInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Public Property [Property] As String + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log(LogLevel.Trace, eventId, [Property], exception, formatter) + logger.Log(LogLevel.Debug, [Property]) + logger.Log(LogLevel.Information, eventId, [Property]) + logger.Log(LogLevel.Warning, exception, [Property]) + logger.Log(LogLevel.[Error], eventId, exception, [Property]) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task PropertyInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Public Property [Property] As String + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log{{logLevel}}([Property]) + logger.Log{{logLevel}}(eventId, [Property]) + logger.Log{{logLevel}}(exception, [Property]) + logger.Log{{logLevel}}(eventId, exception, [Property]) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task PropertyInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + Public Property [Property] As String + + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel([Property]) + End Sub + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task IndexerInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Collections.Generic + Imports Microsoft.Extensions.Logging + + Class C + Private _messages As Dictionary(Of LogLevel, String) + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log(LogLevel.Trace, eventId, _messages(LogLevel.Trace), exception, formatter) + logger.Log(LogLevel.Debug, _messages(LogLevel.Debug)) + logger.Log(LogLevel.Information, eventId, _messages(LogLevel.Information)) + logger.Log(LogLevel.Warning, exception, _messages(LogLevel.Warning)) + logger.Log(LogLevel.[Error], eventId, exception, _messages(LogLevel.[Error])) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task IndexerInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports System.Collections.Generic + Imports Microsoft.Extensions.Logging + + Class C + Private _messages As Dictionary(Of LogLevel, String) + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log{{logLevel}}(_messages(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(eventId, _messages(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(exception, _messages(LogLevel.{{logLevel}})) + logger.Log{{logLevel}}(eventId, exception, _messages(LogLevel.{{logLevel}})) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task IndexerInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Collections.Generic + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + Private _messages As Dictionary(Of LogLevel, String) + + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel(_messages(LogLevel.Information)) + logger.DynamicLogLevel(LogLevel.Debug, _messages(LogLevel.Debug)) + End Sub + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task ArrayIndexerInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Private _messages As String() + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log(LogLevel.Trace, eventId, _messages(0), exception, formatter) + logger.Log(LogLevel.Debug, _messages(0)) + logger.Log(LogLevel.Information, eventId, _messages(0)) + logger.Log(LogLevel.Warning, exception, _messages(0)) + logger.Log(LogLevel.[Error], eventId, exception, _messages(0)) + End Sub + End Class + + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task ArrayIndexerInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Private _messages As String() + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log{{logLevel}}(_messages(0)) + logger.Log{{logLevel}}(eventId, _messages(0)) + logger.Log{{logLevel}}(exception, _messages(0)) + logger.Log{{logLevel}}(eventId, exception, _messages(0)) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task ArrayIndexerInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + Private _messages As String() + + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel(_messages(0)) + logger.DynamicLogLevel(LogLevel.Debug, _messages(0)) + End Sub + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task ConditionalAccessInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log(LogLevel.Trace, eventId, exception?.Message, exception, formatter) + logger.Log(LogLevel.Debug, exception?.Message) + logger.Log(LogLevel.Information, eventId, exception?.Message) + logger.Log(LogLevel.Warning, exception, exception?.Message) + logger.Log(LogLevel.[Error], eventId, exception, exception?.Message) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task ConditionalAccessInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log{{logLevel}}(exception?.Message) + logger.Log{{logLevel}}(eventId, exception?.Message) + logger.Log{{logLevel}}(exception, exception?.Message) + logger.Log{{logLevel}}(eventId, exception, exception?.Message) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task ConditionalAccessInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger, exception As Exception) + logger.StaticLogLevel(exception?.Message) + logger.DynamicLogLevel(LogLevel.Debug, exception?.Message) + End Sub + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task OtherILoggerMethodCalled_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger) + logger.BeginScope(ExpensiveMethodCall()) + logger.BeginScope("Processing calculation result {CalculationResult}", ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + // Tests for operations that get flagged. + + [Fact] + public async Task AnonymousObjectCreationOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Object, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|New With {.Test = "42"}|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task ArrayCreationOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Object, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|New Integer(9) {}|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task AwaitOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Threading.Tasks + Imports Microsoft.Extensions.Logging + + Class C + Async Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Object, Exception, String), task As Task(Of String)) + logger.Log(LogLevel.Debug, eventId, [|Await task|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task BinaryOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Object, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|4 + 2|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task CoalesceOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Object, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|If(exception, New Exception())|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task InterpolatedStringOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String), input As Integer) + logger.Log(LogLevel.Debug, eventId, [|$"{input}"|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task InvocationOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|exception.ToString()|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task IsTypeOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Boolean, Exception, String), input As Object) + logger.Log(LogLevel.Debug, eventId, [|TypeOf input Is Exception|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task NameOfOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|NameOf(logger)|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task ObjectCreationOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Exception, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|New Exception()|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task TypeOfOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Type, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|GetType(Integer)|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task UnaryOperation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Boolean, Exception, String), input As Boolean) + logger.Log(LogLevel.Debug, eventId, [|Not input|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + // Tests for work done in indexers, array element references or instances of member references. + + [Fact] + public async Task WorkInIndexerInstance_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Collections.Generic + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Object, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|ExpensiveMethodCall()(LogLevel.Debug)|], exception, formatter) + End Sub + + Function ExpensiveMethodCall() As Dictionary(Of LogLevel, String) + Return Nothing + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInIndexerArgument_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Collections.Generic + Imports Microsoft.Extensions.Logging + + Class C + Private _messages As Dictionary(Of LogLevel, String) + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Object, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|_messages(ExpensiveMethodCall())|], exception, formatter) + End Sub + + Function ExpensiveMethodCall() As LogLevel + Return LogLevel.Debug + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInConditionalAccess_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|exception?.ToString()|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInFieldInstance_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Private _field As Integer + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Integer, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|ExpensiveMethodCall()._field|], exception, formatter) + End Sub + + Function ExpensiveMethodCall() As C + Return New C() + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInPropertyInstance_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Integer, Exception, String)) + logger.Log(LogLevel.Debug, eventId, [|ExpensiveMethodCall().Length|], exception, formatter) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task WorkInArrayReference_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Private _field As Integer + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of Integer, Exception, String), input As Integer()) + logger.Log(LogLevel.Debug, eventId, [|input(ExpensiveMethodCall())|], exception, formatter) + End Sub + + Function ExpensiveMethodCall() As Integer + Return 0 + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + // Tests when log call is guarded. + + [Fact] + public async Task GuardedWorkInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If logger.IsEnabled(LogLevel.Trace) Then logger.Log(LogLevel.Trace, eventId, ExpensiveMethodCall(), exception, formatter) + If logger.IsEnabled(LogLevel.Debug) Then logger.Log(LogLevel.Debug, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.Information) Then logger.Log(LogLevel.Information, eventId, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.Warning) Then logger.Log(LogLevel.Warning, exception, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.[Error]) Then logger.Log(LogLevel.[Error], eventId, exception, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task GuardedWorkInLogWithDynamicLogLevel_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String), level As LogLevel) + If logger.IsEnabled(level) Then logger.Log(level, eventId, ExpensiveMethodCall(), exception, formatter) + If logger.IsEnabled(level) Then logger.Log(level, ExpensiveMethodCall()) + If logger.IsEnabled(level) Then logger.Log(level, eventId, ExpensiveMethodCall()) + If logger.IsEnabled(level) Then logger.Log(level, exception, ExpensiveMethodCall()) + If logger.IsEnabled(level) Then logger.Log(level, eventId, exception, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.Log{{logLevel}}(ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.Log{{logLevel}}(eventId, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.Log{{logLevel}}(exception, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.Log{{logLevel}}(eventId, exception, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkInLoggerMessage_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.StaticLogLevel(ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task NestedGuardedWorkInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If logger.IsEnabled(LogLevel.Debug) + If exception IsNot Nothing + logger.Log(LogLevel.Debug, eventId, ExpensiveMethodCall(), exception, formatter) + logger.Log(LogLevel.Debug, ExpensiveMethodCall()) + logger.Log(LogLevel.Debug, eventId, ExpensiveMethodCall()) + logger.Log(LogLevel.Debug, exception, ExpensiveMethodCall()) + logger.Log(LogLevel.Debug, eventId, exception, ExpensiveMethodCall()) + End If + End If + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task NestedGuardedWorkInLogWithDynamicLogLevel_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String), level As LogLevel) + If logger.IsEnabled(level) Then + If exception IsNot Nothing Then + logger.Log(level, eventId, ExpensiveMethodCall(), exception, formatter) + logger.Log(level, ExpensiveMethodCall()) + logger.Log(level, eventId, ExpensiveMethodCall()) + logger.Log(level, exception, ExpensiveMethodCall()) + logger.Log(level, eventId, exception, ExpensiveMethodCall()) + End If + End If + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If logger.IsEnabled(LogLevel.{{logLevel}}) + If exception IsNot Nothing + logger.Log{{logLevel}}(ExpensiveMethodCall()) + End If + End If + + If logger.IsEnabled(LogLevel.{{logLevel}}) + If exception IsNot Nothing + logger.Log{{logLevel}}(eventId, ExpensiveMethodCall()) + End If + End If + + If logger.IsEnabled(LogLevel.{{logLevel}}) + If exception IsNot Nothing + logger.Log{{logLevel}}(exception, ExpensiveMethodCall()) + End If + End If + + If logger.IsEnabled(LogLevel.{{logLevel}}) + If exception IsNot Nothing + logger.Log{{logLevel}}(eventId, exception, ExpensiveMethodCall()) + End If + End If + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkInLoggerMessage_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + Public Property IsExpensiveComputationEnabled As Boolean + + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + If logger.IsEnabled(LogLevel.{{logLevel}}) + If IsExpensiveComputationEnabled + logger.StaticLogLevel(ExpensiveMethodCall()) + End If + End If + + If logger.IsEnabled(LogLevel.{{logLevel}}) + If IsExpensiveComputationEnabled + logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()) + End If + End If + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task CustomLoggerGuardedWorkInLog_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class CustomLogger + Implements ILogger + + Public Sub Log(Of TState)(logLevel As LogLevel, eventId As EventId, state As TState, exception As Exception, formatter As Func(Of TState, Exception, String)) Implements ILogger.Log + Throw New NotImplementedException() + End Sub + + Public Function IsEnabled(logLevel As LogLevel) As Boolean Implements ILogger.IsEnabled + Throw New NotImplementedException() + End Function + + Public Function BeginScope(Of TState)(state As TState) As IDisposable Implements ILogger.BeginScope + Throw New NotImplementedException() + End Function + End Class + + Class C + Sub M(logger As CustomLogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If logger.IsEnabled(LogLevel.Trace) Then logger.Log(LogLevel.Trace, eventId, ExpensiveMethodCall(), exception, formatter) + If logger.IsEnabled(LogLevel.Debug) Then logger.Log(LogLevel.Debug, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.Information) Then logger.Log(LogLevel.Information, eventId, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.Warning) Then logger.Log(LogLevel.Warning, exception, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.[Error]) Then logger.Log(LogLevel.[Error], eventId, exception, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task CustomLoggerGuardedWorkInLogWithDynamicLogLevel_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class CustomLogger + Implements ILogger + + Public Sub Log(Of TState)(logLevel As LogLevel, eventId As EventId, state As TState, exception As Exception, formatter As Func(Of TState, Exception, String)) Implements ILogger.Log + Throw New NotImplementedException() + End Sub + + Public Function IsEnabled(logLevel As LogLevel) As Boolean Implements ILogger.IsEnabled + Throw New NotImplementedException() + End Function + + Public Function BeginScope(Of TState)(state As TState) As IDisposable Implements ILogger.BeginScope + Throw New NotImplementedException() + End Function + End Class + + Class C + Sub M(logger As CustomLogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String), level As LogLevel) + If logger.IsEnabled(level) Then logger.Log(level, eventId, ExpensiveMethodCall(), exception, formatter) + If logger.IsEnabled(level) Then logger.Log(level, ExpensiveMethodCall()) + If logger.IsEnabled(level) Then logger.Log(level, eventId, ExpensiveMethodCall()) + If logger.IsEnabled(level) Then logger.Log(level, exception, ExpensiveMethodCall()) + If logger.IsEnabled(level) Then logger.Log(level, eventId, exception, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task CustomLoggerGuardedWorkInLogNamed_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class CustomLogger + Implements ILogger + + Public Sub Log(Of TState)(logLevel As LogLevel, eventId As EventId, state As TState, exception As Exception, formatter As Func(Of TState, Exception, String)) Implements ILogger.Log + Throw New NotImplementedException() + End Sub + + Public Function IsEnabled(logLevel As LogLevel) As Boolean Implements ILogger.IsEnabled + Throw New NotImplementedException() + End Function + + Public Function BeginScope(Of TState)(state As TState) As IDisposable Implements ILogger.BeginScope + Throw New NotImplementedException() + End Function + End Class + + Class C + Sub M(logger As CustomLogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.Log(LogLevel.{{logLevel}}, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.Log(LogLevel.{{logLevel}}, exception, ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.Log(LogLevel.{{logLevel}}, eventId, exception, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task CustomLoggerGuardedWorkInLoggerMessage_NoDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Class CustomLogger + Implements ILogger + + Public Sub Log(Of TState)(logLevel As LogLevel, eventId As EventId, state As TState, exception As Exception, formatter As Func(Of TState, Exception, String)) Implements ILogger.Log + Throw New NotImplementedException() + End Sub + + Public Function IsEnabled(logLevel As LogLevel) As Boolean Implements ILogger.IsEnabled + Throw New NotImplementedException() + End Function + + Public Function BeginScope(Of TState)(state As TState) As IDisposable Implements ILogger.BeginScope + Throw New NotImplementedException() + End Function + End Class + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As CustomLogger) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.StaticLogLevel(ExpensiveMethodCall()) + If logger.IsEnabled(LogLevel.{{logLevel}}) Then logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task WrongLogLevelGuardedWorkInLog_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If logger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.Trace, eventId, [|ExpensiveMethodCall()|], exception, formatter) + If logger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.Debug, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.Information, eventId, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.Warning, exception, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.[Error], eventId, exception, [|ExpensiveMethodCall()|]) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongLogLevelGuardedWorkInLogNamed_ReportsDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If logger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, eventId, [|ExpensiveMethodCall()|], exception, formatter) + If logger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, eventId, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, exception, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, eventId, exception, [|ExpensiveMethodCall()|]) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongLogLevelGuardedWorkInLoggerMessage_ReportsDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + If logger.IsEnabled(LogLevel.None) Then logger.StaticLogLevel([|ExpensiveMethodCall()|]) + If logger.IsEnabled(LogLevel.None) Then logger.DynamicLogLevel(LogLevel.{{logLevel}}, [|ExpensiveMethodCall()|]) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task WrongDynamicLogLevelGuardedWorkInLog_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String), level As LogLevel) + If logger.IsEnabled(level) Then logger.Log(LogLevel.Trace, eventId, [|ExpensiveMethodCall()|], exception, formatter) + If logger.IsEnabled(level) Then logger.Log(LogLevel.Debug, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(level) Then logger.Log(LogLevel.Information, eventId, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(level) Then logger.Log(LogLevel.Warning, exception, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(level) Then logger.Log(LogLevel.[Error], eventId, exception, [|ExpensiveMethodCall()|]) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongDynamicLogLevelGuardedWorkInLogNamed_ReportsDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String), level As LogLevel) + If logger.IsEnabled(level) Then logger.Log(LogLevel.{{logLevel}}, eventId, [|ExpensiveMethodCall()|], exception, formatter) + If logger.IsEnabled(level) Then logger.Log(LogLevel.{{logLevel}}, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(level) Then logger.Log(LogLevel.{{logLevel}}, eventId, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(level) Then logger.Log(LogLevel.{{logLevel}}, exception, [|ExpensiveMethodCall()|]) + If logger.IsEnabled(level) Then logger.Log(LogLevel.{{logLevel}}, eventId, exception, [|ExpensiveMethodCall()|]) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongDynamicLogLevelGuardedWorkInLoggerMessage_ReportsDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger, level As LogLevel) + If logger.IsEnabled(level) Then logger.StaticLogLevel([|ExpensiveMethodCall()|]) + If logger.IsEnabled(level) Then logger.DynamicLogLevel(LogLevel.{{logLevel}}, [|ExpensiveMethodCall()|]) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task WrongInstanceGuardedWorkInLog_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Private _otherLogger As ILogger + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If _otherLogger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.Trace, eventId, [|ExpensiveMethodCall()|], exception, formatter) + If _otherLogger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.Debug, [|ExpensiveMethodCall()|]) + If _otherLogger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.Information, eventId, [|ExpensiveMethodCall()|]) + If _otherLogger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.Warning, exception, [|ExpensiveMethodCall()|]) + If _otherLogger.IsEnabled(LogLevel.Critical) Then logger.Log(LogLevel.[Error], eventId, exception, [|ExpensiveMethodCall()|]) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongInstanceGuardedWorkInLogNamed_ReportsDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Private _otherLogger As ILogger + + Sub M(logger As ILogger, eventId As EventId, exception As Exception, formatter As Func(Of String, Exception, String)) + If _otherLogger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, eventId, [|ExpensiveMethodCall()|], exception, formatter) + If _otherLogger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, [|ExpensiveMethodCall()|]) + If _otherLogger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, eventId, [|ExpensiveMethodCall()|]) + If _otherLogger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, exception, [|ExpensiveMethodCall()|]) + If _otherLogger.IsEnabled(LogLevel.None) Then logger.Log(LogLevel.{{logLevel}}, eventId, exception, [|ExpensiveMethodCall()|]) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task WrongInstanceGuardedWorkInLoggerMessage_ReportsDiagnostic_VB(string logLevel) + { + string source = $$""" + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + Private _otherLogger As ILogger + + + + Partial Private Sub StaticLogLevel(logger As ILogger, message As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, message As String) + End Sub + + Sub M(logger As ILogger) + If _otherLogger.IsEnabled(LogLevel.None) Then logger.StaticLogLevel([|ExpensiveMethodCall()|]) + If _otherLogger.IsEnabled(LogLevel.None) Then logger.DynamicLogLevel(LogLevel.{{logLevel}}, [|ExpensiveMethodCall()|]) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Module + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + // Helpers + + private static async Task VerifyCSharpCodeFixAsync(string source, string fixedSource, CodeAnalysis.CSharp.LanguageVersion? languageVersion = null) + { + await new VerifyCS.Test + { + TestCode = source, + FixedCode = fixedSource, + ReferenceAssemblies = Net60WithMELogging, + LanguageVersion = languageVersion ?? CodeAnalysis.CSharp.LanguageVersion.CSharp10 + }.RunAsync(); + } + + private static async Task VerifyBasicCodeFixAsync(string source, string fixedSource, CodeAnalysis.VisualBasic.LanguageVersion? languageVersion = null) + { + await new VerifyVB.Test + { + TestCode = source, + FixedCode = fixedSource, + ReferenceAssemblies = Net60WithMELogging, + LanguageVersion = languageVersion ?? CodeAnalysis.VisualBasic.LanguageVersion.VisualBasic16_9 + }.RunAsync(); + } + + private static readonly ReferenceAssemblies Net60WithMELogging = + ReferenceAssemblies.Net.Net60.AddPackages([new PackageIdentity("Microsoft.Extensions.Logging", "6.0.0")]); + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 0b07345960..763072542d 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1872 +Performance: HA, CA1800-CA1873 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2265 Naming: CA1700-CA1727 diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 9fa8662c79..386ec40b81 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -79,6 +79,7 @@ internal static class WellKnownTypeNames public const string MicrosoftExtensionsLoggingILogger = "Microsoft.Extensions.Logging.ILogger"; public const string MicrosoftExtensionsLoggingLoggerExtensions = "Microsoft.Extensions.Logging.LoggerExtensions"; public const string MicrosoftExtensionsLoggingLoggerMessage = "Microsoft.Extensions.Logging.LoggerMessage"; + public const string MicrosoftExtensionsLoggingLoggerMessageAttribute = "Microsoft.Extensions.Logging.LoggerMessageAttribute"; public const string MicrosoftIdentityModelTokensAudienceValidator = "Microsoft.IdentityModel.Tokens.AudienceValidator"; public const string MicrosoftIdentityModelTokensLifetimeValidator = "Microsoft.IdentityModel.Tokens.LifetimeValidator"; public const string MicrosoftIdentityModelTokensSecurityToken = "Microsoft.IdentityModel.Tokens.SecurityToken";