xunit/xunit#2849: Update xUnit1030 to handle ConfigureAwaitOptions
This commit is contained in:
Родитель
49afdc9427
Коммит
8aa39e4da2
|
@ -1,4 +1,6 @@
|
|||
using System.Composition;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CodeActions;
|
||||
|
@ -12,6 +14,7 @@ namespace Xunit.Analyzers.Fixes;
|
|||
public class DoNotUseConfigureAwaitFixer : BatchedCodeFixProvider
|
||||
{
|
||||
public const string Key_RemoveConfigureAwait = "xUnit1030_RemoveConfigureAwait";
|
||||
public const string Key_ReplaceArgumentValue = "xUnit1030_ReplaceArgumentValue";
|
||||
|
||||
public DoNotUseConfigureAwaitFixer() :
|
||||
base(Descriptors.X1030_DoNotUseConfigureAwait.Id)
|
||||
|
@ -23,6 +26,16 @@ public class DoNotUseConfigureAwaitFixer : BatchedCodeFixProvider
|
|||
if (root is null)
|
||||
return;
|
||||
|
||||
var diagnostic = context.Diagnostics.FirstOrDefault();
|
||||
if (diagnostic is null)
|
||||
return;
|
||||
|
||||
// Get the original and replacement values
|
||||
if (!diagnostic.Properties.TryGetValue(Constants.Properties.ArgumentValue, out var original))
|
||||
return;
|
||||
if (!diagnostic.Properties.TryGetValue(Constants.Properties.Replacement, out var replacement))
|
||||
return;
|
||||
|
||||
// The syntax node (the invocation) will include "(any preceding trivia)(any preceding code).ConfigureAwait(args)" despite
|
||||
// the context.Span only covering "ConfigureAwait(args)". So we need to replace the whole invocation
|
||||
// with an invocation that does not include the ConfigureAwait call.
|
||||
|
@ -30,19 +43,40 @@ public class DoNotUseConfigureAwaitFixer : BatchedCodeFixProvider
|
|||
var syntaxText = syntaxNode.ToFullString();
|
||||
|
||||
// Remove the context span (plus the preceding .)
|
||||
var newSyntaxText = syntaxText.Substring(0, context.Span.Start - syntaxNode.FullSpan.Start - 1);
|
||||
var newSyntaxNode = SyntaxFactory.ParseExpression(newSyntaxText);
|
||||
var removeConfigureAwaitText = syntaxText.Substring(0, context.Span.Start - syntaxNode.FullSpan.Start - 1);
|
||||
var removeConfigureAwaitNode = SyntaxFactory.ParseExpression(removeConfigureAwaitText);
|
||||
|
||||
// Only offer the removal fix if the replacement value is 'true', because anybody using ConfigureAwaitOptions
|
||||
// will want to just add the extra value, not remove the call entirely.
|
||||
if (replacement == "true")
|
||||
context.RegisterCodeFix(
|
||||
CodeAction.Create(
|
||||
"Remove ConfigureAwait call",
|
||||
async ct =>
|
||||
{
|
||||
var editor = await DocumentEditor.CreateAsync(context.Document, ct).ConfigureAwait(false);
|
||||
editor.ReplaceNode(syntaxNode, removeConfigureAwaitNode);
|
||||
return editor.GetChangedDocument();
|
||||
},
|
||||
Key_RemoveConfigureAwait
|
||||
),
|
||||
context.Diagnostics
|
||||
);
|
||||
|
||||
// Offer the replacement fix
|
||||
var replaceConfigureAwaitText = removeConfigureAwaitText + ".ConfigureAwait(" + replacement + ")";
|
||||
var replaceConfigureAwaitNode = SyntaxFactory.ParseExpression(replaceConfigureAwaitText);
|
||||
|
||||
context.RegisterCodeFix(
|
||||
CodeAction.Create(
|
||||
"Remove ConfigureAwait call",
|
||||
string.Format(CultureInfo.CurrentCulture, "Replace ConfigureAwait({0}) with ConfigureAwait({1})", original, replacement),
|
||||
async ct =>
|
||||
{
|
||||
var editor = await DocumentEditor.CreateAsync(context.Document, ct).ConfigureAwait(false);
|
||||
editor.ReplaceNode(syntaxNode, newSyntaxNode);
|
||||
editor.ReplaceNode(syntaxNode, replaceConfigureAwaitNode);
|
||||
return editor.GetChangedDocument();
|
||||
},
|
||||
Key_RemoveConfigureAwait
|
||||
Key_ReplaceArgumentValue
|
||||
),
|
||||
context.Diagnostics
|
||||
);
|
||||
|
|
|
@ -186,4 +186,130 @@ public class TestClass {{
|
|||
await Verify.VerifyAnalyzer(source, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP
|
||||
|
||||
public class ConfigureAwait_ConfigureAwaitOptions
|
||||
{
|
||||
[Fact]
|
||||
public async void NonTestMethod_DoesNotTrigger()
|
||||
{
|
||||
var source = @"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class NonTestClass {
|
||||
public async Task NonTestMethod() {
|
||||
await Task.Delay(1).ConfigureAwait(ConfigureAwaitOptions.None);
|
||||
}
|
||||
}";
|
||||
|
||||
await Verify.VerifyAnalyzer(source);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("ConfigureAwaitOptions.ContinueOnCapturedContext")]
|
||||
[InlineData("ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext")]
|
||||
[InlineData("ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.SuppressThrowing | ConfigureAwaitOptions.ContinueOnCapturedContext")]
|
||||
public async void ValidValue_DoesNotTrigger(string enumValue)
|
||||
{
|
||||
var source = $@"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
await Task.Delay(1).ConfigureAwait({enumValue});
|
||||
}}
|
||||
}}";
|
||||
|
||||
await Verify.VerifyAnalyzer(source);
|
||||
}
|
||||
|
||||
public static TheoryData<string> InvalidValues = new()
|
||||
{
|
||||
// Literal values
|
||||
"ConfigureAwaitOptions.None",
|
||||
"ConfigureAwaitOptions.SuppressThrowing",
|
||||
"ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.SuppressThrowing",
|
||||
// Reference values (we don't do lookup)
|
||||
"enumVar",
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues))]
|
||||
public async void InvalidValue_TaskWithAwait_DoesNotTrigger(string enumValue)
|
||||
{
|
||||
var source = $@"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var enumVar = ConfigureAwaitOptions.ContinueOnCapturedContext;
|
||||
await Task.Delay(1).ConfigureAwait({enumValue});
|
||||
}}
|
||||
}}";
|
||||
var expected =
|
||||
Verify
|
||||
.Diagnostic()
|
||||
.WithSpan(9, 29, 9, 45 + enumValue.Length)
|
||||
.WithArguments(enumValue, "Ensure ConfigureAwaitOptions.ContinueOnCapturedContext in the flags.");
|
||||
|
||||
await Verify.VerifyAnalyzer(source, expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues))]
|
||||
public async void InvalidValue_TaskWithoutAwait_Triggers(string argumentValue)
|
||||
{
|
||||
var source = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public void TestMethod() {{
|
||||
var enumVar = ConfigureAwaitOptions.ContinueOnCapturedContext;
|
||||
Task.Delay(1).ConfigureAwait({argumentValue}).GetAwaiter().GetResult();
|
||||
}}
|
||||
}}";
|
||||
var expected =
|
||||
Verify
|
||||
.Diagnostic()
|
||||
.WithSpan(9, 23, 9, 39 + argumentValue.Length)
|
||||
.WithArguments(argumentValue, "Ensure ConfigureAwaitOptions.ContinueOnCapturedContext in the flags.");
|
||||
|
||||
await Verify.VerifyAnalyzer(source, expected);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues))]
|
||||
public async void InvalidValue_TaskOfT_Triggers(string argumentValue)
|
||||
{
|
||||
var source = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var enumVar = ConfigureAwaitOptions.ContinueOnCapturedContext;
|
||||
var task = Task.FromResult(42);
|
||||
await task.ConfigureAwait({argumentValue});
|
||||
}}
|
||||
}}";
|
||||
var expected =
|
||||
Verify
|
||||
.Diagnostic()
|
||||
.WithSpan(10, 20, 10, 36 + argumentValue.Length)
|
||||
.WithArguments(argumentValue, "Ensure ConfigureAwaitOptions.ContinueOnCapturedContext in the flags.");
|
||||
|
||||
await Verify.VerifyAnalyzer(source, expected);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -177,5 +177,281 @@ public class TestClass {
|
|||
await Verify.VerifyCodeFix(before, after, DoNotUseConfigureAwaitFixer.Key_RemoveConfigureAwait);
|
||||
}
|
||||
}
|
||||
|
||||
public class ReplaceConfigureAwait
|
||||
{
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues), MemberType = typeof(ConfigureAwait_Boolean))]
|
||||
public async void Task_Async(string argumentValue)
|
||||
{
|
||||
var before = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var booleanVar = true;
|
||||
await Task.Delay(1).[|ConfigureAwait({argumentValue})|];
|
||||
}}
|
||||
}}";
|
||||
|
||||
var after = @"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {
|
||||
[Fact]
|
||||
public async Task TestMethod() {
|
||||
var booleanVar = true;
|
||||
await Task.Delay(1).ConfigureAwait(true);
|
||||
}
|
||||
}";
|
||||
|
||||
await Verify.VerifyCodeFix(before, after, DoNotUseConfigureAwaitFixer.Key_ReplaceArgumentValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues), MemberType = typeof(ConfigureAwait_Boolean))]
|
||||
public async void Task_NonAsync(string argumentValue)
|
||||
{
|
||||
var before = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public void TestMethod() {{
|
||||
var booleanVar = true;
|
||||
Task.Delay(1).[|ConfigureAwait({argumentValue})|].GetAwaiter().GetResult();
|
||||
}}
|
||||
}}";
|
||||
|
||||
var after = @"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {
|
||||
[Fact]
|
||||
public void TestMethod() {
|
||||
var booleanVar = true;
|
||||
Task.Delay(1).ConfigureAwait(true).GetAwaiter().GetResult();
|
||||
}
|
||||
}";
|
||||
|
||||
await Verify.VerifyCodeFix(before, after, DoNotUseConfigureAwaitFixer.Key_ReplaceArgumentValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues), MemberType = typeof(ConfigureAwait_Boolean))]
|
||||
public async void TaskOfT(string argumentValue)
|
||||
{
|
||||
var before = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var booleanVar = true;
|
||||
var task = Task.FromResult(42);
|
||||
await task.[|ConfigureAwait({argumentValue})|];
|
||||
}}
|
||||
}}";
|
||||
|
||||
var after = @"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {
|
||||
[Fact]
|
||||
public async Task TestMethod() {
|
||||
var booleanVar = true;
|
||||
var task = Task.FromResult(42);
|
||||
await task.ConfigureAwait(true);
|
||||
}
|
||||
}";
|
||||
|
||||
await Verify.VerifyCodeFix(before, after, DoNotUseConfigureAwaitFixer.Key_ReplaceArgumentValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues), MemberType = typeof(ConfigureAwait_Boolean))]
|
||||
public async void ValueTask(string argumentValue)
|
||||
{
|
||||
var before = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var booleanVar = true;
|
||||
var valueTask = default(ValueTask);
|
||||
await valueTask.[|ConfigureAwait({argumentValue})|];
|
||||
}}
|
||||
}}";
|
||||
|
||||
var after = @"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {
|
||||
[Fact]
|
||||
public async Task TestMethod() {
|
||||
var booleanVar = true;
|
||||
var valueTask = default(ValueTask);
|
||||
await valueTask.ConfigureAwait(true);
|
||||
}
|
||||
}";
|
||||
|
||||
await Verify.VerifyCodeFix(before, after, DoNotUseConfigureAwaitFixer.Key_ReplaceArgumentValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues), MemberType = typeof(ConfigureAwait_Boolean))]
|
||||
public async void ValueTaskOfT(string argumentValue)
|
||||
{
|
||||
var before = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var booleanVar = true;
|
||||
var valueTask = default(ValueTask<object>);
|
||||
await valueTask.[|ConfigureAwait({argumentValue})|];
|
||||
}}
|
||||
}}";
|
||||
|
||||
var after = @"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {
|
||||
[Fact]
|
||||
public async Task TestMethod() {
|
||||
var booleanVar = true;
|
||||
var valueTask = default(ValueTask<object>);
|
||||
await valueTask.ConfigureAwait(true);
|
||||
}
|
||||
}";
|
||||
|
||||
await Verify.VerifyCodeFix(before, after, DoNotUseConfigureAwaitFixer.Key_ReplaceArgumentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if NETCOREAPP
|
||||
|
||||
public class ConfigureAwait_ConfigureAwaitOptions
|
||||
{
|
||||
public static TheoryData<string> InvalidValues = new()
|
||||
{
|
||||
// Literal values
|
||||
"ConfigureAwaitOptions.None",
|
||||
"ConfigureAwaitOptions.SuppressThrowing",
|
||||
"ConfigureAwaitOptions.ForceYielding | ConfigureAwaitOptions.SuppressThrowing",
|
||||
// Reference values (we don't do lookup)
|
||||
"enumVar",
|
||||
};
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues))]
|
||||
public async void Task_Async(string argumentValue)
|
||||
{
|
||||
var before = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var enumVar = ConfigureAwaitOptions.ContinueOnCapturedContext;
|
||||
await Task.Delay(1).[|ConfigureAwait({argumentValue})|];
|
||||
}}
|
||||
}}";
|
||||
|
||||
var after = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var enumVar = ConfigureAwaitOptions.ContinueOnCapturedContext;
|
||||
await Task.Delay(1).ConfigureAwait({argumentValue} | ConfigureAwaitOptions.ContinueOnCapturedContext);
|
||||
}}
|
||||
}}";
|
||||
|
||||
await Verify.VerifyCodeFix(before, after, DoNotUseConfigureAwaitFixer.Key_ReplaceArgumentValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues))]
|
||||
public async void Task_NonAsync(string argumentValue)
|
||||
{
|
||||
var before = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public void TestMethod() {{
|
||||
var enumVar = ConfigureAwaitOptions.ContinueOnCapturedContext;
|
||||
Task.Delay(1).[|ConfigureAwait({argumentValue})|].GetAwaiter().GetResult();
|
||||
}}
|
||||
}}";
|
||||
|
||||
var after = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public void TestMethod() {{
|
||||
var enumVar = ConfigureAwaitOptions.ContinueOnCapturedContext;
|
||||
Task.Delay(1).ConfigureAwait({argumentValue} | ConfigureAwaitOptions.ContinueOnCapturedContext).GetAwaiter().GetResult();
|
||||
}}
|
||||
}}";
|
||||
|
||||
await Verify.VerifyCodeFix(before, after, DoNotUseConfigureAwaitFixer.Key_ReplaceArgumentValue);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidValues))]
|
||||
public async void TaskOfT(string argumentValue)
|
||||
{
|
||||
var before = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var enumVar = ConfigureAwaitOptions.ContinueOnCapturedContext;
|
||||
var task = Task.FromResult(42);
|
||||
await task.[|ConfigureAwait({argumentValue})|];
|
||||
}}
|
||||
}}";
|
||||
|
||||
var after = @$"
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
public class TestClass {{
|
||||
[Fact]
|
||||
public async Task TestMethod() {{
|
||||
var enumVar = ConfigureAwaitOptions.ContinueOnCapturedContext;
|
||||
var task = Task.FromResult(42);
|
||||
await task.ConfigureAwait({argumentValue} | ConfigureAwaitOptions.ContinueOnCapturedContext);
|
||||
}}
|
||||
}}";
|
||||
|
||||
await Verify.VerifyCodeFix(before, after, DoNotUseConfigureAwaitFixer.Key_ReplaceArgumentValue);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -75,6 +75,7 @@ public static class Constants
|
|||
/// </summary>
|
||||
public static class Properties
|
||||
{
|
||||
public const string ArgumentValue = "ArgumentValue";
|
||||
public const string AssertMethodName = "AssertMethodName";
|
||||
public const string DeclaringType = "DeclaringType";
|
||||
public const string IgnoreCase = "IgnoreCase";
|
||||
|
|
|
@ -20,6 +20,9 @@ public static class TypeSymbolFactory
|
|||
public static INamedTypeSymbol? CollectionDefinitionAttribute(Compilation compilation) =>
|
||||
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("Xunit.CollectionDefinitionAttribute");
|
||||
|
||||
public static INamedTypeSymbol? ConfigureAwaitOptions(Compilation compilation) =>
|
||||
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Threading.Tasks.ConfigureAwaitOptions");
|
||||
|
||||
public static INamedTypeSymbol? ConfiguredTaskAwaitable(Compilation compilation) =>
|
||||
Guard.ArgumentNotNull(compilation).GetTypeByMetadataName("System.Runtime.CompilerServices.ConfiguredTaskAwaitable");
|
||||
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
|
@ -74,7 +76,10 @@ public class DoNotUseConfigureAwait : XunitDiagnosticAnalyzer
|
|||
|
||||
// Determine the invocation type and resolution
|
||||
var parameterType = invocation.TargetMethod.Parameters[0].Type;
|
||||
var configureAwaitOptions = TypeSymbolFactory.ConfigureAwaitOptions(context.Compilation);
|
||||
var argumentValue = argumentSyntax.ToFullString();
|
||||
string resolution;
|
||||
string replacement;
|
||||
|
||||
// We want to exempt calls with "(true)" because of CA2007
|
||||
if (SymbolEqualityComparer.Default.Equals(parameterType, context.Compilation.GetSpecialType(SpecialType.System_Boolean)))
|
||||
|
@ -83,6 +88,18 @@ public class DoNotUseConfigureAwait : XunitDiagnosticAnalyzer
|
|||
return;
|
||||
|
||||
resolution = "Omit ConfigureAwait, or use ConfigureAwait(true) to avoid CA2007.";
|
||||
replacement = "true";
|
||||
}
|
||||
// We want to exempt calls which include ConfigureAwaitOptions.ContinueOnCapturedContext
|
||||
else if (SymbolEqualityComparer.Default.Equals(parameterType, configureAwaitOptions))
|
||||
{
|
||||
if (invocation.SemanticModel is null)
|
||||
return;
|
||||
if (ContainsContinueOnCapturedContext(argumentSyntax.Expression, invocation.SemanticModel, configureAwaitOptions, context.CancellationToken))
|
||||
return;
|
||||
|
||||
resolution = "Ensure ConfigureAwaitOptions.ContinueOnCapturedContext in the flags.";
|
||||
replacement = argumentValue + " | ConfigureAwaitOptions.ContinueOnCapturedContext";
|
||||
}
|
||||
else
|
||||
return;
|
||||
|
@ -97,7 +114,39 @@ public class DoNotUseConfigureAwait : XunitDiagnosticAnalyzer
|
|||
var textSpan = new TextSpan(methodCallChildren[2].SpanStart, length);
|
||||
var location = Location.Create(invocation.Syntax.SyntaxTree, textSpan);
|
||||
|
||||
context.ReportDiagnostic(Diagnostic.Create(Descriptors.X1030_DoNotUseConfigureAwait, location, argumentSyntax.ToFullString(), resolution));
|
||||
// Provide the original value and replacement value to the fixer
|
||||
var builder = ImmutableDictionary.CreateBuilder<string, string?>();
|
||||
builder[Constants.Properties.ArgumentValue] = argumentValue;
|
||||
builder[Constants.Properties.Replacement] = replacement;
|
||||
|
||||
context.ReportDiagnostic(
|
||||
Diagnostic.Create(
|
||||
Descriptors.X1030_DoNotUseConfigureAwait,
|
||||
location,
|
||||
builder.ToImmutable(),
|
||||
argumentValue,
|
||||
resolution
|
||||
)
|
||||
);
|
||||
}, OperationKind.Invocation);
|
||||
}
|
||||
|
||||
static bool ContainsContinueOnCapturedContext(
|
||||
ExpressionSyntax expression,
|
||||
SemanticModel semanticModel,
|
||||
INamedTypeSymbol configureAwaitOptions,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// If we have a binary expression of bitwise OR, we evaluate both sides of the expression
|
||||
if (expression is BinaryExpressionSyntax binaryExpression && binaryExpression.Kind() == SyntaxKind.BitwiseOrExpression)
|
||||
return ContainsContinueOnCapturedContext(binaryExpression.Left, semanticModel, configureAwaitOptions, cancellationToken)
|
||||
|| ContainsContinueOnCapturedContext(binaryExpression.Right, semanticModel, configureAwaitOptions, cancellationToken);
|
||||
|
||||
// Look for constant value of enum type
|
||||
var symbol = semanticModel.GetSymbolInfo(expression, cancellationToken).Symbol;
|
||||
if (symbol is not null && SymbolEqualityComparer.Default.Equals(symbol.ContainingType, configureAwaitOptions) && symbol.Name == "ContinueOnCapturedContext")
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче