Skip to content

Commit 82429a9

Browse files
committed
Figure out form.components.Add()
1 parent af987b9 commit 82429a9

9 files changed

Lines changed: 288 additions & 5 deletions

File tree

IDisposableAnalyzers.Test/Helpers/DisposableMemberTests.cs

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public void Dispose()
3636
}
3737

3838
[Test]
39-
public static void DiposedInOverridden()
39+
public static void DisposedInOverridden()
4040
{
4141
var syntaxTree = CSharpSyntaxTree.ParseText(@"
4242
namespace N
@@ -70,5 +70,35 @@ protected override void M()
7070
var fieldSymbol = semanticModel.GetDeclaredSymbolSafe(field, CancellationToken.None);
7171
Assert.AreEqual(Result.Yes, DisposableMember.IsDisposed(new FieldOrProperty(fieldSymbol), (TypeDeclarationSyntax)field.Parent, semanticModel, CancellationToken.None));
7272
}
73+
74+
[Ignore("tbd")]
75+
[TestCase("this.components.Add(this.stream)")]
76+
[TestCase("components.Add(stream)")]
77+
public static void FieldAddedToFormComponents(string expression)
78+
{
79+
var syntaxTree = CSharpSyntaxTree.ParseText(@"
80+
namespace ValidCode
81+
{
82+
using System.IO;
83+
using System.Windows.Forms;
84+
85+
public class Winform : Form
86+
{
87+
private readonly Stream stream;
88+
89+
Winform()
90+
{
91+
this.stream = File.OpenRead(string.Empty);
92+
// Since this is added to components, it is automatically disposed of with the form.
93+
this.components.Add(this.stream);
94+
}
95+
}
96+
}".AssertReplace("this.components.Add(this.stream)", expression));
97+
var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes());
98+
var semanticModel = compilation.GetSemanticModel(syntaxTree);
99+
var field = syntaxTree.FindFieldDeclaration("stream");
100+
var fieldSymbol = semanticModel.GetDeclaredSymbolSafe(field, CancellationToken.None);
101+
Assert.AreEqual(Result.Yes, DisposableMember.IsDisposed(new FieldOrProperty(fieldSymbol), (TypeDeclarationSyntax)field.Parent, semanticModel, CancellationToken.None));
102+
}
73103
}
74104
}

IDisposableAnalyzers.Test/IDISP001DisposeCreatedTests/Valid.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,5 +586,53 @@ public Pair(T item1, T item2)
586586

587587
RoslynAssert.Valid(Analyzer, code);
588588
}
589+
590+
[TestCase("this.components.Add(stream)")]
591+
[TestCase("components.Add(stream)")]
592+
public static void LocalAddedToFormComponents(string expression)
593+
{
594+
var code = @"
595+
namespace ValidCode
596+
{
597+
using System.IO;
598+
using System.Windows.Forms;
599+
600+
public class Winform : Form
601+
{
602+
Winform()
603+
{
604+
var stream = File.OpenRead(string.Empty);
605+
// Since this is added to components, it is automatically disposed of with the form.
606+
this.components.Add(stream);
607+
}
608+
}
609+
}".AssertReplace("this.components.Add(stream)", expression);
610+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
611+
}
612+
613+
[TestCase("this.components.Add(this.stream)")]
614+
[TestCase("components.Add(stream)")]
615+
public static void FieldAddedToFormComponents(string expression)
616+
{
617+
var code = @"
618+
namespace ValidCode
619+
{
620+
using System.IO;
621+
using System.Windows.Forms;
622+
623+
public class Winform : Form
624+
{
625+
private readonly Stream stream;
626+
627+
Winform()
628+
{
629+
this.stream = File.OpenRead(string.Empty);
630+
// Since this is added to components, it is automatically disposed of with the form.
631+
this.components.Add(this.stream);
632+
}
633+
}
634+
}".AssertReplace("this.components.Add(this.stream)", expression);
635+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
636+
}
589637
}
590638
}

IDisposableAnalyzers.Test/IDISP002DisposeMemberTests/Valid.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,5 +1064,54 @@ protected override void M()
10641064

10651065
RoslynAssert.Valid(Analyzer, baseClass, code);
10661066
}
1067+
1068+
[TestCase("this.components.Add(stream)")]
1069+
[TestCase("components.Add(stream)")]
1070+
public static void LocalAddedToFormComponents(string expression)
1071+
{
1072+
var code = @"
1073+
namespace ValidCode
1074+
{
1075+
using System.IO;
1076+
using System.Windows.Forms;
1077+
1078+
public class Winform : Form
1079+
{
1080+
Winform()
1081+
{
1082+
var stream = File.OpenRead(string.Empty);
1083+
// Since this is added to components, it is automatically disposed of with the form.
1084+
this.components.Add(stream);
1085+
}
1086+
}
1087+
}".AssertReplace("this.components.Add(stream)", expression);
1088+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
1089+
}
1090+
1091+
[Ignore("tbd")]
1092+
[TestCase("this.components.Add(this.stream)")]
1093+
[TestCase("components.Add(stream)")]
1094+
public static void FieldAddedToFormComponents(string expression)
1095+
{
1096+
var code = @"
1097+
namespace ValidCode
1098+
{
1099+
using System.IO;
1100+
using System.Windows.Forms;
1101+
1102+
public class Winform : Form
1103+
{
1104+
private readonly Stream stream;
1105+
1106+
Winform()
1107+
{
1108+
this.stream = File.OpenRead(string.Empty);
1109+
// Since this is added to components, it is automatically disposed of with the form.
1110+
this.components.Add(this.stream);
1111+
}
1112+
}
1113+
}".AssertReplace("this.components.Add(this.stream)", expression);
1114+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
1115+
}
10671116
}
10681117
}

IDisposableAnalyzers.Test/IDISP003DisposeBeforeReassigningTests/Valid.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1407,5 +1407,53 @@ public C1 GetOrCreateFoo()
14071407

14081408
RoslynAssert.Valid(Analyzer, c1, code);
14091409
}
1410+
1411+
[TestCase("this.components.Add(stream)")]
1412+
[TestCase("components.Add(stream)")]
1413+
public static void LocalAddedToFormComponents(string expression)
1414+
{
1415+
var code = @"
1416+
namespace ValidCode
1417+
{
1418+
using System.IO;
1419+
using System.Windows.Forms;
1420+
1421+
public class Winform : Form
1422+
{
1423+
Winform()
1424+
{
1425+
var stream = File.OpenRead(string.Empty);
1426+
// Since this is added to components, it is automatically disposed of with the form.
1427+
this.components.Add(stream);
1428+
}
1429+
}
1430+
}".AssertReplace("this.components.Add(stream)", expression);
1431+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
1432+
}
1433+
1434+
[TestCase("this.components.Add(this.stream)")]
1435+
[TestCase("components.Add(stream)")]
1436+
public static void FieldAddedToFormComponents(string expression)
1437+
{
1438+
var code = @"
1439+
namespace ValidCode
1440+
{
1441+
using System.IO;
1442+
using System.Windows.Forms;
1443+
1444+
public class Winform : Form
1445+
{
1446+
private readonly Stream stream;
1447+
1448+
Winform()
1449+
{
1450+
this.stream = File.OpenRead(string.Empty);
1451+
// Since this is added to components, it is automatically disposed of with the form.
1452+
this.components.Add(this.stream);
1453+
}
1454+
}
1455+
}".AssertReplace("this.components.Add(this.stream)", expression);
1456+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
1457+
}
14101458
}
14111459
}

IDisposableAnalyzers.Test/IDISP004DoNotIgnoreCreatedTests/Valid.cs

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -791,8 +791,9 @@ public async Task M()
791791
RoslynAssert.Valid(Analyzer, code);
792792
}
793793

794-
[Test]
795-
public static void WhenAddedToFormComponents()
794+
[TestCase("this.components.Add(stream)")]
795+
[TestCase("components.Add(stream)")]
796+
public static void LocalAddedToFormComponents(string expression)
796797
{
797798
var code = @"
798799
namespace ValidCode
@@ -809,8 +810,33 @@ public class Winform : Form
809810
this.components.Add(stream);
810811
}
811812
}
812-
}";
813-
RoslynAssert.Valid(Analyzer, code);
813+
}".AssertReplace("this.components.Add(stream)", expression);
814+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
815+
}
816+
817+
[TestCase("this.components.Add(this.stream)")]
818+
[TestCase("components.Add(stream)")]
819+
public static void FieldAddedToFormComponents(string expression)
820+
{
821+
var code = @"
822+
namespace ValidCode
823+
{
824+
using System.IO;
825+
using System.Windows.Forms;
826+
827+
public class Winform : Form
828+
{
829+
private readonly Stream stream;
830+
831+
Winform()
832+
{
833+
this.stream = File.OpenRead(string.Empty);
834+
// Since this is added to components, it is automatically disposed of with the form.
835+
this.components.Add(this.stream);
836+
}
837+
}
838+
}".AssertReplace("this.components.Add(this.stream)", expression);
839+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
814840
}
815841
}
816842
}

IDisposableAnalyzers.Test/IDISP006ImplementIDisposableTests/ValidCode.Inheritance.cs

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,55 @@ protected override void M()
213213

214214
RoslynAssert.Valid(Analyzer, Descriptor, baseClass, code);
215215
}
216+
217+
[TestCase("this.components.Add(stream)")]
218+
[TestCase("components.Add(stream)")]
219+
public static void LocalAddedToFormComponents(string expression)
220+
{
221+
var code = @"
222+
namespace ValidCode
223+
{
224+
using System.IO;
225+
using System.Windows.Forms;
226+
227+
public class Winform : Form
228+
{
229+
Winform()
230+
{
231+
var stream = File.OpenRead(string.Empty);
232+
// Since this is added to components, it is automatically disposed of with the form.
233+
this.components.Add(stream);
234+
}
235+
}
236+
}".AssertReplace("this.components.Add(stream)", expression);
237+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
238+
}
239+
240+
[Ignore("tbd")]
241+
[TestCase("this.components.Add(this.stream)")]
242+
[TestCase("components.Add(stream)")]
243+
public static void FieldAddedToFormComponents(string expression)
244+
{
245+
var code = @"
246+
namespace ValidCode
247+
{
248+
using System.IO;
249+
using System.Windows.Forms;
250+
251+
public class Winform : Form
252+
{
253+
private readonly Stream stream;
254+
255+
Winform()
256+
{
257+
this.stream = File.OpenRead(string.Empty);
258+
// Since this is added to components, it is automatically disposed of with the form.
259+
this.components.Add(this.stream);
260+
}
261+
}
262+
}".AssertReplace("this.components.Add(this.stream)", expression);
263+
RoslynAssert.NoAnalyzerDiagnostics(Analyzer, code);
264+
}
216265
}
217266
}
218267
}

IDisposableAnalyzers/Helpers/KnownSymbols/KnownSymbol.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ internal static class KnownSymbol
6666
internal static readonly CompositeDisposableType CompositeDisposable = new CompositeDisposableType();
6767

6868
internal static readonly PasswordBoxType PasswordBox = new PasswordBoxType();
69+
internal static readonly QualifiedType SystemWindowsFormsForm = new QualifiedType("System.Windows.Forms.Form");
6970

7071
internal static readonly QualifiedType NUnitSetUpAttribute = new QualifiedType("NUnit.Framework.SetUpAttribute");
7172
internal static readonly QualifiedType NUnitTearDownAttribute = new QualifiedType("NUnit.Framework.TearDownAttribute");

IDisposableAnalyzers/Helpers/Walkers/DisposableWalker.Disposes.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ when Identity(candidate, recursion) is { } id &&
162162
{ Parent: EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax variableDeclarator } }
163163
when recursion.Target(variableDeclarator) is { } target
164164
=> Disposes(target, recursion),
165+
{ Parent: ArgumentSyntax { Parent: ArgumentListSyntax { Parent: InvocationExpressionSyntax invocation } } }
166+
when Winform.IsComponentsAdd(invocation, recursion.SemanticModel, recursion.CancellationToken)
167+
=> true,
165168
{ Parent: ArgumentSyntax argument }
166169
when recursion.Target(argument) is { } target
167170
=> DisposedByReturnValue(target, recursion, out var wrapper) &&
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
namespace IDisposableAnalyzers
2+
{
3+
using System.Threading;
4+
using Gu.Roslyn.AnalyzerExtensions;
5+
using Microsoft.CodeAnalysis;
6+
using Microsoft.CodeAnalysis.CSharp.Syntax;
7+
8+
internal static class Winform
9+
{
10+
internal static bool IsComponentsAdd(InvocationExpressionSyntax invocation, SemanticModel semanticModel, CancellationToken cancellationToken)
11+
{
12+
return invocation switch
13+
{
14+
{ Expression: MemberAccessExpressionSyntax { Expression: IdentifierNameSyntax { Identifier: { ValueText: "components" } }, Name: { Identifier: { ValueText: "Add" } } }, ArgumentList: { Arguments: { Count: 1 } } }
15+
=> IsInWinForm(),
16+
{ Expression: MemberAccessExpressionSyntax { Expression: MemberAccessExpressionSyntax { Expression: InstanceExpressionSyntax _, Name: { Identifier: { ValueText: "components" } } }, Name: { Identifier: { ValueText: "Add" } } }, ArgumentList: { Arguments: { Count: 1 } } }
17+
=> IsInWinForm(),
18+
_ => false,
19+
};
20+
21+
bool IsInWinForm()
22+
{
23+
return invocation.TryFirstAncestor(out ClassDeclarationSyntax? containingClass) &&
24+
semanticModel.TryGetNamedType(containingClass, cancellationToken, out var containingType) &&
25+
containingType.IsAssignableTo(KnownSymbol.SystemWindowsFormsForm, semanticModel.Compilation);
26+
}
27+
}
28+
}
29+
}

0 commit comments

Comments
 (0)