From 75081db77b16d864ee06b3ed4bbeb4bd2c61b52b Mon Sep 17 00:00:00 2001 From: JohanLarsson Date: Fri, 17 Aug 2018 12:15:42 +0200 Subject: [PATCH] Better heuristics for extension methods returning IDisposable. Fix #96. --- .../Helpers/DisposableTests.IsCreation.cs | 48 ++++++++++++++++++ .../IDisposableAnalyzers.Test.csproj | 3 ++ IDisposableAnalyzers.Test/Lib/Stubs.dll | Bin 0 -> 4096 bytes .../Properties/AssemblyInfo.cs | 1 + IDisposableAnalyzers.sln | 2 + .../Helpers/Disposable.IsCreation.cs | 2 +- Stubs/Extensions.cs | 17 +++++++ Stubs/ICustomDisposable.cs | 8 +++ Stubs/Stubs.csproj | 11 ++++ 9 files changed, 91 insertions(+), 1 deletion(-) create mode 100644 IDisposableAnalyzers.Test/Lib/Stubs.dll create mode 100644 Stubs/Extensions.cs create mode 100644 Stubs/ICustomDisposable.cs create mode 100644 Stubs/Stubs.csproj diff --git a/IDisposableAnalyzers.Test/Helpers/DisposableTests.IsCreation.cs b/IDisposableAnalyzers.Test/Helpers/DisposableTests.IsCreation.cs index 6e586980..c0adbd41 100644 --- a/IDisposableAnalyzers.Test/Helpers/DisposableTests.IsCreation.cs +++ b/IDisposableAnalyzers.Test/Helpers/DisposableTests.IsCreation.cs @@ -503,6 +503,54 @@ public void Dispose() var value = syntaxTree.FindInvocation("disposable.AddAndReturn(File.OpenRead(string.Empty))"); Assert.AreEqual(Result.No, Disposable.IsCreation(value, semanticModel, CancellationToken.None)); } + + [Test] + public void AssumeYesForExtensionMethodReturningSameTypeAsThisParameter() + { + var testCode = @" +namespace RoslynSandbox +{ + using System; + using Stubs; + + class Foo + { + public Foo(IDisposable disposable) + { + var custom = disposable.AsCustom(); + } + } +}"; + var syntaxTree = CSharpSyntaxTree.ParseText(testCode); + var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); + var semanticModel = compilation.GetSemanticModel(syntaxTree); + var value = syntaxTree.FindInvocation("disposable.AsCustom()"); + Assert.AreEqual(Result.AssumeYes, Disposable.IsCreation(value, semanticModel, CancellationToken.None)); + } + + [Test] + public void AssumeNoForExtensionMethodReturningSameTypeAsThisParameter() + { + var testCode = @" +namespace RoslynSandbox +{ + using System; + using Stubs; + + class Foo + { + public Foo(IDisposable disposable) + { + var custom = disposable.Fluent(); + } + } +}"; + var syntaxTree = CSharpSyntaxTree.ParseText(testCode); + var compilation = CSharpCompilation.Create("test", new[] { syntaxTree }, MetadataReferences.FromAttributes()); + var semanticModel = compilation.GetSemanticModel(syntaxTree); + var value = syntaxTree.FindInvocation("disposable.Fluent()"); + Assert.AreEqual(Result.AssumeNo, Disposable.IsCreation(value, semanticModel, CancellationToken.None)); + } } } } diff --git a/IDisposableAnalyzers.Test/IDisposableAnalyzers.Test.csproj b/IDisposableAnalyzers.Test/IDisposableAnalyzers.Test.csproj index 481afcdc..209e89d7 100644 --- a/IDisposableAnalyzers.Test/IDisposableAnalyzers.Test.csproj +++ b/IDisposableAnalyzers.Test/IDisposableAnalyzers.Test.csproj @@ -27,6 +27,9 @@ + + Lib\Stubs.dll + diff --git a/IDisposableAnalyzers.Test/Lib/Stubs.dll b/IDisposableAnalyzers.Test/Lib/Stubs.dll new file mode 100644 index 0000000000000000000000000000000000000000..380e0135ae324dc2c2379fad4949e9a920c41140 GIT binary patch literal 4096 zcmeHK|8rE;6+dsY2{F+WLipk|g@-{)5#MA>K%}Y2k?C>Ra`?>eM z&2CunAEp>Rcnz_en=!F|j%{wY| zoSGNeq*`LiG!mwq6yvh48@3ozL^G|6sxAh*!(!6tQ>2@2s+#Yb?rA0J5X$Ih$9KJx zU+n_jB328HM3vxFINZ0ti%~@PqZ3W#xX1CEFMpdB~H9x$UOhq%G$b6-Z=*(6PdQtNettcWndYI)Ejrr!Ga(X}{dbgbD z2F5IE3lM*{h`t_LRkyIpvk(h~?_VL1z}1*f15Hw+w77AxubHvRNdr6zalOv`|)B{ zmI8LZA?Ka(s?iyr;j9z=RB*-$%F;=pg1+FeU10b!8WX0|Cs3%M1S#N1!Z6_b#Ng8$ zj&eAI^Lse%^$bBeorbt!$2!Z$qrz0WEF2M*02k<2SbHuVfmnc-JtFhv8QLuz2OV~) zYR~Uo&wY>zH**{d`6vd4v!7nHPL1i zAKqo1MqgS}mQ&J-8>XhlC=^t!lwrv+O`%XAZP`Y$h|^aP29;Pkkzjf)Hcm|}Z7bAb zSxPddWg@Cw%#O%rLb2O$B`I4B^Pz%%>sDLQE!EHqth|XfRa4d|=2r_tdcR>NWg9bP z?Yde4L+@7;X_M;}RtOl$l&W!NucFCYIc62)c@KI_se`|<0WlHe5up|Tuc+6;!)RE`Zs*WBE6VJQ17&BtAlCEA0z73@O?-5YN4 zu?vKKWnXR|IsBh9`^6`6+obI;`>V_T`eY*f{N4jnP2~OG9NbgxJ92WozJBT0mk-a~ zap^+fVoz%Q9hbe03*PWw>X`S>j?W{fe>Xa`^U0CV4`(mGsQ;wz4}-VwI6LR1)PY~u z){XD{&iaQI-ShE@xhJeY1!jL#e=hf0Thp%oyFUoMxxW74b5lZ(d{jX~?Txj~Y-p}+ z_Oi>MQs5Up(H!Pr#4~fPDW|#&y>)9`NwJJaHkrm2OMo5=h^SMbda0{5GI5L-xjT6; zZq}ue4@PS0CWP#p(d0}Qj(i=PB~D00u3aTiIp2O141uQLAS$w@ID`)uHGZfvP~rqz z|GgI;dv;}R+OG4D=>Ph=r&Zdqe95-hI~|V>p4qqXPJ7R%&qUsedOp4D$Dd?AzWVyD z4}LHALG$jDs|KR0Q{DP4Z~fv>c=YAxKOD}?@-O_`!LFx1`|Y2v3{>Xc%;nB!&$r*P zVr1&N(82VUk)6k8-L`6b_5O?RefW>he)P*PzP0VZ!2aPf>B@5E!|7F*PLR;q!!oiK zeH=AuoYhUYvBXYCXC*BR5Wc2}@n?Xm%ZA=?F#OoQH}=&&Ev+2BZ)TNu?d(xzBXD=L zUA3b{cTo#I^vodcgDB5h)QD}4x=D-biruu-=Ln=!UyQ;lTfVc)(Az*Ey=3x7X8zaSiIk2EbP81s1|fs|$1pu+4dO z(W~Bn|JSJ%jx7f#x{q6N-T*~rIX4I>z^Y6p?5KE4pjePj{bX?YeExEXKq`Z4K`xUG zJp+`}M!lD?9BM51CL}s4d#RVt&6y%ykJ5vT+D39ikeNl}J-mY_|rL?ogwn9KFbudosWl6PSjtuhLfir z%r&rX8gbi2UQNzZBhS-h-D@K+#EthM&ru8VVj+ekvaP{;5vO8uwhD7t&c10S84#WS Q8Q#6KMDh*e{~!bZ15{d~?*IS* literal 0 HcmV?d00001 diff --git a/IDisposableAnalyzers.Test/Properties/AssemblyInfo.cs b/IDisposableAnalyzers.Test/Properties/AssemblyInfo.cs index 7df4c886..54636a5c 100644 --- a/IDisposableAnalyzers.Test/Properties/AssemblyInfo.cs +++ b/IDisposableAnalyzers.Test/Properties/AssemblyInfo.cs @@ -20,4 +20,5 @@ typeof(Ninject.StandardKernel), typeof(Gu.Roslyn.AnalyzerExtensions.SyntaxTokenExt), typeof(Gu.Roslyn.CodeFixExtensions.Parse), + typeof(Stubs.Extensions), typeof(NUnit.Framework.Assert))] diff --git a/IDisposableAnalyzers.sln b/IDisposableAnalyzers.sln index a9a29d7f..83fda2c4 100644 --- a/IDisposableAnalyzers.sln +++ b/IDisposableAnalyzers.sln @@ -58,6 +58,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IDisposableAnalyzers", "IDi EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IDisposableAnalyzers.NetCoreTests", "IDisposableAnalyzers.NetCoreTests\IDisposableAnalyzers.NetCoreTests.csproj", "{8534B151-C831-4A31-B05B-7B2DAEA94033}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stubs", "Stubs\Stubs.csproj", "{8CC16990-2621-4854-8556-ED648A2D360F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/IDisposableAnalyzers/Helpers/Disposable.IsCreation.cs b/IDisposableAnalyzers/Helpers/Disposable.IsCreation.cs index ef7d40ba..5e9ae5f1 100644 --- a/IDisposableAnalyzers/Helpers/Disposable.IsCreation.cs +++ b/IDisposableAnalyzers/Helpers/Disposable.IsCreation.cs @@ -373,7 +373,7 @@ private static Result IsCreationCore(ISymbol candidate, Compilation compilation) return Result.AssumeNo; } - if (method.TryGetThisParameter(out var thisParameter)&& + if (method.TryGetThisParameter(out var thisParameter) && thisParameter.Type.Equals(method.ReturnType)) { if (method.ReturnType == KnownSymbol.ILoggerFactory) diff --git a/Stubs/Extensions.cs b/Stubs/Extensions.cs new file mode 100644 index 00000000..bac5fd8b --- /dev/null +++ b/Stubs/Extensions.cs @@ -0,0 +1,17 @@ +namespace Stubs +{ + using System; + + public static class Extensions + { + /// + /// Test method that returns a different type than the in parameter. We assume that the method creates a new disposable then. + /// + public static ICustomDisposable AsCustom(this IDisposable disposable) => default(ICustomDisposable); + + /// + /// Test method that returns the same type as the in parameter. We assume that the method does not create a new disposable then. + /// + public static IDisposable Fluent(this IDisposable disposable) => disposable; + } +} diff --git a/Stubs/ICustomDisposable.cs b/Stubs/ICustomDisposable.cs new file mode 100644 index 00000000..3228d4ab --- /dev/null +++ b/Stubs/ICustomDisposable.cs @@ -0,0 +1,8 @@ +namespace Stubs +{ + using System; + + public interface ICustomDisposable : IDisposable + { + } +} \ No newline at end of file diff --git a/Stubs/Stubs.csproj b/Stubs/Stubs.csproj new file mode 100644 index 00000000..d7f943ad --- /dev/null +++ b/Stubs/Stubs.csproj @@ -0,0 +1,11 @@ + + + + net461 + + + + true + ..\IDisposableAnalyzers.snk + +