-
Notifications
You must be signed in to change notification settings - Fork 157
Add CompletedTaskAttribute to suppress VSTHRD003 for known completed tasks #1510
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
91a1e36
b9f60ec
a864039
d067b66
a593dc1
9e2bda8
83bcc44
1b80964
37e1e3d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||
|---|---|---|---|---|---|---|---|---|
|
|
@@ -10,6 +10,47 @@ When required to await a task that was started earlier, start it within a delega | |||||||
| `JoinableTaskFactory.RunAsync`, storing the resulting `JoinableTask` in a field or variable. | ||||||||
| You can safely await the `JoinableTask` later. | ||||||||
|
|
||||||||
| ## Suppressing warnings for completed tasks | ||||||||
|
|
||||||||
| If you have a property, method, or field that returns a pre-completed task (such as a cached task with a known value), | ||||||||
| you can suppress this warning by applying the `[CompletedTask]` attribute to the member. | ||||||||
| This attribute is automatically included when you install the `Microsoft.VisualStudio.Threading.Analyzers` package. | ||||||||
|
|
||||||||
| ```csharp | ||||||||
| [Microsoft.VisualStudio.Threading.CompletedTask] | ||||||||
| private static readonly Task<bool> TrueTask = Task.FromResult(true); | ||||||||
|
|
||||||||
| async Task MyMethodAsync() | ||||||||
| { | ||||||||
| await TrueTask; // No warning - TrueTask is marked as a completed task | ||||||||
| } | ||||||||
| ``` | ||||||||
|
|
||||||||
| **Important restrictions:** | ||||||||
| - Fields must be marked `readonly` when using this attribute | ||||||||
| - Properties must not have non-private setters (getter-only or private setters are allowed) | ||||||||
|
||||||||
| - Properties must not have non-private setters (getter-only or private setters are allowed) | |
| - Properties must not have non-private setters (getter-only or private setters are allowed) | |
| - Properties with `init` accessors must be `private` when using this attribute |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,6 +40,15 @@ public class VSTHRD003UseJtfRunAsyncAnalyzer : DiagnosticAnalyzer | |
| { | ||
| public const string Id = "VSTHRD003"; | ||
|
|
||
| public static readonly DiagnosticDescriptor InvalidAttributeUseDescriptor = new DiagnosticDescriptor( | ||
| id: Id, | ||
| title: new LocalizableResourceString(nameof(Strings.VSTHRD003InvalidAttributeUse_Title), Strings.ResourceManager, typeof(Strings)), | ||
| messageFormat: new LocalizableResourceString(nameof(Strings.VSTHRD003InvalidAttributeUse_MessageFormat), Strings.ResourceManager, typeof(Strings)), | ||
| helpLinkUri: Utils.GetHelpLink(Id), | ||
| category: "Usage", | ||
| defaultSeverity: DiagnosticSeverity.Warning, | ||
| isEnabledByDefault: true); | ||
|
|
||
| internal static readonly DiagnosticDescriptor Descriptor = new DiagnosticDescriptor( | ||
| id: Id, | ||
| title: new LocalizableResourceString(nameof(Strings.VSTHRD003_Title), Strings.ResourceManager, typeof(Strings)), | ||
|
|
@@ -54,7 +63,7 @@ public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics | |
| { | ||
| get | ||
| { | ||
| return ImmutableArray.Create(Descriptor); | ||
| return ImmutableArray.Create(InvalidAttributeUseDescriptor, Descriptor); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -69,20 +78,77 @@ public override void Initialize(AnalysisContext context) | |
| context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeArrowExpressionClause), SyntaxKind.ArrowExpressionClause); | ||
| context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeLambdaExpression), SyntaxKind.SimpleLambdaExpression); | ||
| context.RegisterSyntaxNodeAction(Utils.DebuggableWrapper(this.AnalyzeLambdaExpression), SyntaxKind.ParenthesizedLambdaExpression); | ||
| context.RegisterSymbolAction(Utils.DebuggableWrapper(this.AnalyzeSymbolForInvalidAttributeUse), SymbolKind.Field, SymbolKind.Property, SymbolKind.Method); | ||
| } | ||
|
|
||
| private static bool IsSymbolAlwaysOkToAwait(ISymbol? symbol) | ||
| private static bool IsSymbolAlwaysOkToAwait(ISymbol? symbol, Compilation compilation) | ||
| { | ||
| if (symbol is IFieldSymbol field) | ||
| if (symbol is null) | ||
| { | ||
| // Allow the TplExtensions.CompletedTask and related fields. | ||
| if (field.ContainingType.Name == Types.TplExtensions.TypeName && field.BelongsToNamespace(Types.TplExtensions.Namespace) && | ||
| (field.Name == Types.TplExtensions.CompletedTask || field.Name == Types.TplExtensions.CanceledTask || field.Name == Types.TplExtensions.TrueTask || field.Name == Types.TplExtensions.FalseTask)) | ||
| return false; | ||
| } | ||
|
|
||
| // Check if the symbol has the CompletedTaskAttribute directly applied | ||
| if (symbol.GetAttributes().Any(attr => | ||
| attr.AttributeClass?.Name == Types.CompletedTaskAttribute.TypeName && | ||
| attr.AttributeClass.BelongsToNamespace(Types.CompletedTaskAttribute.Namespace))) | ||
| { | ||
| // Validate that the attribute is used correctly | ||
| if (symbol is IFieldSymbol fieldSymbol) | ||
| { | ||
| return true; | ||
| // Fields must be readonly | ||
| if (!fieldSymbol.IsReadOnly) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| else if (symbol is IPropertySymbol propertySymbol) | ||
| { | ||
| // Properties must not have non-private setters | ||
| // Init accessors are only allowed if the property itself is private | ||
| if (propertySymbol.SetMethod is not null) | ||
| { | ||
| if (propertySymbol.SetMethod.IsInitOnly) | ||
| { | ||
| // Init accessor - only allowed if property is private | ||
| if (propertySymbol.DeclaredAccessibility != Accessibility.Private) | ||
| { | ||
| return false; | ||
| } | ||
| } | ||
| else if (propertySymbol.SetMethod.DeclaredAccessibility != Accessibility.Private) | ||
| { | ||
| // Regular setter must be private | ||
| return false; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
| else if (symbol is IPropertySymbol property) | ||
|
|
||
| // Check for assembly-level CompletedTaskAttribute | ||
| foreach (AttributeData assemblyAttr in compilation.Assembly.GetAttributes()) | ||
| { | ||
| if (assemblyAttr.AttributeClass?.Name == Types.CompletedTaskAttribute.TypeName && | ||
| assemblyAttr.AttributeClass.BelongsToNamespace(Types.CompletedTaskAttribute.Namespace)) | ||
|
Comment on lines
+130
to
+134
|
||
| { | ||
| // Look for the Member named argument | ||
| foreach (KeyValuePair<string, TypedConstant> namedArg in assemblyAttr.NamedArguments) | ||
| { | ||
| if (namedArg.Key == "Member" && namedArg.Value.Value is string memberName) | ||
| { | ||
| // Check if this symbol matches the specified member name | ||
| if (IsSymbolMatchingMemberName(symbol, memberName)) | ||
| { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (symbol is IPropertySymbol property) | ||
| { | ||
| // Explicitly allow Task.CompletedTask | ||
| if (property.ContainingType.Name == Types.Task.TypeName && property.BelongsToNamespace(Types.Task.Namespace) && | ||
|
|
@@ -95,6 +161,102 @@ private static bool IsSymbolAlwaysOkToAwait(ISymbol? symbol) | |
| return false; | ||
| } | ||
|
|
||
| private static bool IsSymbolMatchingMemberName(ISymbol symbol, string memberName) | ||
| { | ||
| // Build the fully qualified name of the symbol | ||
| string fullyQualifiedName = GetFullyQualifiedName(symbol); | ||
|
|
||
| // Compare with the member name (case-sensitive) | ||
| return string.Equals(fullyQualifiedName, memberName, StringComparison.Ordinal); | ||
| } | ||
|
|
||
| private static string GetFullyQualifiedName(ISymbol symbol) | ||
| { | ||
| if (symbol.ContainingType is null) | ||
| { | ||
| return symbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); | ||
| } | ||
|
|
||
| // For members (properties, fields, methods), construct: Namespace.TypeName.MemberName | ||
| List<string> parts = new List<string>(); | ||
|
|
||
| // Add member name | ||
| parts.Add(symbol.Name); | ||
|
|
||
| // Add containing type hierarchy | ||
| INamedTypeSymbol? currentType = symbol.ContainingType; | ||
| while (currentType is not null) | ||
| { | ||
| parts.Insert(0, currentType.Name); | ||
| currentType = currentType.ContainingType; | ||
| } | ||
|
|
||
| // Add namespace | ||
| if (symbol.ContainingNamespace is not null && !symbol.ContainingNamespace.IsGlobalNamespace) | ||
| { | ||
| parts.Insert(0, symbol.ContainingNamespace.ToDisplayString()); | ||
| } | ||
|
|
||
| return string.Join(".", parts); | ||
| } | ||
|
|
||
| private void AnalyzeSymbolForInvalidAttributeUse(SymbolAnalysisContext context) | ||
| { | ||
| ISymbol symbol = context.Symbol; | ||
|
|
||
| // Check if the symbol has the CompletedTaskAttribute | ||
| AttributeData? completedTaskAttr = symbol.GetAttributes().FirstOrDefault(attr => | ||
| attr.AttributeClass?.Name == Types.CompletedTaskAttribute.TypeName && | ||
| attr.AttributeClass.BelongsToNamespace(Types.CompletedTaskAttribute.Namespace)); | ||
|
|
||
| if (completedTaskAttr is null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| string? errorMessage = null; | ||
|
|
||
| if (symbol is IFieldSymbol fieldSymbol) | ||
| { | ||
| // Fields must be readonly | ||
| if (!fieldSymbol.IsReadOnly) | ||
| { | ||
| errorMessage = Strings.VSTHRD003InvalidAttributeUse_FieldNotReadonly; | ||
| } | ||
| } | ||
| else if (symbol is IPropertySymbol propertySymbol) | ||
| { | ||
| // Check for init accessor (which is a special kind of setter) | ||
| if (propertySymbol.SetMethod is not null) | ||
| { | ||
| // Init accessors are only allowed if the property itself is private | ||
| if (propertySymbol.SetMethod.IsInitOnly) | ||
| { | ||
| if (propertySymbol.DeclaredAccessibility != Accessibility.Private) | ||
| { | ||
| errorMessage = Strings.VSTHRD003InvalidAttributeUse_PropertyWithNonPrivateInit; | ||
| } | ||
| } | ||
| else if (propertySymbol.SetMethod.DeclaredAccessibility != Accessibility.Private) | ||
| { | ||
| // Non-private setters are not allowed | ||
| errorMessage = Strings.VSTHRD003InvalidAttributeUse_PropertyWithNonPrivateSetter; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Methods are always allowed | ||
| if (errorMessage is not null) | ||
| { | ||
| // Report diagnostic on the attribute location | ||
| Location? location = completedTaskAttr.ApplicationSyntaxReference?.GetSyntax(context.CancellationToken).GetLocation(); | ||
| if (location is not null) | ||
| { | ||
| context.ReportDiagnostic(Diagnostic.Create(InvalidAttributeUseDescriptor, location, errorMessage)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private void AnalyzeArrowExpressionClause(SyntaxNodeAnalysisContext context) | ||
| { | ||
| var arrowExpressionClause = (ArrowExpressionClauseSyntax)context.Node; | ||
|
|
@@ -183,7 +345,7 @@ private void AnalyzeAwaitExpression(SyntaxNodeAnalysisContext context) | |
| symbolType = localSymbol.Type; | ||
| dataflowAnalysisCompatibleVariable = true; | ||
| break; | ||
| case IPropertySymbol propertySymbol when !IsSymbolAlwaysOkToAwait(propertySymbol): | ||
| case IPropertySymbol propertySymbol when !IsSymbolAlwaysOkToAwait(propertySymbol, context.Compilation): | ||
| symbolType = propertySymbol.Type; | ||
|
|
||
| if (focusedExpression is MemberAccessExpressionSyntax memberAccessExpression) | ||
|
|
@@ -277,7 +439,7 @@ private void AnalyzeAwaitExpression(SyntaxNodeAnalysisContext context) | |
| } | ||
|
|
||
| ISymbol? definition = declarationSemanticModel.GetSymbolInfo(memberAccessSyntax, cancellationToken).Symbol; | ||
| if (IsSymbolAlwaysOkToAwait(definition)) | ||
| if (IsSymbolAlwaysOkToAwait(definition, context.Compilation)) | ||
| { | ||
| return null; | ||
| } | ||
|
Comment on lines
441
to
445
|
||
|
|
@@ -288,6 +450,12 @@ private void AnalyzeAwaitExpression(SyntaxNodeAnalysisContext context) | |
|
|
||
| break; | ||
| case IMethodSymbol methodSymbol: | ||
| // Check if the method itself has the CompletedTaskAttribute | ||
| if (IsSymbolAlwaysOkToAwait(methodSymbol, context.Compilation)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| if (Utils.IsTask(methodSymbol.ReturnType) && focusedExpression is InvocationExpressionSyntax invocationExpressionSyntax) | ||
| { | ||
| // Consider all arguments | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,51 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copyright (c) Microsoft Corporation. All rights reserved. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #if !COMPLETEDTASKATTRIBUTE_INCLUDED | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #define COMPLETEDTASKATTRIBUTE_INCLUDED | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespace Microsoft.VisualStudio.Threading; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Indicates that a property, method, or field returns a task that is already completed. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// This suppresses VSTHRD003 warnings when awaiting the returned task. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <para> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Apply this attribute to properties, methods, or fields that return cached, pre-completed tasks | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// such as singleton instances with well-known immutable values. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// The VSTHRD003 analyzer will not report warnings when these members are awaited, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// as awaiting an already-completed task does not pose a risk of deadlock. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </para> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <para> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// This attribute can also be applied at the assembly level to mark members in external types | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// that you don't control: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <code> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// [assembly: CompletedTask(Member = "System.Threading.Tasks.TplExtensions.TrueTask")] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </code> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+25
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </para> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field | System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #pragma warning disable SA1649 // File name should match first type name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| internal sealed class CompletedTaskAttribute : System.Attribute | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Initializes a new instance of the <see cref="CompletedTaskAttribute"/> class. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public CompletedTaskAttribute() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// Gets or sets the fully qualified name of the member that returns a completed task. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// This is only used when the attribute is applied at the assembly level. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </summary> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// <remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// The format should be: "Namespace.TypeName.MemberName". | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// For example: "System.Threading.Tasks.TplExtensions.TrueTask". | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /// </remarks> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public string? Member { get; set; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public string? Member { get; set; } | |
| public string Member { get; set; } = string.Empty; |
Copilot
AI
Feb 28, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file uses a file-scoped namespace declaration (namespace ...;), which requires C# 10+. Because this file is injected into consuming projects via buildTransitive and compiled, it will fail to compile for consumers using earlier language versions. Consider switching to a namespace block form (namespace ... { ... }) to keep the injected source compatible.
| namespace Microsoft.VisualStudio.Threading; | |
| /// <summary> | |
| /// Indicates that a property, method, or field returns a task that is already completed. | |
| /// This suppresses VSTHRD003 warnings when awaiting the returned task. | |
| /// </summary> | |
| /// <remarks> | |
| /// <para> | |
| /// Apply this attribute to properties, methods, or fields that return cached, pre-completed tasks | |
| /// such as singleton instances with well-known immutable values. | |
| /// The VSTHRD003 analyzer will not report warnings when these members are awaited, | |
| /// as awaiting an already-completed task does not pose a risk of deadlock. | |
| /// </para> | |
| /// <para> | |
| /// This attribute can also be applied at the assembly level to mark members in external types | |
| /// that you don't control: | |
| /// <code> | |
| /// [assembly: CompletedTask(Member = "System.Threading.Tasks.TplExtensions.TrueTask")] | |
| /// </code> | |
| /// </para> | |
| /// </remarks> | |
| [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field | System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)] | |
| #pragma warning disable SA1649 // File name should match first type name | |
| internal sealed class CompletedTaskAttribute : System.Attribute | |
| { | |
| /// <summary> | |
| /// Initializes a new instance of the <see cref="CompletedTaskAttribute"/> class. | |
| /// </summary> | |
| public CompletedTaskAttribute() | |
| { | |
| } | |
| /// <summary> | |
| /// Gets or sets the fully qualified name of the member that returns a completed task. | |
| /// This is only used when the attribute is applied at the assembly level. | |
| /// </summary> | |
| /// <remarks> | |
| /// The format should be: "Namespace.TypeName.MemberName". | |
| /// For example: "System.Threading.Tasks.TplExtensions.TrueTask". | |
| /// </remarks> | |
| public string? Member { get; set; } | |
| } | |
| #pragma warning restore SA1649 // File name should match first type name | |
| namespace Microsoft.VisualStudio.Threading | |
| { | |
| /// <summary> | |
| /// Indicates that a property, method, or field returns a task that is already completed. | |
| /// This suppresses VSTHRD003 warnings when awaiting the returned task. | |
| /// </summary> | |
| /// <remarks> | |
| /// <para> | |
| /// Apply this attribute to properties, methods, or fields that return cached, pre-completed tasks | |
| /// such as singleton instances with well-known immutable values. | |
| /// The VSTHRD003 analyzer will not report warnings when these members are awaited, | |
| /// as awaiting an already-completed task does not pose a risk of deadlock. | |
| /// </para> | |
| /// <para> | |
| /// This attribute can also be applied at the assembly level to mark members in external types | |
| /// that you don't control: | |
| /// <code> | |
| /// [assembly: CompletedTask(Member = "System.Threading.Tasks.TplExtensions.TrueTask")] | |
| /// </code> | |
| /// </para> | |
| /// </remarks> | |
| [System.AttributeUsage(System.AttributeTargets.Property | System.AttributeTargets.Method | System.AttributeTargets.Field | System.AttributeTargets.Assembly, Inherited = false, AllowMultiple = true)] | |
| #pragma warning disable SA1649 // File name should match first type name | |
| internal sealed class CompletedTaskAttribute : System.Attribute | |
| { | |
| /// <summary> | |
| /// Initializes a new instance of the <see cref="CompletedTaskAttribute"/> class. | |
| /// </summary> | |
| public CompletedTaskAttribute() | |
| { | |
| } | |
| /// <summary> | |
| /// Gets or sets the fully qualified name of the member that returns a completed task. | |
| /// This is only used when the attribute is applied at the assembly level. | |
| /// </summary> | |
| /// <remarks> | |
| /// The format should be: "Namespace.TypeName.MemberName". | |
| /// For example: "System.Threading.Tasks.TplExtensions.TrueTask". | |
| /// </remarks> | |
| public string? Member { get; set; } | |
| } | |
| #pragma warning restore SA1649 // File name should match first type name | |
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,8 +1,11 @@ | ||||||||||||||
| <?xml version="1.0" encoding="utf-8" ?> | ||||||||||||||
| <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> | ||||||||||||||
| <ItemGroup> | ||||||||||||||
| <AdditionalFiles Include="$(MSBuildThisFileDirectory)AdditionalFiles\**"> | ||||||||||||||
| <AdditionalFiles Include="$(MSBuildThisFileDirectory)AdditionalFiles\*.txt"> | ||||||||||||||
| <Visible>false</Visible> | ||||||||||||||
| </AdditionalFiles> | ||||||||||||||
| <Compile Include="$(MSBuildThisFileDirectory)AdditionalFiles\*.cs" Link="%(Filename)%(Extension)"> | ||||||||||||||
| <Visible>false</Visible> | ||||||||||||||
| </Compile> | ||||||||||||||
|
Comment on lines
+7
to
+9
|
||||||||||||||
| <Compile Include="$(MSBuildThisFileDirectory)AdditionalFiles\*.cs" Link="%(Filename)%(Extension)"> | |
| <Visible>false</Visible> | |
| </Compile> | |
| <AdditionalFiles Include="$(MSBuildThisFileDirectory)AdditionalFiles\*.cs" Link="%(Filename)%(Extension)"> | |
| <Visible>false</Visible> | |
| </AdditionalFiles> |
Uh oh!
There was an error while loading. Please reload this page.