Skip to content

Commit 6bc3176

Browse files
author
eddie.stanley
committed
[andrewabestGH-87] Added new conventions to enforce that a project references/does not reference a package (by name)
An example use-case is something like the coverlet collector (https://github.com/coverlet-coverage/coverlet) which should be added ONLY to (ALL) test projects - Added new SDK-style project & bumped project count
1 parent 740a4d3 commit 6bc3176

File tree

8 files changed

+160
-1
lines changed

8 files changed

+160
-1
lines changed

src/Core/Conventional.Tests/AllAssembliesScenarios.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public void GivenAPattern_LocatesAndReturnsAllAssembliesForThatPattern()
1010
{
1111
var assemblySpecimen = AllAssemblies.WithNamesMatching("*");
1212

13-
assemblySpecimen.Should().HaveCount(5);
13+
assemblySpecimen.Should().HaveCount(6);
1414
}
1515
}
1616
}

src/Core/Conventional.Tests/Conventional/Conventions/Assemblies/AssemblyConventionSpecificationTests.cs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,5 +241,45 @@ public void MustNotIncludeProjectReferences_Failure()
241241
result.IsSatisfied.Should().BeFalse();
242242
result.Failures.Single().Should().StartWith("Conventional.Tests includes reference to project");
243243
}
244+
245+
[Test]
246+
public void MustReferencePackage_Success()
247+
{
248+
var result = TheAssembly
249+
.WithNameMatching("SdkClassLibrary1")
250+
.MustConformTo(Convention.MustReferencePackage("coverlet.collector"));
251+
252+
result.IsSatisfied.Should().BeTrue();
253+
}
254+
255+
[Test]
256+
public void MustReferencePackage_Failure()
257+
{
258+
var result = TheAssembly
259+
.WithNameMatching("SdkClassLibrary1")
260+
.MustConformTo(Convention.MustReferencePackage("koverlet.kollector"));
261+
262+
result.IsSatisfied.Should().BeFalse();
263+
}
264+
265+
[Test]
266+
public void MustNotReferencePackage_Success()
267+
{
268+
var result = TheAssembly
269+
.WithNameMatching("SdkClassLibrary1")
270+
.MustConformTo(Convention.MustNotReferencePackage("foo.bar.baz"));
271+
272+
result.IsSatisfied.Should().BeTrue();
273+
}
274+
275+
[Test]
276+
public void MustNotReferencePackage_Failure()
277+
{
278+
var result = TheAssembly
279+
.WithNameMatching("SdkClassLibrary1")
280+
.MustConformTo(Convention.MustNotReferencePackage("coverlet.collector"));
281+
282+
result.IsSatisfied.Should().BeFalse();
283+
}
244284
}
245285
}

src/Core/Conventional/Convention.Assembly.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,25 @@ public static MustBeIncludedInSetOfAssembliesConventionSpecification MustBeInclu
8888
/// <remarks>This convention is unaware of shared build prop files (Directory.Build.Props + Directory.Build.Targets) - see https://github.com/andrewabest/Conventional/issues/88</remarks>
8989
public static MustNotIncludeProjectReferencesConventionSpecification MustNotIncludeProjectReferences =>
9090
new MustNotIncludeProjectReferencesConventionSpecification();
91+
92+
/// <summary>
93+
/// Requires that this assembly (project) references the specified package
94+
/// </summary>
95+
/// <param name="packageName">The name of the package that must be referenced</param>
96+
/// <remarks>This convention is unaware of shared build prop files (Directory.Build.Props + Directory.Build.Targets) - see https://github.com/andrewabest/Conventional/issues/88</remarks>
97+
public static MustReferencePackageAssemblyConventionSpecification MustReferencePackage(string packageName)
98+
{
99+
return new MustReferencePackageAssemblyConventionSpecification(packageName);
100+
}
101+
102+
/// <summary>
103+
/// Requires that this assembly (project) does not reference the specified package
104+
/// </summary>
105+
/// <param name="packageName">The name of the package that must not be referenced</param>
106+
/// <remarks>This convention is unaware of shared build prop files (Directory.Build.Props + Directory.Build.Targets) - see https://github.com/andrewabest/Conventional/issues/88</remarks>
107+
public static MustNotReferencePackageAssemblyConventionSpecification MustNotReferencePackage(string packageName)
108+
{
109+
return new MustNotReferencePackageAssemblyConventionSpecification(packageName);
110+
}
91111
}
92112
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Linq;
2+
using System.Xml.Linq;
3+
4+
namespace Conventional.Conventions.Assemblies
5+
{
6+
public class MustNotReferencePackageAssemblyConventionSpecification : PackageReferenceAssemblyConventionSpecification
7+
{
8+
private readonly string _needlePackage;
9+
10+
public MustNotReferencePackageAssemblyConventionSpecification(string needlePackage)
11+
{
12+
_needlePackage = needlePackage;
13+
}
14+
15+
protected override ConventionResult IsSatisfiedBy(string assemblyName, XDocument projectDocument)
16+
{
17+
if (GetPackageReferences(projectDocument).Contains(_needlePackage))
18+
{
19+
return ConventionResult.NotSatisfied(assemblyName, string.Format(FailureMessage, assemblyName));
20+
}
21+
22+
return ConventionResult.Satisfied(assemblyName);
23+
}
24+
25+
protected override string FailureMessage => "{0} should not reference package " + _needlePackage;
26+
}
27+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using System.Linq;
2+
using System.Xml.Linq;
3+
4+
namespace Conventional.Conventions.Assemblies
5+
{
6+
public class MustReferencePackageAssemblyConventionSpecification : PackageReferenceAssemblyConventionSpecification
7+
{
8+
private readonly string _needlePackage;
9+
10+
public MustReferencePackageAssemblyConventionSpecification(string needlePackage)
11+
{
12+
_needlePackage = needlePackage;
13+
}
14+
15+
protected override ConventionResult IsSatisfiedBy(string assemblyName, XDocument projectDocument)
16+
{
17+
if (GetPackageReferences(projectDocument).Contains(_needlePackage))
18+
{
19+
return ConventionResult.Satisfied(assemblyName);
20+
}
21+
22+
return ConventionResult.NotSatisfied(assemblyName, string.Format(FailureMessage, assemblyName));
23+
}
24+
25+
protected override string FailureMessage => "{0} should reference package " + _needlePackage;
26+
}
27+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
using System.Xml.Linq;
4+
using System.Xml.XPath;
5+
6+
namespace Conventional.Conventions.Assemblies
7+
{
8+
public abstract class PackageReferenceAssemblyConventionSpecification : AssemblyConventionSpecification
9+
{
10+
protected IEnumerable<string> GetPackageReferences(XDocument projectDocument)
11+
{
12+
// Note: The Project element (and descendants) are namespaced in legacy csproj files, so our XPath ignores the
13+
// Note: namespace by considering the local element name only. Once we no-longer need to support legacy csproj
14+
// Note: files, the XPath can be simplified to /Project/ItemGroup/PackageReference
15+
return projectDocument.XPathSelectElements("/*[local-name() = 'Project']/*[local-name() = 'ItemGroup']/*[local-name() = 'PackageReference']")
16+
.Select(referenceElement => referenceElement.Attribute("Include")?.Value)
17+
.Where(value => value != null);
18+
}
19+
20+
protected override ConventionResult IsSatisfiedByLegacyCsprojFormat(string assemblyName, XDocument projectDocument)
21+
{
22+
return IsSatisfiedBy(assemblyName, projectDocument);
23+
}
24+
}
25+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.1</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="coverlet.collector" Version="6.0.0">
9+
<PrivateAssets>all</PrivateAssets>
10+
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
11+
</PackageReference>
12+
</ItemGroup>
13+
14+
</Project>

src/Core/TestSolution/TestSolution.TestProject/TestSolution.TestProject.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestSolution.TestProject",
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProjectTwo", "..\TestProjectTwo\TestProjectTwo.csproj", "{DA39482D-C4B4-41B8-9908-BF715AE9DD7C}"
99
EndProject
10+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SdkClassLibrary1", "SdkClassLibrary1\SdkClassLibrary1.csproj", "{C0988207-0023-4364-BFE0-04F8954501C4}"
11+
EndProject
1012
Global
1113
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1214
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
2123
{DA39482D-C4B4-41B8-9908-BF715AE9DD7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
2224
{DA39482D-C4B4-41B8-9908-BF715AE9DD7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
2325
{DA39482D-C4B4-41B8-9908-BF715AE9DD7C}.Release|Any CPU.Build.0 = Release|Any CPU
26+
{C0988207-0023-4364-BFE0-04F8954501C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27+
{C0988207-0023-4364-BFE0-04F8954501C4}.Debug|Any CPU.Build.0 = Debug|Any CPU
28+
{C0988207-0023-4364-BFE0-04F8954501C4}.Release|Any CPU.ActiveCfg = Release|Any CPU
29+
{C0988207-0023-4364-BFE0-04F8954501C4}.Release|Any CPU.Build.0 = Release|Any CPU
2430
EndGlobalSection
2531
GlobalSection(SolutionProperties) = preSolution
2632
HideSolutionNode = FALSE

0 commit comments

Comments
 (0)