Skip to content

Commit fabb549

Browse files
committed
More tests.
1 parent 19258f0 commit fabb549

12 files changed

Lines changed: 140 additions & 80 deletions

IDisposableAnalyzers.Test/Helpers/DisposableWalkerTests.Assigns.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,43 @@ private void M(IDisposable arg)
209209
Assert.AreEqual(true, DisposableWalker.Assigns(localOrParameter, semanticModel, CancellationToken.None, out var field));
210210
Assert.AreEqual("N.C.Disposable", field.Symbol.ToString());
211211
}
212+
213+
[TestCase("Task.FromResult(File.OpenRead(fileName))")]
214+
[TestCase("Task.FromResult(File.OpenRead(fileName)).ConfigureAwait(true)")]
215+
[TestCase("Task.Run(() => File.OpenRead(fileName))")]
216+
[TestCase("Task.Run(() => File.OpenRead(fileName)).ConfigureAwait(true)")]
217+
public static void AssigningFieldAwait(string expression)
218+
{
219+
var code = @"
220+
namespace N
221+
{
222+
using System;
223+
using System.IO;
224+
using System.Threading.Tasks;
225+
226+
public sealed class C : IDisposable
227+
{
228+
private IDisposable disposable;
229+
230+
public async Task M(string fileName)
231+
{
232+
this.disposable?.Dispose();
233+
this.disposable = await Task.FromResult(File.OpenRead(fileName));
234+
}
235+
236+
public void Dispose()
237+
{
238+
this.disposable?.Dispose();
239+
}
240+
}
241+
}".AssertReplace("Task.FromResult(File.OpenRead(fileName))", expression);
242+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
243+
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
244+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
245+
var value = syntaxTree.FindExpression("File.OpenRead(fileName)");
246+
Assert.AreEqual(true, DisposableWalker.Assigns(value, semanticModel, CancellationToken.None, out var fieldOrProperty));
247+
Assert.AreEqual("disposable", fieldOrProperty.Name);
248+
}
212249
}
213250
}
214251
}

IDisposableAnalyzers.Test/Helpers/DisposableWalkerTests.Ignores.cs

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace IDisposableAnalyzers.Test.Helpers
22
{
33
using System.Threading;
4-
using System.Threading.Tasks;
54
using Gu.Roslyn.Asserts;
65
using Microsoft.CodeAnalysis.CSharp;
76
using NUnit.Framework;
@@ -712,6 +711,7 @@ class C
712711

713712
[TestCase("Task.FromResult(File.OpenRead(fileName))")]
714713
[TestCase("Task.FromResult(File.OpenRead(fileName)).ConfigureAwait(true)")]
714+
[TestCase("Task.Run(() => File.OpenRead(fileName))")]
715715
[TestCase("Task.Run(() => File.OpenRead(fileName)).ConfigureAwait(true)")]
716716
public static void UsingDeclarationAwait(string expression)
717717
{
@@ -729,6 +729,42 @@ async Task M(string fileName)
729729
using var disposable = await Task.FromResult(File.OpenRead(fileName));
730730
}
731731
}
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+
}
739+
740+
[TestCase("Task.FromResult(File.OpenRead(fileName))")]
741+
[TestCase("Task.FromResult(File.OpenRead(fileName)).ConfigureAwait(true)")]
742+
[TestCase("Task.Run(() => File.OpenRead(fileName))")]
743+
[TestCase("Task.Run(() => File.OpenRead(fileName)).ConfigureAwait(true)")]
744+
public static void AssigningFieldAwait(string expression)
745+
{
746+
var code = @"
747+
namespace N
748+
{
749+
using System;
750+
using System.IO;
751+
using System.Threading.Tasks;
752+
753+
public sealed class C : IDisposable
754+
{
755+
private IDisposable disposable;
756+
757+
public async Task M(string fileName)
758+
{
759+
this.disposable?.Dispose();
760+
this.disposable = await Task.FromResult(File.OpenRead(fileName));
761+
}
762+
763+
public void Dispose()
764+
{
765+
this.disposable?.Dispose();
766+
}
767+
}
732768
}".AssertReplace("Task.FromResult(File.OpenRead(fileName))", expression);
733769
var syntaxTree = CSharpSyntaxTree.ParseText(code);
734770
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());

IDisposableAnalyzers.Test/IDISP004DoNotIgnoreCreatedTests/CodeFix.AddUsingForInvocation.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace IDisposableAnalyzers.Test.IDISP004DoNotIgnoreCreatedTests
22
{
33
using Gu.Roslyn.Asserts;
4-
using Microsoft.CodeAnalysis.Diagnostics;
54
using NUnit.Framework;
65

76
public static partial class CodeFix

IDisposableAnalyzers.Test/IDISP004DoNotIgnoreCreatedTests/CodeFix.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
{
33
using Gu.Roslyn.Asserts;
44
using Microsoft.CodeAnalysis.Diagnostics;
5-
using NUnit.Framework;
65

76
public static partial class CodeFix
87
{

IDisposableAnalyzers/Helpers/Walkers/AssignedValueWalker.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,8 @@ private void Run()
501501
}
502502
}
503503

504-
if ((this.CurrentSymbol is IFieldSymbol { IsReadOnly: false } field) ||
505-
(this.CurrentSymbol is IPropertySymbol { IsReadOnly: false } property))
504+
if ((this.CurrentSymbol is IFieldSymbol { IsReadOnly: false }) ||
505+
(this.CurrentSymbol is IPropertySymbol { IsReadOnly: false }))
506506
{
507507
if (TryGetScope(this.context.Node, out scope) &&
508508
!(scope is ConstructorDeclarationSyntax))
Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
namespace IDisposableAnalyzers
22
{
3-
using System;
43
using System.Threading;
54
using Gu.Roslyn.AnalyzerExtensions;
65
using Microsoft.CodeAnalysis;
7-
using Microsoft.CodeAnalysis.CSharp;
86
using Microsoft.CodeAnalysis.CSharp.Syntax;
97

108
internal sealed partial class DisposableWalker
@@ -20,6 +18,12 @@ internal static bool Assigns(LocalOrParameter localOrParameter, SemanticModel se
2018
return false;
2119
}
2220

21+
internal static bool Assigns(ExpressionSyntax candidate, SemanticModel semanticModel, CancellationToken cancellationToken, out FieldOrProperty first)
22+
{
23+
using var recursion = Recursion.Borrow(candidate, semanticModel, cancellationToken);
24+
return Assigns(candidate, recursion, out first);
25+
}
26+
2327
private static bool Assigns<TSource, TSymbol, TNode>(Target<TSource, TSymbol, TNode> target, Recursion recursion, out FieldOrProperty fieldOrProperty)
2428
where TSource : SyntaxNode
2529
where TSymbol : class, ISymbol
@@ -41,32 +45,24 @@ private static bool Assigns<TSource, TSymbol, TNode>(Target<TSource, TSymbol, TN
4145

4246
private static bool Assigns(ExpressionSyntax candidate, Recursion recursion, out FieldOrProperty fieldOrProperty)
4347
{
44-
switch (candidate.Parent.Kind())
45-
{
46-
case SyntaxKind.CastExpression:
47-
case SyntaxKind.AsExpression:
48-
case SyntaxKind.ConditionalExpression:
49-
case SyntaxKind.CoalesceExpression:
50-
return Assigns((ExpressionSyntax)candidate.Parent, recursion, out fieldOrProperty);
51-
}
52-
53-
switch (candidate.Parent)
48+
return candidate switch
5449
{
55-
case AssignmentExpressionSyntax { Left: { } left, Right: { } right }:
56-
return right.Contains(candidate) &&
57-
recursion.SemanticModel.TryGetSymbol(left, recursion.CancellationToken, out var assignedSymbol) &&
58-
FieldOrProperty.TryCreate(assignedSymbol, out fieldOrProperty);
59-
case ArgumentSyntax { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax invocation } } argument
60-
when invocation.IsPotentialThisOrBase() &&
61-
recursion.Target(argument) is { } target:
62-
return Assigns(target, recursion, out fieldOrProperty);
63-
case EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax variableDeclarator }
64-
when recursion.Target(variableDeclarator) is { } target:
65-
return Assigns(target, recursion, out fieldOrProperty);
66-
67-
default:
68-
return false;
69-
}
50+
{ Parent: AssignmentExpressionSyntax { Left: { } left, Right: { } right } }
51+
=> right.Contains(candidate) &&
52+
recursion.SemanticModel.TryGetSymbol(left, recursion.CancellationToken, out var assignedSymbol) &&
53+
FieldOrProperty.TryCreate(assignedSymbol, out fieldOrProperty),
54+
{ Parent: ArgumentSyntax { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax _ } } argument }
55+
=> recursion.Target(argument) is { } target &&
56+
Assigns(target, recursion, out fieldOrProperty) &&
57+
recursion.ContainingType.IsAssignableTo(fieldOrProperty.Symbol.ContainingType, recursion.SemanticModel.Compilation),
58+
{ Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax variableDeclarator } }
59+
=> recursion.Target(variableDeclarator) is { } target &&
60+
Assigns(target, recursion, out fieldOrProperty),
61+
{ Parent: ExpressionSyntax { } parent }
62+
when IsIdentity(parent)
63+
=> Assigns(parent, recursion, out fieldOrProperty),
64+
_ => false,
65+
};
7066
}
7167
}
7268
}

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.DisposedByReturnValue.cs

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,6 @@ internal static bool DisposedByReturnValue(ArgumentSyntax candidate, SemanticMod
2323

2424
private static bool DisposedByReturnValue(ExpressionSyntax candidate, Recursion recursion, [NotNullWhen(true)] out ExpressionSyntax? creation)
2525
{
26-
switch (candidate.Parent.Kind())
27-
{
28-
case SyntaxKind.CastExpression:
29-
case SyntaxKind.AsExpression:
30-
case SyntaxKind.ConditionalExpression:
31-
case SyntaxKind.CoalesceExpression:
32-
return DisposedByReturnValue((ExpressionSyntax)candidate.Parent, recursion, out creation);
33-
}
34-
3526
switch (candidate)
3627
{
3728
case { Parent: ArgumentSyntax argument }
@@ -42,12 +33,11 @@ when recursion.SemanticModel.TryGetType(objectCreation, recursion.CancellationTo
4233
type == KnownSymbol.CompositeDisposable:
4334
creation = objectCreation;
4435
return true;
45-
case ExpressionSyntax { Parent: AwaitExpressionSyntax await } expression
46-
when recursion.Target(expression) is { } target &&
47-
DisposedByReturnValue(target, recursion):
48-
creation = await;
36+
case { Parent: ExpressionSyntax { } parent }
37+
when IsIdentity(parent):
38+
creation = parent;
4939
return true;
50-
case ExpressionSyntax expression
40+
case { } expression
5141
when recursion.Target(expression) is { } target &&
5242
DisposedByReturnValue(target, recursion):
5343
creation = expression;

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Disposes.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,10 +157,7 @@ private static bool Disposes(ExpressionSyntax candidate, Recursion recursion)
157157
when recursion.Target(variableDeclarator) is { } target:
158158
return Disposes(target, recursion);
159159
case ExpressionSyntax parent
160-
when parent.IsKind(SyntaxKind.CastExpression) ||
161-
parent.IsKind(SyntaxKind.AsExpression) ||
162-
parent.IsKind(SyntaxKind.AsExpression) ||
163-
parent.IsKind(SyntaxKind.ParenthesizedExpression):
160+
when IsIdentity(parent):
164161
return Disposes(parent, recursion);
165162
case ArgumentSyntax argument
166163
when recursion.Target(argument) is { } target:

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Ignores.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,17 +53,19 @@ when recursion.Target(argument) is { } target:
5353
return true;
5454
case InitializerExpressionSyntax { Parent: ExpressionSyntax creation }:
5555
return Ignores(creation, recursion);
56+
case ExpressionSyntax parent
57+
when IsIdentity(parent):
58+
return Ignores(parent, recursion);
5659
}
5760

5861
return false;
5962
}
6063

6164
private static bool Ignores(Target<ArgumentSyntax, IParameterSymbol, BaseMethodDeclarationSyntax> target, Recursion recursion)
6265
{
63-
if (target.Source is { Parent: ArgumentListSyntax { Parent: ExpressionSyntax parentExpression } } &&
64-
recursion.SemanticModel.TryGetSymbol(parentExpression, recursion.CancellationToken, out IMethodSymbol? method))
66+
if (target.Source is { Parent: ArgumentListSyntax { Parent: ExpressionSyntax parentExpression } })
6567
{
66-
if (method.DeclaringSyntaxReferences.IsEmpty)
68+
if (target.TargetNode is null)
6769
{
6870
if (!Ignores(parentExpression, recursion))
6971
{

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Returns.cs

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using System.Threading;
44
using Gu.Roslyn.AnalyzerExtensions;
55
using Microsoft.CodeAnalysis;
6-
using Microsoft.CodeAnalysis.CSharp;
76
using Microsoft.CodeAnalysis.CSharp.Syntax;
87

98
internal sealed partial class DisposableWalker
@@ -45,27 +44,19 @@ private static bool Returns<TSource, TSymbol, TNode>(Target<TSource, TSymbol, TN
4544

4645
private static bool Returns(ExpressionSyntax candidate, Recursion recursion)
4746
{
48-
switch (candidate.Parent.Kind())
47+
return candidate.Parent switch
4948
{
50-
case SyntaxKind.ReturnStatement:
51-
case SyntaxKind.ArrowExpressionClause:
52-
return true;
53-
case SyntaxKind.CastExpression:
54-
case SyntaxKind.AsExpression:
55-
case SyntaxKind.ConditionalExpression:
56-
case SyntaxKind.CoalesceExpression:
57-
return Returns((ExpressionSyntax)candidate.Parent, recursion);
58-
}
59-
60-
switch (candidate.Parent)
61-
{
62-
case EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax variableDeclarator }
63-
when recursion.Target(variableDeclarator) is { } target:
64-
return Returns(target, recursion);
65-
66-
default:
67-
return false;
68-
}
49+
ReturnStatementSyntax _
50+
=> true,
51+
ArrowExpressionClauseSyntax _
52+
=> true,
53+
EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax variableDeclarator }
54+
=> recursion.Target(variableDeclarator) is { } target &&
55+
Returns(target, recursion),
56+
ExpressionSyntax parent
57+
when IsIdentity(parent) => Returns(parent, recursion),
58+
_ => false,
59+
};
6960
}
7061
}
7162
}

0 commit comments

Comments
 (0)