diff --git a/analyzers/its/expected/BlazorSample/S6966-BlazorSample-net7.0.json b/analyzers/its/expected/BlazorSample/S6966-BlazorSample-net7.0.json deleted file mode 100644 index adc4239634e..00000000000 --- a/analyzers/its/expected/BlazorSample/S6966-BlazorSample-net7.0.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Issues": [ - { - "Id": "S6966", - "Message": "Await RunAsync instead.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/BlazorSample/BlazorSample/Program.cs#L29", - "Location": "Line 29 Position 1-10" - } - ] -} \ No newline at end of file diff --git a/analyzers/its/expected/CSharpLatest/S6966-NetCore31.MVC.ConfigurableRules-netcoreapp3.1.Views.json b/analyzers/its/expected/CSharpLatest/S6966-NetCore31.MVC.ConfigurableRules-netcoreapp3.1.Views.json deleted file mode 100644 index a78adad83c1..00000000000 --- a/analyzers/its/expected/CSharpLatest/S6966-NetCore31.MVC.ConfigurableRules-netcoreapp3.1.Views.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Issues": [ - { - "Id": "S6966", - "Message": "Await RenderSectionAsync instead.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/CSharpLatest/NetCore31WithConfigurableRules/Views/Shared/_Layout.cshtml#L46", - "Location": "Line 46 Position 7-48" - } - ] -} \ No newline at end of file diff --git a/analyzers/its/expected/ManuallyAddedNoncompliantIssues.CS/S6966-AspNetCore6-net6.0.json b/analyzers/its/expected/ManuallyAddedNoncompliantIssues.CS/S6966-AspNetCore6-net6.0.json deleted file mode 100644 index b10b3a5fa62..00000000000 --- a/analyzers/its/expected/ManuallyAddedNoncompliantIssues.CS/S6966-AspNetCore6-net6.0.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Issues": [ - { - "Id": "S6966", - "Message": "Await RunAsync instead.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/ManuallyAddedNoncompliantIssues.CS/AspNetCore6/Program.cs#L27", - "Location": "Line 27 Position 1-10" - } - ] -} \ No newline at end of file diff --git a/analyzers/its/expected/ManuallyAddedNoncompliantIssues.CS/S6966-AspNetCore7-net7.0.json b/analyzers/its/expected/ManuallyAddedNoncompliantIssues.CS/S6966-AspNetCore7-net7.0.json deleted file mode 100644 index 6b27d03caf0..00000000000 --- a/analyzers/its/expected/ManuallyAddedNoncompliantIssues.CS/S6966-AspNetCore7-net7.0.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Issues": [ - { - "Id": "S6966", - "Message": "Await RunAsync instead.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/ManuallyAddedNoncompliantIssues.CS/AspNetCore7/Program.cs#L27", - "Location": "Line 27 Position 1-10" - } - ] -} \ No newline at end of file diff --git a/analyzers/its/expected/ManuallyAddedNoncompliantIssues.CS/S6966-AspNetCore8-net8.0.json b/analyzers/its/expected/ManuallyAddedNoncompliantIssues.CS/S6966-AspNetCore8-net8.0.json deleted file mode 100644 index 3f5538e5205..00000000000 --- a/analyzers/its/expected/ManuallyAddedNoncompliantIssues.CS/S6966-AspNetCore8-net8.0.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Issues": [ - { - "Id": "S6966", - "Message": "Await RunAsync instead.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/ManuallyAddedNoncompliantIssues.CS/AspNetCore8/Program.cs#L27", - "Location": "Line 27 Position 1-10" - } - ] -} \ No newline at end of file diff --git a/analyzers/its/expected/RazorSample/S6966-RazorSample-net7.0.json b/analyzers/its/expected/RazorSample/S6966-RazorSample-net7.0.json deleted file mode 100644 index d19cf47db78..00000000000 --- a/analyzers/its/expected/RazorSample/S6966-RazorSample-net7.0.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Issues": [ - { - "Id": "S6966", - "Message": "Await RunAsync instead.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/RazorSample/RazorSample/Program.cs#L25", - "Location": "Line 25 Position 1-10" - } - ] -} \ No newline at end of file diff --git a/analyzers/its/expected/akka.net/S6966-Akka.Cluster.Sharding-netstandard2.0.json b/analyzers/its/expected/akka.net/S6966-Akka.Cluster.Sharding-netstandard2.0.json deleted file mode 100644 index 4364c712392..00000000000 --- a/analyzers/its/expected/akka.net/S6966-Akka.Cluster.Sharding-netstandard2.0.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "Issues": [ - { - "Id": "S6966", - "Message": "Await StartProxyAsync instead.", - "Uri": "https://github.com/SonarSource/sonar-dotnet/blob/master/analyzers/its/Projects/akka.net/src/contrib/cluster/Akka.Cluster.Sharding/ClusterSharding.cs#L742", - "Location": "Line 742 Position 24-92" - } - ] -} \ No newline at end of file diff --git a/analyzers/rspec/cs/S6966.html b/analyzers/rspec/cs/S6966.html deleted file mode 100644 index 99eaf88ba54..00000000000 --- a/analyzers/rspec/cs/S6966.html +++ /dev/null @@ -1,70 +0,0 @@ -

In an async method, any blocking operations should be avoided.

-

Why is this an issue?

-

Using a synchronous method instead of its asynchronous -counterpart in an async method blocks the execution and is considered bad practice for several reasons:

-
-
- Resource Utilization -
-
-

Each thread consumes system resources, such as memory. When a thread is blocked, it’s not doing any useful work, but it’s still consuming these - resources. This can lead to inefficient use of system resources.

-
-
- Scalability -
-
-

Blocking threads can limit the scalability of your application. In a high-load scenario where many operations are happening concurrently, each - blocked thread represents a missed opportunity to do useful work. This can prevent your application from effectively handling increased load.

-
-
- Performance -
-
-

Blocking threads can degrade the performance of your application. If all threads in the thread pool become blocked, new tasks can’t start - executing until an existing task completes and frees up a thread. This can lead to delays and poor responsiveness.

-
-
-

Instead of blocking, it’s recommended to use the async operator with async methods. This -allows the system to release the current thread back to the thread pool until the awaited task is complete, improving scalability and -responsiveness.

-

How to fix it

-

Code examples

-

Noncompliant code example

-
-public async Task Examples(Stream stream, DbSet<Person> dbSet)
-{
-    stream.Read(array, 0, 1024);            // Noncompliant
-    File.ReadAllLines("path");              // Noncompliant
-    dbSet.ToList();                         // Noncompliant in Entity Framework Core queries
-    dbSet.FirstOrDefault(x => x.Age >= 18); // Noncompliant in Entity Framework Core queries
-}
-
-

Compliant solution

-
-public async Task Examples(Stream stream, DbSet<Person> dbSet)
-{
-    await stream.ReadAsync(array, 0, 1024);
-    await File.ReadAllLinesAsync("path");
-    await dbSet.ToListAsync();
-    await dbSet.FirstOrDefaultAsync(x => x.Age >= 18);
-}
-
-

Resources

-

Documentation

- -

Articles & blog posts

- - diff --git a/analyzers/rspec/cs/S6966.json b/analyzers/rspec/cs/S6966.json deleted file mode 100644 index 02f3331eb45..00000000000 --- a/analyzers/rspec/cs/S6966.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "title": "Awaitable method should be used", - "type": "CODE_SMELL", - "status": "ready", - "remediation": { - "func": "Constant\/Issue", - "constantCost": "5min" - }, - "tags": [ - "async-await" - ], - "defaultSeverity": "Major", - "ruleSpecification": "RSPEC-6966", - "sqKey": "S6966", - "scope": "All", - "quickfix": "targeted", - "code": { - "impacts": { - "RELIABILITY": "MEDIUM" - }, - "attribute": "COMPLETE" - } -} diff --git a/analyzers/rspec/cs/Sonar_way_profile.json b/analyzers/rspec/cs/Sonar_way_profile.json index c2b1e24c7a2..5006acc1f55 100644 --- a/analyzers/rspec/cs/Sonar_way_profile.json +++ b/analyzers/rspec/cs/Sonar_way_profile.json @@ -317,6 +317,6 @@ "S6930", "S6931", "S6934", - "S6966" + "S6961" ] } diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.cs index 52fed73eedb..2f75c0e0210 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/ExpressionSyntaxExtensions.cs @@ -97,20 +97,5 @@ public static bool TryGetExpressionComparedToNull(this ExpressionSyntax expressi return false; } - - /// - /// Returns the expression, representing the left side of the dot. This is useful for finding the expression of an invoked expression.
- /// For the expression of the invocation M() in the expression this.A.B.M() the member access this.A.B is returned and
- /// for this.A?.B?.M() the member binding .B is returned. - ///
- /// The expression to start the search of. Should be an MemberAccess or a MemberBinding. - /// The expression left of the dot or question mark dot. - public static ExpressionSyntax GetLeftOfDot(this ExpressionSyntax expression) => - expression switch - { - MemberAccessExpressionSyntax memberAccessExpression => memberAccessExpression.Expression, - MemberBindingExpressionSyntax memberBindingExpression => memberBindingExpression.GetParentConditionalAccessExpression()?.Expression, - _ => null, - }; } } diff --git a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs index f262ca1d14e..1bbff8b46c3 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Extensions/SyntaxNodeExtensions.cs @@ -31,21 +31,18 @@ public static partial class SyntaxNodeExtensions SyntaxKind.AddAccessorDeclaration, SyntaxKind.AnonymousMethodExpression, SyntaxKind.BaseConstructorInitializer, - SyntaxKind.CompilationUnit, SyntaxKind.ConstructorDeclaration, SyntaxKind.ConversionOperatorDeclaration, SyntaxKind.DestructorDeclaration, - SyntaxKind.EnumMemberDeclaration, - SyntaxKind.FieldDeclaration, + SyntaxKind.EqualsValueClause, SyntaxKind.GetAccessorDeclaration, + SyntaxKind.GlobalStatement, SyntaxKindEx.InitAccessorDeclaration, SyntaxKindEx.LocalFunctionStatement, SyntaxKind.MethodDeclaration, SyntaxKind.OperatorDeclaration, - SyntaxKind.Parameter, SyntaxKind.ParenthesizedLambdaExpression, SyntaxKindEx.PrimaryConstructorBaseType, - SyntaxKind.PropertyDeclaration, SyntaxKind.RemoveAccessorDeclaration, SyntaxKind.SetAccessorDeclaration, SyntaxKind.SimpleLambdaExpression, diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAwaitableMethod.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAwaitableMethod.cs deleted file mode 100644 index 4a8c48dd9cb..00000000000 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/UseAwaitableMethod.cs +++ /dev/null @@ -1,180 +0,0 @@ -/* - * 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 Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Shared.Extensions; -using WellKnownExtensionMethodContainer = SonarAnalyzer.Common.MultiValueDictionary; -namespace SonarAnalyzer.Rules.CSharp; - -[DiagnosticAnalyzer(LanguageNames.CSharp)] -public sealed class UseAwaitableMethod : SonarDiagnosticAnalyzer -{ - private const string DiagnosticId = "S6966"; - private const string MessageFormat = "Await {0} instead."; - - private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat); - - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); - - protected override void Initialize(SonarAnalysisContext context) => - context.RegisterCompilationStartAction(compilationStart => - { - // Not every async method is defined in the same class/interface as its non-async counterpart. - // For example the EntityFrameworkQueryableExtensions.AnyAsync() method provides an async version of the Enumerable.Any() method for IQueryable types. - // WellKnownExtensionMethodContainer stores where to look for the async versions of certain methods from a type, e.g. async versions of methods from - // System.Linq.Enumerable can be found in Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions. - var wellKnownExtensionMethodContainer = BuildWellKnownExtensionMethodContainers(compilationStart.Compilation); - context.RegisterCodeBlockStartAction(CSharpGeneratedCodeRecognizer.Instance, codeBlockStart => - { - if (IsAsyncCodeBlock(codeBlockStart.CodeBlock)) - { - codeBlockStart.RegisterNodeAction(nodeContext => - { - var invocationExpression = (InvocationExpressionSyntax)nodeContext.Node; - - var awaitableAlternatives = FindAwaitableAlternatives(wellKnownExtensionMethodContainer, codeBlockStart.CodeBlock, invocationExpression, - nodeContext.SemanticModel, nodeContext.ContainingSymbol, nodeContext.Cancel); - if (awaitableAlternatives.FirstOrDefault() is { Name: { } alternative }) - { - nodeContext.ReportIssue(Rule, invocationExpression, alternative); - } - }, SyntaxKind.InvocationExpression); - } - }); - }); - - private static WellKnownExtensionMethodContainer BuildWellKnownExtensionMethodContainers(Compilation compilation) - { - var wellKnownExtensionMethodContainer = new WellKnownExtensionMethodContainer(); - var queryable = compilation.GetTypeByMetadataName(KnownType.System_Linq_Queryable); - var enumerable = compilation.GetTypeByMetadataName(KnownType.System_Linq_Enumerable); - if (queryable is not null && enumerable is not null) - { - if (compilation.GetTypeByMetadataName(KnownType.Microsoft_EntityFrameworkCore_EntityFrameworkQueryableExtensions) is { } entityFrameworkQueryableExtensions) - { - wellKnownExtensionMethodContainer.Add(queryable, entityFrameworkQueryableExtensions); - wellKnownExtensionMethodContainer.Add(enumerable, entityFrameworkQueryableExtensions); - } - if (compilation.GetTypeByMetadataName(KnownType.Microsoft_EntityFrameworkCore_RelationalQueryableExtensions) is { } relationalQueryableExtensions) - { - wellKnownExtensionMethodContainer.Add(queryable, relationalQueryableExtensions); - wellKnownExtensionMethodContainer.Add(enumerable, relationalQueryableExtensions); - } - } - if (compilation.GetTypeByMetadataName(KnownType.System_Net_Sockets_Socket) is { } socket - && compilation.GetTypeByMetadataName(KnownType.System_Net_Sockets_SocketTaskExtensions) is { } socketTaskExtensions) - { - wellKnownExtensionMethodContainer.Add(socket, socketTaskExtensions); - } - return wellKnownExtensionMethodContainer; - } - - private static ImmutableArray FindAwaitableAlternatives(WellKnownExtensionMethodContainer wellKnownExtensionMethodContainer, SyntaxNode codeBlock, - InvocationExpressionSyntax invocationExpression, SemanticModel semanticModel, ISymbol containingSymbol, CancellationToken cancel) - { - var awaitableRoot = GetAwaitableRootOfInvocation(invocationExpression); - if (awaitableRoot is { Parent: AwaitExpressionSyntax }) - { - return ImmutableArray.Empty; // Invocation result is already awaited. - } - if (invocationExpression.EnclosingScope() is { } scope && !IsAsyncCodeBlock(scope)) - { - return ImmutableArray.Empty; // Not in an async scope - } - if (semanticModel.GetSymbolInfo(invocationExpression, cancel).Symbol is IMethodSymbol methodSymbol - && !methodSymbol.IsAwaitableNonDynamic(semanticModel, invocationExpression.SpanStart)) // The invoked method returns something awaitable (but it isn't awaited). - { - // Perf: Before doing (expensive) speculative re-binding in SpeculativeBindCandidates, we check if there is an "..Async()" alternative in scope. - var invokedType = invocationExpression.Expression.GetLeftOfDot() is { } expression && semanticModel.GetTypeInfo(expression) is { Type: { } type } - ? type // A dotted expression: Lookup the type, left of the dot (this may be different from methodSymbol.ContainingType) - : containingSymbol.ContainingType; // If not dotted, than the scope is the current type. Local function support is missing here. - var members = GetMethodSymbolsInScope($"{methodSymbol.Name}Async", wellKnownExtensionMethodContainer, invokedType, methodSymbol.ContainingType); - var awaitableCandidates = members.Where(x => x.IsAwaitableNonDynamic(semanticModel, invocationExpression.SpanStart)); - var awaitableAlternatives = SpeculativeBindCandidates(semanticModel, codeBlock, awaitableRoot, invocationExpression, awaitableCandidates).ToImmutableArray(); - return awaitableAlternatives; - } - return ImmutableArray.Empty; - } - - private static IEnumerable GetMethodSymbolsInScope(string methodName, WellKnownExtensionMethodContainer wellKnownExtensionMethodContainer, - ITypeSymbol invokedType, ITypeSymbol methodContainer) => - ((ITypeSymbol[])[.. invokedType.GetSelfAndBaseTypes(), .. WellKnownExtensionMethodContainer(wellKnownExtensionMethodContainer, methodContainer), methodContainer]) - .Distinct() - .SelectMany(x => x.GetMembers(methodName)) - .OfType(); - - private static IEnumerable WellKnownExtensionMethodContainer(WellKnownExtensionMethodContainer lookup, ITypeSymbol invokedType) => - lookup.TryGetValue(invokedType, out var extensionMethodContainer) - ? extensionMethodContainer - : []; - - private static IEnumerable SpeculativeBindCandidates(SemanticModel semanticModel, SyntaxNode codeBlock, SyntaxNode awaitableRoot, - InvocationExpressionSyntax invocationExpression, IEnumerable awaitableCandidates) => - awaitableCandidates.Where(x => SpeculativeBindCandidate(semanticModel, x, codeBlock, awaitableRoot, invocationExpression)); - - private static bool SpeculativeBindCandidate(SemanticModel semanticModel, IMethodSymbol candidate, SyntaxNode codeBlock, SyntaxNode awaitableRoot, InvocationExpressionSyntax invocationExpression) - { - var root = codeBlock.SyntaxTree.GetRoot(); - var invocationIdentifierName = invocationExpression.GetMethodCallIdentifier()?.Parent as IdentifierNameSyntax; - - var invocationAnnotation = new SyntaxAnnotation(); - var replace = root.ReplaceNodes([awaitableRoot, invocationIdentifierName, invocationExpression], (original, newNode) => - { - var result = newNode; - if (original == invocationIdentifierName) - { - result = SyntaxFactory.IdentifierName(candidate.Name).WithTriviaFrom(invocationIdentifierName); - } - if (original == invocationExpression) - { - result = result.WithAdditionalAnnotations(invocationAnnotation); - } - if (original == awaitableRoot && result is ExpressionSyntax resultExpression) - { - result = SyntaxFactory.AwaitExpression(resultExpression); - } - return result; - }); - var invocationReplaced = replace.GetAnnotatedNodes(invocationAnnotation).First(); - var speculativeSymbolInfo = semanticModel.GetSpeculativeSymbolInfo(invocationReplaced.SpanStart, invocationReplaced, SpeculativeBindingOption.BindAsExpression); - var speculativeSymbol = speculativeSymbolInfo.Symbol as IMethodSymbol; - return candidate.Equals(speculativeSymbol) || candidate.Equals(speculativeSymbol?.ReducedFrom); - } - - private static ExpressionSyntax GetAwaitableRootOfInvocation(ExpressionSyntax expression) => - expression switch - { - { Parent: ConditionalAccessExpressionSyntax conditional } => conditional.GetRootConditionalAccessExpression(), - { Parent: MemberAccessExpressionSyntax memberAccess } => memberAccess.GetRootConditionalAccessExpression() ?? GetAwaitableRootOfInvocation(memberAccess), - { Parent: PostfixUnaryExpressionSyntax { RawKind: (int)SyntaxKindEx.SuppressNullableWarningExpression } parent } => GetAwaitableRootOfInvocation(parent), - { Parent: ParenthesizedExpressionSyntax parent } => GetAwaitableRootOfInvocation(parent), - { } self => self, - }; - - private static bool IsAsyncCodeBlock(SyntaxNode codeBlock) => - codeBlock switch - { - CompilationUnitSyntax => true, - BaseMethodDeclarationSyntax { Modifiers: { } modifiers } => modifiers.AnyOfKind(SyntaxKind.AsyncKeyword), - AnonymousFunctionExpressionSyntax { AsyncKeyword: { } asyncKeyword } => asyncKeyword.IsKind(SyntaxKind.AsyncKeyword), - var localFunction when LocalFunctionStatementSyntaxWrapper.IsInstance(localFunction) => ((LocalFunctionStatementSyntaxWrapper)localFunction).Modifiers.AnyOfKind(SyntaxKind.AsyncKeyword), - _ => false, - }; -} diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/ISymbolExtensions.Roslyn.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/ISymbolExtensions.Roslyn.cs deleted file mode 100644 index b49f9001cd8..00000000000 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/ISymbolExtensions.Roslyn.cs +++ /dev/null @@ -1,83 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. -// See the LICENSE file in the project root for more information. - -#nullable enable - -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.CodeAnalysis.Shared.Extensions; - -// Copied from https://github.com/dotnet/roslyn/blob/ca66296efa86bd8078508fe7b38b91b415364f78/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs -[ExcludeFromCodeCoverage] -public static class ISymbolExtensions -{ - /// - /// If the is a method symbol, returns if the method's return type is "awaitable", but not if it's . - /// If the is a type symbol, returns if that type is "awaitable". - /// An "awaitable" is any type that exposes a GetAwaiter method which returns a valid "awaiter". This GetAwaiter method may be an instance method or an extension method. - /// - /// - /// Copied from . - /// - public static bool IsAwaitableNonDynamic(this ISymbol? symbol, SemanticModel semanticModel, int position) - { - var methodSymbol = symbol as IMethodSymbol; - ITypeSymbol? typeSymbol = null; - - if (methodSymbol == null) - { - typeSymbol = symbol as ITypeSymbol; - if (typeSymbol == null) - { - return false; - } - } - else - { - if (methodSymbol.ReturnType == null) - { - return false; - } - } - - // otherwise: needs valid GetAwaiter - var potentialGetAwaiters = semanticModel.LookupSymbols(position, - container: typeSymbol ?? methodSymbol!.ReturnType.OriginalDefinition, - name: WellKnownMemberNames.GetAwaiter, - includeReducedExtensionMethods: true); - var getAwaiters = potentialGetAwaiters.OfType().Where(x => !x.Parameters.Any()); - return getAwaiters.Any(VerifyGetAwaiter); - } - - // Copied from https://github.com/dotnet/roslyn/blob/ca66296efa86bd8078508fe7b38b91b415364f78/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs#L611 - private static bool VerifyGetAwaiter(IMethodSymbol getAwaiter) - { - var returnType = getAwaiter.ReturnType; - if (returnType == null) - { - return false; - } - - // bool IsCompleted { get } - if (!returnType.GetMembers().OfType().Any(p => p.Name == WellKnownMemberNames.IsCompleted && p.Type.SpecialType == SpecialType.System_Boolean && p.GetMethod != null)) - { - return false; - } - - var methods = returnType.GetMembers().OfType(); - - // NOTE: (vladres) The current version of C# Spec, §7.7.7.3 'Runtime evaluation of await expressions', requires that - // NOTE: the interface method INotifyCompletion.OnCompleted or ICriticalNotifyCompletion.UnsafeOnCompleted is invoked - // NOTE: (rather than any OnCompleted method conforming to a certain pattern). - // NOTE: Should this code be updated to match the spec? - - // void OnCompleted(Action) - // Actions are delegates, so we'll just check for delegates. - if (!methods.Any(x => x.Name == WellKnownMemberNames.OnCompleted && x.ReturnsVoid && x.Parameters is { Length: 1 } parameter && parameter[0] is { Type.TypeKind: TypeKind.Delegate })) - return false; - - // void GetResult() || T GetResult() - return methods.Any(m => m.Name == WellKnownMemberNames.GetResult && !m.Parameters.Any()); - } -} diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs index 6ec98cf264e..80ee19193ec 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownType.cs @@ -106,7 +106,6 @@ public sealed partial class KnownType public static readonly KnownType Microsoft_EntityFrameworkCore_DbContext = new("Microsoft.EntityFrameworkCore.DbContext"); public static readonly KnownType Microsoft_EntityFrameworkCore_DbContextOptionsBuilder = new("Microsoft.EntityFrameworkCore.DbContextOptionsBuilder"); public static readonly KnownType Microsoft_EntityFrameworkCore_DbSet_TEntity = new("Microsoft.EntityFrameworkCore.DbSet", "TEntity"); - public static readonly KnownType Microsoft_EntityFrameworkCore_EntityFrameworkQueryableExtensions = new("Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_Migrations_Migration = new("Microsoft.EntityFrameworkCore.Migrations.Migration"); public static readonly KnownType Microsoft_EntityFrameworkCore_MySQLDbContextOptionsExtensions = new("Microsoft.EntityFrameworkCore.MySQLDbContextOptionsExtensions"); public static readonly KnownType Microsoft_EntityFrameworkCore_NpgsqlDbContextOptionsExtensions = new("Microsoft.EntityFrameworkCore.NpgsqlDbContextOptionsExtensions"); @@ -392,7 +391,6 @@ public sealed partial class KnownType public static readonly KnownType System_Net_Security_SslPolicyErrors = new("System.Net.Security.SslPolicyErrors"); public static readonly KnownType System_Net_SecurityProtocolType = new("System.Net.SecurityProtocolType"); public static readonly KnownType System_Net_Sockets_Socket = new("System.Net.Sockets.Socket"); - public static readonly KnownType System_Net_Sockets_SocketTaskExtensions = new("System.Net.Sockets.SocketTaskExtensions"); public static readonly KnownType System_Net_Sockets_TcpClient = new("System.Net.Sockets.TcpClient"); public static readonly KnownType System_Net_Sockets_UdpClient = new("System.Net.Sockets.UdpClient"); public static readonly KnownType System_Net_WebClient = new("System.Net.WebClient"); diff --git a/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs b/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs index 6e11c9860d8..27f024af618 100644 --- a/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs +++ b/analyzers/tests/SonarAnalyzer.Test/Extensions/SyntaxNodeExtensionsTest.cs @@ -690,7 +690,7 @@ public X M() } } """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.CSharp); + var node = NodeBetweenMarkers(code, LanguageNames.CSharp); var parentConditional = ExtensionsCS.GetParentConditionalAccessExpression(node); parentConditional.ToString().Should().Be(parent); parentConditional.Expression.ToString().Should().Be(parentExpression); @@ -726,7 +726,7 @@ Return Nothing End Function End Class """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.VisualBasic); + var node = NodeBetweenMarkers(code, LanguageNames.VisualBasic); var parentConditional = ExtensionsVB.GetParentConditionalAccessExpression(node); parentConditional.ToString().Should().Be(parent); parentConditional.Expression.ToString().Should().Be(parentExpression); @@ -758,7 +758,7 @@ public X M() }} }} "; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.CSharp); + var node = NodeBetweenMarkers(code, LanguageNames.CSharp); var parentConditional = ExtensionsCS.GetRootConditionalAccessExpression(node); parentConditional.ToString().Should().Be(expression.Replace("$$", string.Empty)); } @@ -789,7 +789,7 @@ public void M(int p) { } } """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.CSharp); + var node = NodeBetweenMarkers(code, LanguageNames.CSharp); var argumentList = ExtensionsCS.ArgumentList(node).Arguments; var argument = argumentList.Should().ContainSingle().Which; (argument is { Expression: SyntaxCS.LiteralExpressionSyntax { Token.ValueText: "1" } }).Should().BeTrue(); @@ -810,7 +810,7 @@ public C(int p): base(p) { } } """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.CSharp); + var node = NodeBetweenMarkers(code, LanguageNames.CSharp); var argumentList = ExtensionsCS.ArgumentList(node).Arguments; var argument = argumentList.Should().ContainSingle().Which; (argument is { Expression: SyntaxCS.LiteralExpressionSyntax { Token.ValueText: "1" } }).Should().BeTrue(); @@ -823,7 +823,7 @@ public void ArgumentList_CS_PrimaryConstructorBaseType() public class Base(int p); public class Derived(int p): $$Base(1)$$; """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.CSharp); + var node = NodeBetweenMarkers(code, LanguageNames.CSharp); var argumentList = ExtensionsCS.ArgumentList(node).Arguments; var argument = argumentList.Should().ContainSingle().Which; (argument is { Expression: SyntaxCS.LiteralExpressionSyntax { Token.ValueText: "1" } }).Should().BeTrue(); @@ -840,7 +840,7 @@ public void M() { } } """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.CSharp); + var node = NodeBetweenMarkers(code, LanguageNames.CSharp); ExtensionsCS.ArgumentList(node).Should().BeNull(); } @@ -860,7 +860,7 @@ public void M() { } } """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.CSharp); + var node = NodeBetweenMarkers(code, LanguageNames.CSharp); var sut = () => ExtensionsCS.ArgumentList(node); sut.Should().Throw(); } @@ -887,7 +887,7 @@ Public Sub M(p As Integer) End Sub End Class """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.VisualBasic, getInnermostNodeForTie: true); + var node = NodeBetweenMarkers(code, LanguageNames.VisualBasic, getInnermostNodeForTie: true); var argumentList = ExtensionsVB.ArgumentList(node); var argument = argumentList.Arguments.Should().ContainSingle().Which; (argument.GetExpression() is SyntaxVB.LiteralExpressionSyntax { Token.ValueText: "1" }).Should().BeTrue(); @@ -904,7 +904,7 @@ Public Sub M() End Sub End Class """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.VisualBasic, getInnermostNodeForTie: true); + var node = NodeBetweenMarkers(code, LanguageNames.VisualBasic, getInnermostNodeForTie: true); var argumentList = ExtensionsVB.ArgumentList(node); argumentList.Arguments.Should().SatisfyRespectively( a => (a.GetExpression() is SyntaxVB.IdentifierNameSyntax { Identifier.ValueText: "s" }).Should().BeTrue(), @@ -919,7 +919,7 @@ public void ArgumentList_VB_Attribute() Public Class C End Class """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.VisualBasic, getInnermostNodeForTie: true); + var node = NodeBetweenMarkers(code, LanguageNames.VisualBasic, getInnermostNodeForTie: true); var argumentList = ExtensionsVB.ArgumentList(node); var argument = argumentList.Arguments.Should().ContainSingle().Which; (argument.GetExpression() is SyntaxVB.LiteralExpressionSyntax { Token.ValueText: "1" }).Should().BeTrue(); @@ -939,7 +939,7 @@ Dim arr(0) As Integer End Sub End Class """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.VisualBasic, getInnermostNodeForTie: true); + var node = NodeBetweenMarkers(code, LanguageNames.VisualBasic, getInnermostNodeForTie: true); var argumentList = ExtensionsVB.ArgumentList(node); var argument = argumentList.Arguments.Should().ContainSingle().Which; (argument.GetExpression() is SyntaxVB.LiteralExpressionSyntax { Token.ValueText: "1" }).Should().BeTrue(); @@ -955,7 +955,7 @@ Public Sub M() End Sub End Class """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.VisualBasic, getInnermostNodeForTie: false); + var node = NodeBetweenMarkers(code, LanguageNames.VisualBasic, getInnermostNodeForTie: false); ExtensionsVB.ArgumentList(node).Should().BeNull(); } @@ -974,7 +974,7 @@ Public Sub M() End Sub End Class """; - var node = NodeBetweenMarkers(code, AnalyzerLanguage.VisualBasic, getInnermostNodeForTie: true); + var node = NodeBetweenMarkers(code, LanguageNames.VisualBasic, getInnermostNodeForTie: true); var sut = () => ExtensionsVB.ArgumentList(node); sut.Should().Throw(); } @@ -992,7 +992,7 @@ public class C { $${{declarations}}$$ } - """, AnalyzerLanguage.CSharp); + """, LanguageNames.CSharp); var actual = ExtensionsCS.ParameterList(node); actual.Should().NotBeNull(); var entry = actual.Parameters.Should().ContainSingle().Which; @@ -1016,7 +1016,7 @@ public void M() {{declarations}} } } - """, AnalyzerLanguage.CSharp); + """, LanguageNames.CSharp); var actual = ExtensionsCS.ParameterList(node); actual.Should().NotBeNull(); var entry = actual.Parameters.Should().ContainSingle().Which; @@ -1031,7 +1031,7 @@ public class C { $$~C() { }$$ } - """, AnalyzerLanguage.CSharp); + """, LanguageNames.CSharp); var actual = ExtensionsCS.ParameterList(node); actual.Should().NotBeNull(); actual.Parameters.Should().BeEmpty(); @@ -1054,7 +1054,7 @@ public void ParameterList_PrimaryConstructors(string type) { }$$ - """, AnalyzerLanguage.CSharp); + """, LanguageNames.CSharp); var actual = ExtensionsCS.ParameterList(node); actual.Should().NotBeNull(); var entry = actual.Parameters.Should().ContainSingle().Which; @@ -1072,7 +1072,7 @@ public class C { {{declaration}} } - """, AnalyzerLanguage.CSharp); + """, LanguageNames.CSharp); var actual = ExtensionsCS.ParameterList(node); actual.Should().BeNull(); } @@ -1141,7 +1141,7 @@ unsafe class Test public Test(int i) { } {{member}} } - """, AnalyzerLanguage.CSharp); + """, LanguageNames.CSharp); var actual = ExtensionsCS.GetIdentifier(node); if (expected is null) { @@ -1163,7 +1163,7 @@ public void GetIdentifier_CompilationUnit(string member, string expected) { var node = NodeBetweenMarkers($$""" {{member}} - """, AnalyzerLanguage.CSharp); + """, LanguageNames.CSharp); var actual = ExtensionsCS.GetIdentifier(node); if (expected is null) { @@ -1187,7 +1187,7 @@ public class Base(int i) { } public class Derived(int i) {{baseType}} { } - """, AnalyzerLanguage.CSharp); + """, LanguageNames.CSharp); var actual = ExtensionsCS.GetIdentifier(node); if (expected is null) { @@ -1207,8 +1207,8 @@ public class Derived(int i) {{baseType}} { } [DataRow("""Derived() { $$var x = 42;$$ }""", SyntaxKind.ConstructorDeclaration)] [DataRow("""public static implicit operator int(Derived d) => $$42$$;""", SyntaxKind.ConversionOperatorDeclaration)] [DataRow("""~Derived() { $$var x = 42;$$ }""", SyntaxKind.DestructorDeclaration)] - [DataRow("""int field = $$int.Parse("42")$$;""", SyntaxKind.FieldDeclaration)] - [DataRow("""int Property { get; set; } = $$int.Parse("42")$$;""", SyntaxKind.PropertyDeclaration)] + [DataRow("""int field = $$int.Parse("42")$$;""", SyntaxKind.EqualsValueClause)] + [DataRow("""int Property { get; set; } = $$int.Parse("42")$$;""", SyntaxKind.EqualsValueClause)] [DataRow("""int Property { set { $$_ = value;$$ } }""", SyntaxKind.SetAccessorDeclaration)] [DataRow("""int Property { set { $$_ = value;$$ } }""", SyntaxKind.SetAccessorDeclaration)] [DataRow("""int Method() { return LocalFunction(); int LocalFunction() { $$return 42;$$ } }""", SyntaxKindEx.LocalFunctionStatement)] @@ -1220,8 +1220,6 @@ public class Derived(int i) {{baseType}} { } [DataRow("""int Method() { Func lambda = x => $$x + 1$$; return lambda(42); }""", SyntaxKind.SimpleLambdaExpression)] [DataRow("""event EventHandler SomeEvent { add { int x = 42; } remove { $$int x = 42;$$ } }""", SyntaxKind.RemoveAccessorDeclaration)] [DataRow("""Derived(int arg) : this($$arg.ToString()$$) { }""", SyntaxKind.ThisConstructorInitializer)] - [DataRow("""enum E { A = $$1$$ }""", SyntaxKind.EnumMemberDeclaration)] - [DataRow("""void M(int i = $$1$$) { }""", SyntaxKind.Parameter)] #if NET [DataRow("""int Property { init { $$_ = value;$$ } }""", SyntaxKindEx.InitAccessorDeclaration)] [DataRow("""record BaseRec(int I); record DerivedRec(int I): BaseRec($$I++$$);""", SyntaxKindEx.PrimaryConstructorBaseType)] @@ -1242,33 +1240,22 @@ public class Derived: Base Derived(string arg) { } {{member}} } - """, AnalyzerLanguage.CSharp); + """, LanguageNames.CSharp); var actual = ExtensionsCS.EnclosingScope(node)?.Kind() ?? SyntaxKind.None; actual.Should().Be(expectedSyntaxKind); } - [TestMethod] - public void EnclosingScope_TopLevelStatements() - { - var node = NodeBetweenMarkers($$""" - using System; - - $$Console.WriteLine("")$$; - - """, AnalyzerLanguage.CSharp, outputKind: OutputKind.ConsoleApplication); - var actual = ExtensionsCS.EnclosingScope(node)?.Kind() ?? SyntaxKind.None; - actual.Should().Be(SyntaxKind.CompilationUnit); - } - - private static SyntaxNode NodeBetweenMarkers(string code, AnalyzerLanguage language, bool getInnermostNodeForTie = false, OutputKind outputKind = OutputKind.DynamicallyLinkedLibrary) + private static SyntaxNode NodeBetweenMarkers(string code, string language, bool getInnermostNodeForTie = false) { var position = code.IndexOf("$$"); var lastPosition = code.LastIndexOf("$$"); var length = lastPosition == position ? 0 : lastPosition - position - "$$".Length; code = code.Replace("$$", string.Empty); - var (tree, _) = TestHelper.Compile(code, ignoreErrors: false, language, outputKind: outputKind); + var (tree, _) = IsCSharp() ? TestHelper.CompileCS(code) : TestHelper.CompileVB(code); var node = tree.GetRoot().FindNode(new TextSpan(position, length), getInnermostNodeForTie: getInnermostNodeForTie); return node; + + bool IsCSharp() => language == LanguageNames.CSharp; } private static SyntaxToken GetFirstTokenOfKind(SyntaxTree syntaxTree, SyntaxKind kind) => diff --git a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs index f2615a021ff..d70ecf1e58e 100644 --- a/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs +++ b/analyzers/tests/SonarAnalyzer.Test/PackagingTests/RuleTypeMappingCS.cs @@ -6890,7 +6890,7 @@ internal static class RuleTypeMappingCS // ["S6963"], // ["S6964"], // ["S6965"], - ["S6966"] = "CODE_SMELL", + // ["S6966"], // ["S6967"], // ["S6968"], // ["S6969"], diff --git a/analyzers/tests/SonarAnalyzer.Test/Rules/UseAwaitableMethodTest.cs b/analyzers/tests/SonarAnalyzer.Test/Rules/UseAwaitableMethodTest.cs deleted file mode 100644 index e041152b9ba..00000000000 --- a/analyzers/tests/SonarAnalyzer.Test/Rules/UseAwaitableMethodTest.cs +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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 SonarAnalyzer.Rules.CSharp; - -namespace SonarAnalyzer.Test.Rules; - -[TestClass] -public class UseAwaitableMethodTest -{ - private const string EntityFrameworkVersion = "7.0.18"; - - private readonly VerifierBuilder builder = new VerifierBuilder(); - - [TestMethod] - public void UseAwaitableMethod_CS() => - builder.AddPaths("UseAwaitableMethod.cs").Verify(); - - [TestMethod] - public void UseAwaitableMethod_CS_Test() => - builder - .WithOptions(ParseOptionsHelper.FromCSharp11) - .AddReferences(MetadataReferenceFacade.SystemNetPrimitives) - .AddReferences(MetadataReferenceFacade.SystemNetSockets) - .AddSnippet(""" - using System; - using System.Text; - using System.IO; - using System.Net; - using System.Net.Sockets; - using System.Threading.Tasks; - using System.Collections.Generic; - - public class Sockets - { - async Task CreateActionAsync(StreamReader reader) - { - Action action = () => - { - reader.ReadLine(); // Compliant - }; - return action; - } - } - """).Verify(); - - [TestMethod] - public void UseAwaitableMethod_Sockets() => - builder - .AddReferences(MetadataReferenceFacade.SystemNetPrimitives) - .AddReferences(MetadataReferenceFacade.SystemNetSockets) - .AddPaths("UseAwaitableMethod_Sockets.cs") - .Verify(); - -#if NET - [TestMethod] - public void UseAwaitableMethod_CSharp9() => - builder - .WithTopLevelStatements() - .AddPaths("UseAwaitableMethod_CSharp9.cs") - .Verify(); - - [TestMethod] - public void UseAwaitableMethod_CSharp8() => - builder - .WithOptions(ParseOptionsHelper.FromCSharp8) - .AddPaths("UseAwaitableMethod_CSharp8.cs") - .Verify(); - - [TestMethod] - public void UseAwaitableMethod_EF() => - builder - .WithOptions(ParseOptionsHelper.FromCSharp11) - .AddReferences([CoreMetadataReference.SystemComponentModelTypeConverter]) - .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCore(EntityFrameworkVersion)) - .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCoreRelational(EntityFrameworkVersion)) - .AddReferences(NuGetMetadataReference.MicrosoftEntityFrameworkCoreSqlServer(EntityFrameworkVersion)) - .AddPaths("UseAwaitableMethod_EF.cs") - .Verify(); -#endif -} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod.cs deleted file mode 100644 index 33209cc0b19..00000000000 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod.cs +++ /dev/null @@ -1,193 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using System; -using System.Threading; - -public class C -{ - public C Child { get; } - void VoidMethod() { } - Task VoidMethodAsync() => Task.CompletedTask; - - C ReturnMethod() => null; - Task ReturnMethodAsync() => Task.FromResult(null); - - bool BoolMethod() => true; - Task BoolMethodAsync() => Task.FromResult(true); - - C this[int i] => null; - public static C operator +(C c) => default(C); - public static C operator +(C c1, C c2) => default(C); - public static C operator -(C c) => default(C); - public static C operator -(C c1, C c2) => default(C); - public static C operator !(C c) => default(C); - public static C operator ~(C c) => default(C); - public static implicit operator int(C c) => default(C); - - async Task MethodInvocations() - { - VoidMethod(); // Noncompliant {{Await VoidMethodAsync instead.}} - await VoidMethodAsync(); // Compliant - VoidMethodAsync(); // Compliant: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs4014 - this.VoidMethod(); // Noncompliant - this.Child?.VoidMethod(); // Noncompliant - this.Child.Child?.VoidMethod(); // Noncompliant - - ReturnMethod(); // Noncompliant - _ = ReturnMethod(); // Noncompliant - this.ReturnMethod().ReturnMethod().ReturnMethod(); -// ^^^^^^^^^^^^^^^^^^^ -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @-1 -// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @-2 - _ = true ? ReturnMethod() : ReturnMethod(); - // ^^^^^^^^^^^^^^ - // ^^^^^^^^^^^^^^ @-1 - await ReturnMethod().ReturnMethodAsync(); // Noncompliant - // ^^^^^^^^^^^^^^ - } - - public void NonAsyncMethod_VoidReturning() - { - VoidMethod(); // Compliant - } - - public Task NonAsyncMethod_TaskReturning() - { - VoidMethod(); // Compliant: Enclosing Method must be async - return Task.CompletedTask; - } - - async Task OperatorPrecedence() // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/#operator-precedence - { - _ = new C().ReturnMethod(); // Noncompliant - this[0].VoidMethod(); // Noncompliant - Child[0].VoidMethod(); // Noncompliant - Child.Child[0]?.VoidMethod(); // Noncompliant - Child?.Child[0].VoidMethod(); // Noncompliant - Child?.Child?[0].VoidMethod(); // Noncompliant - Child.Child?[0].VoidMethod(); // Noncompliant - Child?.Child?[0]?.VoidMethod(); // Noncompliant - _ = Child?.Child?[0]?.ReturnMethod()?.Child[0]; // Noncompliant - _ = (ReturnMethod()); // Noncompliant - _ = nameof(VoidMethod); // Compliant - _ = !BoolMethod(); // Noncompliant - _ = BoolMethod() ? ReturnMethod() : default(C); - // ^^^^^^^^^^^^ - // ^^^^^^^^^^^^^^ @-1 - _ = +ReturnMethod(); // Noncompliant - _ = -ReturnMethod(); // Noncompliant - _ = !ReturnMethod(); // Noncompliant - _ = ~ReturnMethod(); // Noncompliant - _ = ReturnMethod() + default(C); // Noncompliant - _ = ReturnMethod() - default(C); // Noncompliant - _ = ReturnMethod() - !ReturnMethod(); - // ^^^^^^^^^^^^^^ - // ^^^^^^^^^^^^^^ @-1 - } - - async Task ExtensionMethods() - { - this.ExtVoidMethod(); // Noncompliant - } -} - -public static class Extensions -{ - public static void ExtVoidMethod(this C c) { } - public static Task ExtVoidMethodAsync(this C c) => Task.CompletedTask; -} - -public class Overloads -{ - public long ImplicitConversionsMethod(int i, IComparable j) => 0; - public Task ImplicitConversionsMethodAsync(long otherName1, int otherName2) => Task.FromResult(0); - public Task ImplicitConversionsMethodAsync(byte otherName1, byte otherName2) => Task.FromResult((byte)0); - - public void TypeParameter(C c) { } - public Task TypeParameter(T t) where T : C => Task.CompletedTask; - - public async Task Test(int i, byte j) - { - long l1 = ImplicitConversionsMethod(i, j); // Noncompliant Can be resolved to first overload - long l2 = ImplicitConversionsMethod(i, (IComparable)j); // Compliant No fitting overload - var l3 = ImplicitConversionsMethod((byte)i, j); // Noncompliant Can be resolved to second overload - int l4 = (int)ImplicitConversionsMethod((byte)i, j); // Noncompliant Can be resolved to second overload - - TypeParameter(new C()); // Compliant: Adding "await" does never resolve to another overload - } -} - -public class Inheritance -{ - class Child : Inheritance - { - public void OuterVoidMethod() { } - public void InnerVoidMethod() { } - } - - class GrandChild : Child - { - public GrandChild Chain { get; } - - public async Task Test() - { - OuterVoidMethod(); // Noncompliant - this.OuterVoidMethod(); // Noncompliant - (this as Child).OuterVoidMethod(); // Noncompliant - this.Chain?.OuterVoidMethod(); // Noncompliant - this.Chain?.Chain?.OuterVoidMethod(); // Noncompliant - InnerVoidMethod(); // Noncompliant - this.InnerVoidMethod(); // Noncompliant - (this as Child).InnerVoidMethod(); // Compliant: InnerVoidMethodAsync is defined on GrandChild - this.Chain?.InnerVoidMethod(); // Noncompliant - this.Chain?.Chain?.InnerVoidMethod(); // Noncompliant - } - - public Task InnerVoidMethodAsync() => Task.CompletedTask; - } - - public Task OuterVoidMethodAsync() => Task.CompletedTask; - - async Task Test() - { - var grandChild = new GrandChild(); - grandChild.OuterVoidMethod(); // Noncompliant - grandChild?.OuterVoidMethod(); // Noncompliant - grandChild?.Chain?.OuterVoidMethod(); // Noncompliant - grandChild.InnerVoidMethod(); // Noncompliant - grandChild?.InnerVoidMethod(); // Noncompliant - grandChild?.Chain?.InnerVoidMethod(); // Noncompliant - } -} - -class AsynchronousLambdas -{ - async Task CallAsyncLambda(StreamReader reader) - { - await Task.Run(async () => { - await Foo(); - reader.ReadLine(); // Noncompliant - }); - Func a = async () => - { - await Foo(); - reader.ReadLine(); // Noncompliant - }; - Func b = async delegate () - { - await Foo(); - reader.ReadLine(); // Noncompliant - }; - } - - async Task CreateActionAsync(StreamReader reader) - { - Action action = () => - { - reader.ReadLine(); // Compliant - }; - return action; - } - - Task Foo() => null; -} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_CSharp8.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_CSharp8.cs deleted file mode 100644 index 7ffb9194861..00000000000 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_CSharp8.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using System; - -public class C -{ - public C Child { get; } - C this[int i] => null; - public static implicit operator int(C c) => default(C); - - C ReturnMethod() => null; - Task ReturnMethodAsync() => null; - - async Task OperatorPrecedence() // https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/#operator-precedence - { - _ = ^ReturnMethod(); // Noncompliant - _ = Child?.ReturnMethod()!.Child; // Noncompliant - _ = (Child!?.Child?[0]!)?.ReturnMethod()!?.Child[0]!; // Noncompliant - _ = (ReturnMethod()!); // Noncompliant - _ = (ReturnMethod())!; // Noncompliant - _ = ((ReturnMethod())!); // Noncompliant - } - - async Task LocalFunctions() - { - VoidMethod(); // FN - - void VoidMethod() { } - Task VoidMethodAsync() => null; - } - - async Task InLocalFunction() - { - async Task AsyncLocalFunction(C c) - { - c.ReturnMethod(); // Noncompliant - } - void LocalFunction(C c) - { - c.ReturnMethod(); // Compliant - } - } -} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_CSharp9.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_CSharp9.cs deleted file mode 100644 index c3134a9189c..00000000000 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_CSharp9.cs +++ /dev/null @@ -1,6 +0,0 @@ -using System.IO; -using System.Threading.Tasks; -using System; - -var ms = new MemoryStream(); -ms.Dispose(); // Noncompliant {{Await DisposeAsync instead.}} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_EF.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_EF.cs deleted file mode 100644 index 934fcf50039..00000000000 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_EF.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using System.IO; -using System.Threading.Tasks; -using System; -using System.Linq; - -public class EnitityFramework -{ - public async Task Query() - { - // Note to implementers: Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions and RelationalQueryableExtensions might be needed to be added to some sort of whitelist for IQueryables - DbSet dbSet = default; - dbSet.Add(null); // Noncompliant - dbSet.AddRange(null); // Noncompliant - dbSet.All(x => true); // Noncompliant - dbSet.Any(x => true); // Noncompliant - dbSet.Average(x => 1); // Noncompliant - dbSet.Contains(null); // Noncompliant - dbSet.Count(); // Noncompliant - dbSet.ElementAt(1); // Compliant: EF 8.0 only - dbSet.ElementAtOrDefault(1); // Compliant: EF 8.0 only - dbSet.ExecuteDelete(); // Noncompliant - dbSet.ExecuteUpdate(x => x.SetProperty(x => x.ToString(), x => string.Empty)); // Noncompliant - dbSet.Find(null); // Noncompliant - dbSet.First(); // Noncompliant - dbSet.FirstOrDefault(); // Noncompliant - dbSet.Last(); // Noncompliant - dbSet.LastOrDefault(); // Noncompliant - dbSet.Load(); // Noncompliant - dbSet.LongCount(); // Noncompliant - dbSet.Max(); // Noncompliant - dbSet.Min(); // Noncompliant - dbSet.Single(); // Noncompliant - dbSet.SingleOrDefault(); // Noncompliant - dbSet.Sum(x => 0); // Noncompliant - dbSet.ToArray(); // Noncompliant - dbSet.ToDictionary(x => 0); // Noncompliant - dbSet.ToList(); // Noncompliant - } -} diff --git a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_Sockets.cs b/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_Sockets.cs deleted file mode 100644 index 242d727c490..00000000000 --- a/analyzers/tests/SonarAnalyzer.Test/TestCases/UseAwaitableMethod_Sockets.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using System.Text; -using System.IO; -using System.Net; -using System.Net.Sockets; -using System.Threading.Tasks; -using System.Collections.Generic; - -public class Sockets -{ - public async Task Connect() - { - var hostEndPoint = new IPEndPoint(new IPAddress(new byte[] { 127, 0, 0, 1 }), 80); - var buffer = new List>(); - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - socket.Connect(hostEndPoint); // Noncompliant - socket.Accept(); // Noncompliant - socket.Receive(buffer, SocketFlags.Broadcast); // Noncompliant - socket.Send(buffer, SocketFlags.Broadcast); // Noncompliant - } -} diff --git a/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/CoreMetadataReference.cs b/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/CoreMetadataReference.cs index d9e6a8bac33..0bf4a722355 100644 --- a/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/CoreMetadataReference.cs +++ b/analyzers/tests/SonarAnalyzer.TestFramework/MetadataReferences/CoreMetadataReference.cs @@ -40,7 +40,6 @@ public static class CoreMetadataReference public static MetadataReference SystemCollectionsNonGeneric { get; } = CreateReference("System.Collections.NonGeneric.dll"); public static MetadataReference SystemConsole { get; } = CreateReference("System.Console.dll"); public static MetadataReference SystemComponentModelPrimitives { get; } = CreateReference("System.ComponentModel.Primitives.dll"); - public static MetadataReference SystemComponentModelTypeConverter { get; } = CreateReference("System.ComponentModel.TypeConverter.dll"); public static MetadataReference SystemDataCommon { get; } = CreateReference("System.Data.Common.dll"); public static MetadataReference SystemDiagnosticsTraceSource { get; } = CreateReference("System.Diagnostics.TraceSource.dll"); public static MetadataReference SystemDiagnosticsProcess { get; } = CreateReference("System.Diagnostics.Process.dll");