Skip to content

Commit

Permalink
Introduction of Qowaiv.Diagnostics.Contracts package (#375)
Browse files Browse the repository at this point in the history
  • Loading branch information
Corniel committed Feb 23, 2024
1 parent 7eca118 commit 59f982e
Show file tree
Hide file tree
Showing 38 changed files with 349 additions and 37 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Expand Up @@ -100,6 +100,7 @@ dotnet_diagnostic.SA1515.severity = suggestion # Single-line comment should be p

dotnet_diagnostic.S100.severity = none # Properties should be named in PascalCase
dotnet_diagnostic.S101.severity = none # Types should be named in PascalCase
dotnet_diagnostic.S1694.severity = none # An abstract class should have both abstract and concrete methods
dotnet_diagnostic.S2328.severity = none # GetHashCode() should not reference mutable fields
dotnet_diagnostic.S2178.severity = none # Short-circuit logic should be used in boolean contexts - '&' is used instead of && to accommodate expressions with nullable booleans too.
dotnet_diagnostic.S3376.severity = none # EventArgs, and Exception type names should end with the type being extended
Expand All @@ -122,7 +123,6 @@ dotnet_diagnostic.S1227.severity = warning # break statements should not be used
dotnet_diagnostic.S1479.severity = warning # Consider reworking this 'switch' to reduce the number of 'case's to at most 30
dotnet_diagnostic.S1541.severity = warning # Methods and properties should not be too complex
dotnet_diagnostic.S1858.severity = warning # "ToString()" calls should not be redundant
dotnet_diagnostic.S1694.severity = warning # An abstract class should have both abstract and concrete methods
dotnet_diagnostic.S2197.severity = warning # Modulus results should not be checked for direct equa
dotnet_diagnostic.S2302.severity = warning # "nameof" should be used
dotnet_diagnostic.S2342.severity = warning # Rename this enumeration to match naming convention
Expand Down
6 changes: 6 additions & 0 deletions Qowaiv.sln
Expand Up @@ -70,6 +70,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "shared", "shared", "{7F291C
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Qowaiv.Benchmarks", "specs\Qowaiv.Benchmarks\Qowaiv.Benchmarks.csproj", "{6F2DEAC5-6E5D-4144-BA24-37039F387D67}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Qowaiv.Diagnostics.Contracts", "src\Qowaiv.Diagnostics.Contracts\Qowaiv.Diagnostics.Contracts.csproj", "{75090D12-3917-4CDD-8DDE-09B8407723A8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -108,6 +110,10 @@ Global
{6F2DEAC5-6E5D-4144-BA24-37039F387D67}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6F2DEAC5-6E5D-4144-BA24-37039F387D67}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6F2DEAC5-6E5D-4144-BA24-37039F387D67}.Release|Any CPU.Build.0 = Release|Any CPU
{75090D12-3917-4CDD-8DDE-09B8407723A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{75090D12-3917-4CDD-8DDE-09B8407723A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75090D12-3917-4CDD-8DDE-09B8407723A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75090D12-3917-4CDD-8DDE-09B8407723A8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down
15 changes: 10 additions & 5 deletions README.md
Expand Up @@ -6,11 +6,12 @@
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=Qowaiv_Qowaiv&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=Qowaiv_Qowaiv)
[![Coverage Status](https://coveralls.io/repos/github/Qowaiv/Qowaiv/badge.svg?branch=master)](https://coveralls.io/github/Qowaiv/Qowaiv?branch=master)

| version | downloads | package |
|--------------------------------------------------------------------|-----------------------------------------------------------|-----------------------------------------------------------------------------|
|![v](https://img.shields.io/nuget/v/Qowaiv?color=18C) |![v](https://img.shields.io/nuget/dt/Qowaiv) |[Qowaiv](https://www.nuget.org/packages/Qowaiv/) |
|![v](https://img.shields.io/nuget/v/Qowaiv.Data.SqlClient?color=18C)|![v](https://img.shields.io/nuget/dt/Qowaiv.Data.SqlClient)|[Qowaiv.Data.SqlCient](https://www.nuget.org/packages/Qowaiv.Data.SqlClient/)|
|![v](https://img.shields.io/nuget/v/Qowaiv.TestTools?color=118) |![v](https://img.shields.io/nuget/dt/Qowaiv.TestTools) |[Qowaiv.TestTools](https://www.nuget.org/packages/Qowaiv.TestTools/) |
| version | downloads | package |
|----------------------------------------------------------------------------------|------------------------------------------------------------------|--------------------------------------------------------------------------------|
|![v](https://img.shields.io/nuget/v/Qowaiv?color=18C) |![v](https://img.shields.io/nuget/dt/Qowaiv) |[Qowaiv](https://www.nuget.org/packages/Qowaiv/) |
|![v](https://img.shields.io/nuget/v/Qowaiv.Data.SqlClient?color=18C) |![v](https://img.shields.io/nuget/dt/Qowaiv.Data.SqlClient) |[Qowaiv.Data.SqlCient](https://www.nuget.org/packages/Qowaiv.Data.SqlClient/) |
|![v](https://img.shields.io/nuget/v/Qowaiv.Qowaiv.Diagnostics.Contracts?color=118)|![v](https://img.shields.io/nuget/dt/Qowaiv.Diagnostics.Contracts)|[Qowaiv.TestTools](https://www.nuget.org/packages/Qowaiv.Diagnostics.Contracts/)|
|![v](https://img.shields.io/nuget/v/Qowaiv.TestTools?color=118) |![v](https://img.shields.io/nuget/dt/Qowaiv.TestTools) |[Qowaiv.TestTools](https://www.nuget.org/packages/Qowaiv.TestTools/) |

# Qowaiv

Expand Down Expand Up @@ -1134,3 +1135,7 @@ Since .NET 8.0, Microsoft provides a `TimeProvider`. To benefit from both the
`Qowaiv.Clock` mechanism, and this time provider, the `Clock.TimeProvider`,
a singleton which provides access to `Clock.UtcNow()` and `Clock.TimeZone` is
added.

# Qowaiv Diagnostics Contracts
This packages contains attributes to define (expected) behavior on code
[(..)](src/Qowaiv.Diagnostics.Contracts/README.md)
4 changes: 2 additions & 2 deletions props/common.props
Expand Up @@ -56,8 +56,8 @@

<ItemGroup Label="Analyzers">
<PackageReference Include="AsyncFixer" Version="*" PrivateAssets="all" />
<PackageReference Include="DotNetProjectFile.Analyzers" Version="*" Condition="'$(TargetFramework)'=='net8.0'" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="*" PrivateAssets="all" />
<PackageReference Include="DotNetProjectFile.Analyzers" Version="*" PrivateAssets="all" Condition="'$(TargetFramework)'=='net8.0'" />
<PackageReference Include="SonarAnalyzer.CSharp" Version="*" PrivateAssets="all" />
</ItemGroup>

<ItemGroup Label="Additional files">
Expand Down
2 changes: 1 addition & 1 deletion specs/Qowaiv.Benchmarks/Properties/GlobalUsings.cs
@@ -1,7 +1,7 @@
global using BenchmarkDotNet.Attributes;
global using MathNet.Numerics.Random;
global using Qowaiv.Diagnostics.Contracts;
global using Qowaiv.Financial;
global using Qowaiv.Diagnostics.Contracts;
global using Qowaiv.Globalization;
global using Qowaiv.Text;
global using System;
Expand Down
3 changes: 2 additions & 1 deletion specs/Qowaiv.Benchmarks/Qowaiv.Benchmarks.csproj
Expand Up @@ -24,10 +24,11 @@

<ItemGroup Label="Analyzers">
<PackageReference Include="FluentAssertions.Analyzers" Version="*" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="*" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="*" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Qowaiv.Diagnostics.Contracts\Qowaiv.Diagnostics.Contracts.csproj" />
<ProjectReference Include="..\..\src\Qowaiv\Qowaiv.csproj" />
<ProjectReference Include="..\..\src\Qowaiv.TestTools\Qowaiv.TestTools.csproj" />
</ItemGroup>
Expand Down
1 change: 0 additions & 1 deletion specs/Qowaiv.Benchmarks/UuidBenchmark.cs
@@ -1,6 +1,5 @@
using Qowaiv;
using Qowaiv.Identifiers;
using Qowaiv.TestTools;

namespace Benchmarks;

Expand Down
22 changes: 22 additions & 0 deletions specs/Qowaiv.Specs/Diagnostics/Contracts/Conditional_specs.cs
@@ -0,0 +1,22 @@
namespace Diagnostics.Contracts.Conditional_specs;

public class Is_decorated_with
{
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.CollectionMutationAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.FluentSyntaxAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.ImpureAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.InheritableAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.MutableAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.EmptyTypeAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.EmptyClassAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.EmptyEnumAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.EmptyInterfaceAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.EmptyStructAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.EmptyTestClassAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.EmptyTestEnumAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.EmptyTestInterfaceAttribute))]
[TestCase(typeof(Qowaiv.Diagnostics.Contracts.EmptyTestStructAttribute))]
public void condtional_CONTRACTS_FULL(Type attribute)
=> attribute.Should().BeDecoratedWith<ConditionalAttribute>()
.Which.ConditionString.Should().Be("CONTRACTS_FULL");
}
@@ -0,0 +1,32 @@
namespace Diagnostics.Contracts.Empty_type_attribute_specs;

public class Can_decorate_empty
{
[Test]
public void Classes()
=> typeof(SomeEmptyClass).Should().BeDecoratedWith<EmptyTestClassAttribute>();

[Test]
public void Enums()
=> typeof(SomeEmptyEnumeration).Should().BeDecoratedWith<EmptyTestEnumAttribute>();

[Test]
public void Interfaces()
=> typeof(ISomeEmptyInterface).Should().BeDecoratedWith<EmptyTestInterfaceAttribute>();

[Test]
public void Structs()
=> typeof(SomeEmptyStruct).Should().BeDecoratedWith<EmptyTestStructAttribute>();
}

[EmptyTestClass]
internal class SomeEmptyClass { }

[EmptyTestEnum]
internal enum SomeEmptyEnumeration { }

[EmptyTestInterface]
internal interface ISomeEmptyInterface { }

[EmptyTestStruct]
internal struct SomeEmptyStruct { }
28 changes: 28 additions & 0 deletions specs/Qowaiv.Specs/Diagnostics/Contracts/Impure_attribute_specs.cs
@@ -0,0 +1,28 @@
namespace Diagnostics.Contracts.Impure_attribute_specs;

public class Can_decorate_methods_with
{
[Test]
public void Collection_mutation_attribute()
=> typeof(SomeClass).GetMethod(nameof(SomeClass.CollectionMutation)).Should().BeDecoratedWith<CollectionMutationAttribute>();

[Test]
public void Impure_attribute()
=> typeof(SomeClass).GetMethod(nameof(SomeClass.Impure)).Should().BeDecoratedWith<ImpureAttribute>();

[Test]
public void Fluent_syntax_attribute()
=> typeof(SomeClass).GetMethod(nameof(SomeClass.FluentSyntax)).Should().BeDecoratedWith<FluentSyntaxAttribute>();
}

internal class SomeClass
{
[CollectionMutation("It just returns if addition worked.")]
public bool CollectionMutation(HashSet<SomeClass> set) => set.Add(this);

[Impure("It has side effects.")]
public static int Impure() => 42;

[FluentSyntax("We like fluent syntaxes.")]
public SomeClass FluentSyntax() => this;
}
@@ -0,0 +1,13 @@
namespace Diagnostics.Contracts.Inheritable_attribute_specs;

internal class Can_decorate
{
[Test]
public void Classes()
=> typeof(SomeClass).Should().BeDecoratedWith<InheritableAttribute>();
}

[Inheritable("For test purposes")]
internal class SomeClass
{
}
@@ -0,0 +1,13 @@
namespace Diagnostics.Contracts.Mutable_attribute_specs;

internal class Can_decorate
{
[Test]
public void Classes()
=> typeof(SomeClass).Should().BeDecoratedWith<MutableAttribute>();
}

[Mutable("For test purposes")]
internal class SomeClass
{
}
@@ -0,0 +1,26 @@
namespace Diagnostics.Contracts.Will_be_sealed_attribute_specs;

internal class Can_decorate
{
[Test]
public void Classes()
=> typeof(SomeClass).Should().BeDecoratedWith<WillBeSealedAttribute>();

[Test]
public void Properties()
=> typeof(SomeClass).GetProperty(nameof(SomeClass.Property)).Should().BeDecoratedWith<WillBeSealedAttribute>();

[Test]
public void Methods()
=> typeof(SomeClass).GetMethod(nameof(SomeClass.Method)).Should().BeDecoratedWith<WillBeSealedAttribute>();
}

[WillBeSealed("For test purposes")]
internal class SomeClass
{
[WillBeSealed("No reason to change this property.")]
public virtual int Property { get; } = 42;

[WillBeSealed("No reason to change this method.")]
public virtual int Method() => 42;
}
4 changes: 3 additions & 1 deletion specs/Qowaiv.Specs/Obsolete_code.cs
@@ -1,4 +1,6 @@
namespace Obsolete_code;
using Qowaiv.Diagnostics.Contracts;

namespace Obsolete_code;

[Obsolete("Will be dropped when the next major version is released.")]
public class Will_be_dropped
Expand Down
2 changes: 2 additions & 0 deletions specs/Qowaiv.Specs/Qowaiv.Specs.csproj
Expand Up @@ -5,6 +5,7 @@
<PropertyGroup>
<Nullable>enable</Nullable>
<TargetFrameworks>netcoreapp3.1;net6.0;net8.0</TargetFrameworks>
<DefineConstants>CONTRACTS_FULL</DefineConstants>
</PropertyGroup>

<ItemGroup>
Expand All @@ -31,6 +32,7 @@
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Qowaiv.Diagnostics.Contracts\Qowaiv.Diagnostics.Contracts.csproj" />
<ProjectReference Include="..\..\src\Qowaiv\Qowaiv.csproj" />
<ProjectReference Include="..\..\src\Qowaiv.Data.SqlClient\Qowaiv.Data.SqlClient.csproj" />
<ProjectReference Include="..\..\src\Qowaiv.TestTools\Qowaiv.TestTools.csproj" />
Expand Down
3 changes: 2 additions & 1 deletion src/Qowaiv.Data.SqlClient/Qowaiv.Data.SqlClient.csproj
Expand Up @@ -5,7 +5,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net6.0;net8.0</TargetFrameworks>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Version>6.4.0</Version>
<Version>7.0.0</Version>
<PackageId>Qowaiv.Data.SqlClient</PackageId>
<PackageReleaseNotes>
v7.0.0
Expand Down Expand Up @@ -39,6 +39,7 @@ v6.0.0

<ItemGroup>
<ProjectReference Include="..\Qowaiv\Qowaiv.csproj" />
<ProjectReference Include="..\..\src\Qowaiv.Diagnostics.Contracts\Qowaiv.Diagnostics.Contracts.csproj" PrivateAssets="all" IncludeAssets="compiler" />

Check warning on line 42 in src/Qowaiv.Data.SqlClient/Qowaiv.Data.SqlClient.csproj

View workflow job for this annotation

GitHub Actions / Build

Project '..\..\src\Qowaiv.Diagnostics.Contracts\Qowaiv.Diagnostics.Contracts.csproj' is not ordered alphabetically and should appear before '..\Qowaiv\Qowaiv.csproj'. (https://dotnet-project-file-analyzers.github.io/rules/Proj0016.html)
</ItemGroup>

</Project>
Expand Up @@ -5,4 +5,4 @@
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
[Conditional("CONTRACTS_FULL")]
public sealed class CollectionMutationAttribute : ImpureAttribute { }
public sealed class CollectionMutationAttribute(string? justification = null) : ImpureAttribute(justification) { }
10 changes: 10 additions & 0 deletions src/Qowaiv.Diagnostics.Contracts/EmptyClassAttribute.cs
@@ -0,0 +1,10 @@
namespace Qowaiv.Diagnostics.Contracts;

/// <summary>Indicates the class is empty by design.</summary>
/// <remarks>
/// Using this attribute prevents S2094 (Classes should not be empty) from
/// showing up.
/// </remarks>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[Conditional("CONTRACTS_FULL")]
public class EmptyClassAttribute(string justification) : EmptyTypeAttribute(justification) { }
6 changes: 6 additions & 0 deletions src/Qowaiv.Diagnostics.Contracts/EmptyEnumAttribute.cs
@@ -0,0 +1,6 @@
namespace Qowaiv.Diagnostics.Contracts;

/// <summary>Indicates the enum is empty by design.</summary>
[AttributeUsage(AttributeTargets.Enum, AllowMultiple = false)]
[Conditional("CONTRACTS_FULL")]
public class EmptyEnumAttribute(string justification) : EmptyTypeAttribute(justification) { }
6 changes: 6 additions & 0 deletions src/Qowaiv.Diagnostics.Contracts/EmptyInterfaceAttribute.cs
@@ -0,0 +1,6 @@
namespace Qowaiv.Diagnostics.Contracts;

/// <summary>Indicates the class is empty by design.</summary>
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
[Conditional("CONTRACTS_FULL")]
public class EmptyInterfaceAttribute(string justification) : EmptyTypeAttribute(justification) { }
6 changes: 6 additions & 0 deletions src/Qowaiv.Diagnostics.Contracts/EmptyStructAttribute.cs
@@ -0,0 +1,6 @@
namespace Qowaiv.Diagnostics.Contracts;

/// <summary>Indicates the struct is empty by design.</summary>
[AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)]
[Conditional("CONTRACTS_FULL")]
public class EmptyStructAttribute(string justification) : EmptyTypeAttribute(justification) { }
10 changes: 10 additions & 0 deletions src/Qowaiv.Diagnostics.Contracts/EmptyTestClassAttribute.cs
@@ -0,0 +1,10 @@
namespace Qowaiv.Diagnostics.Contracts;

/// <summary>Indicates the class is empty by design.</summary>
/// <remarks>
/// Using this attribute prevents S2094 (Classes should not be empty) from
/// showing up.
/// </remarks>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
[Conditional("CONTRACTS_FULL")]
public sealed class EmptyTestClassAttribute(string? justification = null) : EmptyClassAttribute(justification ?? "For test purposes.") { }
6 changes: 6 additions & 0 deletions src/Qowaiv.Diagnostics.Contracts/EmptyTestEnumAttribute.cs
@@ -0,0 +1,6 @@
namespace Qowaiv.Diagnostics.Contracts;

/// <summary>Indicates the enum is empty by design.</summary>
[AttributeUsage(AttributeTargets.Enum, AllowMultiple = false)]
[Conditional("CONTRACTS_FULL")]
public sealed class EmptyTestEnumAttribute(string? justification = null) : EmptyEnumAttribute(justification ?? "For test purposes.") { }
@@ -0,0 +1,6 @@
namespace Qowaiv.Diagnostics.Contracts;

/// <summary>Indicates the class is empty by design.</summary>
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false)]
[Conditional("CONTRACTS_FULL")]
public sealed class EmptyTestInterfaceAttribute(string? justification = null) : EmptyInterfaceAttribute(justification ?? "For test purposes.") { }
6 changes: 6 additions & 0 deletions src/Qowaiv.Diagnostics.Contracts/EmptyTestStructAttribute.cs
@@ -0,0 +1,6 @@
namespace Qowaiv.Diagnostics.Contracts;

/// <summary>Indicates the struct is empty by design.</summary>
[AttributeUsage(AttributeTargets.Struct, AllowMultiple = false)]
[Conditional("CONTRACTS_FULL")]
public sealed class EmptyTestStructAttribute(string? justification = null) : EmptyStructAttribute(justification ?? "For test purposes.") { }
9 changes: 9 additions & 0 deletions src/Qowaiv.Diagnostics.Contracts/EmptyTypeAttribute.cs
@@ -0,0 +1,9 @@
namespace Qowaiv.Diagnostics.Contracts;

/// <summary>Indicates the type is empty by design.</summary>
[Conditional("CONTRACTS_FULL")]
public abstract class EmptyTypeAttribute(string justification) : Attribute

Check warning on line 5 in src/Qowaiv.Diagnostics.Contracts/EmptyTypeAttribute.cs

View workflow job for this annotation

GitHub Actions / Build

Specify AttributeUsage on 'EmptyTypeAttribute'. (https://rules.sonarsource.com/csharp/RSPEC-3993)

Check warning on line 5 in src/Qowaiv.Diagnostics.Contracts/EmptyTypeAttribute.cs

View workflow job for this annotation

GitHub Actions / Build

Specify AttributeUsage on 'EmptyTypeAttribute'. (https://rules.sonarsource.com/csharp/RSPEC-3993)

Check warning on line 5 in src/Qowaiv.Diagnostics.Contracts/EmptyTypeAttribute.cs

View workflow job for this annotation

GitHub Actions / Build

Specify AttributeUsage on 'EmptyTypeAttribute'. (https://rules.sonarsource.com/csharp/RSPEC-3993)
{
/// <summary>The justification of this decoration.</summary>
public string Justification { get; } = justification;
}
Expand Up @@ -5,4 +5,4 @@
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
[Conditional("CONTRACTS_FULL")]
public sealed class FluentSyntaxAttribute : ImpureAttribute { }
public sealed class FluentSyntaxAttribute(string? justification = null) : ImpureAttribute(justification) { }
Expand Up @@ -3,4 +3,8 @@
/// <summary>To mark a method explicitly as impure.</summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
[Conditional("CONTRACTS_FULL")]
public class ImpureAttribute : Attribute { }
public class ImpureAttribute(string? justification = null) : Attribute
{
/// <summary>The justification of this decoration.</summary>
public string? Justification { get; init; } = justification;
}
Expand Up @@ -3,8 +3,8 @@
/// <summary>Indicates the a class is designed to be inheritable.</summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
[Conditional("CONTRACTS_FULL")]
public class InheritableAttribute : Attribute
public class InheritableAttribute(string? justification = null) : Attribute
{
/// <summary>Initializes a new instance of the <see cref="InheritableAttribute"/> class.</summary>
public InheritableAttribute(string? message = null) => _ = message;
/// <summary>The justification of this decoration.</summary>
public string? Justification { get; init; } = justification;
}

0 comments on commit 59f982e

Please sign in to comment.