diff --git a/analyzers/its/expected/Ember-MM/S2629-Ember.Plugins.json b/analyzers/its/expected/Ember-MM/S2629-Ember.Plugins.json new file mode 100644 index 00000000000..2aab807c344 --- /dev/null +++ b/analyzers/its/expected/Ember-MM/S2629-Ember.Plugins.json @@ -0,0 +1,16 @@ +{ + "Issues": [ + { + "Id": "S2629", + "Message": "Don\u0027t use String.Format in logging message templates.", + "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/Ember-MM/Ember.Plugins/PluginBase.cs#L71-L73", + "Location": "Lines 71-73 Position 27-49" + }, + { + "Id": "S2629", + "Message": "Don\u0027t use String.Format in logging message templates.", + "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/sources/Ember-MM/Ember.Plugins/PluginSectionHandler.cs#L70", + "Location": "Line 70 Position 35-94" + } + ] +} \ No newline at end of file diff --git a/analyzers/rspec/cs/S2629.html b/analyzers/rspec/cs/S2629.html new file mode 100644 index 00000000000..12bf4e0bfdc --- /dev/null +++ b/analyzers/rspec/cs/S2629.html @@ -0,0 +1,38 @@ +

Why is this an issue?

+

Logging arguments should not require evaluation in order to avoid unnecessary performance overhead. When passing concatenated strings or string +interpolations directly into a logging method, the evaluation of these expressions occurs every time the logging method is called, regardless of the +log level. This can lead to inefficient code execution and increased resource consumption.

+

Instead, it is recommended to use the overload of the logger that accepts a log format and its arguments as separate parameters. By separating the +log format from the arguments, the evaluation of expressions can be deferred until it is necessary, based on the log level. This approach improves +performance by reducing unnecessary evaluations and ensures that logging statements are only evaluated when needed.

+

Furthermore, using a constant log format enhances observability and facilitates searchability in log aggregation and monitoring software.

+

The rule covers the following logging frameworks:

+ +

Code examples

+

Noncompliant code example

+
+public void Method(ILogger logger, bool parameter)
+{
+    logger.DebugFormat($"The value of the parameter is: {parameter}.");
+}
+
+

Compliant solution

+
+public void Method(ILogger logger, bool parameter)
+{
+    logger.DebugFormat("The value of the parameter is: {Parameter}.", parameter);
+}
+
+

Resources

+

Documentation

+ + diff --git a/analyzers/rspec/cs/S2629.json b/analyzers/rspec/cs/S2629.json new file mode 100644 index 00000000000..d8e943e44fd --- /dev/null +++ b/analyzers/rspec/cs/S2629.json @@ -0,0 +1,18 @@ +{ + "title": "Logging templates should be constant", + "type": "CODE_SMELL", + "status": "ready", + "remediation": { + "func": "Constant\/Issue", + "constantCost": "5min" + }, + "tags": [ + "performance", + "logging" + ], + "defaultSeverity": "Major", + "ruleSpecification": "RSPEC-2629", + "sqKey": "S2629", + "scope": "Main", + "quickfix": "infeasible" +} diff --git a/analyzers/rspec/cs/Sonar_way_profile.json b/analyzers/rspec/cs/Sonar_way_profile.json index ad3d37fb0b5..02c67d435e2 100644 --- a/analyzers/rspec/cs/Sonar_way_profile.json +++ b/analyzers/rspec/cs/Sonar_way_profile.json @@ -106,6 +106,7 @@ "S2583", "S2589", "S2612", + "S2629", "S2681", "S2688", "S2692", diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs index e49abc905ff..97eecabf797 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs @@ -162,6 +162,7 @@ public static SyntaxNode WalkUpParentheses(this SyntaxNode node) MemberAccessExpressionSyntax { Name.Identifier: var identifier } => identifier, MemberBindingExpressionSyntax { Name.Identifier: var identifier } => identifier, MethodDeclarationSyntax { Identifier: var identifier } => identifier, + NameColonSyntax nameColon => nameColon.Name.Identifier, NamespaceDeclarationSyntax { Name: { } name } => GetIdentifier(name), NullableTypeSyntax { ElementType: { } elementType } => GetIdentifier(elementType), ObjectCreationExpressionSyntax { Type: var type } => GetIdentifier(type), diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/UseConstantLoggingTemplate.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/UseConstantLoggingTemplate.cs new file mode 100644 index 00000000000..cefeee92656 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/UseConstantLoggingTemplate.cs @@ -0,0 +1,125 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2024 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Rules.CSharp; + +[DiagnosticAnalyzer(LanguageNames.CSharp)] +public sealed class UseConstantLoggingTemplate : SonarDiagnosticAnalyzer +{ + private const string DiagnosticId = "S2629"; + private const string MessageFormat = "{0}"; + private const string OnUsingStringInterpolation = "Don't use string interpolation in logging message templates."; + private const string OnUsingStringFormat = "Don't use String.Format in logging message templates."; + private const string OnUsingStringConcatenation = "Don't use string concatenation in logging message templates."; + + private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); + + private static readonly ImmutableDictionary Messages = new Dictionary + { + {SyntaxKind.AddExpression, OnUsingStringConcatenation}, + {SyntaxKind.InterpolatedStringExpression, OnUsingStringInterpolation}, + {SyntaxKind.InvocationExpression, OnUsingStringFormat}, + }.ToImmutableDictionary(); + + private static readonly ImmutableArray LoggerTypes = ImmutableArray.Create( + KnownType.Castle_Core_Logging_ILogger, + KnownType.log4net_ILog, + KnownType.log4net_Util_ILogExtensions, + KnownType.Microsoft_Extensions_Logging_LoggerExtensions, + KnownType.NLog_ILogger, + KnownType.NLog_ILoggerBase, + KnownType.NLog_ILoggerExtensions, + KnownType.Serilog_ILogger, + KnownType.Serilog_Log); + + private static readonly ImmutableHashSet LoggerMethodNames = ImmutableHashSet.Create( + "ConditionalDebug", + "ConditionalTrace", + "Debug", + "DebugFormat", + "Error", + "ErrorFormat", + "Fatal", + "FatalFormat", + "Info", + "InfoFormat", + "Information", + "Log", + "LogCritical", + "LogDebug", + "LogError", + "LogFormat", + "LogInformation", + "LogTrace", + "LogWarning", + "Trace", + "TraceFormat", + "Verbose", + "Warn", + "WarnFormat", + "Warning"); + + private static readonly ImmutableHashSet LogMessageParameterNames = ImmutableHashSet.Create( + "format", + "message", + "messageTemplate"); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + protected override void Initialize(SonarAnalysisContext context) => + context.RegisterNodeAction(c => + { + var invocation = (InvocationExpressionSyntax)c.Node; + if (LoggerMethodNames.Contains(invocation.GetName()) + && c.SemanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol method + && LoggerTypes.Any(x => x.Matches(method.ContainingType)) + && method.Parameters.FirstOrDefault(x => LogMessageParameterNames.Contains(x.Name)) is { } messageParameter + && ArgumentValue(invocation, method, messageParameter) is { } argumentValue + && InvalidSyntaxNode(argumentValue, c.SemanticModel) is { } invalidNode) + { + c.ReportIssue(Diagnostic.Create(Rule, invalidNode.GetLocation(), Messages[invalidNode.Kind()])); + } + }, + SyntaxKind.InvocationExpression); + + private static CSharpSyntaxNode ArgumentValue(InvocationExpressionSyntax invocation, IMethodSymbol method, IParameterSymbol parameter) + { + if (invocation.ArgumentList.Arguments.FirstOrDefault(x => x.NameColon?.GetName() == parameter.Name) is { } argument) + { + return argument.Expression; + } + else + { + var paramIndex = method.Parameters.IndexOf(parameter); + return invocation.ArgumentList.Arguments[paramIndex].Expression; + } + } + + private static SyntaxNode InvalidSyntaxNode(SyntaxNode messageArgument, SemanticModel model) => + messageArgument.DescendantNodesAndSelf().FirstOrDefault(x => + x.Kind() is SyntaxKind.InterpolatedStringExpression or SyntaxKind.AddExpression + || IsStringFormatInvocation(x, model)); + + private static bool IsStringFormatInvocation(SyntaxNode node, SemanticModel model) => + node is InvocationExpressionSyntax invocation + && node.GetName() == "Format" + && model.GetSymbolInfo(invocation).Symbol is IMethodSymbol method + && KnownType.System_String.Matches(method.ContainingType); +} diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs index 1dd5d46e368..e8e6dee705e 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs @@ -151,6 +151,8 @@ public sealed partial class KnownType public static readonly KnownType NHibernate_Impl_AbstractSessionImpl = new("NHibernate.Impl.AbstractSessionImpl"); public static readonly KnownType NLog_ILogger = new("NLog.ILogger"); public static readonly KnownType NLog_ILoggerBase = new("NLog.ILoggerBase"); + public static readonly KnownType NLog_ILoggerExtensions = new("NLog.ILoggerExtensions"); + public static readonly KnownType NLog_Logger = new("NLog.Logger"); public static readonly KnownType NLog_LogManager = new("NLog.LogManager"); public static readonly KnownType NUnit_Framework_Assert = new("NUnit.Framework.Assert"); public static readonly KnownType NUnit_Framework_AssertionException = new("NUnit.Framework.AssertionException"); @@ -174,6 +176,8 @@ public sealed partial class KnownType public static readonly KnownType Serilog_LoggerConfiguration = new("Serilog.LoggerConfiguration"); public static readonly KnownType ServiceStack_OrmLite_OrmLiteReadApi = new("ServiceStack.OrmLite.OrmLiteReadApi"); public static readonly KnownType ServiceStack_OrmLite_OrmLiteReadApiAsync = new("ServiceStack.OrmLite.OrmLiteReadApiAsync"); + public static readonly KnownType Serilog_ILogger = new("Serilog.ILogger"); + public static readonly KnownType Serilog_Log = new("Serilog.Log"); public static readonly KnownType System_Action = new("System.Action"); public static readonly KnownType System_Action_T = new("System.Action", "T"); public static readonly KnownType System_Action_T1_T2 = new("System.Action", "T1", "T2"); @@ -377,6 +381,7 @@ public sealed partial class KnownType public static readonly KnownType System_Numerics_IEqualityOperators_TSelf_TOther_TResult = new("System.Numerics.IEqualityOperators", "TSelf", "TOther", "TResult"); public static readonly KnownType System_Numerics_IFloatingPointIeee754_TSelf = new("System.Numerics.IFloatingPointIeee754", "TSelf"); public static readonly KnownType System_Object = new("System.Object"); + public static readonly KnownType System_Object_Array = new("System.Object") { IsArray = true }; public static readonly KnownType System_ObsoleteAttribute = new("System.ObsoleteAttribute"); public static readonly KnownType System_OutOfMemoryException = new("System.OutOfMemoryException"); public static readonly KnownType System_Random = new("System.Random"); diff --git a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs index 8668f79db2d..53331ff8e41 100644 --- a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs +++ b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs @@ -2553,7 +2553,7 @@ internal static class RuleTypeMappingCS // ["S2626"], // ["S2627"], // ["S2628"], - // ["S2629"], + ["S2629"] = "CODE_SMELL", // ["S2630"], // ["S2631"], // ["S2632"], diff --git a/analyzers/tests/SonarAnalyzer.Test/Rules/UseConstantLoggingTemplateTest.cs b/analyzers/tests/SonarAnalyzer.Test/Rules/UseConstantLoggingTemplateTest.cs new file mode 100644 index 00000000000..31a327290fd --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/Rules/UseConstantLoggingTemplateTest.cs @@ -0,0 +1,177 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2024 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using CS = SonarAnalyzer.Rules.CSharp; + +namespace SonarAnalyzer.Test.Rules; + +[TestClass] +public class UseConstantLoggingTemplateTest +{ + private readonly VerifierBuilder builder = CreateVerifier(); + + [TestMethod] + public void UseConstantLoggingTemplate_CS() => + builder.AddPaths("UseConstantLoggingTemplate.cs").Verify(); + + [DataTestMethod] + [DataRow("Debug")] + [DataRow("Debug")] + [DataRow("Error")] + [DataRow("Fatal")] + [DataRow("Info")] + [DataRow("Trace")] + [DataRow("Warn")] + public void UseConstantLoggingTemplate_CastleCoreLogging_CS(string methodName) => + builder.AddSnippet($$""" + using Castle.Core.Logging; + + public class Program + { + public void Method(ILogger logger, int arg) + { + logger.{{methodName}}("Message"); // Compliant + logger.{{methodName}}($"{arg}"); // Noncompliant + logger.{{methodName}}Format("{Arg}", arg); // Compliant + logger.{{methodName}}Format($"{arg}"); // Noncompliant + } + } + """).Verify(); + + [DataTestMethod] + [DataRow("Debug")] + [DataRow("Error")] + [DataRow("Fatal")] + [DataRow("Info")] + [DataRow("Warn")] + public void UseConstantLoggingTemplate_Log4Net_CS(string methodName) => + builder.AddSnippet($$""" + using log4net; + + public class Program + { + public void Method(ILog logger, int arg) + { + logger.{{methodName}}("Message"); // Compliant + logger.{{methodName}}($"{arg}"); // Noncompliant + logger.{{methodName}}Format("Arg: {0}", arg); // Compliant + logger.{{methodName}}Format($"{arg}"); // Noncompliant + } + } + """).Verify(); + + [DataTestMethod] + [DataRow("Log", "LogLevel.Warning,")] + [DataRow("LogCritical")] + [DataRow("LogDebug")] + [DataRow("LogError")] + [DataRow("LogInformation")] + [DataRow("LogTrace")] + [DataRow("LogWarning")] + public void UseConstantLoggingTemplate_MicrosoftExtensionsLogging_CS(string methodName, string logLevel = "") => + builder.AddSnippet($$""" + using Microsoft.Extensions.Logging; + + public class Program + { + public void Method(ILogger logger, int arg) + { + logger.{{methodName}}({{logLevel}} "Message"); // Compliant + logger.{{methodName}}({{logLevel}} $"{arg}"); // Noncompliant + } + } + """).Verify(); + + [DataTestMethod] + [DataRow("ConditionalDebug")] + [DataRow("ConditionalTrace")] + [DataRow("Debug")] + [DataRow("Error")] + [DataRow("Fatal")] + [DataRow("Info")] + [DataRow("Log", "LogLevel.Warn,")] + [DataRow("Trace")] + [DataRow("Warn")] + public void UseConstantLoggingTemplate_NLog_CS(string methodName, string logLevel = "") => + builder.AddSnippet($$""" + using NLog; + + public class Program + { + public void Method(ILogger logger, int arg) + { + logger.{{methodName}}({{logLevel}} "Message"); // Compliant + logger.{{methodName}}({{logLevel}} $"{arg}"); // Noncompliant + } + } + """).Verify(); + + public void UseConstantLoggingTemplate_NLog_AdditionalLoggers_CS() => + builder.AddSnippet(""" + using NLog; + + public class Program + { + public void Method(ILoggerBase logger, NullLogger nullLogger, int arg) + { + logger.Log(LogLevel.Warn, "Message"); // Compliant + logger.Log(LogLevel.Warn, $"{arg}"); // Noncompliant + + nullLogger.Log(LogLevel.Warn, "Message"); // Compliant + nullLogger.Log(LogLevel.Warn, $"{arg}"); // Noncompliant + } + } + """).Verify(); + + [DataTestMethod] + [DataRow("Debug")] + [DataRow("Error")] + [DataRow("Fatal")] + [DataRow("Information")] + [DataRow("Verbose")] + [DataRow("Warning")] + public void UseConstantLoggingTemplate_Serilog_CS(string methodName) => + builder.AddSnippet($$""" + using Serilog; + + public class Program + { + public void Method(ILogger logger, int arg) + { + logger.{{methodName}}("Message without argument"); // Compliant + logger.{{methodName}}("The argument is {@Argument}", arg); // Compliant + logger.{{methodName}}($"The argument is {arg}"); // Noncompliant + + Log.{{methodName}}("Message without argument"); // Compliant + Log.{{methodName}}("The argument is {@Argument}", arg); // Compliant + Log.{{methodName}}($"The argument is {arg}"); // Noncompliant + } + } + """).Verify(); + + private static VerifierBuilder CreateVerifier() + where TAnalyzer : DiagnosticAnalyzer, new() => + new VerifierBuilder() + .AddReferences(NuGetMetadataReference.MicrosoftExtensionsLoggingPackages(Constants.NuGetLatestVersion)) + .AddReferences(NuGetMetadataReference.CastleCore(Constants.NuGetLatestVersion)) + .AddReferences(NuGetMetadataReference.Serilog(Constants.NuGetLatestVersion)) + .AddReferences(NuGetMetadataReference.Log4Net("2.0.8", "net45-full")) + .AddReferences(NuGetMetadataReference.NLog(Constants.NuGetLatestVersion)); +} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseConstantLoggingTemplate.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/UseConstantLoggingTemplate.cs new file mode 100644 index 00000000000..5519bebce3e --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.Test/TestCases/UseConstantLoggingTemplate.cs @@ -0,0 +1,90 @@ +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using AliasedLogger = Microsoft.Extensions.Logging.ILogger; + +public class Program +{ + private string _stringField = ""; + private string StringProperty => ""; + private string StringMethod() => ""; + + public void BasicScenarios(ILogger logger, int arg) + { + string localVariable = ""; + + logger.Log(LogLevel.Warning, ""); // Compliant + logger.Log(LogLevel.Warning, "", 42); // Compliant - this rule doesn't care whether the additional arguments are properly used + logger.Log(LogLevel.Warning, "{Arg}", arg); // Compliant + logger.Log(LogLevel.Warning, localVariable); // Compliant + logger.Log(LogLevel.Warning, _stringField); // Compliant + logger.Log(LogLevel.Warning, StringProperty, 42); // Compliant + logger.Log(LogLevel.Warning, StringMethod(), 42); // Compliant + logger.Log(message: "{Param}", logLevel: LogLevel.Warning, args: 42); // Compliant + + logger.Log(LogLevel.Warning, $"{arg}"); // Noncompliant {{Don't use string interpolation in logging message templates.}} + // ^^^^^^^^ + + logger.Log(LogLevel.Warning, "Argument: " + arg); // Noncompliant {{Don't use string concatenation in logging message templates.}} + // ^^^^^^^^^^^^^^^^^^ + + logger.Log(LogLevel.Warning, string.Format("{0}", arg)); // Noncompliant {{Don't use String.Format in logging message templates.}} + // ^^^^^^^^^^^^^^^^^^^^^^^^^ + + logger.Log(message: $"{arg}", logLevel: LogLevel.Warning); // Noncompliant + logger.Log(message: (string)$"{arg}", logLevel: LogLevel.Warning); // Noncompliant + // ^^^^^^^^ + logger.Log(LogLevel.Warning, (arg + " " + arg).ToLower()); // Noncompliant + // ^^^^^^^^^^^^^^^ + + logger.Log(LogLevel.Warning, new EventId(42), $"{arg}"); // Noncompliant + logger.Log(LogLevel.Warning, new Exception(), $"{arg}"); // Noncompliant + logger.Log(LogLevel.Warning, new Exception(), "{Arg}", arg); // Compliant + + LoggerExtensions.Log(logger, LogLevel.Warning, "{Arg}", arg); // Compliant + LoggerExtensions.Log(logger, LogLevel.Warning, $"{arg}"); // Noncompliant + } + + public void NotLoggingMethod(ILogger logger, int arg) + { + logger.BeginScope($"{arg}", 1, 2, 3); // Compliant - not a log method, the message argument is always evaluated + } + + public void ImplementsILogger(NullLogger nullLogger, CustomLogger customLogger, int arg) + { + nullLogger.Log(LogLevel.Warning, "Arg: {Arg}", arg); // Compliant + nullLogger.Log(LogLevel.Warning, $"Arg: {arg}"); // Noncompliant + customLogger.Log(LogLevel.Warning, "Arg: {Arg}", arg); // Compliant + customLogger.Log(LogLevel.Warning, $"Arg: {arg}"); // Noncompliant + } + + public void DoesNotImplementILogger(NotILogger notILogger, int arg) + { + notILogger.Log(LogLevel.Warning, "Arg: {Arg}", arg); // Compliant + notILogger.Log(LogLevel.Warning, $"Arg: {arg}"); // Compliant + } + + public void AliasedILogger(AliasedLogger aliasedLogger, int arg) + { + aliasedLogger.Log(LogLevel.Warning, "Arg: {Arg}", arg); // Compliant + aliasedLogger.Log(LogLevel.Warning, $"Arg: {arg}"); // Noncompliant + } + + public class CustomLogger : ILogger + { + public IDisposable BeginScope(TState state) => null; + + public bool IsEnabled(LogLevel logLevel) => false; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + { + } + } + + public class NotILogger + { + public void Log(LogLevel logLevel, string message, params object[] args) + { + } + } +} diff --git a/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/NuGetMetadataReference.cs b/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/NuGetMetadataReference.cs index 7333d9f6b4c..535e25652a8 100644 --- a/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/NuGetMetadataReference.cs +++ b/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/NuGetMetadataReference.cs @@ -43,8 +43,8 @@ public static class NuGetMetadataReference public static References AzureStorageFilesShares(string packageVersion = Constants.NuGetLatestVersion) => Create("Azure.Storage.Files.Shares", packageVersion); public static References AzureStorageQueues(string packageVersion = Constants.NuGetLatestVersion) => Create("Azure.Storage.Queues", packageVersion); public static References BouncyCastle(string packageVersion = "1.8.5") => Create("BouncyCastle", packageVersion); + public static References CastleCore(string packageVersion = "5.1.1") => Create("Castle.Core", packageVersion); public static References Dapper(string packageVersion = "1.50.5") => Create("Dapper", packageVersion); - public static References CastleCore(string packageVersion = Constants.NuGetLatestVersion) => Create("Castle.Core", packageVersion); public static References CommonLoggingCore(string packageVersion = Constants.NuGetLatestVersion) => Create("Common.Logging.Core", packageVersion); public static References EntityFramework(string packageVersion = "6.2.0") => Create("EntityFramework", packageVersion); public static References FluentAssertions(string packageVersion) => Create("FluentAssertions", packageVersion);