Skip to content

Commit a048f67

Browse files
committed
handle using declaration
1 parent f872c9d commit a048f67

4 files changed

Lines changed: 96 additions & 31 deletions

File tree

IDisposableAnalyzers.Test/IDISP011DontReturnDisposedTests/Diagnostics.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,26 @@ public object M()
3030
RoslynAssert.Diagnostics(Analyzer, ExpectedDiagnostic, code);
3131
}
3232

33+
[Test]
34+
public static void ReturnFileOpenReadFromUsingDeclaration()
35+
{
36+
var code = @"
37+
namespace N
38+
{
39+
using System.IO;
40+
41+
public sealed class C
42+
{
43+
public object M()
44+
{
45+
using var stream = File.OpenRead(string.Empty);
46+
return ↓stream;
47+
}
48+
}
49+
}";
50+
RoslynAssert.Diagnostics(Analyzer, ExpectedDiagnostic, code);
51+
}
52+
3353
[Test]
3454
public static void ReturnFileOpenReadDisposed()
3555
{

IDisposableAnalyzers.Test/IDISP013AwaitInUsingTests/Valid.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,10 @@ namespace N
4444
4545
public class C
4646
{
47-
public Task<string> M()
47+
public async Task<string> M()
4848
{
49-
var client = new WebClient();
50-
return client.DownloadStringTaskAsync(string.Empty);
49+
using var client = new WebClient();
50+
return await client.DownloadStringTaskAsync(string.Empty);
5151
}
5252
}
5353
}";
@@ -326,5 +326,41 @@ public class C
326326
}";
327327
RoslynAssert.Valid(Analyzer, code);
328328
}
329+
330+
[Test]
331+
public static void ReturnInValueTask()
332+
{
333+
var disposable = """
334+
335+
namespace N;
336+
337+
using System;
338+
339+
public class Disposable : IDisposable
340+
{
341+
public void Dispose()
342+
{
343+
}
344+
}
345+
""";
346+
347+
var code = """
348+
349+
namespace N;
350+
351+
using System;
352+
using System.Threading.Tasks;
353+
354+
public class C
355+
{
356+
public ValueTask<IDisposable> M()
357+
{
358+
var disposable = new Disposable();
359+
return new ValueTask<IDisposable>(disposable);
360+
}
361+
}
362+
""";
363+
RoslynAssert.Valid(Analyzer, disposable, code);
364+
}
329365
}
330366
}

IDisposableAnalyzers/Analyzers/ReturnValueAnalyzer.cs

Lines changed: 31 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
namespace IDisposableAnalyzers
22
{
33
using System.Collections.Immutable;
4-
using System.Linq;
54
using System.Threading;
65

76
using Gu.Roslyn.AnalyzerExtensions;
7+
88
using Microsoft.CodeAnalysis;
99
using Microsoft.CodeAnalysis.CSharp;
1010
using Microsoft.CodeAnalysis.CSharp.Syntax;
@@ -77,7 +77,7 @@ private static void HandleReturnValue(SyntaxNodeAnalysisContext context, Express
7777
if (Disposable.IsCreation(returnValue, context.SemanticModel, context.CancellationToken) &&
7878
context.SemanticModel.TryGetSymbol(returnValue, context.CancellationToken, out var returnedSymbol))
7979
{
80-
if (IsInUsing(returnedSymbol, context.CancellationToken) ||
80+
if (IsUsing(returnedSymbol, context.CancellationToken) ||
8181
Disposable.IsDisposedBefore(returnedSymbol, returnValue, context.SemanticModel, context.CancellationToken))
8282
{
8383
context.ReportDiagnostic(Diagnostic.Create(Descriptors.IDISP011DontReturnDisposed, returnValue.GetLocation()));
@@ -110,7 +110,7 @@ private static void HandleReturnValue(SyntaxNodeAnalysisContext context, Express
110110
Disposable.IsCreation(expression, context.SemanticModel, context.CancellationToken) &&
111111
context.SemanticModel.TryGetSymbol(expression, context.CancellationToken, out var argumentSymbol))
112112
{
113-
if (IsInUsing(argumentSymbol, context.CancellationToken) ||
113+
if (IsUsing(argumentSymbol, context.CancellationToken) ||
114114
Disposable.IsDisposedBefore(argumentSymbol, expression, context.SemanticModel, context.CancellationToken))
115115
{
116116
if (IsLazyEnumerable(invocation, containingType, context.SemanticModel, context.CancellationToken))
@@ -123,7 +123,7 @@ private static void HandleReturnValue(SyntaxNodeAnalysisContext context, Express
123123
}
124124

125125
if (ReturnType(context)?.IsAwaitable() == true &&
126-
IsInUsingScope(returnValue) &&
126+
IsInUsing(returnValue) &&
127127
!returnValue.TryFirstAncestorOrSelf<AwaitExpressionSyntax>(out _) &&
128128
context.SemanticModel.TryGetType(returnValue, context.CancellationToken, out var returnValueType2) &&
129129
returnValueType2.IsAwaitable() &&
@@ -133,42 +133,45 @@ private static void HandleReturnValue(SyntaxNodeAnalysisContext context, Express
133133
}
134134
}
135135

136-
private static bool IsInUsingScope(SyntaxNode node)
137-
{
138-
return IsInUsingStatement(node) || HasPrecedingUsingDeclaration(node);
139-
}
140-
141-
private static bool IsInUsingStatement(SyntaxNode node)
142-
{
143-
return node.TryFirstAncestor<UsingStatementSyntax>(out var usingStatement) &&
144-
usingStatement.Statement.Contains(node);
145-
}
146-
147-
private static bool HasPrecedingUsingDeclaration(SyntaxNode node)
136+
private static bool IsInUsing(SyntaxNode node)
148137
{
149-
if (node.TryFirstAncestor<UsingStatementSyntax>(out var usingStatement) &&
150-
usingStatement.Statement.Contains(node))
138+
if (node.TryFirstAncestor<UsingStatementSyntax>(out var usingStatement))
151139
{
152-
return true;
140+
return usingStatement.Statement.Contains(node);
153141
}
154142

155-
if (node.Parent?.ChildNodes().TakeWhile(x => x != node).OfType<LocalDeclarationStatementSyntax>().Any(x => x.UsingKeyword.Text == "using") ?? false)
143+
if (node.TryFirstAncestor<BlockSyntax>(out var block))
156144
{
157-
return true;
158-
}
145+
foreach (var statement in block.Statements)
146+
{
147+
if (statement.SpanStart >= node.SpanStart)
148+
{
149+
return false;
150+
}
159151

160-
if (node.Parent is { } parent)
161-
{
162-
return HasPrecedingUsingDeclaration(parent);
152+
if (statement is LocalDeclarationStatementSyntax { UsingKeyword.ValueText: "using" })
153+
{
154+
return true;
155+
}
156+
}
163157
}
164158

165159
return false;
166160
}
167161

168-
private static bool IsInUsing(ISymbol symbol, CancellationToken cancellationToken)
162+
private static bool IsUsing(ISymbol symbol, CancellationToken cancellationToken)
169163
{
170-
return symbol.TrySingleDeclaration<SyntaxNode>(cancellationToken, out var declaration) &&
171-
declaration.Parent?.Parent is UsingStatementSyntax;
164+
if (symbol.TrySingleDeclaration<SyntaxNode>(cancellationToken, out var declaration))
165+
{
166+
return declaration switch
167+
{
168+
{ Parent.Parent: UsingStatementSyntax } => true,
169+
{ Parent.Parent: LocalDeclarationStatementSyntax { UsingKeyword.ValueText: "using" } } => true,
170+
_ => false,
171+
};
172+
}
173+
174+
return false;
172175
}
173176

174177
private static bool ShouldAwait(SyntaxNodeAnalysisContext context, ExpressionSyntax returnValue)

ValidCode/Async.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,12 @@ public static ValueTask<IDisposable> ValueTask()
9494
return new ValueTask<IDisposable>(disposable);
9595
}
9696

97+
public static async void UseValueTask()
98+
{
99+
var disposable = await ValueTask();
100+
disposable.Dispose();
101+
}
102+
97103
public static async Task<string?> Bar1Async()
98104
{
99105
using (var stream = await ReadAsync(string.Empty))

0 commit comments

Comments
 (0)