Index Archive GitHub Twitter
30 Jan 2022

Using StyleCop with Unity

Sometimes, it feels like you have to fight tooth and nail to get Unity to do the right thing. Using StyleCop Analyzers in a project is no exception.

Foreground: What is StyleCop?

StyleCop.Analyzers is technically a static analysis tool for C#, but mostly does code style enforcement. It's comparable to ESLint, a similar tool for JavaScript.

StyleCop "Classic" was an implementation of the same idea, but StyleCop.Analyzers uses the .NET Compiler Platform for code analysis, making it pluggable into the OmniSharp language server.

Sidenote: If you are not using OmniSharp for your Unity code, you're missing out on some of the best tooling for any programming language, period. Also, this guide won't be useful to you until you have it configured, anyway. Visual Studio and ReSharper have support built in, but other editors require some tweaking. My article on using Unity/OmniSharp/Emacs might be helpful even if you aren't an Emacs user.

How is this normally done?

In modern, non-Unity C# projects, this is typically done by just adding a reference to StyleCop.Analyzers to the csproj file to be retrieved via NuGet.

<Project Sdk="Microsoft.NET.Sdk">
    <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376">
        runtime; build; native; contentfiles; analyzers; buildtransitive
    <AdditionalFiles Include="stylecop.json" />

Two lines in particular here are relevant for Unity: <PackageReference> and <AdditionalFiles>.

The <PackageReference> line informs the dotnet SDK which NuGet package to download. At the time of writing, it's not possible to use NuGet in this manner with Unity. We can instead download the StyleCop NuGet package and copy the relevant DLL files. Using the NuGet CLI:

nuget install StyleCop.Analyzers -Version 1.2.0-beta.406 -Framework netstandard2.1

Note that you may want to specify a different version or framework.

Adding the reference to the DLL directly would normally be as simple as the following csproj configuration:

<Project Sdk="Microsoft.NET.Sdk">
    <Analyzer Include="StyleCop/StyleCop.Analyzers.dll" />

…except that Unity will clobber these changes anytime it regenerates project files. More on that later.

The other important line, the <AdditionalFiles> include for stylecop.json, is documented here. The simplest possible stylecop.json file looks like this:

  "$schema": ""

We will now focus on the issue of the clobbered configuration.

Previous solutions that no longer work

Elsewhere online, you may see solutions that use the undocumented OnGeneratedCSProject hook to modify csproj files to add a reference to the assembly (example 1, example 2). Unfortunately, this hook was unceremoniously dropped in the 2021.1 series.

Directory.Build.props to the rescue

Fortunately, we can place these configuration changes in a special file: Directory.Build.props. Essentially, this is an XML file that gets pulled in with every csproj file in the directory its located without actually modifying those files.

The solution is that simple: we can create a Directory.Build.props file to make the above configuration changes.

    <Analyzer Include="StyleCop/StyleCop.Analyzers.dll" />
    <AdditionalFiles Include="stylecop.json" />

The solution turns out to be rather simple in the end, fortunately.

Now with any LSP-enabled editor, you should begin to see the various StyleCop lints in your code. You may need to enable analyzers by creating or adding to the omnisharp.json file in the same directory as your projects/Directory.Build.props:

  "RoslynExtensionsOptions": {
    "enableAnalyzersSupport": true

Next Steps

Once you've configured stylecop.json, you might want to fine-tune which rules are enabled. Traditionally this is done with a ruleset file, which is, frankly, a huge pain to create and edit by hand, especially if you're not using Visual Studio. Luckily, the language server is capable of reading rules from .editorconfig files.

First, ensure OmniSharp is configured to use .editorconfig by adding or editing the omnisharp.json file located in the same directory as your generated csproj files:

  "FormattingOptions": {
    "enableEditorConfigSupport": true

Then, create or add the following to your .editorconfig file in the same directory.

# Remove the line below if you want to inherit
# .editorconfig settings from higher directories
root = true

# C# files

# EXAMPLE: Disable all naming convention analyzers.
dotnet_diagnostic.SA1300.severity = none
dotnet_diagnostic.SA1301.severity = none
dotnet_diagnostic.SA1302.severity = none
dotnet_diagnostic.SA1303.severity = none
dotnet_diagnostic.SA1304.severity = none
dotnet_diagnostic.SA1305.severity = none
dotnet_diagnostic.SA1306.severity = none
dotnet_diagnostic.SA1307.severity = none
dotnet_diagnostic.SA1308.severity = none
dotnet_diagnostic.SA1309.severity = none
dotnet_diagnostic.SA1310.severity = none
dotnet_diagnostic.SA1311.severity = none
dotnet_diagnostic.SA1312.severity = none
dotnet_diagnostic.SA1313.severity = none
dotnet_diagnostic.SA1314.severity = none

You can see the names of all of tweakable rules in the ruleset file linked above.

Sidenote: OmniSharp provides an impressive number of custom .editorconfig settings that can be used to unify and enforce your code style across a variety of editors. Unfortunately, generating an extensive set of tweaks from scratch is very difficult without Visual Studio, as a lot of the options aren't documented. As is evident by this article, this is a frustrating pattern with a lot of C# tooling that hopefully will improve in the future.

Tags: unity csharp
If you have feedback for this blog, please create an issue here.