Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 10 additions & 12 deletions src/Meziantou.Analyzer.CodeFixers/Rules/UseConfigureAwaitFixer.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.CodeAnalysis.Formatting;

namespace Meziantou.Analyzer.Rules;

[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
Expand Down Expand Up @@ -123,7 +125,8 @@ private static async Task<Document> AddConfigureAwait(CodeFixContext context, Sy
{
var newBlock = SyntaxFactory.Block(variablesStatement, newUsingBlock.WithoutLeadingTrivia())
.WithLeadingTrivia(usingBlock.GetLeadingTrivia())
.WithTrailingTrivia(usingBlock.GetTrailingTrivia());
.WithTrailingTrivia(usingBlock.GetTrailingTrivia())
.WithAdditionalAnnotations(Formatter.Annotation);
editor.ReplaceNode(usingBlock, newBlock);
}

Expand Down Expand Up @@ -205,21 +208,16 @@ private static async Task<Document> AddConfigureAwait(CodeFixContext context, Sy

bool TryInsertVariableStatementBeforeUsing(LocalDeclarationStatementSyntax variableStatement, UsingStatementSyntax usingStatement)
{
var insertionTarget = usingStatement;
while (insertionTarget.Parent is UsingStatementSyntax parentUsing &&
parentUsing.Statement == insertionTarget &&
parentUsing.Declaration is null)
{
insertionTarget = parentUsing;
}

if (insertionTarget.Parent is BlockSyntax or SwitchSectionSyntax)
// The declaration can only be extracted where a statement can be inserted just before the using statement.
// Moving it before an enclosing statement would evaluate the initializer before that statement,
// so the caller wraps the declaration and the using statement in a block instead.
if (usingStatement.Parent is BlockSyntax or SwitchSectionSyntax)
{
editor.InsertBefore(insertionTarget, variableStatement);
editor.InsertBefore(usingStatement, variableStatement);
return true;
}

if (insertionTarget.Parent is GlobalStatementSyntax { Parent: CompilationUnitSyntax } globalStatement)
if (usingStatement.Parent is GlobalStatementSyntax { Parent: CompilationUnitSyntax } globalStatement)
{
editor.InsertBefore(globalStatement, SyntaxFactory.GlobalStatement(variableStatement));
return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -790,11 +790,13 @@ class ClassTest
async Task Test()
{
Stream stream = OpenWrite();
var streamWriter = new StreamWriter(stream);
await using (stream.ConfigureAwait(false))
await using (streamWriter.ConfigureAwait(false))
{
await streamWriter.WriteAsync("test-data").ConfigureAwait(false);
var streamWriter = new StreamWriter(stream);
await using (streamWriter.ConfigureAwait(false))
{
await streamWriter.WriteAsync("test-data").ConfigureAwait(false);
}
}
}

Expand All @@ -805,6 +807,59 @@ async Task Test()
return test.RunAsync();
}

[Fact]
public Task AwaitUsing_UnderUsingWithSideEffect_KeepsEvaluationOrder()
{
var test = CreateTest();
test.TestCode = """
using System;
using System.Threading.Tasks;
class ClassTest
{
async Task Test()
{
using (Create("outer"))
await using (var {|MA0004:resource = Create("inner")|})
{
}
}

static Resource Create(string text) => throw null;
}
class Resource : IDisposable, IAsyncDisposable
{
public void Dispose() => throw null;
public ValueTask DisposeAsync() => throw null;
}
""";
test.FixedCode = """
using System;
using System.Threading.Tasks;
class ClassTest
{
async Task Test()
{
using (Create("outer"))
{
var resource = Create("inner");
await using (resource.ConfigureAwait(false))
{
}
}
}

static Resource Create(string text) => throw null;
}
class Resource : IDisposable, IAsyncDisposable
{
public void Dispose() => throw null;
public ValueTask DisposeAsync() => throw null;
}
""";

return test.RunAsync();
}

[Fact]
public Task AwaitUsingVar_InsideConfiguredUsing_ShouldNotThrow()
{
Expand Down