diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 7a743fdb2d..579df745a8 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -4,5 +4,6 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- +CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873) CA2023 | Reliability | Warning | LoggerMessageDefineAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2023) CA2024 | Reliability | Warning | DoNotUseEndOfStreamInAsyncMethods, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2024) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 84ad020608..3de9a32206 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -2132,6 +2132,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..8806a6c080 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLogging.cs @@ -0,0 +1,420 @@ +// 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 Analyzer.Utilities.Lightup; +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 we have a log invocation and capture the log level used, either as IOperation or as int. + // Then, check if the invocation is guarded by 'ILogger.IsEnabled' and bail out if it is. + if (!symbols.IsLogInvocation(invocation, out var logLevel, out var logLevelArgumentOperation) || + symbols.IsGuardedByIsEnabled(invocation, logLevel, logLevelArgumentOperation)) + { + return; + } + + var arguments = invocation.Arguments.Skip(invocation.IsExtensionMethodAndHasNoInstance() ? 1 : 0); + + // Check each argument if it is potentially expensive to evaluate and raise a diagnostic if it is. + foreach (var argument in arguments) + { + if (IsPotentiallyExpensive(argument.Value)) + { + context.ReportDiagnostic(argument.CreateDiagnostic(Rule)); + } + } + } + } + + private static bool IsPotentiallyExpensive(IOperation? operation) + { + if (operation is null) + { + return false; + } + + if (ICollectionExpressionOperationWrapper.IsInstance(operation) || + operation is IAnonymousObjectCreationOperation or + IAwaitOperation or + IInvocationOperation or + IObjectCreationOperation { Type.IsReferenceType: true } or + IWithOperation) + { + return true; + } + + if (operation is IArrayCreationOperation arrayCreationOperation) + { + return !IsEmptyImplicitParamsArrayCreation(arrayCreationOperation); + } + + if (operation is IConversionOperation conversionOperation) + { + return IsBoxing(conversionOperation) || IsPotentiallyExpensive(conversionOperation.Operand); + } + + if (operation is IArrayElementReferenceOperation arrayElementReferenceOperation) + { + return IsPotentiallyExpensive(arrayElementReferenceOperation.ArrayReference) || + arrayElementReferenceOperation.Indices.Any(IsPotentiallyExpensive); + } + + if (operation is IBinaryOperation binaryOperation) + { + return IsPotentiallyExpensive(binaryOperation.LeftOperand) || + IsPotentiallyExpensive(binaryOperation.RightOperand); + } + + if (operation is ICoalesceOperation coalesceOperation) + { + return IsPotentiallyExpensive(coalesceOperation.Value) || + IsPotentiallyExpensive(coalesceOperation.WhenNull); + } + + if (operation is IConditionalAccessOperation conditionalAccessOperation) + { + return IsPotentiallyExpensive(conditionalAccessOperation.WhenNotNull); + } + + if (operation is IIncrementOrDecrementOperation incrementOrDecrementOperation) + { + return IsPotentiallyExpensive(incrementOrDecrementOperation.Target); + } + + if (operation is IInterpolatedStringOperation interpolatedStringOperation) + { + return interpolatedStringOperation.Parts.Any(p => p is + IInterpolationOperation { Expression.ConstantValue.HasValue: false } or + IInterpolatedStringTextOperation { Text.ConstantValue.HasValue: false }); + } + + if (operation is IMemberReferenceOperation memberReferenceOperation) + { + if (IsPotentiallyExpensive(memberReferenceOperation.Instance)) + { + return true; + } + + if (memberReferenceOperation is IPropertyReferenceOperation { Arguments.IsEmpty: false } indexerReferenceOperation) + { + return indexerReferenceOperation.Arguments.Any(a => IsPotentiallyExpensive(a.Value)); + } + } + + if (operation is IUnaryOperation unaryOperation) + { + return IsPotentiallyExpensive(unaryOperation.Operand); + } + + return false; + + static bool IsBoxing(IConversionOperation conversionOperation) + { + var targetIsReferenceType = conversionOperation.Type?.IsReferenceType ?? false; + var operandIsValueType = conversionOperation.Operand.Type?.IsValueType ?? false; + + return targetIsReferenceType && operandIsValueType; + } + + static bool IsEmptyImplicitParamsArrayCreation(IArrayCreationOperation arrayCreationOperation) + { + return arrayCreationOperation.IsImplicit && + arrayCreationOperation.DimensionSizes.Length == 1 && + arrayCreationOperation.DimensionSizes[0].ConstantValue.HasValue && + arrayCreationOperation.DimensionSizes[0].ConstantValue.Value is int size && + size == 0; + } + } + + 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 IsLogInvocation(IInvocationOperation invocation, out int logLevel, out IArgumentOperation? logLevelArgumentOperation) + { + logLevel = LogLevelPassedAsParameter; + logLevelArgumentOperation = default; + + 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(IInvocationOperation logInvocation, int logLevel, IArgumentOperation? logLevelArgumentOperation) + { + // Check each block for conditionals that contain an 'ILogger.IsEnabled' invocation that guards the log invocation: + // 1. If the 'ILogger.IsEnabled' invocation is negated, the 'WhenTrue' branch must contain a return. + // 2. If the 'ILogger.IsEnabled' invocation is not negated, the 'WhenTrue' branch must contain the log invocation. + // This is also not perfect, but should be good enough to prevent false positives. + var currentBlockAncestor = logInvocation.GetAncestor(OperationKind.Block); + while (currentBlockAncestor is not null) + { + var guardConditionals = currentBlockAncestor.Descendants().OfType(); + if (guardConditionals.Any(IsValidGuardConditional)) + { + return true; + } + + currentBlockAncestor = currentBlockAncestor.GetAncestor(OperationKind.Block); + } + + return false; + + bool IsValidGuardConditional(IConditionalOperation conditional) + { + if (conditional.Syntax.SpanStart > logInvocation.Syntax.SpanStart) + { + return false; + } + + var conditionInvocations = conditional.Condition + .DescendantsAndSelf() + .OfType(); + + if (conditionInvocations.Any(IsValidIsEnabledGuardInvocation)) + { + return true; + } + + return false; + + bool IsValidIsEnabledGuardInvocation(IInvocationOperation invocation) + { + if (!IsIsEnabledInvocation(invocation) || + !AreInvocationsOnSameInstance(logInvocation, invocation) || + !IsSameLogLevel(invocation.Arguments[0])) + { + return false; + } + + var isNegated = invocation.Parent is IUnaryOperation { OperatorKind: UnaryOperatorKind.Not }; + var descendants = conditional.WhenTrue.DescendantsAndSelf(); + + return isNegated && descendants.OfType().Any() || !isNegated && descendants.Contains(logInvocation); + } + } + + bool IsIsEnabledInvocation(IInvocationOperation invocation) + { + return SymbolEqualityComparer.Default.Equals(_isEnabledMethod, invocation.TargetMethod) || + invocation.TargetMethod.IsOverrideOrImplementationOfInterfaceMember(_isEnabledMethod); + } + + static bool AreInvocationsOnSameInstance(IInvocationOperation invocation1, IInvocationOperation invocation2) + { + return SymbolEqualityComparer.Default.Equals( + GetInstanceResolvingConditionalAccess(invocation1).GetReferencedMemberOrLocalOrParameter(), + GetInstanceResolvingConditionalAccess(invocation2).GetReferencedMemberOrLocalOrParameter()); + } + + static IOperation? GetInstanceResolvingConditionalAccess(IInvocationOperation invocation) + { + var instance = invocation.GetInstance()?.WalkDownConversion(); + + if (instance is IConditionalAccessInstanceOperation conditionalAccessInstance) + { + return conditionalAccessInstance.GetConditionalAccess()?.Operation; + } + + return instance; + } + + 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 SymbolEqualityComparer.Default.Equals( + 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 8620824df4..09eda85d7d 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 edbf16152c..9f60a4f73f 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 741e88f833..58bdd810fd 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 9bbae9c121..a4f7ef0d7d 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 727d2d0c72..e55ae22733 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 6b265d4a6c..baa1b0b00f 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 957ea51b8a..0ae09fed49 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 5e5fc39b5d..88e60d33a2 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 392e385097..d5ded437ee 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 f809ac49ff..2257594ed7 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 e76c270811..868029639b 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 495e53c115..86f3156882 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 7e383f5b72..60b58f36ef 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 2c54407efb..bfb110318e 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 dbb96f5541..d6d19ab152 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 b59a35228c..aca4ac13b4 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,5 +2,5 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| +CA1873 | | Avoid potentially expensive logging | CA2023 | | Invalid braces in message template | -CA2024 | | Do not use 'StreamReader.EndOfStream' in async methods | 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..52ef278988 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/AvoidPotentiallyExpensiveCallWhenLoggingTests.cs @@ -0,0 +1,5633 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel("literal"); + logger.DynamicLogLevel(LogLevel.Debug, "literal"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + string local = "local"; + + logger.StaticLogLevel(local); + logger.DynamicLogLevel(LogLevel.Debug, local); + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel(_field); + logger.DynamicLogLevel(LogLevel.Debug, _field); + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel(Property); + logger.DynamicLogLevel(LogLevel.Debug, Property); + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel(_messages[LogLevel.Information]); + logger.DynamicLogLevel(LogLevel.Debug, _messages[LogLevel.Debug]); + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel(_messages[0]); + logger.DynamicLogLevel(LogLevel.Debug, _messages[0]); + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string? argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string? argument); + + static void M(ILogger logger, Exception? exception) + { + logger.StaticLogLevel(exception?.Message); + logger.DynamicLogLevel(LogLevel.Debug, exception?.Message); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task BinaryOperationInLog_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, 4 + 2, exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task BinaryOperationInLogNamed_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}}("a" + "b"); + logger.Log{{logLevel}}(eventId, "a" + "b"); + logger.Log{{logLevel}}(exception, "a" + "b"); + logger.Log{{logLevel}}(eventId, exception, "a" + "b"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task BinaryOperationInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel("a" + "b"); + logger.DynamicLogLevel(LogLevel.Debug, "a" + "b"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task CoalesceOperationInLog_NoDiagnostic_CS() + { + string source = """ + #nullable enable + + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception? exception, Func formatter, string? message) + { + logger.Log(LogLevel.Trace, eventId, message ?? "null", exception, formatter); + + logger.Log(LogLevel.Debug, message ?? "null"); + logger.Log(LogLevel.Information, eventId, message ?? "null"); + logger.Log(LogLevel.Warning, exception, message ?? "null"); + logger.Log(LogLevel.Error, eventId, exception, message ?? "null"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task CoalesceOperationInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + #nullable enable + + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception? exception, string? message) + { + logger.Log{{logLevel}}(message ?? "null"); + logger.Log{{logLevel}}(eventId, message ?? "null"); + logger.Log{{logLevel}}(exception, message ?? "null"); + logger.Log{{logLevel}}(eventId, exception, message ?? "null"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task CoalesceOperationInLoggerMessage_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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger, string? message) + { + logger.StaticLogLevel(message ?? "null"); + logger.DynamicLogLevel(LogLevel.Debug, message ?? "null"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task DefaultValueOperationInLog_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.Debug, eventId, default, exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task DefaultValueOperationInLogNamed_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}}(default); + logger.Log{{logLevel}}(eventId, default); + logger.Log{{logLevel}}(exception, default); + logger.Log{{logLevel}}(eventId, exception, default); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task DefaultValueOperationInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel(default); + logger.DynamicLogLevel(LogLevel.Debug, default); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task IncrementOrDecrementOperationInLog_NoDiagnostic_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 VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task IncrementOrDecrementOperationInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, int argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, int argument); + + static void M(ILogger logger, int input) + { + logger.StaticLogLevel(input++); + logger.DynamicLogLevel(LogLevel.Debug, input++); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task IsPatternOperationInLog_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.Debug, eventId, exception is not null, exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task IsPatternOperationInLoggerMessage_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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, bool argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, bool argument); + + static void M(ILogger logger, Exception? exception) + { + logger.StaticLogLevel(exception is not null); + logger.DynamicLogLevel(LogLevel.Debug, exception is not null); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task IsTypeOperationInLog_NoDiagnostic_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 VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task IsTypeOperationInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, bool argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, bool argument); + + static void M(ILogger logger, object input) + { + logger.StaticLogLevel(input is Exception); + logger.DynamicLogLevel(LogLevel.Debug, input is Exception); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task NameOfOperationInLog_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, nameof(logger), exception, formatter); + + logger.Log(LogLevel.Debug, nameof(logger)); + logger.Log(LogLevel.Information, eventId, nameof(logger)); + logger.Log(LogLevel.Warning, exception, nameof(logger)); + logger.Log(LogLevel.Error, eventId, exception, nameof(logger)); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NameOfOperationInLogNamed_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}}(nameof(logger)); + logger.Log{{logLevel}}(eventId, nameof(logger)); + logger.Log{{logLevel}}(exception, nameof(logger)); + logger.Log{{logLevel}}(eventId, exception, nameof(logger)); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task NameOfOperationInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel(nameof(logger)); + logger.DynamicLogLevel(LogLevel.Debug, nameof(logger)); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task ObjectCreationOperationValueTypeInLog_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.Debug, eventId, new TimeSpan(), exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task ObjectCreationOperationValueTypeInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, TimeSpan argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, TimeSpan argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel(new TimeSpan()); + logger.DynamicLogLevel(LogLevel.Debug, new TimeSpan()); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task SizeOfOperationInLog_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.Debug, eventId, sizeof(int), exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task SizeOfOperationInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, int argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, int argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel(sizeof(int)); + logger.DynamicLogLevel(LogLevel.Debug, sizeof(int)); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task TypeOfOperationInLog_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.Debug, eventId, typeof(int), exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task TypeOfOperationInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, Type argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, Type argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel(typeof(int)); + logger.DynamicLogLevel(LogLevel.Debug, typeof(int)); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task UnaryOperationInLog_NoDiagnostic_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 VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task UnaryOperationInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, bool argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, bool argument); + + static void M(ILogger logger, bool input) + { + logger.StaticLogLevel(!input); + logger.DynamicLogLevel(LogLevel.Debug, !input); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationLiteralInLog_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.Debug, 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 VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task InterpolatedStringOperationLiteralInLogNamed_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 VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationLiteralInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel($"literal"); + logger.DynamicLogLevel(LogLevel.Debug, $"literal"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationConstantInLog_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + const string constant = "constant"; + + logger.Log(LogLevel.Debug, eventId, $"{constant}", exception, formatter); + + logger.Log(LogLevel.Debug, $"{constant}"); + logger.Log(LogLevel.Information, eventId, $"{constant}"); + logger.Log(LogLevel.Warning, exception, $"{constant}"); + logger.Log(LogLevel.Error, eventId, exception, $"{constant}"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task InterpolatedStringOperationConstantInLogNamed_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception) + { + const string constant = "constant"; + + logger.Log{{logLevel}}($"{constant}"); + logger.Log{{logLevel}}(eventId, $"{constant}"); + logger.Log{{logLevel}}(exception, $"{constant}"); + logger.Log{{logLevel}}(eventId, exception, $"{constant}"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationConstantInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + const string constant = "constant"; + + logger.StaticLogLevel($"{constant}"); + logger.DynamicLogLevel(LogLevel.Debug, $"{constant}"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationNameOfInLog_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.Debug, eventId, $"logger name: {nameof(logger)}", exception, formatter); + + logger.Log(LogLevel.Debug, $"logger name: {nameof(logger)}"); + logger.Log(LogLevel.Information, eventId, $"logger name: {nameof(logger)}"); + logger.Log(LogLevel.Warning, exception, $"logger name: {nameof(logger)}"); + logger.Log(LogLevel.Error, eventId, exception, $"logger name: {nameof(logger)}"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task InterpolatedStringOperationNameOfInLogNamed_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}}($"logger name: {nameof(logger)}"); + logger.Log{{logLevel}}(eventId, $"logger name: {nameof(logger)}"); + logger.Log{{logLevel}}(exception, $"logger name: {nameof(logger)}"); + logger.Log{{logLevel}}(eventId, exception, $"logger name: {nameof(logger)}"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationNameOfInLoggerMessage_NoDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + static partial class C + { + [LoggerMessage(EventId = 0, Level = LogLevel.Information, Message = "Static log level `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + logger.StaticLogLevel($"logger name: {nameof(logger)}"); + logger.DynamicLogLevel(LogLevel.Debug, $"logger name: {nameof(logger)}"); + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(source, CodeAnalysis.CSharp.LanguageVersion.CSharp12); + } + + [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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task ObjectCreationOperationReferenceType_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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(source); + } + + // Tests for work done in other operations. + + [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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task WorkInArrayReference_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[ExpensiveMethodCall()]|], exception, formatter); + } + + int ExpensiveMethodCall() + { + return 0; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task WorkInUnaryOperand_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()|], exception, formatter); + } + + bool ExpensiveMethodCall() + { + return true; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task WorkInBinaryOperand_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() + ExpensiveMethodCall()|], exception, formatter); + } + + int ExpensiveMethodCall() + { + return 0; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task WorkInCoalesceOperationValue_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, [|ExpensiveMethodCall() ?? 0|], exception, formatter); + } + + int? ExpensiveMethodCall() + { + return 0; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task WorkInCoalesceOperationWhenNull_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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task GuardedWorkInLogConditionalAccess_NoDiagnostic_CS() + { + string source = """ + #nullable enable + + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger? logger, EventId eventId, Exception? exception, Func formatter) + { + if (logger?.IsEnabled(LogLevel.Debug) ?? false) + logger?.Log(LogLevel.Debug, eventId, new Exception(), exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + 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 VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkInLog_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + if (logger.IsEnabled(LogLevel.{{logLevel}})) + { + if (exception is not null) + { + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall(), exception, formatter); + logger.Log(LogLevel.{{logLevel}}, ExpensiveMethodCall()); + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall()); + logger.Log(LogLevel.{{logLevel}}, exception, ExpensiveMethodCall()); + logger.Log(LogLevel.{{logLevel}}, eventId, exception, ExpensiveMethodCall()); + } + } + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + 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 VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkWithReturnInLog_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + if (!logger.IsEnabled(LogLevel.{{logLevel}})) + return; + + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall(), exception, formatter); + logger.Log(LogLevel.{{logLevel}}, ExpensiveMethodCall()); + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall()); + logger.Log(LogLevel.{{logLevel}}, exception, ExpensiveMethodCall()); + logger.Log(LogLevel.{{logLevel}}, eventId, exception, ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task GuardedWorkWithReturnInLogWithDynamicLogLevel_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)) + return; + + 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 VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkWithReturnInLogNamed_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}})) + return; + + logger.Log{{logLevel}}(ExpensiveMethodCall()); + logger.Log{{logLevel}}(eventId, ExpensiveMethodCall()); + logger.Log{{logLevel}}(exception, ExpensiveMethodCall()); + logger.Log{{logLevel}}(eventId, exception, ExpensiveMethodCall()); + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkWithReturnInLoggerMessage_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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + if (!logger.IsEnabled(LogLevel.{{logLevel}})) + return; + + logger.StaticLogLevel(ExpensiveMethodCall()); + logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()); + } + + static string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkWithReturnInLog_NoDiagnostic_CS(string logLevel) + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter) + { + if (!logger.IsEnabled(LogLevel.{{logLevel}})) + return; + + if (exception is not null) + { + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall(), exception, formatter); + logger.Log(LogLevel.{{logLevel}}, ExpensiveMethodCall()); + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall()); + logger.Log(LogLevel.{{logLevel}}, exception, ExpensiveMethodCall()); + logger.Log(LogLevel.{{logLevel}}, eventId, exception, ExpensiveMethodCall()); + } + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task NestedGuardedWorkWithReturnInLogWithDynamicLogLevel_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)) + return; + + 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 VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkWithReturnInLogNamed_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}})) + return; + + if (exception is not null) + { + logger.Log{{logLevel}}(ExpensiveMethodCall()); + logger.Log{{logLevel}}(eventId, ExpensiveMethodCall()); + logger.Log{{logLevel}}(exception, ExpensiveMethodCall()); + logger.Log{{logLevel}}(eventId, exception, ExpensiveMethodCall()); + } + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkWithReturnInLoggerMessage_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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + if (!logger.IsEnabled(LogLevel.{{logLevel}})) + return; + + if (IsExpensiveComputationEnabled) + { + logger.StaticLogLevel(ExpensiveMethodCall()); + logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()); + } + } + + static string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + 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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + 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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + 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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 VerifyCSharpDiagnosticAsync(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 `{argument}`")] + static partial void StaticLogLevel(this ILogger logger, string argument); + + [LoggerMessage(EventId = 1, Message = "Dynamic log level `{argument}`")] + static partial void DynamicLogLevel(this ILogger logger, LogLevel level, string argument); + + static void M(ILogger logger) + { + if (_otherLogger.IsEnabled(LogLevel.{{logLevel}})) + { + logger.StaticLogLevel([|ExpensiveMethodCall()|]); + } + } + + static string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task GuardAfterLogInvocation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger) + { + logger.Log(LogLevel.Debug, [|ExpensiveMethodCall()|]); + + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + } + + string ExpensiveMethodCall() + { + return "very expensive call"; + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + // Boxing tests + + [Fact] + public async Task ArgumentIsBoxed_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, [|42|], exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task ArgumentIsUnboxed_NoDiagnostic_CS() + { + string source = $$""" + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger, EventId eventId, Exception exception, Func formatter, object value) + { + logger.Log(LogLevel.Debug, eventId, (int)value, exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task BinaryOperationWithBoxing_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, [|"Hello " + 42|], exception, formatter); + } + } + """; + + await VerifyCSharpDiagnosticAsync(source); + } + + [Fact] + public async Task ImplicitBoxingParamsArrayCreation_ReportsDiagnostic_CS() + { + string source = """ + using System; + using Microsoft.Extensions.Logging; + + class C + { + void M(ILogger logger) + { + [|logger.LogError("Test: {Number1} and {Number2}", 1, 2)|]; + } + } + """; + + await VerifyCSharpDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel("literal") + logger.DynamicLogLevel(LogLevel.Debug, "literal") + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument 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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel(_field) + logger.DynamicLogLevel(LogLevel.Debug, _field) + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel([Property]) + logger.DynamicLogLevel(LogLevel.Debug, [Property]) + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument 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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel(_messages(0)) + logger.DynamicLogLevel(LogLevel.Debug, _messages(0)) + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument 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 VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task BinaryOperationInLog_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 Integer, Exception, String)) + logger.Log(LogLevel.Debug, eventId, 4 + 2, exception, formatter) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task BinaryOperationInLogNamed_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) + logger.Log{{logLevel}}("4" + "2") + logger.Log{{logLevel}}(eventId, "4" + "2") + logger.Log{{logLevel}}(exception, "4" + "2") + logger.Log{{logLevel}}(eventId, exception, "4" + "2") + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task BinaryOperationInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel("4" + "2") + logger.DynamicLogLevel(LogLevel.Debug, "4" + "2") + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task CoalesceOperationInLog_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 Object, Exception, String), message As String) + logger.Log(LogLevel.Debug, eventId, If(message, "null"), exception, formatter) + + logger.Log(LogLevel.Debug, If(message, "null")) + logger.Log(LogLevel.Information, eventId, If(message, "null")) + logger.Log(LogLevel.Warning, exception, If(message, "null")) + logger.Log(LogLevel.[Error], eventId, exception, If(message, "null")) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task CoalesceOperationInLogNamed_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, message As String) + logger.Log{{logLevel}}(If(message, "null")) + logger.Log{{logLevel}}(eventId, If(message, "null")) + logger.Log{{logLevel}}(exception, If(message, "null")) + logger.Log{{logLevel}}(eventId, exception, If(message, "null")) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task CoalesceOperationInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger, message As String) + logger.StaticLogLevel(If(message, "null")) + logger.DynamicLogLevel(LogLevel.Debug, If(message, "null")) + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task IsTypeOperationInLog_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 Boolean, Exception, String), input As Object) + logger.Log(LogLevel.Debug, eventId, TypeOf input Is Exception, exception, formatter) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task IsTypeOperationInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As Boolean) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As Boolean) + End Sub + + Sub M(logger As ILogger, input As Object) + logger.StaticLogLevel(TypeOf input Is Exception) + logger.DynamicLogLevel(LogLevel.Debug, TypeOf input Is Exception) + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task NameOfOperationInLog_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.Debug, eventId, NameOf(logger), exception, formatter) + + logger.Log(LogLevel.Debug, NameOf(logger)) + logger.Log(LogLevel.Information, eventId, NameOf(logger)) + logger.Log(LogLevel.Warning, exception, NameOf(logger)) + logger.Log(LogLevel.[Error], eventId, exception, NameOf(logger)) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NameOfOperationInLogNamed_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) + logger.Log{{logLevel}}(NameOf(logger)) + logger.Log{{logLevel}}(eventId, NameOf(logger)) + logger.Log{{logLevel}}(exception, NameOf(logger)) + logger.Log{{logLevel}}(eventId, exception, NameOf(logger)) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task NameOfOperationInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel(NameOf(logger)) + logger.DynamicLogLevel(LogLevel.Debug, NameOf(logger)) + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task ObjectCreationOperationValueTypeInLog_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 TimeSpan, Exception, String)) + logger.Log(LogLevel.Debug, eventId, New TimeSpan(), exception, formatter) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task ObjectCreationOperationValueTypeInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As TimeSpan) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As TimeSpan) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel(New TimeSpan()) + logger.DynamicLogLevel(LogLevel.Debug, New TimeSpan()) + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task TypeOfOperationInLog_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 Type, Exception, String)) + logger.Log(LogLevel.Debug, eventId, GetType(Integer), exception, formatter) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task TypeOfOperationInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As Type) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As Type) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel(GetType(Integer)) + logger.DynamicLogLevel(LogLevel.Debug, GetType(Integer)) + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task UnaryOperationInLog_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 Boolean, Exception, String), input As Boolean) + logger.Log(LogLevel.Debug, eventId, Not input, exception, formatter) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task UnaryOperationInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As Boolean) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As Boolean) + End Sub + + Sub M(logger As ILogger, input As Boolean) + logger.StaticLogLevel(Not input) + logger.DynamicLogLevel(LogLevel.Debug, Not input) + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationLiteralInLog_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, $"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 VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task InterpolatedStringOperationLiteralInLogNamed_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) + 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 VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationLiteralInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel($"literal") + logger.DynamicLogLevel(LogLevel.Debug, $"literal") + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationConstantInLog_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)) + Const constant As String = "constant" + + logger.Log(LogLevel.Debug, eventId, $"{constant}", exception, formatter) + + logger.Log(LogLevel.Debug, $"{constant}") + logger.Log(LogLevel.Information, eventId, $"{constant}") + logger.Log(LogLevel.Warning, exception, $"{constant}") + logger.Log(LogLevel.[Error], eventId, exception, $"{constant}") + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task InterpolatedStringOperationConstantInLogNamed_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) + Const constant As String = "constant" + + logger.Log{{logLevel}}($"{constant}") + logger.Log{{logLevel}}(eventId, $"{constant}") + logger.Log{{logLevel}}(exception, $"{constant}") + logger.Log{{logLevel}}(eventId, exception, $"{constant}") + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationConstantInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + Const constant As String = "constant" + + logger.StaticLogLevel($"{constant}") + logger.DynamicLogLevel(LogLevel.Debug, $"{constant}") + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationNameOfInLog_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, $"logger name: {NameOf(logger)}", exception, formatter) + + logger.Log(LogLevel.Debug, $"logger name: {NameOf(logger)}") + logger.Log(LogLevel.Information, eventId, $"logger name: {NameOf(logger)}") + logger.Log(LogLevel.Warning, exception, $"logger name: {NameOf(logger)}") + logger.Log(LogLevel.[Error], eventId, exception, $"logger name: {NameOf(logger)}") + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task InterpolatedStringOperationNameOfInLogNamed_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) + logger.Log{{logLevel}}($"logger name: {NameOf(logger)}") + logger.Log{{logLevel}}(eventId, $"logger name: {NameOf(logger)}") + logger.Log{{logLevel}}(exception, $"logger name: {NameOf(logger)}") + logger.Log{{logLevel}}(eventId, exception, $"logger name: {NameOf(logger)}") + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task InterpolatedStringOperationNameOfInLoggerMessage_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + Imports Microsoft.Extensions.Logging + + Partial Module C + + + Partial Private Sub StaticLogLevel(logger As ILogger, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + logger.StaticLogLevel($"logger name: {NameOf(logger)}") + logger.DynamicLogLevel(LogLevel.Debug, $"logger name: {NameOf(logger)}") + End Sub + End Module + """; + + await VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task ObjectCreationOperationReferenceType_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 VerifyBasicDiagnosticAsync(source); + } + + // Tests for work done in other operations. + + [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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task WorkInUnaryOperand_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)) + logger.Log(LogLevel.Debug, eventId, [|Not ExpensiveMethodCall()|], exception, formatter) + End Sub + + Function ExpensiveMethodCall() As Boolean + Return 0 + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task WorkInBinaryOperand_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() + ExpensiveMethodCall()|], exception, formatter) + End Sub + + Function ExpensiveMethodCall() As Integer + Return 0 + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task WorkInCoalesceOperationValue_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), message As String) + logger.Log(LogLevel.Debug, eventId, [|If(ExpensiveMethodCall(), exception)|], exception, formatter) + End Sub + + Function ExpensiveMethodCall() As Exception + Return Nothing + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task WorkInCoalesceOperationWhenNull_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), message As String) + logger.Log(LogLevel.Debug, eventId, [|If(exception, new Exception())|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task GuardedWorkInLogConditionalAccess_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)) Then + logger?.Log(LogLevel.Debug, eventId, ExpensiveMethodCall(), exception, formatter) + End If + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument 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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument 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 VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkWithReturnInLog_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 Not logger.IsEnabled(LogLevel.{{logLevel}}) Then Return + + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall(), exception, formatter) + logger.Log(LogLevel.{{logLevel}}, ExpensiveMethodCall()) + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall()) + logger.Log(LogLevel.{{logLevel}}, exception, ExpensiveMethodCall()) + logger.Log(LogLevel.{{logLevel}}, eventId, exception, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task GuardedWorkWithReturnInLogWithDynamicLogLevel_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 Not logger.IsEnabled(level) Then Return + + 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 Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkWithReturnInLogNamed_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 Not logger.IsEnabled(LogLevel.{{logLevel}}) Then Return + + logger.Log{{logLevel}}(ExpensiveMethodCall()) + logger.Log{{logLevel}}(eventId, ExpensiveMethodCall()) + logger.Log{{logLevel}}(exception, ExpensiveMethodCall()) + logger.Log{{logLevel}}(eventId, exception, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task GuardedWorkWithReturnInLoggerMessage_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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + If Not logger.IsEnabled(LogLevel.{{logLevel}}) Then Return + + logger.StaticLogLevel(ExpensiveMethodCall()) + logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()) + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Module + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkWithReturnInLog_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 Not logger.IsEnabled(LogLevel.{{logLevel}}) Then Return + + If exception IsNot Nothing + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall(), exception, formatter) + logger.Log(LogLevel.{{logLevel}}, ExpensiveMethodCall()) + logger.Log(LogLevel.{{logLevel}}, eventId, ExpensiveMethodCall()) + logger.Log(LogLevel.{{logLevel}}, exception, ExpensiveMethodCall()) + logger.Log(LogLevel.{{logLevel}}, eventId, exception, ExpensiveMethodCall()) + End If + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task NestedGuardedWorkWithReturnInLogWithDynamicLogLevel_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 Not logger.IsEnabled(level) Then Return + + 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 Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkWithReturnInLogNamed_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 Not logger.IsEnabled(LogLevel.{{logLevel}}) Then Return + + If exception IsNot Nothing + logger.Log{{logLevel}}(ExpensiveMethodCall()) + logger.Log{{logLevel}}(eventId, ExpensiveMethodCall()) + logger.Log{{logLevel}}(exception, ExpensiveMethodCall()) + logger.Log{{logLevel}}(eventId, exception, ExpensiveMethodCall()) + End If + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Theory] + [MemberData(nameof(LogLevels))] + public async Task NestedGuardedWorkWithReturnInLoggerMessage_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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument As String) + End Sub + + Sub M(logger As ILogger) + If Not logger.IsEnabled(LogLevel.{{logLevel}}) Then Return + + If IsExpensiveComputationEnabled + logger.StaticLogLevel(ExpensiveMethodCall()) + logger.DynamicLogLevel(LogLevel.{{logLevel}}, ExpensiveMethodCall()) + End If + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Module + """; + + await VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument 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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument 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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument 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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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 VerifyBasicDiagnosticAsync(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, argument As String) + End Sub + + + + Partial Private Sub DynamicLogLevel(logger As ILogger, level As LogLevel, argument 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 VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task GuardAfterLogInvocation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger) + logger.Log(LogLevel.Debug, [|ExpensiveMethodCall()|]) + + If logger.IsEnabled(LogLevel.Debug) Then Return + End Sub + + Function ExpensiveMethodCall() As String + Return "very expensive call" + End Function + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + // Boxing tests + + [Fact] + public async Task ArgumentIsBoxed_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, [|42|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task ArgumentIsUnboxed_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 Integer, Exception, String), value As Object) + logger.Log(LogLevel.Debug, eventId, CType(value, Integer), exception, formatter) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task BinaryOperationWithBoxing_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, [|"Hello " + 42|], exception, formatter) + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + [Fact] + public async Task ImplicitBoxingParamsArrayCreation_ReportsDiagnostic_VB() + { + string source = """ + Imports System + Imports Microsoft.Extensions.Logging + + Class C + Sub M(logger As ILogger) + [|logger.LogError("Test: {Number1} and {Number2}", 1, 2)|] + End Sub + End Class + """; + + await VerifyBasicDiagnosticAsync(source); + } + + // Helpers + + private static async Task VerifyCSharpDiagnosticAsync([StringSyntax($"{LanguageNames.CSharp}-Test")] string source, CodeAnalysis.CSharp.LanguageVersion? languageVersion = null) + { + await new VerifyCS.Test + { + TestCode = source, + FixedCode = source, + ReferenceAssemblies = Net60WithMELogging, + LanguageVersion = languageVersion ?? CodeAnalysis.CSharp.LanguageVersion.CSharp10 + }.RunAsync(); + } + + private static async Task VerifyBasicDiagnosticAsync(string source, CodeAnalysis.VisualBasic.LanguageVersion? languageVersion = null) + { + await new VerifyVB.Test + { + TestCode = source, + FixedCode = source, + 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 2b0204b8e2..7f78652b66 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 b028806dbc..82c6b00ff5 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";