Skip to content

Commit

Permalink
MA0050 Fix code fixer for top level statements
Browse files Browse the repository at this point in the history
  • Loading branch information
meziantou committed Oct 24, 2023
1 parent 0971083 commit 1e55cc0
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 19 deletions.
4 changes: 2 additions & 2 deletions Directory.Build.targets
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@

<Otherwise>
<ItemGroup>
<PackageReference Update="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
<PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.6.0" />
<PackageReference Update="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0-beta1.23472.1" />
<PackageReference Update="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.8.0-2.final" />
</ItemGroup>
<PropertyGroup>
<DefineConstants>$(DefineConstants);ROSLYN4_4;ROSLYN_4_2_OR_GREATER;ROSLYN_4_4_OR_GREATER;ROSLYN_4_5_OR_GREATER;ROSLYN_4_6_OR_GREATER</DefineConstants>
Expand Down
59 changes: 42 additions & 17 deletions src/Meziantou.Analyzer/Rules/UseConfigureAwaitFixer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@ private static async Task<Document> AddConfigureAwait(CodeFixContext context, Sy
.WithExpression(AppendConfigureAwait(SyntaxFactory.IdentifierName(usingBlock.Declaration.Variables[0].Identifier)))
.WithoutLeadingTrivia();

editor.InsertBefore(usingBlock, variablesStatement);
editor.ReplaceNode(usingBlock, newUsingBlock);
editor.InsertBefore(newUsingBlock, variablesStatement);
return editor.GetChangedDocument();
}
}
Expand All @@ -96,31 +96,56 @@ private static async Task<Document> AddConfigureAwait(CodeFixContext context, Sy
var variablesStatement = SyntaxFactory.LocalDeclarationStatement(SyntaxFactory.VariableDeclaration(usingStatement.Declaration.Type, usingStatement.Declaration.Variables))
.WithLeadingTrivia(usingStatement.GetLeadingTrivia());


var usingStatements = SyntaxFactory.Block();
var statements = usingStatement.Parent as BlockSyntax;
if (statements != null)
if (usingStatement.Parent is GlobalStatementSyntax { Statement: LocalDeclarationStatementSyntax, Parent: CompilationUnitSyntax compilationUnit } globalStatement)
{
var index = statements.Statements.IndexOf(usingStatement);
usingStatements = SyntaxFactory.Block(SyntaxFactory.List(statements.Statements.Skip(index + 1)));
var index = compilationUnit.Members.IndexOf(globalStatement);
var usingStatements = SyntaxFactory.Block(SyntaxFactory.List(compilationUnit.Members
.Skip(index + 1)
.TakeWhile(m => m.IsKind(SyntaxKind.GlobalStatement))
.Select(m => ((GlobalStatementSyntax)m).Statement)));

foreach (var node in statements.Statements.Skip(index + 1))
foreach (var node in compilationUnit.Members.Skip(index + 1).TakeWhile(m => m.IsKind(SyntaxKind.GlobalStatement)))
{
editor.RemoveNode(node);
}

var newUsingStatement = SyntaxFactory.UsingStatement(
declaration: null,
expression: AppendConfigureAwait(SyntaxFactory.IdentifierName(usingStatement.Declaration.Variables[0].Identifier)),
statement: usingStatements)
.WithUsingKeyword(usingStatement.UsingKeyword)
.WithAwaitKeyword(usingStatement.AwaitKeyword)
.WithLeadingTrivia(usingStatement.GetLeadingTrivia());

editor.InsertBefore(usingStatement, variablesStatement);
editor.ReplaceNode(usingStatement, newUsingStatement);
}
else
{
var usingStatements = SyntaxFactory.Block();
if (usingStatement.Parent is BlockSyntax statements)
{
var index = statements.Statements.IndexOf(usingStatement);
usingStatements = SyntaxFactory.Block(SyntaxFactory.List(statements.Statements.Skip(index + 1)));

var newUsingStatement = SyntaxFactory.UsingStatement(
declaration: null,
expression: AppendConfigureAwait(SyntaxFactory.IdentifierName(usingStatement.Declaration.Variables[0].Identifier)),
statement: usingStatements.WithLeadingTrivia(usingStatement.GetTrailingTrivia()))
.WithUsingKeyword(usingStatement.UsingKeyword)
.WithAwaitKeyword(usingStatement.AwaitKeyword)
.WithLeadingTrivia(usingStatement.GetLeadingTrivia());
foreach (var node in statements.Statements.Skip(index + 1))
{
editor.RemoveNode(node);
}
}

var newUsingStatement = SyntaxFactory.UsingStatement(
declaration: null,
expression: AppendConfigureAwait(SyntaxFactory.IdentifierName(usingStatement.Declaration.Variables[0].Identifier)),
statement: usingStatements.WithLeadingTrivia(usingStatement.GetTrailingTrivia()))
.WithUsingKeyword(usingStatement.UsingKeyword)
.WithAwaitKeyword(usingStatement.AwaitKeyword)
.WithLeadingTrivia(usingStatement.GetLeadingTrivia());

editor.InsertBefore(usingStatement, variablesStatement);
editor.ReplaceNode(usingStatement, newUsingStatement);
}

editor.ReplaceNode(usingStatement, newUsingStatement);
editor.InsertBefore(newUsingStatement, variablesStatement);
return editor.GetChangedDocument();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using Meziantou.Analyzer.Rules;
using Microsoft.CodeAnalysis;
using TestHelper;
using Xunit;

Expand Down Expand Up @@ -229,6 +230,83 @@ await CreateProjectBuilder()
.ValidateAsync();
}

[Fact]
public async Task MissingConfigureAwait_AwaitDispose_TopLevelStatement_ShouldReportError()
{
const string SourceCode = """
using System;
using System.Threading.Tasks;
await using var [||]a = new AsyncDisposable();
Console.WriteLine();
class AsyncDisposable : IAsyncDisposable
{
public ValueTask DisposeAsync() => throw null;
}
""";

const string CodeFix = """
using System;
using System.Threading.Tasks;
var a = new AsyncDisposable();
await using (a.ConfigureAwait(false))
{
Console.WriteLine();
}
class AsyncDisposable : IAsyncDisposable
{
public ValueTask DisposeAsync() => throw null;
}
""";

await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ShouldFixCodeWith(CodeFix)
.WithOutputKind(OutputKind.ConsoleApplication)
.ValidateAsync();
}

[Fact]
public async Task MissingConfigureAwait_AwaitDispose_Block_TopLevelStatement_ShouldReportError()
{
const string SourceCode = """
using System;
using System.Threading.Tasks;
await using (var [||]a = new AsyncDisposable())
{
}
class AsyncDisposable : IAsyncDisposable
{
public ValueTask DisposeAsync() => throw null;
}
""";

const string CodeFix = """
using System;
using System.Threading.Tasks;
var a = new AsyncDisposable();
await using (a.ConfigureAwait(false))
{
}
class AsyncDisposable : IAsyncDisposable
{
public ValueTask DisposeAsync() => throw null;
}
""";
await CreateProjectBuilder()
.WithSourceCode(SourceCode)
.ShouldFixCodeWith(CodeFix)
.WithOutputKind(@OutputKind.ConsoleApplication)
.ValidateAsync();
}

[Fact]
public async Task MissingConfigureAwait_AwaitDispose_BlockWithoutVariable()
{
Expand Down

0 comments on commit 1e55cc0

Please sign in to comment.