Skip to content

Commit 19258f0

Browse files
committed
refactor
1 parent df4031f commit 19258f0

5 files changed

Lines changed: 125 additions & 30 deletions

File tree

IDisposableAnalyzers.Test/Helpers/DisposableWalkerTests.Disposes.cs

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ internal C(string fileName)
4040
[TestCase("disposable?.Dispose()")]
4141
[TestCase("(disposable as IDisposable)?.Dispose()")]
4242
[TestCase("((IDisposable)disposable)?.Dispose()")]
43-
public static void WhenDisposed(string expression)
43+
public static void DisposeInvocation(string expression)
4444
{
4545
var code = @"
4646
namespace N
@@ -66,7 +66,7 @@ internal C(string fileName)
6666
}
6767

6868
[Test]
69-
public static void WhenUsing()
69+
public static void Using()
7070
{
7171
var code = @"
7272
namespace N
@@ -93,7 +93,32 @@ internal C(string fileName)
9393
}
9494

9595
[Test]
96-
public static void WhenUsingAfterDeclaration()
96+
public static void UsingDeclaration()
97+
{
98+
var code = @"
99+
namespace N
100+
{
101+
using System;
102+
using System.IO;
103+
104+
internal class C
105+
{
106+
internal C(string fileName)
107+
{
108+
using var disposable = File.OpenRead(fileName);
109+
}
110+
}
111+
}";
112+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
113+
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
114+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
115+
var value = syntaxTree.FindVariableDeclaration("disposable");
116+
Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out ILocalSymbol symbol));
117+
Assert.AreEqual(true, DisposableWalker.Disposes(symbol, semanticModel, CancellationToken.None));
118+
}
119+
120+
[Test]
121+
public static void UsingAfterDeclaration()
97122
{
98123
var code = @"
99124
namespace N

IDisposableAnalyzers.Test/Helpers/DisposableWalkerTests.Ignores.cs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace IDisposableAnalyzers.Test.Helpers
22
{
33
using System.Threading;
4+
using System.Threading.Tasks;
45
using Gu.Roslyn.Asserts;
56
using Microsoft.CodeAnalysis.CSharp;
67
using NUnit.Framework;
@@ -194,6 +195,35 @@ public static void M(string fileName)
194195
Assert.AreEqual(false, DisposableWalker.Ignores(value, semanticModel, CancellationToken.None));
195196
}
196197

198+
[TestCase("Task.Run(() => File.OpenRead(fileName))")]
199+
[TestCase("Task.Run(() => File.OpenRead(fileName)).ConfigureAwait(false)")]
200+
[TestCase("Task.FromResult(File.OpenRead(fileName))")]
201+
[TestCase("Task.FromResult(File.OpenRead(fileName)).ConfigureAwait(false)")]
202+
public static void AssignedToFieldAsync(string expression)
203+
{
204+
var code = @"
205+
namespace N
206+
{
207+
using System;
208+
using System.IO;
209+
210+
public class C
211+
{
212+
private readonly object value;
213+
214+
public static async Task M(string fileName)
215+
{
216+
this.value = await Task.Run(() => File.OpenRead(fileName));
217+
}
218+
}
219+
}".AssertReplace("Task.Run(() => File.OpenRead(fileName))", expression);
220+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
221+
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
222+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
223+
var value = syntaxTree.FindExpression("File.OpenRead(fileName)");
224+
Assert.AreEqual(false, DisposableWalker.Ignores(value, semanticModel, CancellationToken.None));
225+
}
226+
197227
[TestCase("File.OpenRead(fileName)")]
198228
[TestCase("_ = File.OpenRead(fileName)")]
199229
[TestCase("var _ = File.OpenRead(fileName)")]
@@ -656,7 +686,7 @@ internal object M(string fileName)
656686
[TestCase("File.OpenRead(fileName)")]
657687
[TestCase("Task.FromResult(File.OpenRead(fileName)).Result")]
658688
[TestCase("Task.FromResult(File.OpenRead(fileName)).GetAwaiter().GetResult()")]
659-
public static void WhenUsingDeclaration(string expression)
689+
public static void UsingDeclaration(string expression)
660690
{
661691
var code = @"
662692
namespace N
@@ -679,6 +709,33 @@ class C
679709
var value = syntaxTree.FindExpression("File.OpenRead(fileName)");
680710
Assert.AreEqual(false, DisposableWalker.Ignores(value, semanticModel, CancellationToken.None));
681711
}
712+
713+
[TestCase("Task.FromResult(File.OpenRead(fileName))")]
714+
[TestCase("Task.FromResult(File.OpenRead(fileName)).ConfigureAwait(true)")]
715+
[TestCase("Task.Run(() => File.OpenRead(fileName)).ConfigureAwait(true)")]
716+
public static void UsingDeclarationAwait(string expression)
717+
{
718+
var code = @"
719+
namespace N
720+
{
721+
using System;
722+
using System.IO;
723+
using System.Threading.Tasks;
724+
725+
class C
726+
{
727+
async Task M(string fileName)
728+
{
729+
using var disposable = await Task.FromResult(File.OpenRead(fileName));
730+
}
731+
}
732+
}".AssertReplace("Task.FromResult(File.OpenRead(fileName))", expression);
733+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
734+
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
735+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
736+
var value = syntaxTree.FindExpression("File.OpenRead(fileName)");
737+
Assert.AreEqual(false, DisposableWalker.Ignores(value, semanticModel, CancellationToken.None));
738+
}
682739
}
683740
}
684741
}

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.DisposedByReturnValue.cs

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,25 +32,25 @@ private static bool DisposedByReturnValue(ExpressionSyntax candidate, Recursion
3232
return DisposedByReturnValue((ExpressionSyntax)candidate.Parent, recursion, out creation);
3333
}
3434

35-
switch (candidate.Parent)
35+
switch (candidate)
3636
{
37-
case ArgumentSyntax argument
37+
case { Parent: ArgumentSyntax argument }
3838
when recursion.Target(argument) is { } target:
3939
return DisposedByReturnValue(target, recursion, out creation);
40-
case InitializerExpressionSyntax { Parent: ObjectCreationExpressionSyntax objectCreation }
40+
case { Parent: InitializerExpressionSyntax { Parent: ObjectCreationExpressionSyntax objectCreation } }
4141
when recursion.SemanticModel.TryGetType(objectCreation, recursion.CancellationToken, out var type) &&
4242
type == KnownSymbol.CompositeDisposable:
4343
creation = objectCreation;
4444
return true;
45-
case MemberAccessExpressionSyntax memberAccess
46-
when recursion.Target<MemberAccessExpressionSyntax, ISymbol, CSharpSyntaxNode>(memberAccess) is { } target &&
47-
DisposedByReturnValue(target, recursion):
48-
creation = memberAccess;
45+
case ExpressionSyntax { Parent: AwaitExpressionSyntax await } expression
46+
when recursion.Target(expression) is { } target &&
47+
DisposedByReturnValue(target, recursion):
48+
creation = await;
4949
return true;
50-
case ConditionalAccessExpressionSyntax conditionalAccess
51-
when recursion.Target<ConditionalAccessExpressionSyntax, ISymbol, CSharpSyntaxNode>(conditionalAccess) is { } target &&
52-
DisposedByReturnValue(target, recursion):
53-
creation = conditionalAccess;
50+
case ExpressionSyntax expression
51+
when recursion.Target(expression) is { } target &&
52+
DisposedByReturnValue(target, recursion):
53+
creation = expression;
5454
return true;
5555
default:
5656
creation = null;
@@ -119,7 +119,7 @@ leaveOpenArgument.Expression is LiteralExpressionSyntax literal &&
119119
}
120120

121121
break;
122-
case { Symbol: { ContainingSymbol: IMethodSymbol method } , Source: { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax invocation } } }:
122+
case { Symbol: { ContainingSymbol: IMethodSymbol method }, Source: { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax invocation } } }:
123123
if (method == KnownSymbol.Task.FromResult)
124124
{
125125
creation = invocation;
@@ -153,12 +153,14 @@ private static bool DisposedByReturnValue<TSource, TSymbol, TNode>(Target<TSourc
153153
case IMethodSymbol { ReturnType: { MetadataName: "Task" } }:
154154
return false;
155155
case IMethodSymbol { ReturnType: { MetadataName: "ConfiguredTaskAwaitable`1" } }:
156+
case IMethodSymbol { ReturnType: { MetadataName: "TaskAwaiter`1" } }:
157+
case IMethodSymbol { ContainingSymbol: { MetadataName: "TaskAwaiter`1" }, Name: "GetResult" }:
156158
return true;
157159
case IMethodSymbol { ReturnType: INamedTypeSymbol { MetadataName: "Task`1" } taskOfT }
158160
when taskOfT.TypeArguments.TrySingle(out var type):
159161
return Disposable.IsAssignableFrom(type, recursion.SemanticModel.Compilation);
160162
case IMethodSymbol { ReturnType: { } returnType, DeclaringSyntaxReferences: { Length: 0 } }:
161-
// we assume here not sure it is the best assumption.
163+
// we assume here, not sure it is the best assumption.
162164
return Disposable.IsAssignableFrom(returnType, recursion.SemanticModel.Compilation);
163165
case IMethodSymbol { IsExtensionMethod: true, ReducedFrom: { } reducedFrom }
164166
when reducedFrom.Parameters.TryFirst(out var parameter):

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Disposes.cs

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -136,23 +136,23 @@ private static bool Disposes<TSource, TSymbol, TNode>(Target<TSource, TSymbol, T
136136

137137
private static bool Disposes(ExpressionSyntax candidate, Recursion recursion)
138138
{
139-
switch (candidate.Parent.Kind())
140-
{
141-
case SyntaxKind.UsingStatement:
142-
return true;
143-
}
144-
145139
switch (candidate.Parent)
146140
{
141+
case UsingStatementSyntax _:
142+
case EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: UsingStatementSyntax _ } }:
143+
case EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax { UsingKeyword: { ValueText: "using" } } } } }:
144+
return true;
147145
case ConditionalAccessExpressionSyntax { WhenNotNull: InvocationExpressionSyntax invocation }:
148-
return IsDispose(invocation);
146+
return IsDisposeOrReturnValueDisposed(invocation);
149147
case MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax invocation }:
150-
return IsDispose(invocation);
148+
return IsDisposeOrReturnValueDisposed(invocation);
149+
case ConditionalAccessExpressionSyntax { }:
150+
case MemberAccessExpressionSyntax { }:
151+
return DisposedByReturnValue((ExpressionSyntax)candidate.Parent, recursion, out var creation) &&
152+
Disposes(creation, recursion);
151153
case AssignmentExpressionSyntax { Left: { } left } assignment
152154
when left == candidate:
153155
return Disposes(assignment, recursion);
154-
case EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: UsingStatementSyntax _ } }:
155-
return true;
156156
case EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax variableDeclarator }
157157
when recursion.Target(variableDeclarator) is { } target:
158158
return Disposes(target, recursion);
@@ -164,12 +164,23 @@ when parent.IsKind(SyntaxKind.CastExpression) ||
164164
return Disposes(parent, recursion);
165165
case ArgumentSyntax argument
166166
when recursion.Target(argument) is { } target:
167-
return DisposedByReturnValue(target, recursion, out var creation) &&
168-
Disposes(creation, recursion);
167+
return DisposedByReturnValue(target, recursion, out var wrapper) &&
168+
Disposes(wrapper, recursion);
169169
}
170170

171171
return false;
172172

173+
bool IsDisposeOrReturnValueDisposed(InvocationExpressionSyntax invocation)
174+
{
175+
if (IsDispose(invocation))
176+
{
177+
return true;
178+
}
179+
180+
return DisposedByReturnValue(invocation, recursion, out var creation) &&
181+
Disposes(creation, recursion);
182+
}
183+
173184
static bool IsDispose(InvocationExpressionSyntax invocation)
174185
{
175186
return invocation is { ArgumentList: { Arguments: { Count: 0 } } } &&

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Ignores.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ when recursion.Target(argument) is { } target:
5151
}
5252

5353
return true;
54-
case InitializerExpressionSyntax { Parent: ExpressionSyntax creation } initializer:
54+
case InitializerExpressionSyntax { Parent: ExpressionSyntax creation }:
5555
return Ignores(creation, recursion);
5656
}
5757

0 commit comments

Comments
 (0)