Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Proposed new conventions #87

Closed
flakey-bit opened this issue Dec 14, 2023 · 2 comments
Closed

Proposed new conventions #87

flakey-bit opened this issue Dec 14, 2023 · 2 comments

Comments

@flakey-bit
Copy link
Contributor

flakey-bit commented Dec 14, 2023

Hey 👋

I was thinking about contributing back to the project with some conventions that I've used in various repos at my organisation. But before I open a PR (or PRs), I thought I'd check if these conventions would fit the project goals - happy to cherry-pick 🙂

Also, are tests for the conventions a must-have or a nice-to-have?

MvcControllersMustNotUseForbid

What does it do: Checks that controllers deriving from (ASP.NET Core ControllerBase) don't call the Forbid virtual method.

Why would you want that: If you're relaying forbidden from an upstream API you might want to avoid interactions with ASP.NET authentication code. You also might want to return a ProblemDetails response

  • Derives from MustNotCallMethodConventionSpecification
  • Would introduce a dependency on Microsoft.AspNetCore.Mvc.Core

Probably not generally applicable to be honest, but I thought I'd check

MustNotUseGuidNewGuid

What does it do: Checks that code doesn't call Guid.NewGuid()

Why would you want that: If you're trying to avoid random data and/or you're trying to write things in a functional way.

  • Derives from MustNotCallMethodConventionSpecification
  • No dependencies

NamespaceMustStartWith

What does it do: Checks that the type is in a namespace beginning with some needle

Why would you want that: In case MustLiveInNamespaceConventionSpecification is not flexible enough for you.

We're using it to partition a namespace e.g.

private const string RequestDtoNamespacePrefix = "Acme.Foobar.BFF.DTOs.Request";
private const string ResponseDtoNamespacePrefix = "Acme.Foobar.BFF.DTOs.Response";

typeof(StatementLine).Assembly.GetExportedTypes()
    .MustConformTo(new NamespaceMustStartWith(RequestDtoNamespacePrefix)
        .Or(new NamespaceMustStartWith(ResponseDtoNamespacePrefix)))
    .WithFailureAssertion(ConventionHelpers.Fail);
  • Derives from ConventionSpecification
  • No dependencies

AssembliesMustBeIncludedInListConventionSpecification

Derives from AssemblyConventionSpecification
What does it do: Checks that an assembly (name) is included in a hard-coded list/set (of assemblies)

Why would you want that: To help synchronise type-level convention checking

For example

// SolutionTests.cs

private AssemblySpecimen[] NonTestApiAssemblies =>
    AllSolutionAssemblies
        .Where(IsNonTestProject)
        .ToArray();

[Theory]
[MemberData(nameof(EnumerateWarningsToBeTreatedAsErrors))]
public void NonTestProjectsMustTreatWarningAsError(string warning)
{
    NonTestApiAssemblies
        .ToArray()
        .MustConformTo(new TreatsWarningAsErrorConventionSpecification(warning))
        .WithFailureAssertion(ConventionHelpers.Fail);
}

[Fact]
public void NonTestProjectsMustBeIncludedInNonTestAssembliesList()
{
    // We have an (admittedly small) set of convention tests we want to run across all non-test assemblies
    // This test enforces that list of assemblies is up-to-date
    NonTestApiAssemblies
        .ToArray()
        .MustConformTo(
            new AssembliesMustBeIncludedInListConventionSpecification(
                CommonConventionTests.NonTestAssemblies,
                $"{nameof(CommonConventionTests)}:{nameof(CommonConventionTests.NonTestAssemblies)}"
            ))
        .WithFailureAssertion(ConventionHelpers.Fail);
}

// CommonConventionTests.cs

[Theory]
[MemberData(nameof(EnumerateNonTestAssemblies))]
public void Convention_MustNotAccessNewRelicAgentStatically(Assembly assembly)
{
    // NewRelic agent (IAgent) should be injected rather than accessed statically via NewRelic.GetAgent()
    assembly.GetTypes()
        .Where(t => ((t.DeclaringType ?? t) != typeof(Startup)))
        .Where(ConventionHelpers.TypeIsNotGeneratedByCoverlet)
        .MustConformTo(new MustNotAccessNewRelicAgentStatically())
        .WithFailureAssertion(ConventionHelpers.Fail);
}

public static IEnumerable<object[]> EnumerateNonTestAssemblies => NonTestAssemblies.Select(a => new[] { a });

AssemblyReferenceConventionSpecification

Abstract type

MustReferenceAssemblyConventionSpecification

What does it do: Checks that an assembly includes a reference to another assembly (either project reference or package reference)

Derives from AssemblyReferenceConventionSpecification

MustNotReferenceAssemblyConventionSpecification

What does it do: Checks that an assembly does not include a reference to another assembly

Derives from AssemblyReferenceConventionSpecification

MustNotIncludeProjectReferencesConventionSpecification

What does it do: Checks that a project doesn't reference any other projects
Why would you want that: Avoiding bringing in types unintentionally

Derives from AssemblyReferenceConventionSpecification

ProjectMustSpecifyPropertyValueConventionSpecification

What does it do: Checks that a project sets a property in the .csproj

TreatsWarningAsErrorConventionSpecification

What does it do: Checks that a project treats a given warning as an error (via WarningsAsErrors)
Why would you want that: Perhaps you want to enforce that non-test projects treat specific warnings (e.g. possible null reference) as errors.

@andrewabest
Copy link
Owner

👋 hello @flakey-bit!

I was thinking about contributing back to the project

🙇

Also, are tests for the conventions a must-have or a nice-to-have?

Must-have, but if you are struggling to test something, let's talk about why. The tests also help people see how to hold the convention.

Let's go through the potential contributions in the order you've given them:

MvcControllersMustNotUseForbid

I'd prefer to avoid taking the aspnet dependency. I've already done this with System.Data.SqlClient, and it has caused issues. If I saw a bigger need for these types of conventions I'd probably create a Best.Conventional.Aspnet and Best.Conventional.Sql. Right now I'm living with the sql dependency, but I'd prefer to avoid the aspnet one.

MustNotUseGuidNewGuid

This sounds fine, as you've pointed out the infrastructure required to write it is right there to use 👍

NamespaceMustStartWith

AssembliesMustBeIncludedInListConventionSpecification

MustReferenceAssemblyConventionSpecification

MustNotReferenceAssemblyConventionSpecification

MustNotIncludeProjectReferencesConventionSpecification

ProjectMustSpecifyPropertyValueConventionSpecification

TreatsWarningAsErrorConventionSpecification

These all sound like useful additions for controlling assembly-level concerns 👍

Thanks for reaching out before throwing up a PR!

flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 5, 2024
…o check that code doesn't directly call Guid.NewGuid()

For example, if you're trying to write code in a functional style
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 5, 2024
…n assembly is included (by name) in a haystack set of assemblies

This can be used to ensure a list of assemblies (e.g. those containing "production code") is kept up-to-date *so that you can apply conventions to the types in those assemblies*.
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 5, 2024
…ct doesn't include project references to (any) other projects
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 5, 2024
…ferences/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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 5, 2024
…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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ode doesn't directly call Guid.NewGuid()

A possible use-case is code written in a functional style (that wants to avoid non-deterministic behaviour)
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…included (by name) in a haystack set of assemblies

This can be used to ensure a list of assemblies (e.g. those containing "production code") is kept up-to-date *so that you can apply conventions to the types in those assemblies*.
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ct doesn't include project references to (any) other projects
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ferences/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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ct doesn't include project references to (any) other projects
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ferences/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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ode doesn't directly call Guid.NewGuid()

A possible use-case is code written in a functional style (that wants to avoid non-deterministic behaviour)
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…included (by name) in a haystack set of assemblies

This can be used to ensure a list of assemblies (e.g. those containing "production code") is kept up-to-date *so that you can apply conventions to the types in those assemblies*.
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ct doesn't include project references to (any) other projects
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ferences/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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ode doesn't directly call Guid.NewGuid()

A possible use-case is code written in a functional style (that wants to avoid non-deterministic behaviour)
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…included (by name) in a haystack set of assemblies

This can be used to ensure a list of assemblies (e.g. those containing "production code") is kept up-to-date *so that you can apply conventions to the types in those assemblies*.
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ct doesn't include project references to (any) other projects
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…ferences/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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
…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
flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Jan 6, 2024
@flakey-bit
Copy link
Contributor Author

@andrewabest I've opened #89

flakey-bit pushed a commit to flakey-bit/Conventional that referenced this issue Feb 17, 2024
…n.NamespaceMustEndWith (which were discussed but not implemented under pull-request andrewabestGH-90)

Currently (as of 11.0.11) there's only
- Convention.NameMustStartWith / Convention.NameMustEndWith (which only consider the immediate type name, not FullName) and
- Convention.MustLiveInNamespace (which doesn't support a prefix/suffix/regex)
andrewabest pushed a commit that referenced this issue Feb 20, 2024
…MustEndWith (which were discussed but not implemented under pull-request GH-90) (#93)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants