My deep dive into Nuget packages made me ask a new question: Can I use Nuget packages to deploy code standards for C#?
It’s Good To Have Standards
Generally, the goal of coding standards is to produce a uniform code base. My go-to explanation of what coding standards are is this: “If you look at the code, you should not be able to tell who wrote it.”
Linting is, to some degree, an extension of formatting. Linting applies a level of analysis to your code, identifying potential runtime problems. Linting rules typically derive from various best practices which can (and do) change over time, especially for languages in active development. Thing of it this way: C++ has not changed much in the last 20 years, but C#, JavaScript, and, by association, TypeScript, have all seen active changes in recent history.
There are many tools for each language that offer linting and formatting. I have developed some favorites:
- Formatting
dotnet format
– C#prettier
– Javascript/TypeScriptblack
– Python
- Linting
- Sonar – support for various languages
eslint
– Javascript/TypeScriptflake8
– Python
Regardless of your tool of choice, being able to deploy standards across multiple projects has been difficult. If your projects live in the same repository, you can store settings files in a common location, but for projects across multiple repositories, the task becomes harder.
Nuget to the Rescue!
I spent a good amount of time in the last week relearning Nuget. In this journey, it occurred to me that I could create a Nuget package that only contained references and settings for our C# standards. So I figured I’d give it a shot. Turns out, it was easier than I anticipated.
There are 3 files in the project:
- The csproj file – MyStandards.CSharp.csproj
main.editorconfig
- MyStandards.CSharp.props
I put the content of the CSProj and Props files below. main.editorconfig
will get copied into the project where this package is referenced as .editorconfig
. You can read all about customizing .editorconfig
here.
MyStandards.CSharp.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
<IncludeBuildOutput>false</IncludeBuildOutput>
<DevelopmentDependency>true</DevelopmentDependency>
</PropertyGroup>
<ItemGroup>
<Content Include="main.editorconfig">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<PackagePath>build</PackagePath>
</Content>
<Content Include="MyStandards.CSharp.props">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
<PackagePath>build</PackagePath>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.15.0.81779" PrivateAssets="None" />
</ItemGroup>
</Project>
MyStandards.CSharp.props
<Project>
<Target Name="CopyFiles" BeforeTargets="Build">
<ItemGroup>
<SourceFile Include="$(MSBuildThisFileDirectory)main.editorconfig"></SourceFile>
<DestFile Include="$(ProjectDir)\.editorconfig"></DestFile>
</ItemGroup>
<Copy SourceFiles="@(SourceFile)" DestinationFiles="@(DestFile)"></Copy>
</Target>
</Project>
What does it do?
When this project is built as a Nuget package, it contains the .props
and main.editorconfig
files in the build
folder of the package. Additionally, it has a dependency on SonarAnalyzer.CSharp
.
Make it a Development Dependency
Note that IncludeBuildOutput
is set to false
, and DevelopmentDependency
is set to true
. This tells Nuget to not include the empty MyStandards.CSharp.dll
file that gets built, and to package this Nuget file as a development only dependency.
When referenced by another project, the reference will be added with the following defaults:
<PackageReference Include="MyStandards.CSharp" Version="1.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
So, the consuming package won’t include a dependency for my standards, meaning no “chain” of references.
Set the Settings
The .props
file does the yeoman’s work: it copies main.editorconfig
into the target project’s folder as .editorconfig
. Notice that this happens before every build, which prevents individual projects from overriding the .editorconfig
file.
Referencing Additional Development Tools
As I mentioned, I’m a fan of the Sonar toolset for linting. As such, I like to add their built-in analyzers wherever I can. This includes adding a reference to SonarAnalyzer.CSharp
to get some built-in analysis.
Since SonarAnalyzer.CSharp
is a development dependency, when installed, it is not included in the package. It would be nice if my standards package would carry this over to projects which use it. To do this, I set PrivateAssets="None"
in the PackageReference
, and removed the default settings.
The Road Ahead
With my C# standards wrapped nicely in a Nuget package, my next steps will be to automate the build pipeline for the packages so that changes can be tracked.
For my next trick, I would like to dig in to eslint
and prettier
, to see if there is a way to create my own extensions for those tools to distribute our standards for JavaScript/TypeScript. However, that task may have to wait a bit, as I have some build pipelines to create.