diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeFromDispose.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeFromDispose.cs index 2e155c8e37a..fc53f438324 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeFromDispose.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeFromDispose.cs @@ -73,7 +73,7 @@ protected override void Initialize(SonarAnalysisContext context) private static bool IsDisposeMethodCalled(InvocationExpressionSyntax invocation, SemanticModel semanticModel, LanguageVersion languageVersion) => semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol && KnownMethods.IsIDisposableDispose(methodSymbol) - && semanticModel.Compilation.GetTypeMethod(SpecialType.System_IDisposable, DisposeMethodName) is { } disposeMethodSignature + && semanticModel.Compilation.SpecialTypeMethod(SpecialType.System_IDisposable, DisposeMethodName) is { } disposeMethodSignature && (methodSymbol.Equals(methodSymbol.ContainingType.FindImplementationForInterfaceMember(disposeMethodSignature)) || methodSymbol.ContainingType.IsDisposableRefStruct(languageVersion)); diff --git a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeNotImplementingDispose.cs b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeNotImplementingDispose.cs index b3cc230d70a..382e249afe6 100644 --- a/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeNotImplementingDispose.cs +++ b/analyzers/src/SonarAnalyzer.CSharp/Rules/DisposeNotImplementingDispose.cs @@ -43,7 +43,7 @@ public sealed class DisposeNotImplementingDispose : SonarDiagnosticAnalyzer return; } - var disposeMethod = c.Compilation.GetTypeMethod(SpecialType.System_IDisposable, "Dispose"); + var disposeMethod = c.Compilation.SpecialTypeMethod(SpecialType.System_IDisposable, "Dispose"); if (disposeMethod == null) { return; diff --git a/analyzers/src/SonarAnalyzer.Common/Extensions/CompilationExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/CompilationExtensions.cs index e0c5953eebd..2288e0481c2 100644 --- a/analyzers/src/SonarAnalyzer.Common/Extensions/CompilationExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/CompilationExtensions.cs @@ -24,4 +24,15 @@ internal static class CompilationExtensions { public static INamedTypeSymbol GetTypeByMetadataName(this Compilation compilation, KnownType knownType) => compilation.GetTypeByMetadataName(knownType.FullName); + + public static IMethodSymbol SpecialTypeMethod(this Compilation compilation, SpecialType type, string methodName) => + (IMethodSymbol)compilation.GetSpecialType(type).GetMembers(methodName).SingleOrDefault(); + + public static bool IsNetFrameworkTarget(this Compilation compilation) => + // There's no direct way of checking compilation target framework yet (09/2020). + // See https://github.com/dotnet/roslyn/issues/3798 + compilation.ObjectType.ContainingAssembly.Name == "mscorlib"; + + public static bool References(this Compilation compilation, KnownAssembly assembly) + => assembly.IsReferencedBy(compilation); } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/CompilationExtensions.cs b/analyzers/src/SonarAnalyzer.Common/Extensions/KnownAssemblyExtensions.cs similarity index 56% rename from analyzers/src/SonarAnalyzer.Common/Helpers/CompilationExtensions.cs rename to analyzers/src/SonarAnalyzer.Common/Extensions/KnownAssemblyExtensions.cs index 5f1a42e0849..3051c8a9ae6 100644 --- a/analyzers/src/SonarAnalyzer.Common/Helpers/CompilationExtensions.cs +++ b/analyzers/src/SonarAnalyzer.Common/Extensions/KnownAssemblyExtensions.cs @@ -18,18 +18,13 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -namespace SonarAnalyzer.Helpers +namespace SonarAnalyzer.Extensions; + +internal static class KnownAssemblyExtensions { - public static class CompilationExtensions - { - public static IMethodSymbol GetTypeMethod(this Compilation compilation, SpecialType type, string methodName) => - (IMethodSymbol)compilation.GetSpecialType(type) - .GetMembers(methodName) - .SingleOrDefault(); + internal static Func And(this Func @this, Func predicate) + => identity => @this(identity) && predicate(identity); - public static bool IsNetFrameworkTarget(this Compilation compilation) => - // There's no direct way of checking compilation target framework yet (09/2020). - // See https://github.com/dotnet/roslyn/issues/3798 - compilation.ObjectType.ContainingAssembly.Name == "mscorlib"; - } + internal static Func Or(this Func @this, Func predicate) + => identity => @this(identity) || predicate(identity); } diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownAssembly.Predicates.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownAssembly.Predicates.cs new file mode 100644 index 00000000000..5aaa53dda82 --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownAssembly.Predicates.cs @@ -0,0 +1,74 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +namespace SonarAnalyzer.Helpers; + +public sealed partial class KnownAssembly +{ + private const StringComparison AssemblyNameComparission = StringComparison.OrdinalIgnoreCase; + + internal static class Predicates + { + internal static Func NameIs(string name) => + x => x.Name.Equals(name, AssemblyNameComparission); + + internal static Func StartsWith(string name) => + x => x.Name.StartsWith(name, AssemblyNameComparission); + + internal static Func EndsWith(string name) => + x => x.Name.EndsWith(name, AssemblyNameComparission); + + internal static Func Contains(string name) => + x => x.Name.IndexOf(name, 0, AssemblyNameComparission) >= 0; + + internal static Func VersionLowerThen(string version) => + VersionLowerThen(Version.Parse(version)); + + internal static Func VersionLowerThen(Version version) => + x => x.Version < version; + + internal static Func VersionGreaterOrEqual(string version) => + VersionGreaterOrEqual(Version.Parse(version)); + + internal static Func VersionGreaterOrEqual(Version version) => + x => x.Version >= version; + + internal static Func VersionBetween(string from, string to) => + VersionBetween(Version.Parse(from), Version.Parse(to)); + + internal static Func VersionBetween(Version from, Version to) => + x => x.Version >= from && x.Version <= to; + + internal static Func OptionalPublicKeyTokenIs(string key) => + x => !x.HasPublicKey || PublicKeyEqualHex(x, key); + + internal static Func PublicKeyTokenIs(string key) => + x => x.HasPublicKey && PublicKeyEqualHex(x, key); + + private static bool PublicKeyEqualHex(AssemblyIdentity identity, string hexString) + { + var normalizedHexString = hexString.Replace("-", string.Empty); + return ArraysEqual(identity.PublicKeyToken.ToArray(), normalizedHexString) || ArraysEqual(identity.PublicKey.ToArray(), normalizedHexString); + + static bool ArraysEqual(byte[] key, string hexString) => + BitConverter.ToString(key).Replace("-", string.Empty).Equals(hexString, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/analyzers/src/SonarAnalyzer.Common/Helpers/KnownAssembly.cs b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownAssembly.cs new file mode 100644 index 00000000000..d7f085bcefd --- /dev/null +++ b/analyzers/src/SonarAnalyzer.Common/Helpers/KnownAssembly.cs @@ -0,0 +1,48 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using static SonarAnalyzer.Helpers.KnownAssembly.Predicates; + +namespace SonarAnalyzer.Helpers; + +public sealed partial class KnownAssembly +{ + private readonly Func, bool> predicate; + + public static KnownAssembly XUnit_Assert { get; } = new( + And(NameIs("xunit.assert").Or(NameIs("xunit").And(VersionLowerThen("2.0"))), + PublicKeyTokenIs("8d05b1bb7a6fdb6c"))); + + internal KnownAssembly(Func predicate, params Func[] or) + : this(predicate is null || or.Any(x => x is null) + ? throw new ArgumentNullException(nameof(predicate), "All predicates must be non-null.") + : identities => identities.Any(identitiy => predicate(identitiy) || or.Any(orPredicate => orPredicate(identitiy)))) + { + } + + internal KnownAssembly(Func, bool> predicate) => + this.predicate = predicate ?? throw new ArgumentNullException(nameof(predicate)); + + public bool IsReferencedBy(Compilation compilation) => + predicate(compilation.ReferencedAssemblyNames); + + internal static Func And(Func left, Func right) => + KnownAssemblyExtensions.And(left, right); +} diff --git a/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/KnownAssemblyTest.cs b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/KnownAssemblyTest.cs new file mode 100644 index 00000000000..9170933ef03 --- /dev/null +++ b/analyzers/tests/SonarAnalyzer.UnitTest/Helpers/KnownAssemblyTest.cs @@ -0,0 +1,284 @@ +/* + * SonarAnalyzer for .NET + * Copyright (C) 2015-2023 SonarSource SA + * mailto: contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using Moq; +using SonarAnalyzer.Extensions; +using static SonarAnalyzer.Helpers.KnownAssembly; +using static SonarAnalyzer.Helpers.KnownAssembly.Predicates; + +namespace SonarAnalyzer.UnitTest.Helpers; + +[TestClass] +public class KnownAssemblyTest +{ + [TestMethod] + public void KnownReference_ThrowsWhenPredicateNull_1() + { + var sut = () => new KnownAssembly(default(Func)); + sut.Should().Throw().Which.ParamName.Should().Be("predicate"); + } + + [TestMethod] + public void KnownReference_ThrowsWhenPredicateNull_2() + { + var sut = () => new KnownAssembly(default(Func, bool>)); + sut.Should().Throw().Which.ParamName.Should().Be("predicate"); + } + + [TestMethod] + public void KnownReference_ThrowsWhenPredicateNull_Params() + { + var sut = () => new KnownAssembly(_ => true, null, _ => true); + sut.Should().Throw().Which.ParamName.Should().Be("predicate"); + } + + [DataTestMethod] + [DataRow("Test", true)] + [DataRow("test", true)] + [DataRow("TEST", true)] + [DataRow("MyTest", false)] + [DataRow("TestMy", false)] + [DataRow("MyTestMy", false)] + [DataRow("MyTESTMy", false)] + [DataRow("Without", false)] + public void NameIs_Test(string name, bool expected) + { + var sut = new KnownAssembly(NameIs("Test")); + var identity = new AssemblyIdentity(name); + var compilation = CompilationWithReferenceTo(identity); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [DataTestMethod] + [DataRow("Test", true)] + [DataRow("test", true)] + [DataRow("TEST", true)] + [DataRow("MyTest", true)] + [DataRow("TestMy", true)] + [DataRow("MyTestMy", true)] + [DataRow("MyTESTMy", true)] + [DataRow("Without", false)] + public void NameContains_Test(string name, bool expected) + { + var sut = new KnownAssembly(Contains("Test")); + var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [DataTestMethod] + [DataRow("Test", true)] + [DataRow("test", true)] + [DataRow("TEST", true)] + [DataRow("MyTest", false)] + [DataRow("TestMy", true)] + [DataRow("MyTestMy", false)] + [DataRow("MyTESTMy", false)] + [DataRow("Without", false)] + public void NameStartsWith_Test(string name, bool expected) + { + var sut = new KnownAssembly(StartsWith("Test")); + var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [DataTestMethod] + [DataRow("Test", true)] + [DataRow("test", true)] + [DataRow("TEST", true)] + [DataRow("MyTest", true)] + [DataRow("TestMy", false)] + [DataRow("MyTestMy", false)] + [DataRow("MyTESTMy", false)] + [DataRow("Without", false)] + public void NameEndsWith_Test(string name, bool expected) + { + var sut = new KnownAssembly(EndsWith("Test")); + var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [DataTestMethod] + [DataRow("1.0.0.0", false)] + [DataRow("1.9.9.99", false)] + [DataRow("2.0.0.0", true)] + [DataRow("2.0.0.1", true)] + [DataRow("2.1.0.0", true)] + [DataRow("3.1.0.0", true)] + public void Version_GreaterOrEqual_2_0(string version, bool expected) + { + var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", new Version(version))); + var sut = new KnownAssembly(VersionGreaterOrEqual(new Version(2, 0))); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + sut = new KnownAssembly(VersionGreaterOrEqual("2.0")); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [DataTestMethod] + [DataRow("1.0.0.0", true)] + [DataRow("1.9.9.99", true)] + [DataRow("2.0.0.0", false)] + [DataRow("2.0.0.1", false)] + [DataRow("2.1.0.0", false)] + [DataRow("3.1.0.0", false)] + public void Version_LowerThen_2_0(string version, bool expected) + { + var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", new Version(version))); + var sut = new KnownAssembly(VersionLowerThen(new Version(2, 0))); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + sut = new KnownAssembly(VersionLowerThen("2.0")); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [DataTestMethod] + [DataRow("1.0.0.0", false)] + [DataRow("1.9.9.99", false)] + [DataRow("2.0.0.0", true)] + [DataRow("2.0.0.1", true)] + [DataRow("2.1.0.0", true)] + [DataRow("3.1.0.0", true)] + [DataRow("3.4.9.99", true)] + [DataRow("3.5.0.0", true)] + [DataRow("3.5.0.1", false)] + [DataRow("10.0.0.0", false)] + public void Version_Between_2_0_and_3_5(string version, bool expected) + { + var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", new Version(version))); + var sut = new KnownAssembly(VersionBetween(new Version(2, 0, 0, 0), new Version(3, 5, 0, 0))); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + sut = new KnownAssembly(VersionBetween("2.0.0.0", "3.5.0.0")); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [DataTestMethod] + [DataRow("c5b62af9de6d7244", true)] + [DataRow("C5B62AF9DE6D7244", true)] + [DataRow("c5-b6-2a-f9-de-6d-72-44", true)] + [DataRow( + "002400000480000094000000060200000024000052534131000400000100010081b4345a022cc0f4b42bdc795a5a7a1623c1e58dc2246645d751ad41ba98f2749dc5c4e0da3a9e09febcb2cd5b088a0f" + + "041f8ac24b20e736d8ae523061733782f9c4cd75b44f17a63714aced0b29a59cd1ce58d8e10ccdb6012c7098c39871043b7241ac4ab9f6b34f183db716082cd57c1ff648135bece256357ba735e67dc6", true)] + [DataRow("AA-68-91-16-d3-a4-ae-33", false)] + public void PublicKeyTokenIs_c5b62af9de6d7244(string publicKeyToken, bool expected) + { + var identity = new AssemblyIdentity("assemblyName", publicKeyOrToken: ImmutableArray.Create( + 0x00, 0x24, 0x00, 0x00, 0x04, 0x80, 0x00, 0x00, 0x94, 0x00, 0x00, 0x00, 0x06, 0x02, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x52, 0x53, 0x41, 0x31, 0x00, + 0x04, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x81, 0xb4, 0x34, 0x5a, 0x02, 0x2c, 0xc0, 0xf4, 0xb4, 0x2b, 0xdc, 0x79, 0x5a, 0x5a, 0x7a, 0x16, 0x23, 0xc1, + 0xe5, 0x8d, 0xc2, 0x24, 0x66, 0x45, 0xd7, 0x51, 0xad, 0x41, 0xba, 0x98, 0xf2, 0x74, 0x9d, 0xc5, 0xc4, 0xe0, 0xda, 0x3a, 0x9e, 0x09, 0xfe, 0xbc, 0xb2, + 0xcd, 0x5b, 0x08, 0x8a, 0x0f, 0x04, 0x1f, 0x8a, 0xc2, 0x4b, 0x20, 0xe7, 0x36, 0xd8, 0xae, 0x52, 0x30, 0x61, 0x73, 0x37, 0x82, 0xf9, 0xc4, 0xcd, 0x75, + 0xb4, 0x4f, 0x17, 0xa6, 0x37, 0x14, 0xac, 0xed, 0x0b, 0x29, 0xa5, 0x9c, 0xd1, 0xce, 0x58, 0xd8, 0xe1, 0x0c, 0xcd, 0xb6, 0x01, 0x2c, 0x70, 0x98, 0xc3, + 0x98, 0x71, 0x04, 0x3b, 0x72, 0x41, 0xac, 0x4a, 0xb9, 0xf6, 0xb3, 0x4f, 0x18, 0x3d, 0xb7, 0x16, 0x08, 0x2c, 0xd5, 0x7c, 0x1f, 0xf6, 0x48, 0x13, 0x5b, + 0xec, 0xe2, 0x56, 0x35, 0x7b, 0xa7, 0x35, 0xe6, 0x7d, 0xc6), hasPublicKey: true); + var compilation = CompilationWithReferenceTo(identity); + var sut = new KnownAssembly(PublicKeyTokenIs(publicKeyToken)); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + sut = new KnownAssembly(OptionalPublicKeyTokenIs(publicKeyToken)); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [TestMethod] + public void PublicKeyTokenIs_FailsWhenKeyIsMissing() + { + var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", hasPublicKey: false)); + var sut = new KnownAssembly(PublicKeyTokenIs("c5b62af9de6d7244")); + sut.IsReferencedBy(compilation.Object).Should().BeFalse(); + } + + [TestMethod] + public void OptionalPublicKeyTokenIs_SucceedsWhenKeyIsMissing() + { + var compilation = CompilationWithReferenceTo(new AssemblyIdentity("assemblyName", hasPublicKey: false)); + var sut = new KnownAssembly(OptionalPublicKeyTokenIs("c5b62af9de6d7244")); + sut.IsReferencedBy(compilation.Object).Should().BeTrue(); + } + + [DataTestMethod] + [DataRow("Test", "1.0.0.0", false)] + [DataRow("Test", "1.9.9.99", false)] + [DataRow("TestMy", "2.0.0.0", true)] + [DataRow("MyTest", "2.0.0.0", false)] + [DataRow("TestMy", "3.5.0.0", true)] + [DataRow("TestMy", "3.5.0.1", false)] + [DataRow("Test", "10.0.0.0", false)] + public void Combinator_NameStartWith_Test_And_Version_Between_2_0_And_3_5(string name, string version, bool expected) + { + var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name, new Version(version))); + var sut = new KnownAssembly(StartsWith("Test").And(VersionBetween(new Version(2, 0, 0, 0), new Version(3, 5, 0, 0)))); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [DataTestMethod] + [DataRow("Start", true)] + [DataRow("End", true)] + [DataRow("StartOrEnd", true)] + [DataRow("StartTest", true)] + [DataRow("TestEnd", true)] + [DataRow("EndStart", false)] + [DataRow("EndSomething", false)] + [DataRow("SomethingStart", false)] + public void Combinator_StartsWith_Start_Or_EndsWith_End_1(string name, bool expected) + { + var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); + var sut = new KnownAssembly(StartsWith("Start"), EndsWith("End")); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [DataTestMethod] + [DataRow("Start", true)] + [DataRow("End", true)] + [DataRow("StartOrEnd", true)] + [DataRow("StartTest", true)] + [DataRow("TestEnd", true)] + [DataRow("EndStart", false)] + [DataRow("EndSomething", false)] + [DataRow("SomethingStart", false)] + public void Combinator_StartsWith_Start_Or_EndsWith_End_2(string name, bool expected) + { + var compilation = CompilationWithReferenceTo(new AssemblyIdentity(name)); + var sut = new KnownAssembly(StartsWith("Start").Or(EndsWith("End"))); + sut.IsReferencedBy(compilation.Object).Should().Be(expected); + } + + [TestMethod] + public void XUnitAssert_2_4() + { + var compilation = TestHelper.CompileCS("// Empty file", NuGetMetadataReference.XunitFramework("2.4.2").ToArray()).Model.Compilation; + compilation.References(XUnit_Assert).Should().BeTrue(); + } + + [TestMethod] + public void XUnitAssert_1_9() + { + var compilation = TestHelper.CompileCS("// Empty file", NuGetMetadataReference.XunitFrameworkV1.ToArray()).Model.Compilation; + compilation.References(XUnit_Assert).Should().BeTrue(); + } + + [TestMethod] + public void XUnitAssert_NoReference() + { + var compilation = TestHelper.CompileCS("// Empty file").Model.Compilation; + compilation.References(XUnit_Assert).Should().BeFalse(); + } + + private static Mock CompilationWithReferenceTo(AssemblyIdentity identity) + { + var compilation = new Mock("compilationName", ImmutableArray.Empty, new Dictionary(), false, null, null); + compilation.SetupGet(x => x.ReferencedAssemblyNames).Returns(new[] { identity }); + return compilation; + } +}