Skip to content

Commit b14ffe8

Browse files
committed
Code fix for DisposeAsync when not null.
No fix when possibly null for now, not sure what ideal code gen is.
1 parent 4b7088a commit b14ffe8

3 files changed

Lines changed: 138 additions & 65 deletions

File tree

IDisposableAnalyzers.NetCoreTests/IDISP002DisposeMemberTests/CodeFix.cs

Lines changed: 42 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using Microsoft.CodeAnalysis.Diagnostics;
66
using NUnit.Framework;
77

8-
[Ignore("Not sure how we want the code gen.")]
98
public static class CodeFix
109
{
1110
private static readonly DiagnosticAnalyzer Analyzer = new FieldAndPropertyDeclarationAnalyzer();
@@ -16,6 +15,7 @@ public static class CodeFix
1615
public static void FieldIAsyncDisposable()
1716
{
1817
var before = @"
18+
#nullable enable
1919
namespace N
2020
{
2121
using System;
@@ -32,7 +32,8 @@ public ValueTask DisposeAsync()
3232
}
3333
}";
3434

35-
var code = @"
35+
var after = @"
36+
#nullable enable
3637
namespace N
3738
{
3839
using System;
@@ -49,13 +50,14 @@ public async ValueTask DisposeAsync()
4950
}
5051
}
5152
}";
52-
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, code);
53+
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, after);
5354
}
5455

5556
[Test]
5657
public static void FieldOfTypeObjectIAsyncDisposable()
5758
{
5859
var before = @"
60+
#nullable enable
5961
namespace N
6062
{
6163
using System;
@@ -72,7 +74,8 @@ public ValueTask DisposeAsync()
7274
}
7375
}";
7476

75-
var code = @"
77+
var after = @"
78+
#nullable enable
7679
namespace N
7780
{
7881
using System;
@@ -89,13 +92,14 @@ public async ValueTask DisposeAsync()
8992
}
9093
}
9194
}";
92-
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, code);
95+
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, after);
9396
}
9497

9598
[Test]
9699
public static void FieldIAsyncDisposableAndIDisposable1()
97100
{
98101
var before = @"
102+
#nullable enable
99103
namespace N
100104
{
101105
using System;
@@ -117,7 +121,8 @@ public ValueTask DisposeAsync()
117121
}
118122
}";
119123

120-
var code = @"
124+
var after = @"
125+
#nullable enable
121126
namespace N
122127
{
123128
using System;
@@ -126,7 +131,7 @@ namespace N
126131
127132
sealed class C : IDisposable, IAsyncDisposable
128133
{
129-
private readonly IAsyncDisposable disposable = File.OpenRead(string.Empty);
134+
private readonly Stream disposable = File.OpenRead(string.Empty);
130135
131136
public void Dispose()
132137
{
@@ -139,13 +144,14 @@ public async ValueTask DisposeAsync()
139144
}
140145
}
141146
}";
142-
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, code);
147+
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, after);
143148
}
144149

145150
[Test]
146151
public static void FieldIAsyncDisposableAndIDisposable2()
147152
{
148153
var before = @"
154+
#nullable enable
149155
namespace N
150156
{
151157
using System;
@@ -167,7 +173,8 @@ public async ValueTask DisposeAsync()
167173
}
168174
}";
169175

170-
var code = @"
176+
var after = @"
177+
#nullable enable
171178
namespace N
172179
{
173180
using System;
@@ -189,7 +196,32 @@ public async ValueTask DisposeAsync()
189196
}
190197
}
191198
}";
192-
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, code);
199+
RoslynAssert.CodeFix(Analyzer, Fix, ExpectedDiagnostic, before, after);
200+
}
201+
202+
[Test]
203+
public static void NullableFieldIAsyncDisposable()
204+
{
205+
var code = @"
206+
#nullable enable
207+
namespace N
208+
{
209+
using System;
210+
using System.IO;
211+
using System.Threading.Tasks;
212+
213+
public class C : IAsyncDisposable
214+
{
215+
↓private readonly IAsyncDisposable? disposable = File.OpenRead(string.Empty);
216+
217+
public ValueTask DisposeAsync()
218+
{
219+
return default(ValueTask);
220+
}
221+
}
222+
}";
223+
224+
RoslynAssert.NoFix(Analyzer, Fix, ExpectedDiagnostic, code);
193225
}
194226
}
195227
}

IDisposableAnalyzers/CodeFixes/DisposeMemberFix.cs

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -39,30 +39,32 @@ protected override async Task RegisterCodeFixesAsync(DocumentEditorCodeFixContex
3939
{
4040
switch (method)
4141
{
42-
case { Identifier: { ValueText: "DisposeAsync" } }:
43-
// Not sure how we want the code gen.
44-
////context.RegisterCodeFix(
45-
//// $"{symbol.Name}.DisposeAsync() in {method}",
46-
//// (editor, cancellationToken) => editor.ReplaceNode(
47-
//// method,
48-
//// x => DisposeAsync(x, editor, cancellationToken)),
49-
//// "DisposeAsync",
50-
//// diagnostic);
51-
52-
////MethodDeclarationSyntax DisposeAsync(MethodDeclarationSyntax old, DocumentEditor editor, CancellationToken cancellationToken)
53-
////{
54-
//// return old switch
55-
//// {
56-
//// { ExpressionBody: { Expression: { } expression } }
57-
//// => old.AsBlockBody(
58-
//// SyntaxFactory.ExpressionStatement(expression),
59-
//// IDisposableFactory.DisposeAsyncStatement(disposable, editor.SemanticModel, cancellationToken)),
60-
//// { Body: { } body }
61-
//// => old.WithBody(
62-
//// body.AddStatements(IDisposableFactory.DisposeAsyncStatement(disposable, editor.SemanticModel, cancellationToken))),
63-
//// _ => throw new InvalidOperationException("Error generating DisposeAsync"),
64-
//// };
65-
////}
42+
case { Identifier: { ValueText: "DisposeAsync" } }
43+
when IDisposableFactory.MemberAccessContext.Create(disposable, method, semanticModel, context.CancellationToken) is { NotNull: { } }:
44+
context.RegisterCodeFix(
45+
$"{symbol.Name}.DisposeAsync() in {method}",
46+
(editor, cancellationToken) => editor.ReplaceNode(
47+
method,
48+
x => DisposeAsync(x, editor, cancellationToken)),
49+
"DisposeAsync",
50+
diagnostic);
51+
52+
MethodDeclarationSyntax DisposeAsync(MethodDeclarationSyntax old, DocumentEditor editor, CancellationToken cancellationToken)
53+
{
54+
return old switch
55+
{
56+
{ ExpressionBody: { Expression: { } expression } }
57+
=> old.AsBlockBody(
58+
SyntaxFactory.ExpressionStatement(expression),
59+
IDisposableFactory.DisposeAsyncStatement(disposable, method!, editor.SemanticModel, cancellationToken))
60+
.WithAsync(),
61+
{ Body: { } body }
62+
=> old.WithBody(
63+
body.AddStatements(IDisposableFactory.DisposeAsyncStatement(disposable, method!, editor.SemanticModel, cancellationToken)))
64+
.WithAsync(),
65+
_ => throw new InvalidOperationException("Error generating DisposeAsync"),
66+
};
67+
}
6668

6769
break;
6870
case { Identifier: { ValueText: "Dispose" }, ParameterList: { Parameters: { Count: 1 } parameters }, Body: { } body }:
@@ -79,21 +81,21 @@ void DisposeInVirtual(DocumentEditor editor, CancellationToken cancellationToken
7981
{
8082
editor.InsertAfter(
8183
ifNotDisposingReturn,
82-
IDisposableFactory.DisposeStatement(disposable, editor.SemanticModel, cancellationToken));
84+
IDisposableFactory.DisposeStatement(disposable, method!, editor.SemanticModel, cancellationToken));
8385
}
8486
else if (TryFindIfDisposing(method!, out var ifDisposing))
8587
{
8688
_ = editor.ReplaceNode(
8789
ifDisposing.Statement,
8890
x => x is BlockSyntax ifBlock
89-
? ifBlock.AddStatements(IDisposableFactory.DisposeStatement(disposable, editor.SemanticModel, cancellationToken))
90-
: SyntaxFactory.Block(x, IDisposableFactory.DisposeStatement(disposable, editor.SemanticModel, cancellationToken)));
91+
? ifBlock.AddStatements(IDisposableFactory.DisposeStatement(disposable, method!, editor.SemanticModel, cancellationToken))
92+
: SyntaxFactory.Block(x, IDisposableFactory.DisposeStatement(disposable, method!, editor.SemanticModel, cancellationToken)));
9193
}
9294
else
9395
{
9496
ifDisposing = SyntaxFactory.IfStatement(
9597
SyntaxFactory.IdentifierName(parameters[0].Identifier),
96-
SyntaxFactory.Block(IDisposableFactory.DisposeStatement(disposable, editor.SemanticModel, cancellationToken)));
98+
SyntaxFactory.Block(IDisposableFactory.DisposeStatement(disposable, method!, editor.SemanticModel, cancellationToken)));
9799
if (DisposeMethod.TryFindBaseCall(method!, editor.SemanticModel, cancellationToken, out var baseCall))
98100
{
99101
editor.InsertBefore(baseCall.Parent, ifDisposing);
@@ -119,13 +121,13 @@ void DisposeWhenNoParameter(DocumentEditor editor, CancellationToken cancellatio
119121
{
120122
editor.InsertBefore(
121123
baseCall.Parent,
122-
IDisposableFactory.DisposeStatement(disposable, editor.SemanticModel, cancellationToken));
124+
IDisposableFactory.DisposeStatement(disposable, method!, editor.SemanticModel, cancellationToken));
123125
}
124126
else
125127
{
126128
_ = editor.ReplaceNode(
127129
body,
128-
x => x.AddStatements(IDisposableFactory.DisposeStatement(disposable, editor.SemanticModel, cancellationToken)));
130+
x => x.AddStatements(IDisposableFactory.DisposeStatement(disposable, method!, editor.SemanticModel, cancellationToken)));
129131
}
130132
}
131133

@@ -138,11 +140,12 @@ void DisposeWhenNoParameter(DocumentEditor editor, CancellationToken cancellatio
138140
method,
139141
x => x.AsBlockBody(
140142
SyntaxFactory.ExpressionStatement(expression),
141-
IDisposableFactory.DisposeStatement(disposable, editor.SemanticModel, cancellationToken))),
143+
IDisposableFactory.DisposeStatement(disposable, method!, editor.SemanticModel, cancellationToken))),
142144
"Dispose member.",
143145
diagnostic);
144146
break;
145147
case { Identifier: { ValueText: "Dispose" } }:
148+
case { Identifier: { ValueText: "DisposeAsync" } }:
146149
break;
147150
default:
148151
context.RegisterCodeFix(
@@ -160,10 +163,10 @@ MethodDeclarationSyntax Dispose(MethodDeclarationSyntax old, DocumentEditor edit
160163
{ ExpressionBody: { Expression: { } expression } }
161164
=> old.AsBlockBody(
162165
SyntaxFactory.ExpressionStatement(expression),
163-
IDisposableFactory.DisposeStatement(disposable, editor.SemanticModel, cancellationToken)),
166+
IDisposableFactory.DisposeStatement(disposable, method!, editor.SemanticModel, cancellationToken)),
164167
{ Body: { } body }
165168
=> old.WithBody(body.AddStatements(
166-
IDisposableFactory.DisposeStatement(disposable, editor.SemanticModel, cancellationToken))),
169+
IDisposableFactory.DisposeStatement(disposable, method!, editor.SemanticModel, cancellationToken))),
167170
_ => throw new InvalidOperationException("Error generating Dispose"),
168171
};
169172
}

0 commit comments

Comments
 (0)