Skip to content

Commit

Permalink
[andrewabestGH-87] Added convention to enforce a project file sets a …
Browse files Browse the repository at this point in the history
…property

Some examples:
- Convention.MustSetPropertyValue("Nullable", "enable") // Ensure nullable types are enabled for the project
- Convention.MustSetPropertyValue("IsPackable", "true") // Ensure the project can be packed for NuGet

See https://learn.microsoft.com/en-us/visualstudio/msbuild/property-element-msbuild?view=vs-2022#example
  • Loading branch information
eddie.stanley committed Jan 5, 2024
1 parent ee0b163 commit 0db29cc
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -281,5 +281,60 @@ public void MustNotReferencePackage_Failure()

result.IsSatisfied.Should().BeFalse();
}

[Test]
public void MustSetPropertyValue_SingleValue_Success()
{
var result = TheAssembly
.WithNameMatching("SdkClassLibrary1")
.MustConformTo(Convention.MustSetPropertyValue("TheUniversalAnswer", "42"));

result.IsSatisfied.Should().BeTrue();
}

[Theory]
[TestCase("Potato")]
[TestCase("Carrot")]
public void MustSetPropertyValue_MultipleValues_Success(string value)
{
var result = TheAssembly
.WithNameMatching("SdkClassLibrary1")
.MustConformTo(Convention.MustSetPropertyValue("Vegetable", value));

result.IsSatisfied.Should().BeTrue();
}

[Test]
public void MustSetPropertyValue_SingleValue_Failure()
{
var result = TheAssembly
.WithNameMatching("SdkClassLibrary1")
.MustConformTo(Convention.MustSetPropertyValue("TheUniversalAnswer", "41.999"));

result.IsSatisfied.Should().BeFalse();
result.Failures.Single().Should().Be("SdkClassLibrary1 should have property TheUniversalAnswer with value 41.999");
}

[Test]
public void MustSetPropertyValue_MultipleValues_Failure()
{
var result = TheAssembly
.WithNameMatching("SdkClassLibrary1")
.MustConformTo(Convention.MustSetPropertyValue("Vegetable", "Turnip")); // There's no <Vegetable>Turnip</Vegetable> in the csproj

result.IsSatisfied.Should().BeFalse();
result.Failures.Single().Should().Be("SdkClassLibrary1 should have property Vegetable with value Turnip");
}

[Test]
public void MustSetPropertyValue_NoValues_Failure()
{
var result = TheAssembly
.WithNameMatching("SdkClassLibrary1")
.MustConformTo(Convention.MustSetPropertyValue("ThisPropertyShouldNeverEverExist", "x")); // There's no <ThisPropertyShouldNeverEverExist>x</ThisPropertyShouldNeverEverExist> in the csproj

result.IsSatisfied.Should().BeFalse();
result.Failures.Single().Should().Be("SdkClassLibrary1 should have property ThisPropertyShouldNeverEverExist with value x");
}
}
}
12 changes: 12 additions & 0 deletions src/Core/Conventional/Convention.Assembly.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,17 @@ public static MustNotReferencePackageAssemblyConventionSpecification MustNotRefe
{
return new MustNotReferencePackageAssemblyConventionSpecification(packageName);
}

/// <summary>
/// Require this project to set a <see href="https://learn.microsoft.com/en-us/visualstudio/msbuild/property-element-msbuild?view=vs-2022#example">property value</see>
/// </summary>
/// <param name="propertyName">The name of the property</param>
/// <param name="value">The value the property should have</param>
/// <remarks>This convention is currently ignorant of <see href="https://learn.microsoft.com/en-us/visualstudio/msbuild/msbuild-conditions">MSBuild conditions</see></remarks>
public static MustSetPropertyValueAssemblyConventionSpecification MustSetPropertyValue(
string propertyName, string value)
{
return new MustSetPropertyValueAssemblyConventionSpecification(propertyName, value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;

namespace Conventional.Conventions.Assemblies
{
public class MustSetPropertyValueAssemblyConventionSpecification : AssemblyConventionSpecification
{
private string ExpectedPropertyName { get; }
private string ExpectedPropertyValue { get; }

public MustSetPropertyValueAssemblyConventionSpecification(string expectedPropertyName, string expectedPropertyValue)
{
ExpectedPropertyName = expectedPropertyName;
ExpectedPropertyValue = expectedPropertyValue;
}

protected override ConventionResult IsSatisfiedByLegacyCsprojFormat(string assemblyName, XDocument projectDocument)
{
return IsSatisfiedBy(assemblyName, projectDocument);
}

protected override ConventionResult IsSatisfiedBy(string assemblyName, XDocument projectDocument)
{
var matchingProperties = projectDocument.XPathSelectElements($"/Project/PropertyGroup/{ExpectedPropertyName}")
.Select(propertyElement => propertyElement.Value)
.Where(propertyValue => string.Equals(ExpectedPropertyValue, propertyValue, StringComparison.InvariantCulture));

return matchingProperties.Count() == 1
? ConventionResult.Satisfied(assemblyName)
: ConventionResult.NotSatisfied(assemblyName, string.Format(FailureMessage, assemblyName));
}

protected override string FailureMessage => "{0} should have property " + ExpectedPropertyName + " with value " + ExpectedPropertyValue;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TheUniversalAnswer>42</TheUniversalAnswer>
<Vegetable>Potato</Vegetable>
<Vegetable>Carrot</Vegetable>
</PropertyGroup>

<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
Expand Down

0 comments on commit 0db29cc

Please sign in to comment.