Roslyn analyzers and code fixes in OmniSharp and VS Code

Recently we merged a big (albeit still experimental) feature into OmniSharp – the support for Roslyn analyzers.

I wanted to take a few moments today to discuss this feature with you, share some background info, show you how to get it enabled and share some plans for the next steps.

Some background

The feature was by far the most requested and upvoted issue in OmniSharp, ever. It's really been in the back since the beginning of the project.

Some original prototyping on analyzers and code fixes was done a few years ago already by David. He demoed this as WIP at Dotnet Unboxed in Dallas way back in 2015 (and I was there too!). I did my own share of experimentation on that as well, and showed another prototype of OmniSharp with analyzers support during my session at 2016 NDC Sydney (starts at about 44:30 mark). All of that was a long time ago, but, for various resons, it hasn't materialized into concrete features in OmniSharp.

Instead of focusing on analyzers, we started by adding support for Roslyn refactorings. This was in 2017. Refactorings are another type of code actions, and are slightly easier to support, because they are invoked on demand (think: introduce a field from a constructor parameter or convert a for loop into a foreach and so on). In other words they are not offered to the user automatically, but the user has to explicitly ask for them.

The feature was quite limited though, as you had to configure the refactorings externally via omnisharp.json file. You could think of it as equivalent to VSIX-based refactorings in Visual Studio. I actually blogged about that when the feature was released.

Now, with analyzers, the story is different. It took a long, long time to get things going, and it wouldn't have happened without all the amazing hard work of Pekka who stepped in and drove the feature. It was by far the biggest PR in the history of OmniSharp, it took over a year to process, refine, review and finalize and it finally was merged at this year's MVP Summit when David, Martin and I sat down together for a final review and decided to punch it in.

It was a pretty epic PR, I can tell you that:

One important note – at the moment, the feature is disabled by default because it is considered “experimental” only. This means that you have to explicitly opt-into using it.

Enabling Analyzers in OmniSharp (and in VS Code)

Analyzers are supported in OmniSharp in versions 1.32.13 and higher. In terms of C# Extension for VS Code, they are supported from version 1.19.0 of the extension, which shipped with Omnisharp 1.32.18.

There are three primary ways of enabling analyzers support:

– Using a local omnisharp.json file – just add that file to the root of your workspace (the folder you open in your editor, typically root of the repository). This will enable analyzers support on a project level.

The benefit of this approach is that you can also commit this file to your source control and have all contributors to your codebase benefit automatically from the enabled analyzers feature.

– Using a global omnisharp.json file – the location of the file is:

  • On Win: %USERPROFILE%\.omnisharp\omnisharp.json
  • On Mac/Linux: ~/.omnisharp/omnisharp.json

This will enable analyzers globally on your machine. The structure of the file should be same as before:

The benefit here is that it would apply to all the projects that you open on your machine.

– Using VS Code settings:

This has similar effect to the above mentioned global omnisharp.json file – it will apply to all the projects on your machine. However, just like before, that setting is local to you and will not be shared via source control.

As a bonus we can mention that since a while ago, settings in VS Code have a visual designer as well, so this setting actually shows up in an elegant way too (you don't even need to edit the JSON manually).

OK – once enabled, you should see analyzers picked up automatically. This is a screenshot from the omnisharp-roslyn codebase itself.

And of course, typically, each analyzer has a backing code fix which can be invoked using the “light bulb” in VS Code.

So what is supported?

– Referencing analyzer Nuget packages is supported – those analyzers will be automatically discovered by OmniSharp and respected when you open your projects. This is, of course, the same behavior that you already have in Visual Studio and when compiling from the command line, and is the primary way of consuming analyzers. When we think about “supporting analyzers”, this right here is the main thing we refer to.

– Rulesets are also supported – you can tweak the severity of analyzer rules via rulesets. You can find more information about rulesets here

– Analyzers can also be referenced via omnisharp.json file – both globally (then they apply to all projects on your machine but cannot be committed into source control) or locally (then they can be committed to source control but will not participate in command line / Visual Studio builds). This provides and interesting alternative to having analyzers as part of your project references – at the end of the day it's all about finding a flexible solution that works best for you.

Configuring analyzers via omnisharp.json is the same as configuring refactorings support; you have to give OmniSharp some folders to look at to discover analyzer DLLs:

As mentioned earlier, this is, in many ways, conceptually equivalent to having VSIX with analyzers installed in Visual Studio.

What is not supported?

– At the moment there is no support for re-analyzing the project when analyzers or rulesets change, for example as a result of manual editing or a dotnet restore (analyzers would get stale and you need to restart OmniSharp). This is already being addressed though.

– Global rulesets are not supported – you have to define them per project/solution at the moment.

– There is no intelligent model for prioritization/order of analysis. For example, ideally the currently opened files would be given higher priority as errors and warnings for them are typically most interesting to the user and so on.

– No .editorconfig support. This is a larger issue, because OmniSharp doesn't support .editorconfig at all (i.e. for formatting) at the moment. However, there are some built-in analyzers that can be tweaked using editor config settings, and that will not work for the time being.

– The overall performance is not the best, but it will improve long term.

– Analyzers are not shadow copied which can lead to lock issues – however that is already fixed and will ship in the next release.

Your feedback is needed!

Of course this entire feature – as well as, in principle, OmniSharp as a whole – can only get better if you provide your feedback or experiences.

If you have anything to share, any positive or negative observations, please visit https://github.com/OmniSharp/omnisharp-roslyn and open an issue / comment on an existing one. Thanks!