Skip to content

Commit e93c22a

Browse files
committed
Move logic to Identity.
1 parent 0498639 commit e93c22a

10 files changed

Lines changed: 84 additions & 47 deletions

IDisposableAnalyzers.Test/Helpers/DisposableWalkerTests.Assigns.cs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,9 @@ private void M(IDisposable arg)
212212

213213
[TestCase("Task.FromResult(File.OpenRead(fileName))")]
214214
[TestCase("Task.FromResult(File.OpenRead(fileName)).ConfigureAwait(true)")]
215-
[TestCase("Task.Run(() => File.OpenRead(fileName))")]
216-
[TestCase("Task.Run(() => File.OpenRead(fileName)).ConfigureAwait(true)")]
215+
//[TestCase("Task.Run(() => File.OpenRead(fileName))")]
216+
//[TestCase("Task.Run(() => { return File.OpenRead(fileName); })")]
217+
//[TestCase("Task.Run(() => File.OpenRead(fileName)).ConfigureAwait(true)")]
217218
public static void AssigningFieldAwait(string expression)
218219
{
219220
var code = @"
@@ -246,6 +247,45 @@ public void Dispose()
246247
Assert.AreEqual(true, DisposableWalker.Assigns(value, semanticModel, CancellationToken.None, out var fieldOrProperty));
247248
Assert.AreEqual("disposable", fieldOrProperty.Name);
248249
}
250+
251+
[TestCase("Task.FromResult(File.OpenRead(fileName)).Result")]
252+
[TestCase("Task.FromResult(File.OpenRead(fileName)).GetAwaiter().GetResult()")]
253+
//[TestCase("Task.Run(() => File.OpenRead(fileName)).Result")]
254+
//[TestCase("Task.Run(() => File.OpenRead(fileName)).GetAwaiter().GetResult()")]
255+
//[TestCase("Task.Run(() => { return File.OpenRead(fileName); }).Result")]
256+
//[TestCase("Task.Run(() => { return File.OpenRead(fileName); }).GetAwaiter().GetResult()")]
257+
public static void AssigningFieldGetAwaiterGetResult(string expression)
258+
{
259+
var code = @"
260+
namespace N
261+
{
262+
using System;
263+
using System.IO;
264+
using System.Threading.Tasks;
265+
266+
public sealed class C : IDisposable
267+
{
268+
private IDisposable disposable;
269+
270+
public async Task M(string fileName)
271+
{
272+
this.disposable?.Dispose();
273+
this.disposable = Task.FromResult(File.OpenRead(fileName));
274+
}
275+
276+
public void Dispose()
277+
{
278+
this.disposable?.Dispose();
279+
}
280+
}
281+
}".AssertReplace("Task.FromResult(File.OpenRead(fileName))", expression);
282+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
283+
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
284+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
285+
var value = syntaxTree.FindExpression("File.OpenRead(fileName)");
286+
Assert.AreEqual(true, DisposableWalker.Assigns(value, semanticModel, CancellationToken.None, out var fieldOrProperty));
287+
Assert.AreEqual("disposable", fieldOrProperty.Name);
288+
}
249289
}
250290
}
251291
}

IDisposableAnalyzers/Helpers/KnownSymbols/KnownSymbol.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ internal static class KnownSymbol
5454
internal static readonly QualifiedType ConditionalWeakTable = Create("System.Runtime.CompilerServices.ConditionalWeakTable`2");
5555
internal static readonly TaskType Task = new TaskType();
5656
internal static readonly QualifiedType TaskOfT = new QualifiedType("System.Threading.Tasks.Task`1");
57+
internal static readonly QualifiedType INotifyCompletion = new QualifiedType("System.Runtime.CompilerServices.INotifyCompletion");
5758
internal static readonly QualifiedType HttpClient = new QualifiedType("System.Net.Http.HttpClient");
5859
internal static readonly QualifiedType HttpMessageHandler = new QualifiedType("System.Net.Http.HttpMessageHandler");
5960
internal static readonly HttpResponseMessageType HttpResponseMessage = new HttpResponseMessageType();

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Assigns.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ private static bool Assigns(ExpressionSyntax candidate, Recursion recursion, out
4747
{
4848
return candidate switch
4949
{
50+
{ }
51+
when Identity(candidate, recursion) is { } id
52+
=> Assigns(id, recursion, out fieldOrProperty),
5053
{ Parent: AssignmentExpressionSyntax { Left: { } left, Right: { } right } }
5154
=> right.Contains(candidate) &&
5255
recursion.SemanticModel.TryGetSymbol(left, recursion.CancellationToken, out var assignedSymbol) &&
@@ -58,9 +61,6 @@ private static bool Assigns(ExpressionSyntax candidate, Recursion recursion, out
5861
{ Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax variableDeclarator } }
5962
=> recursion.Target(variableDeclarator) is { } target &&
6063
Assigns(target, recursion, out fieldOrProperty),
61-
{ }
62-
when Identity(candidate) is { } id
63-
=> Assigns(id, recursion, out fieldOrProperty),
6464
_ => false,
6565
};
6666
}

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.DisposedByReturnValue.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,8 @@ when recursion.SemanticModel.TryGetType(objectCreation, recursion.CancellationTo
3434
creation = objectCreation;
3535
return true;
3636
case { }
37-
when Identity(candidate) is { } id:
38-
creation = id;
39-
return true;
37+
when Identity(candidate, recursion) is { } id:
38+
return DisposedByReturnValue(id, recursion, out creation);
4039
case { } expression
4140
when recursion.Target(expression) is { } target &&
4241
DisposedByReturnValue(target, recursion):

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Disposes.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ private static bool Disposes(ExpressionSyntax candidate, Recursion recursion)
144144
=> true,
145145
{ Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax { Parent: VariableDeclarationSyntax { Parent: LocalDeclarationStatementSyntax { UsingKeyword: { ValueText: "using" } } } } } }
146146
=> true,
147+
{ }
148+
when Identity(candidate, recursion) is { } id
149+
=> Disposes(id, recursion),
147150
{ Parent: ConditionalAccessExpressionSyntax { WhenNotNull: InvocationExpressionSyntax invocation } }
148151
=> IsDisposeOrReturnValueDisposed(invocation),
149152
{ Parent: MemberAccessExpressionSyntax { Parent: InvocationExpressionSyntax invocation } }
@@ -164,9 +167,6 @@ when recursion.Target(variableDeclarator) is { } target
164167
when recursion.Target(argument) is { } target
165168
=> DisposedByReturnValue(target, recursion, out var wrapper) &&
166169
Disposes(wrapper, recursion),
167-
{ }
168-
when Identity(candidate) is { } id
169-
=> Disposes(id, recursion),
170170
_ => false
171171
};
172172

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,45 @@
11
namespace IDisposableAnalyzers
22
{
3-
using System;
4-
using Microsoft.CodeAnalysis.CSharp;
3+
using Gu.Roslyn.AnalyzerExtensions;
54
using Microsoft.CodeAnalysis.CSharp.Syntax;
65

76
internal sealed partial class DisposableWalker
87
{
9-
private static ExpressionSyntax? Identity(ExpressionSyntax expression)
8+
private static ExpressionSyntax? Identity(ExpressionSyntax candidate, Recursion recursion)
109
{
11-
return expression switch
10+
return candidate switch
1211
{
12+
{ Parent: ArgumentSyntax { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax { Expression: MemberAccessExpressionSyntax { Name: IdentifierNameSyntax { Identifier: { ValueText: "FromResult" } } } } invocation } } }
13+
when invocation.IsSymbol(KnownSymbol.Task.FromResult, recursion.SemanticModel, recursion.CancellationToken)
14+
=> Recursive(invocation, recursion),
15+
{ Parent: AwaitExpressionSyntax parent }
16+
=> Recursive(parent, recursion),
1317
{ Parent: BinaryExpressionSyntax { OperatorToken: { ValueText: "as" } } parent }
14-
=> Recursive(parent),
18+
=> Recursive(parent, recursion),
1519
{ Parent: BinaryExpressionSyntax { OperatorToken: { ValueText: "??" } } parent }
16-
=> Recursive(parent),
20+
=> Recursive(parent, recursion),
1721
{ Parent: CastExpressionSyntax parent }
18-
=> Recursive(parent),
22+
=> Recursive(parent, recursion),
1923
{ Parent: ConditionalExpressionSyntax parent }
20-
=> Recursive(parent),
24+
=> Recursive(parent, recursion),
25+
{ Parent: MemberAccessExpressionSyntax { Name: IdentifierNameSyntax { Identifier: { ValueText: "ConfigureAwait" } }, Parent: InvocationExpressionSyntax invocation } }
26+
=> Recursive(invocation, recursion),
27+
{ Parent: MemberAccessExpressionSyntax { Name: IdentifierNameSyntax { Identifier: { ValueText: "GetAwaiter" } }, Parent: InvocationExpressionSyntax invocation } }
28+
=> Recursive(invocation, recursion),
29+
{ Parent: MemberAccessExpressionSyntax { Expression: { } expression, Name: IdentifierNameSyntax { Identifier: { ValueText: "Result" } } } memberAccess }
30+
when recursion.SemanticModel.TryGetNamedType(expression, recursion.CancellationToken, out var type) &&
31+
type.IsAssignableTo(KnownSymbol.Task, recursion.SemanticModel.Compilation)
32+
=> Recursive(memberAccess, recursion),
33+
{ Parent: MemberAccessExpressionSyntax { Expression: { } expression, Name: IdentifierNameSyntax { Identifier: { ValueText: "GetResult" } }, Parent: InvocationExpressionSyntax invocation } }
34+
when recursion.SemanticModel.TryGetNamedType(expression, recursion.CancellationToken, out var type) &&
35+
type.IsAssignableTo(KnownSymbol.INotifyCompletion, recursion.SemanticModel.Compilation)
36+
=> Recursive(invocation, recursion),
2137
{ Parent: ParenthesizedExpressionSyntax parent }
22-
=> Recursive(parent),
38+
=> Recursive(parent, recursion),
2339
_ => null,
2440
};
2541

26-
static ExpressionSyntax Recursive(ExpressionSyntax parent) => Identity(parent) ?? parent;
27-
}
28-
29-
[Obsolete("Use Identity")]
30-
private static bool IsIdentity(ExpressionSyntax expression)
31-
{
32-
switch (expression.Kind())
33-
{
34-
case SyntaxKind.AsExpression:
35-
case SyntaxKind.AwaitExpression:
36-
case SyntaxKind.CastExpression:
37-
case SyntaxKind.CoalesceExpression:
38-
case SyntaxKind.ConditionalExpression:
39-
case SyntaxKind.ParenthesizedExpression:
40-
return true;
41-
default:
42-
return false;
43-
}
42+
static ExpressionSyntax Recursive(ExpressionSyntax parent, Recursion recursion) => Identity(parent, recursion) ?? parent;
4443
}
4544
}
4645
}

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Ignores.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ private static bool Ignores(ExpressionSyntax candidate, Recursion recursion)
3535
=> false,
3636
{ Parent: StatementSyntax _ }
3737
=> true,
38+
{ }
39+
when Identity(candidate, recursion) is { } id
40+
=> Ignores(id, recursion),
3841
{ Parent: ArgumentSyntax { Parent: TupleExpressionSyntax tuple } }
3942
=> Ignores(tuple, recursion),
4043
{ Parent: ArgumentSyntax argument }
@@ -46,9 +49,6 @@ when recursion.Target(argument) is { } target
4649
=> WrappedAndIgnored(),
4750
{ Parent: InitializerExpressionSyntax { Parent: ExpressionSyntax creation } }
4851
=> Ignores(creation, recursion),
49-
{ }
50-
when Identity(candidate) is { } id
51-
=> Ignores(id, recursion),
5252
_ => false
5353
};
5454

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Returns.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private static bool Returns(ExpressionSyntax candidate, Recursion recursion)
5454
=> recursion.Target(variableDeclarator) is { } target &&
5555
Returns(target, recursion),
5656
{ }
57-
when Identity(candidate) is { } id
57+
when Identity(candidate, recursion) is { } id
5858
=> Returns(id, recursion),
5959
_ => false,
6060
};

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Stores.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ private static bool Stores(ExpressionSyntax candidate, Recursion recursion, [Not
6060
case { Parent: AssignmentExpressionSyntax { Right: { } right, Left: ElementAccessExpressionSyntax { Expression: { } element } } }
6161
when right.Contains(candidate):
6262
return recursion.SemanticModel.TryGetSymbol(element, recursion.CancellationToken, out container);
63+
case { }
64+
when Identity(candidate, recursion) is { } id:
65+
return Stores(id, recursion, out container);
6366
case { Parent: ArgumentSyntax { Parent: ArgumentListSyntax { Parent: ObjectCreationExpressionSyntax _ } } argument }
6467
when recursion.Target(argument) is { } target:
6568
if (DisposedByReturnValue(target, recursion, out var objectCreation) ||
@@ -110,10 +113,6 @@ when recursion.Target(argument) is { Symbol: { } parameter } target:
110113
case { Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax variableDeclarator } }
111114
when recursion.Target(variableDeclarator) is { } target:
112115
return Stores(target, recursion, out container);
113-
114-
case { }
115-
when Identity(candidate) is { } id:
116-
return Stores(id, recursion, out container);
117116
default:
118117
container = null;
119118
return false;

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Collections.Generic;
55
using Gu.Roslyn.AnalyzerExtensions;
66
using Microsoft.CodeAnalysis;
7-
using Microsoft.CodeAnalysis.CSharp;
87
using Microsoft.CodeAnalysis.CSharp.Syntax;
98

109
internal sealed partial class DisposableWalker : PooledWalker<DisposableWalker>

0 commit comments

Comments
 (0)