Skip to content

Commit b72e7e7

Browse files
committed
IDISP004 handle System.Net.Mail.Attachment
Fix #217
1 parent 5b386d7 commit b72e7e7

File tree

8 files changed

+134
-2
lines changed

8 files changed

+134
-2
lines changed

IDisposableAnalyzers.Test/AssemblyAttributes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
[assembly: TransitiveMetadataReferences(typeof(System.Reactive.Linq.Observable))]
88
[assembly: MetadataReferences(
99
typeof(System.Net.WebClient),
10+
typeof(System.Net.Mail.Attachment),
1011
typeof(System.Data.Common.DbConnection),
1112
typeof(System.Threading.Tasks.ValueTask),
1213
typeof(System.Xml.Serialization.XmlSerializer),

IDisposableAnalyzers.Test/Helpers/Disposable.DisposedByReturnValue.cs

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
namespace IDisposableAnalyzers.Test.Helpers
22
{
33
using System.Threading;
4+
using Gu.Roslyn.AnalyzerExtensions;
45
using Gu.Roslyn.Asserts;
56
using Microsoft.CodeAnalysis.CSharp;
67
using NUnit.Framework;
@@ -41,6 +42,97 @@ public void Dispose()
4142
var value = syntaxTree.FindArgument("File.OpenRead(string.Empty)");
4243
Assert.AreEqual(true, Disposable.DisposedByReturnValue(value, semanticModel, CancellationToken.None, out _));
4344
}
45+
46+
[TestCase("new BinaryReader(stream)", true)]
47+
[TestCase("new BinaryReader(stream, new UTF8Encoding(), true)", false)]
48+
[TestCase("new BinaryReader(stream, new UTF8Encoding(), leaveOpen: true)", false)]
49+
[TestCase("new BinaryReader(stream, encoding: new UTF8Encoding(), leaveOpen: true)", false)]
50+
[TestCase("new BinaryReader(stream, leaveOpen: true, encoding: new UTF8Encoding())", false)]
51+
[TestCase("new BinaryReader(stream, new UTF8Encoding(), false)", true)]
52+
[TestCase("new BinaryReader(stream, leaveOpen: false, encoding: new UTF8Encoding())", true)]
53+
[TestCase("new BinaryWriter(stream, new UTF8Encoding(), leaveOpen: false)", true)]
54+
[TestCase("new BinaryWriter(stream, new UTF8Encoding(), leaveOpen: true)", false)]
55+
[TestCase("new StreamReader(stream, new UTF8Encoding(), true, 1024, leaveOpen: false)", true)]
56+
[TestCase("new StreamReader(stream, new UTF8Encoding(), true, 1024, leaveOpen: true)", false)]
57+
[TestCase("new StreamWriter(stream, new UTF8Encoding(), 1024, leaveOpen: false)", true)]
58+
[TestCase("new StreamWriter(stream, new UTF8Encoding(), 1024, leaveOpen: true)", false)]
59+
[TestCase("new CryptoStream(stream, new FromBase64Transform(), CryptoStreamMode.Read, leaveOpen: true)", false)]
60+
[TestCase("new CryptoStream(stream, new FromBase64Transform(), CryptoStreamMode.Read, leaveOpen: false)", true)]
61+
[TestCase("new DeflateStream(stream, CompressionLevel.Fastest)", true)]
62+
[TestCase("new DeflateStream(stream, CompressionLevel.Fastest, leaveOpen: true)", false)]
63+
[TestCase("new DeflateStream(stream, CompressionLevel.Fastest, leaveOpen: false)", true)]
64+
[TestCase("new GZipStream(stream, CompressionLevel.Fastest)", true)]
65+
[TestCase("new GZipStream(stream, CompressionLevel.Fastest, leaveOpen: true)", false)]
66+
[TestCase("new GZipStream(stream, CompressionLevel.Fastest, leaveOpen: false)", true)]
67+
[TestCase("new System.Net.Mail.Attachment(stream, string.Empty)", true)]
68+
public static void InLeaveOpen(string expression, bool stores)
69+
{
70+
var code = @"
71+
namespace N
72+
{
73+
using System;
74+
using System.IO;
75+
using System.IO.Compression;
76+
using System.Security.Cryptography;
77+
using System.Text;
78+
79+
public class C
80+
{
81+
private readonly IDisposable disposable;
82+
83+
public C(Stream stream)
84+
{
85+
this.disposable = new BinaryReader(stream);
86+
}
87+
}
88+
}".AssertReplace("new BinaryReader(stream)", expression);
89+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
90+
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
91+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
92+
var value = syntaxTree.FindParameter("stream");
93+
Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol));
94+
Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter));
95+
Assert.AreEqual(stores, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container));
96+
Assert.AreEqual(stores, Disposable.DisposedByReturnValue(syntaxTree.FindArgument("stream"), semanticModel, CancellationToken.None, out _));
97+
if (stores)
98+
{
99+
Assert.AreEqual("N.C.disposable", container.ToString());
100+
}
101+
}
102+
103+
[TestCase("new HttpClient(handler)", true)]
104+
[TestCase("new HttpClient(handler, disposeHandler: true)", true)]
105+
[TestCase("new HttpClient(handler, disposeHandler: false)", false)]
106+
public static void InHttpClient(string expression, bool stores)
107+
{
108+
var code = @"
109+
namespace N
110+
{
111+
using System.Net.Http;
112+
113+
public class C
114+
{
115+
private readonly IDisposable disposable;
116+
117+
public C(HttpClientHandler handler)
118+
{
119+
this.disposable = new HttpClient(handler);
120+
}
121+
}
122+
}".AssertReplace("new HttpClient(handler)", expression);
123+
var syntaxTree = CSharpSyntaxTree.ParseText(code);
124+
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
125+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
126+
var value = syntaxTree.FindParameter("handler");
127+
Assert.AreEqual(true, semanticModel.TryGetSymbol(value, CancellationToken.None, out var symbol));
128+
Assert.AreEqual(true, LocalOrParameter.TryCreate(symbol, out var localOrParameter));
129+
Assert.AreEqual(stores, Disposable.Stores(localOrParameter, semanticModel, CancellationToken.None, out var container));
130+
Assert.AreEqual(stores, Disposable.DisposedByReturnValue(syntaxTree.FindArgument("handler"), semanticModel, CancellationToken.None, out _));
131+
if (stores)
132+
{
133+
Assert.AreEqual("N.C.disposable", container.ToString());
134+
}
135+
}
44136
}
45137
}
46138
}

IDisposableAnalyzers.Test/Helpers/Disposable.Stores.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,7 @@ public void Dispose()
706706
[TestCase("new GZipStream(stream, CompressionLevel.Fastest)", true)]
707707
[TestCase("new GZipStream(stream, CompressionLevel.Fastest, leaveOpen: true)", false)]
708708
[TestCase("new GZipStream(stream, CompressionLevel.Fastest, leaveOpen: false)", true)]
709+
[TestCase("new System.Net.Mail.Attachment(stream, string.Empty)", true)]
709710
public static void InLeaveOpen(string expression, bool stores)
710711
{
711712
var code = @"

IDisposableAnalyzers.Test/IDISP004DoNotIgnoreCreatedTests/Valid.Argument.cs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,27 @@ protected override void Dispose(bool disposing)
259259
RoslynAssert.Valid(Analyzer, DisposableCode, baseClass, code);
260260
}
261261

262+
[TestCase("new StreamReader(File.OpenRead(fileName))")]
263+
[TestCase("new StreamReader(File.OpenRead(fileName), new System.Text.UTF8Encoding(), true, 1024, leaveOpen: false)")]
264+
[TestCase("new System.Net.Mail.Attachment(File.OpenRead(fileName), string.Empty)")]
265+
public static void LeaveOpenFalse(string expression)
266+
{
267+
var code = @"
268+
namespace N
269+
{
270+
using System.IO;
271+
272+
public class C
273+
{
274+
public void M(string fileName)
275+
{
276+
using var reader = new StreamReader(File.OpenRead(fileName));
277+
}
278+
}
279+
}".AssertReplace("new StreamReader(File.OpenRead(fileName))", expression);
280+
RoslynAssert.Valid(Analyzer, code);
281+
}
282+
262283
[Test]
263284
public static void UsingStreamInStreamReader()
264285
{

IDisposableAnalyzers/Helpers/Disposable.DisposedByReturnValue.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ private static bool DisposedByReturnValue(Target<ArgumentSyntax, IParameterSymbo
7272
constructor.ContainingType == KnownSymbol.StreamWriter ||
7373
constructor.ContainingType == KnownSymbol.CryptoStream ||
7474
constructor.ContainingType == KnownSymbol.DeflateStream ||
75+
constructor.ContainingType == KnownSymbol.Attachment ||
7576
constructor.ContainingType == KnownSymbol.GZipStream ||
7677
constructor.ContainingType == KnownSymbol.StreamMemoryBlockProvider)
7778
{

IDisposableAnalyzers/Helpers/KnownSymbols/KnownSymbol.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal static class KnownSymbol
1919
internal static readonly QualifiedType DeflateStream = Create("System.IO.Compression.DeflateStream");
2020
internal static readonly QualifiedType GZipStream = Create("System.IO.Compression.GZipStream");
2121
internal static readonly QualifiedType StreamMemoryBlockProvider = Create("System.Reflection.Internal.StreamMemoryBlockProvider");
22+
internal static readonly QualifiedType Attachment = Create("System.Net.Mail.Attachment");
2223

2324
internal static readonly TupleType Tuple = new TupleType();
2425
internal static readonly IDisposableType IDisposable = new IDisposableType();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace ValidCode.LeaveOpen
2+
{
3+
using System.IO;
4+
using System.Text;
5+
6+
public sealed class LeaveOpenFalse
7+
{
8+
public void M(string fileName)
9+
{
10+
using var reader1 = new StreamReader(File.OpenRead(fileName), new UTF8Encoding(), true, 1024, leaveOpen: false);
11+
using var reader2 = new StreamReader(File.OpenRead(fileName));
12+
using var attachment = new System.Net.Mail.Attachment(File.OpenRead(fileName), string.Empty);
13+
}
14+
}
15+
}

ValidCode/LeaveOpen/LeaveOpenFields.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace ValidCode.LeaveOpen
1+
namespace ValidCode.LeaveOpen
22
{
33
using System;
44
using System.IO;
@@ -12,7 +12,7 @@ public sealed class LeaveOpenFields : IDisposable
1212
public LeaveOpenFields(string fileName)
1313
{
1414
this.stream = File.OpenRead(fileName);
15-
this.reader = new StreamReader(stream, new UTF8Encoding(), true, 1024, leaveOpen: true);
15+
this.reader = new StreamReader(this.stream, new UTF8Encoding(), true, 1024, leaveOpen: true);
1616
}
1717

1818
public string ReadLine() => this.reader.ReadLine();

0 commit comments

Comments
 (0)