diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAwaitableMethod.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAwaitableMethod.cs index ddfec7025d3..837ade40b3a 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAwaitableMethod.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAwaitableMethod.cs @@ -27,7 +27,8 @@ public sealed class UseAwaitableMethod : SonarDiagnosticAnalyzer { private const string DiagnosticId = "S6966"; private const string MessageFormat = "Await {0} instead."; - private static readonly string[] ExcludedMethodNamesAddAddRange = ["Add", "AddRange"]; + private static readonly string[] ExcludedMethodNames = ["Add", "AddRange"]; + private static readonly ImmutableArray ExcludedTypes = ImmutableArray.Create(KnownType.System_Xml_XmlWriter, KnownType.System_Xml_XmlReader); private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); @@ -92,8 +93,8 @@ private static ImmutableArray> BuildExclusions(Compila var exclusions = ImmutableArray.CreateBuilder>(); if (compilation.GetTypeByMetadataName(KnownType.Microsoft_EntityFrameworkCore_DbSet_TEntity) is not null) { - exclusions.Add(x => x.IsAny(KnownType.Microsoft_EntityFrameworkCore_DbSet_TEntity, ExcludedMethodNamesAddAddRange)); // https://github.com/SonarSource/sonar-dotnet/issues/9269 - exclusions.Add(x => x.IsAny(KnownType.Microsoft_EntityFrameworkCore_DbContext, ExcludedMethodNamesAddAddRange)); // https://github.com/SonarSource/sonar-dotnet/issues/9269 + exclusions.Add(x => x.IsAny(KnownType.Microsoft_EntityFrameworkCore_DbSet_TEntity, ExcludedMethodNames)); // https://github.com/SonarSource/sonar-dotnet/issues/9269 + exclusions.Add(x => x.IsAny(KnownType.Microsoft_EntityFrameworkCore_DbContext, ExcludedMethodNames)); // https://github.com/SonarSource/sonar-dotnet/issues/9269 } if (compilation.GetTypeByMetadataName(KnownType.FluentValidation_IValidator) is not null) { @@ -115,7 +116,8 @@ private static ImmutableArray FindAwaitableAlternatives(WellKnownExtens && invocationExpression.EnclosingScope() is { } scope && IsAsyncCodeBlock(scope) && semanticModel.GetSymbolInfo(invocationExpression, cancel).Symbol is IMethodSymbol { MethodKind: not MethodKind.DelegateInvoke } methodSymbol - && !methodSymbol.IsAwaitableNonDynamic() // The invoked method returns something awaitable (but it isn't awaited). + && !(methodSymbol.IsAwaitableNonDynamic() // The invoked method returns something awaitable (but it isn't awaited). + || methodSymbol.ContainingType.DerivesFromAny(ExcludedTypes)) && !exclusions.Any(x => x(methodSymbol))) { // Perf: Before doing (expensive) speculative re-binding in SpeculativeBindCandidates, we check if there is an "..Async()" alternative in scope. diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs index cf501d1ed0e..4c0d9187b3c 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs @@ -624,6 +624,7 @@ public sealed partial class KnownType public static readonly KnownType System_Windows_Markup_XmlnsPrefixAttribute = new("System.Windows.Markup.XmlnsPrefixAttribute"); public static readonly KnownType System_Windows_Markup_XmlnsDefinitionAttribute = new("System.Windows.Markup.XmlnsDefinitionAttribute"); public static readonly KnownType System_Windows_Markup_XmlnsCompatibleWithAttribute = new("System.Windows.Markup.XmlnsCompatibleWithAttribute"); + public static readonly KnownType System_Xml_Resolvers_XmlPreloadedResolver = new("System.Xml.Resolvers.XmlPreloadedResolver"); public static readonly KnownType System_Xml_Serialization_XmlElementAttribute = new("System.Xml.Serialization.XmlElementAttribute"); public static readonly KnownType System_Xml_XmlDocument = new("System.Xml.XmlDocument"); public static readonly KnownType System_Xml_XmlDataDocument = new("System.Xml.XmlDataDocument"); @@ -633,7 +634,7 @@ public sealed partial class KnownType public static readonly KnownType System_Xml_XmlReaderSettings = new("System.Xml.XmlReaderSettings"); public static readonly KnownType System_Xml_XmlUrlResolver = new("System.Xml.XmlUrlResolver"); public static readonly KnownType System_Xml_XmlTextReader = new("System.Xml.XmlTextReader"); - public static readonly KnownType System_Xml_Resolvers_XmlPreloadedResolver = new("System.Xml.Resolvers.XmlPreloadedResolver"); + public static readonly KnownType System_Xml_XmlWriter = new("System.Xml.XmlWriter"); public static readonly KnownType Void = new("System.Void"); public static readonly KnownType NSubstitute_SubstituteExtensions = new("NSubstitute.SubstituteExtensions"); public static readonly KnownType NSubstitute_Received = new("NSubstitute.Received"); diff --git a/analyzers/tests/SonarAnalyzer.Test/Rules/UseAwaitableMethodTest.cs b/analyzers/tests/SonarAnalyzer.Test/Rules/UseAwaitableMethodTest.cs index 35d13607ed8..ee03337efe7 100644 --- a/analyzers/tests/SonarAnalyzer.Test/Rules/UseAwaitableMethodTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/Rules/UseAwaitableMethodTest.cs @@ -57,7 +57,7 @@ async Task MethodInvocations() { ActionProperty(); // Compliant; } - } + } """).VerifyNoIssues(); [TestMethod] @@ -75,6 +75,50 @@ public void UseAwaitableMethod_FluentValidation() => .AddPaths("UseAwaitableMethod_FluentValidation.cs") .Verify(); + [TestMethod] + public void UseAwaitableMethod_ExcludeXmlReaderAndWriter() => + builder + .AddReferences(MetadataReferenceFacade.SystemXml) + .AddSnippet(""" + using System.IO; + using System.Threading.Tasks; + using System.Xml; + + public class Test + { + async Task TestReader(Stream stream) + { + using (XmlReader reader = XmlReader.Create(stream)) + { + reader.Read(); // Compliant, we don't raise for XmlReader methods https://github.com/SonarSource/sonar-dotnet/issues/9336 + reader.ReadContentAs(typeof(int), null); // Compliant + reader.MoveToContent(); // Compliant + reader.ReadContentAsBase64(null, 0, 0); // Compliant + reader.ReadContentAsBinHex(null, 0, 0); // Compliant + reader.ReadContentAsObject(); // Compliant + reader.ReadContentAsString(); // Compliant + reader.ReadInnerXml(); // Compliant + reader.ReadOuterXml(); // Compliant + reader.ReadValueChunk(null, 0, 0); // Compliant + } + + using (XmlWriter writer = XmlWriter.Create(stream)) + { + writer.WriteStartElement("pf", "root", "http://ns"); // Compliant, we don't raise for XmlWriter methods https://github.com/SonarSource/sonar-dotnet/issues/9336 + writer.WriteStartElement(null, "sub", null); // Compliant + writer.WriteAttributeString(null, "att", null, "val"); // Compliant + writer.WriteString("text"); // Compliant + writer.WriteEndElement(); // Compliant + writer.WriteProcessingInstruction("pName", "pValue"); // Compliant + writer.WriteComment("cValue"); // Compliant + writer.WriteCData("cdata value"); // Compliant + writer.WriteEndElement(); // Compliant + writer.Flush(); // Compliant + } + } + } + """).VerifyNoIssues(); + #if NET [TestMethod] public void UseAwaitableMethod_CSharp9() =>