diff --git a/doc/analyzers/VSTHRD114.md b/doc/analyzers/VSTHRD114.md new file mode 100644 index 000000000..c20c3fb53 --- /dev/null +++ b/doc/analyzers/VSTHRD114.md @@ -0,0 +1,31 @@ +# VSTHRD114 Avoid returning a null Task + +Returning `null` from a non-async `Task`/`Task` method will cause a `NullReferenceException` at runtime. This problem can be avoided by returning `Task.CompletedTask`, `Task.FromResult(null)` or `Task.FromResult(default(T))` instead. + +## Examples of patterns that are flagged by this analyzer + +Any non-async `Task` returning method with an explicit `return null;` will be flagged. + +```csharp +Task DoAsync() { + return null; +} + +Task GetSomethingAsync() { + return null; +} +``` + +## Solution + +Return a task like `Task.CompletedTask` or `Task.FromResult`. + +```csharp +Task DoAsync() { + return Task.CompletedTask; +} + +Task GetSomethingAsync() { + return Task.FromResult(null); +} +``` diff --git a/doc/analyzers/index.md b/doc/analyzers/index.md index 69da90121..038fdd038 100644 --- a/doc/analyzers/index.md +++ b/doc/analyzers/index.md @@ -26,6 +26,7 @@ ID | Title | Severity | Supports | Default diagnostic severity [VSTHRD111](VSTHRD111.md) | Use `.ConfigureAwait(bool)` | Advisory | | Hidden [VSTHRD112](VSTHRD112.md) | Implement `System.IAsyncDisposable` | Advisory | | Info [VSTHRD113](VSTHRD113.md) | Check for `System.IAsyncDisposable` | Advisory | | Info +[VSTHRD114](VSTHRD114.md) | Avoid returning null from a `Task`-returning method. | Advisory | | Warning [VSTHRD200](VSTHRD200.md) | Use `Async` naming convention | Guideline | [VSTHRD103](VSTHRD103.md) | Warning ## Severity descriptions diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/VSTHRD114AvoidReturningNullTaskCodeFix.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/VSTHRD114AvoidReturningNullTaskCodeFix.cs new file mode 100644 index 000000000..ac3d1a40c --- /dev/null +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.CodeFixes/VSTHRD114AvoidReturningNullTaskCodeFix.cs @@ -0,0 +1,83 @@ +namespace Microsoft.VisualStudio.Threading.Analyzers +{ + using System.Collections.Immutable; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.CodeActions; + using Microsoft.CodeAnalysis.CodeFixes; + using Microsoft.CodeAnalysis.CSharp; + using Microsoft.CodeAnalysis.CSharp.Syntax; + using Microsoft.CodeAnalysis.Simplification; + + [ExportCodeFixProvider(LanguageNames.CSharp)] + public class VSTHRD114AvoidReturningNullTaskCodeFix : CodeFixProvider + { + private static readonly ImmutableArray ReusableFixableDiagnosticIds = ImmutableArray.Create( + VSTHRD114AvoidReturningNullTaskAnalyzer.Id); + + /// + public override ImmutableArray FixableDiagnosticIds => ReusableFixableDiagnosticIds; + + /// + public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + foreach (var diagnostic in context.Diagnostics) + { + var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + var syntaxRoot = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + if (!(syntaxRoot.FindNode(diagnostic.Location.SourceSpan) is LiteralExpressionSyntax nullLiteral)) + { + continue; + } + + var methodDeclaration = nullLiteral.FirstAncestorOrSelf(); + if (methodDeclaration == null) + { + continue; + } + + if (!(methodDeclaration.ReturnType is GenericNameSyntax genericReturnType)) + { + context.RegisterCodeFix(CodeAction.Create(Strings.VSTHRD114_CodeFix_CompletedTask, ct => ApplyTaskCompletedTaskFix(ct), "CompletedTask"), diagnostic); + } + else + { + if (genericReturnType.TypeArgumentList.Arguments.Count != 1) + { + continue; + } + + context.RegisterCodeFix(CodeAction.Create(Strings.VSTHRD114_CodeFix_FromResult, ct => ApplyTaskFromResultFix(genericReturnType.TypeArgumentList.Arguments[0], ct), "FromResult"), diagnostic); + } + + Task ApplyTaskCompletedTaskFix(CancellationToken cancellationToken) + { + ExpressionSyntax completedTaskExpression = SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("Task"), + SyntaxFactory.IdentifierName("CompletedTask")) + .WithAdditionalAnnotations(Simplifier.Annotation); + + return Task.FromResult(context.Document.WithSyntaxRoot(syntaxRoot.ReplaceNode(nullLiteral, completedTaskExpression))); + } + + Task ApplyTaskFromResultFix(TypeSyntax returnTypeArgument, CancellationToken cancellationToken) + { + ExpressionSyntax completedTaskExpression = SyntaxFactory.InvocationExpression( + SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.IdentifierName("Task"), + SyntaxFactory.GenericName("FromResult").AddTypeArgumentListArguments(returnTypeArgument))) + .AddArgumentListArguments(SyntaxFactory.Argument(nullLiteral)) + .WithAdditionalAnnotations(Simplifier.Annotation); + + return Task.FromResult(context.Document.WithSyntaxRoot(syntaxRoot.ReplaceNode(nullLiteral, completedTaskExpression))); + } + } + } + } +} diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/CSharpCodeFixVerifier`2+Test.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/CSharpCodeFixVerifier`2+Test.cs index aa40f0d62..0c81c5f38 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/CSharpCodeFixVerifier`2+Test.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/CSharpCodeFixVerifier`2+Test.cs @@ -27,37 +27,38 @@ public Test() this.SolutionTransforms.Add((solution, projectId) => { - var parseOptions = (CSharpParseOptions)solution.GetProject(projectId).ParseOptions; - solution = solution.WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(LanguageVersion.CSharp7_1)); + Project project = solution.GetProject(projectId)!; + + var parseOptions = (CSharpParseOptions)project.ParseOptions!; + project = project.WithParseOptions(parseOptions.WithLanguageVersion(LanguageVersion.CSharp7_1)); if (this.HasEntryPoint) { - var compilationOptions = solution.GetProject(projectId).CompilationOptions; - solution = solution.WithProjectCompilationOptions(projectId, compilationOptions.WithOutputKind(OutputKind.ConsoleApplication)); + project = project.WithCompilationOptions(project.CompilationOptions!.WithOutputKind(OutputKind.ConsoleApplication)); } if (this.IncludeMicrosoftVisualStudioThreading) { - solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(JoinableTaskFactory).Assembly.Location)); + project = project.AddMetadataReference(MetadataReference.CreateFromFile(typeof(JoinableTaskFactory).Assembly.Location)); } if (this.IncludeWindowsBase) { - solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(Dispatcher).Assembly.Location)); + project = project.AddMetadataReference(MetadataReference.CreateFromFile(typeof(Dispatcher).Assembly.Location)); } if (this.IncludeVisualStudioSdk) { - solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(IOleServiceProvider).Assembly.Location)); + project = project.AddMetadataReference(MetadataReference.CreateFromFile(typeof(IOleServiceProvider).Assembly.Location)); var nugetPackagesFolder = Environment.CurrentDirectory; foreach (var reference in ReferencesHelper.VSSDKPackageReferences) { - solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(Path.Combine(nugetPackagesFolder, reference))); + project = project.AddMetadataReference(MetadataReference.CreateFromFile(Path.Combine(nugetPackagesFolder, reference))); } } - return solution; + return project.Solution; }); this.TestState.AdditionalFilesFactories.Add(() => diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifierCodeFixVerifier`2+Test.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifier`2+Test.cs similarity index 65% rename from src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifierCodeFixVerifier`2+Test.cs rename to src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifier`2+Test.cs index f9a514927..20139e51d 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifierCodeFixVerifier`2+Test.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifier`2+Test.cs @@ -19,43 +19,52 @@ public static partial class VisualBasicCodeFixVerifier { public class Test : VisualBasicCodeFixTest { + private static readonly ImmutableArray VSSDKPackageReferences = ImmutableArray.Create(new string[] { + "Microsoft.VisualStudio.Shell.Interop.dll", + "Microsoft.VisualStudio.Shell.Interop.11.0.dll", + "Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll", + "Microsoft.VisualStudio.Shell.Immutable.14.0.dll", + "Microsoft.VisualStudio.Shell.14.0.dll", + }); + public Test() { this.ReferenceAssemblies = ReferencesHelper.DefaultReferences; this.SolutionTransforms.Add((solution, projectId) => { - var parseOptions = (VisualBasicParseOptions)solution.GetProject(projectId).ParseOptions; - solution = solution.WithProjectParseOptions(projectId, parseOptions.WithLanguageVersion(LanguageVersion.VisualBasic15_5)); + Project? project = solution.GetProject(projectId); + + var parseOptions = (VisualBasicParseOptions)project!.ParseOptions!; + project = project.WithParseOptions(parseOptions.WithLanguageVersion(LanguageVersion.VisualBasic15_5)); if (this.HasEntryPoint) { - var compilationOptions = solution.GetProject(projectId).CompilationOptions; - solution = solution.WithProjectCompilationOptions(projectId, compilationOptions.WithOutputKind(OutputKind.ConsoleApplication)); + project = project.WithCompilationOptions(project.CompilationOptions!.WithOutputKind(OutputKind.ConsoleApplication)); } if (this.IncludeMicrosoftVisualStudioThreading) { - solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(JoinableTaskFactory).Assembly.Location)); + project = project.AddMetadataReference(MetadataReference.CreateFromFile(typeof(JoinableTaskFactory).Assembly.Location)); } if (this.IncludeWindowsBase) { - solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(Dispatcher).Assembly.Location)); + project = project.AddMetadataReference(MetadataReference.CreateFromFile(typeof(Dispatcher).Assembly.Location)); } if (this.IncludeVisualStudioSdk) { - solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(typeof(IOleServiceProvider).Assembly.Location)); + project = project.AddMetadataReference(MetadataReference.CreateFromFile(typeof(IOleServiceProvider).Assembly.Location)); var nugetPackagesFolder = Environment.CurrentDirectory; - foreach (var reference in ReferencesHelper.VSSDKPackageReferences) + foreach (var reference in VisualBasicCodeFixVerifier.Test.VSSDKPackageReferences) { - solution = solution.AddMetadataReference(projectId, MetadataReference.CreateFromFile(Path.Combine(nugetPackagesFolder, reference))); + project = project.AddMetadataReference(MetadataReference.CreateFromFile(Path.Combine(nugetPackagesFolder, reference))); } } - return solution; + return project.Solution; }); this.TestState.AdditionalFilesFactories.Add(() => diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifierCodeFixVerifier`2.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifier`2.cs similarity index 100% rename from src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifierCodeFixVerifier`2.cs rename to src/Microsoft.VisualStudio.Threading.Analyzers.Tests/Helpers/VisualBasicCodeFixVerifier`2.cs diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/MultiAnalyzerTests.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/MultiAnalyzerTests.cs index b96212466..a726859ec 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/MultiAnalyzerTests.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/MultiAnalyzerTests.cs @@ -6,10 +6,8 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.CodeAnalysis; - using Microsoft.CodeAnalysis.CSharp.Testing; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Testing; - using Microsoft.CodeAnalysis.Testing.Verifiers; using Xunit; using Verify = MultiAnalyzerTests.Verifier; @@ -32,7 +30,7 @@ Task FooAsync() { return Task.FromResult(1); } - Task BarAsync() => null; + Task BarAsync() => Task.CompletedTask; static void SetTaskSourceIfCompleted(Task task, TaskCompletionSource tcs) { if (task.IsCompleted) { @@ -171,7 +169,7 @@ public Task BAsync() { E().ToString(); E()(); string v = nameof(E); - return null; + return Task.CompletedTask; } internal Task CAsync() { @@ -181,7 +179,7 @@ internal Task CAsync() { E().ToString(); E()(); string v = nameof(E); - return null; + return Task.CompletedTask; } private void D() { } diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD003UseJtfRunAsyncAnalyzerTests.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD003UseJtfRunAsyncAnalyzerTests.cs index 415f62a7e..c640d0d52 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD003UseJtfRunAsyncAnalyzerTests.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD003UseJtfRunAsyncAnalyzerTests.cs @@ -1208,7 +1208,7 @@ static Task MyMethodAsync() { var projectA = solution.AddProject("ProjectA", "ProjectA", LanguageNames.CSharp) .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) - .WithMetadataReferences(solution.GetProject(projectId).MetadataReferences.Concat(test!.TestState.AdditionalReferences)) + .WithMetadataReferences(solution.GetProject(projectId)!.MetadataReferences.Concat(test!.TestState.AdditionalReferences)) .AddDocument("SpecialTasks.cs", specialTasksCs).Project; solution = projectA.Solution; solution = solution.AddProjectReference(projectId, new ProjectReference(projectA.Id)); diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD114AvoidReturningNullTaskAnalyzerTests.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD114AvoidReturningNullTaskAnalyzerTests.cs new file mode 100644 index 000000000..e8a83b8c2 --- /dev/null +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD114AvoidReturningNullTaskAnalyzerTests.cs @@ -0,0 +1,196 @@ +namespace Microsoft.VisualStudio.Threading.Analyzers.Tests +{ + using System.Threading.Tasks; + using Microsoft.CodeAnalysis.CSharp; + using Xunit; + using VerifyCS = CSharpCodeFixVerifier; + using VerifyVB = VisualBasicCodeFixVerifier; + + public class VSTHRD114AvoidReturningNullTaskAnalyzerTests + { + [Fact] + public async Task TaskOfTReturnsNull_Diagnostic() + { + var csharpTest = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj() + { + return [|null|]; + } +} +"; + await new VerifyCS.Test + { + TestCode = csharpTest, + }.RunAsync(); + + var vbTest = @" +Imports System.Threading.Tasks + +Friend Class Test + Public Function GetTaskObj() As Task(Of Object) + Return [|Nothing|] + End Function +End Class +"; + await new VerifyVB.Test + { + TestCode = vbTest, + }.RunAsync(); + } + + [Fact] + public async Task TaskReturnsNull_Diagnostic() + { + var csharpTest = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTask() + { + return [|null|]; + } +} +"; + await new VerifyCS.Test + { + TestCode = csharpTest, + }.RunAsync(); + + var vbTest = @" +Imports System.Threading.Tasks + +Friend Class Test + Public Function GetTask() As Task + Return [|Nothing|] + End Function +End Class +"; + await new VerifyVB.Test + { + TestCode = vbTest, + }.RunAsync(); + } + + [Fact] + public async Task TaskArrowReturnsNull_Diagnostic() + { + var test = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTask() => [|null|]; +} +"; + await new VerifyCS.Test + { + TestCode = test, + }.RunAsync(); + } + + [Fact] + public async Task AsyncReturnsNull_NoDiagnostic() + { + var csharpTest = @" +using System.Threading.Tasks; + +class Test +{ + public async Task GetTaskObj() + { + return null; + } +} +"; + await new VerifyCS.Test + { + TestCode = csharpTest, + }.RunAsync(); + + var vbTest = @" +Imports System.Threading.Tasks + +Friend Class Test + Public Async Function GetTaskObj() As Task(Of Object) + Return Nothing + End Function +End Class +"; + await new VerifyVB.Test + { + TestCode = vbTest, + }.RunAsync(); + } + + [Fact] + public async Task VariableIsNullAndReturned_NoDiagnostic_FalseNegative() + { + var test = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj() + { + Task o = null; + return o; + } +} +"; + await new VerifyCS.Test + { + TestCode = test, + }.RunAsync(); + } + + [Fact] + public async Task NullInTernary_NoDiagnostic_FalseNegative() + { + var test = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj(bool b) + { + return b ? default(Task) : null; + } +} +"; + await new VerifyCS.Test + { + TestCode = test, + }.RunAsync(); + } + + [Fact] + public async Task MultipleFaultyReturns_MultipleDiagnostics() + { + var test = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj(string s) + { + if (string.IsNullOrEmpty(s)) + { + return [|null|]; + } + + return [|null|]; + } +} +"; + await new VerifyCS.Test + { + TestCode = test, + }.RunAsync(); + } + } +} diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD114AvoidReturningNullTaskCodeFixTests.cs b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD114AvoidReturningNullTaskCodeFixTests.cs new file mode 100644 index 000000000..996e5e05a --- /dev/null +++ b/src/Microsoft.VisualStudio.Threading.Analyzers.Tests/VSTHRD114AvoidReturningNullTaskCodeFixTests.cs @@ -0,0 +1,181 @@ +namespace Microsoft.VisualStudio.Threading.Analyzers.Tests +{ + using System.Threading.Tasks; + using Xunit; + using Verify = CSharpCodeFixVerifier; + + public class VSTHRD114AvoidReturningNullTaskCodeFixTests + { + [Fact] + public async Task TaskOfTReturnsNull() + { + var test = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj() + { + return [|null|]; + } +}"; + + var withFix = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj() + { + return Task.FromResult(null); + } +}"; + + await Verify.VerifyCodeFixAsync(test, withFix); + } + + [Fact] + public async Task TaskReturnsNull() + { + var test = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTask() + { + return [|null|]; + } +} +"; + var withFix = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTask() + { + return Task.CompletedTask; + } +} +"; + + await Verify.VerifyCodeFixAsync(test, withFix); + } + + [Fact] + public async Task TaskArrowReturnsNull_Diagnostic() + { + var test = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTask() => [|null|]; +} +"; + var withFix = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTask() => Task.CompletedTask; +} +"; + + await Verify.VerifyCodeFixAsync(test, withFix); + } + + [Fact] + public async Task TaskOfTArrowReturnsNull_Diagnostic() + { + var test = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj() => [|null|]; +} +"; + var withFix = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj() => Task.FromResult(null); +} +"; + + await Verify.VerifyCodeFixAsync(test, withFix); + } + + [Fact] + public async Task MultipleFaultyReturns() + { + var test = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj(string s) + { + if (string.IsNullOrEmpty(s)) + { + return [|null|]; + } + + return [|null|]; + } +} +"; + var withFix = @" +using System.Threading.Tasks; + +class Test +{ + public Task GetTaskObj(string s) + { + if (string.IsNullOrEmpty(s)) + { + return Task.FromResult(null); + } + + return Task.FromResult(null); + } +} +"; + + await Verify.VerifyCodeFixAsync(test, withFix); + } + + [Fact] + public async Task ComplexTaskOfTReturnsNull() + { + var test = @" +using System.Collections.Generic; +using System.Threading.Tasks; + +class Test +{ + public Task>> GetTaskObj() + { + return [|null|]; + } +}"; + + var withFix = @" +using System.Collections.Generic; +using System.Threading.Tasks; + +class Test +{ + public Task>> GetTaskObj() + { + return Task.FromResult>>(null); + } +}"; + + await Verify.VerifyCodeFixAsync(test, withFix); + } + } +} diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/Strings.Designer.cs b/src/Microsoft.VisualStudio.Threading.Analyzers/Strings.Designer.cs index 3c11e5436..953d2964d 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/Strings.Designer.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/Strings.Designer.cs @@ -549,6 +549,42 @@ internal static string VSTHRD113_Title { } } + /// + /// Looks up a localized string similar to Use 'Task.CompletedTask' instead. + /// + internal static string VSTHRD114_CodeFix_CompletedTask { + get { + return ResourceManager.GetString("VSTHRD114_CodeFix_CompletedTask", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Use 'Task.FromResult' instead. + /// + internal static string VSTHRD114_CodeFix_FromResult { + get { + return ResourceManager.GetString("VSTHRD114_CodeFix_FromResult", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid returning null from a Task-returning method.. + /// + internal static string VSTHRD114_MessageFormat { + get { + return ResourceManager.GetString("VSTHRD114_MessageFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Avoid returning a null Task. + /// + internal static string VSTHRD114_Title { + get { + return ResourceManager.GetString("VSTHRD114_Title", resourceCulture); + } + } + /// /// Looks up a localized string similar to Use "Async" suffix in names of methods that return an awaitable type.. /// diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/Strings.resx b/src/Microsoft.VisualStudio.Threading.Analyzers/Strings.resx index 82670c605..405403307 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/Strings.resx +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/Strings.resx @@ -1,17 +1,17 @@  - @@ -327,4 +327,20 @@ Use AsyncLazy<T> instead. The System.IAsyncDisposable interface is defined in the Microsoft.Bcl.AsyncInterfaces NuGet package. + + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Task is a type name and should not be translated. + + + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + \ No newline at end of file diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs b/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs index 659898aa1..645536f41 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/Utils.cs @@ -101,6 +101,43 @@ internal static Action DebuggableWrapper(Action DebuggableWrapper(Action handler) + { + return ctxt => + { + try + { + handler(ctxt); + } + catch (OperationCanceledException) + { + throw; + } + catch (Exception ex) when (LaunchDebuggerExceptionFilter()) + { + var messageBuilder = new StringBuilder(); + messageBuilder.Append("Analyzer failure while processing syntax(es) at "); + + for (int i = 0; i < ctxt.OperationBlocks.Length; i++) + { + var operation = ctxt.OperationBlocks[i]; + var lineSpan = operation.Syntax.GetLocation()?.GetLineSpan(); + + if (i > 0) + { + messageBuilder.Append(", "); + } + + messageBuilder.Append($"{operation.Syntax.SyntaxTree.FilePath}({lineSpan?.StartLinePosition.Line + 1},{lineSpan?.StartLinePosition.Character + 1}). Syntax: {operation.Syntax}."); + } + + messageBuilder.Append($". {ex.GetType()} {ex.Message}"); + + throw new Exception(messageBuilder.ToString(), ex); + } + }; + } + internal static Location? GetLocationOfBaseTypeName(INamedTypeSymbol symbol, INamedTypeSymbol baseType, Compilation compilation, CancellationToken cancellationToken) { foreach (var syntaxReference in symbol.DeclaringSyntaxReferences) diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD114AvoidReturningNullTaskAnalyzer.cs b/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD114AvoidReturningNullTaskAnalyzer.cs new file mode 100644 index 000000000..05d735d67 --- /dev/null +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/VSTHRD114AvoidReturningNullTaskAnalyzer.cs @@ -0,0 +1,60 @@ +namespace Microsoft.VisualStudio.Threading.Analyzers +{ + using System.Collections.Immutable; + using System.Threading.Tasks; + using Microsoft.CodeAnalysis; + using Microsoft.CodeAnalysis.Diagnostics; + using Microsoft.CodeAnalysis.Operations; + + /// + /// Finds await expressions on that do not use . + /// Also works on . + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public class VSTHRD114AvoidReturningNullTaskAnalyzer : DiagnosticAnalyzer + { + public const string Id = "VSTHRD114"; + + internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( + id: Id, + title: new LocalizableResourceString(nameof(Strings.VSTHRD112_Title), Strings.ResourceManager, typeof(Strings)), + messageFormat: new LocalizableResourceString(nameof(Strings.VSTHRD112_MessageFormat), Strings.ResourceManager, typeof(Strings)), + helpLinkUri: Utils.GetHelpLink(Id), + category: "Usage", + defaultSeverity: DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + /// + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Descriptor); + + /// + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze); + + context.RegisterOperationBlockStartAction(Utils.DebuggableWrapper(context => AnalyzeOperationBlockStart(context))); + } + + private static void AnalyzeOperationBlockStart(OperationBlockStartAnalysisContext context) + { + if (context.OwningSymbol is IMethodSymbol method && + !method.IsAsync && + Utils.IsTask(method.ReturnType)) + { + context.RegisterOperationAction(Utils.DebuggableWrapper(context => AnalyzerReturnOperation(context)), OperationKind.Return); + } + } + + private static void AnalyzerReturnOperation(OperationAnalysisContext context) + { + var returnOperation = (IReturnOperation)context.Operation; + + if (returnOperation.ReturnedValue is { ConstantValue: { HasValue: true, Value: null } } && // could be null for implicit returns + returnOperation.ReturnedValue.Syntax is { } returnedValueSyntax) + { + context.ReportDiagnostic(Diagnostic.Create(Descriptor, returnedValueSyntax.GetLocation())); + } + } + } +} diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.cs.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.cs.xlf index 464cb5c57..3cb87020c 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.cs.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.cs.xlf @@ -279,6 +279,26 @@ Použijte místo toho AsyncLazy<T>. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. V názvech metod, které vrací typ awaitable, používejte příponu „Async“. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.de.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.de.xlf index 9d999b00f..8e6101df0 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.de.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.de.xlf @@ -279,6 +279,26 @@ Verwenden Sie stattdessen "AsyncLazy<T>". Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. Verwenden Sie das Suffix "Async" in Namen von Methoden, die einen Typ zurückgeben, der "await" unterstützt. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.es.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.es.xlf index 412d696cf..2cec2f9f6 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.es.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.es.xlf @@ -279,6 +279,26 @@ Use AsyncLazy<T> en su lugar. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. Use el sufijo "Async" en nombres de métodos que devuelven un tipo que admite await. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.fr.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.fr.xlf index 01975cebe..33eccbd00 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.fr.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.fr.xlf @@ -279,6 +279,26 @@ Utilisez AsyncLazy<T> à la place. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. Utilisez le suffixe "Async" dans les noms des méthodes qui retournent un type awaitable. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.it.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.it.xlf index 316d77add..f07e86ec5 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.it.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.it.xlf @@ -279,6 +279,26 @@ In alternativa, usare AsyncLazy<T>. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. Usare il suffisso "Async" in nomi di metodi che restituiscono un tipo awaitable. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ja.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ja.xlf index 5586308cd..af9751e3a 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ja.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ja.xlf @@ -279,6 +279,26 @@ Use AsyncLazy<T> instead. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. 待機可能な型を戻すメソッドの名前に "Async" サフィックスを使用します。 diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ko.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ko.xlf index 3c494047e..60ff78160 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ko.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ko.xlf @@ -279,6 +279,26 @@ Use AsyncLazy<T> instead. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. 대기할 수 있는 형식을 반환하는 메서드의 이름에 "Async" 접미사를 사용합니다. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.pl.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.pl.xlf index e7524e7c5..ac84dd1ee 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.pl.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.pl.xlf @@ -279,6 +279,26 @@ Zamiast tego użyj elementu AsyncLazy<T>. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. Używaj sufiksu „Async” w nazwach metod zwracających typ awaitable. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.pt-BR.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.pt-BR.xlf index e7c7eb82d..214ee4dc9 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.pt-BR.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.pt-BR.xlf @@ -279,6 +279,26 @@ Nesse caso, use AsyncLazy<T>. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. Use o sufixo "Async" em nomes de métodos que retornam um tipo esperável. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ru.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ru.xlf index 969e49fd0..af873a5bb 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ru.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.ru.xlf @@ -279,6 +279,26 @@ Use AsyncLazy<T> instead. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. Используйте суффикс "Async" в именах методов, которые возвращают тип, поддерживающий ожидание. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.tr.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.tr.xlf index c77f528cd..cd18e5793 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.tr.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.tr.xlf @@ -279,6 +279,26 @@ Bunun yerine AsyncLazy<T> kullanın. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. Beklenebilir bir tür döndüren metotların adlarında “Async” ekini kullanın. diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.zh-Hans.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.zh-Hans.xlf index 09de8b97b..c3af83832 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.zh-Hans.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.zh-Hans.xlf @@ -279,6 +279,26 @@ Use AsyncLazy<T> instead. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. 如果方法返回可等待的类型,则在其名称中使用 “Async” 语法。 diff --git a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.zh-Hant.xlf b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.zh-Hant.xlf index db2101622..9e86d4155 100644 --- a/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.zh-Hant.xlf +++ b/src/Microsoft.VisualStudio.Threading.Analyzers/xlf/Strings.zh-Hant.xlf @@ -279,6 +279,26 @@ Use AsyncLazy<T> instead. Check for System.IAsyncDisposable + + Use 'Task.CompletedTask' instead + Use 'Task.CompletedTask' instead + "Task.CompletedTask" should not be translated. + + + Use 'Task.FromResult' instead + Use 'Task.FromResult' instead + "Task.FromResult" should not be translated. + + + Avoid returning null from a Task-returning method. + Avoid returning null from a Task-returning method. + Task is a type name and should not be translated. + + + Avoid returning a null Task + Avoid returning a null Task + Task is a type name and should not be translated. + Use "Async" suffix in names of methods that return an awaitable type. 請在傳回可等候類型的方法名稱中使用 "Async" 後置詞。