.NET Tools
Essential productivity kit for .NET and game developers
Language Injection Improvements in Rider 2022.3
Good news everyone! Language injections in C# just got better! They are a powerful feature that lets you embed code from other languages directly into a string in your C# code, while getting syntax highlighting, code inspections and more.
In this blog post we’ll check out all the updates in Rider. But before diving in let’s shortly refresh what it’s all about.
What is language injection?
Every once in a while, you have to work with strings that are not just text but a fragment of JSON, XML, HTML, or any other language. However, for the C# compiler and the editor, these are still mere strings, so nothing would prevent you from making silly mistakes. The code would build successfully, and then your program suddenly fails at runtime. Fortunately, there is a solution! Language injections let you enable code assistance for the embedded language right inside a string literal.
There are several ways of instructing Rider to perform a language injection. The first one is to use the Mark as injected language or reference context action. It registers a temporary language injection which will last until the solution is closed.
While temporary language injection works great for quick edits, sometimes you’ll want a permanent effect, so the language injection is there when you next open your solution (or someone on your team opens the same codebase).
For such purposes you can put a special comment // language=language_name
before a string literal. This is where the first and smallest update with Rider 2022.3 comes in: now you can use the shorthand lang
instead of language
.
If you write SQL queries inside C# strings then you probably know about automatic language injections: if you have a database connection, Rider will recognize certain queries, and injects the SQL language straight away.
Note: we recently hosted a webinar on how to work with databases in Rider. Check it out if you want to learn more about database support in Rider.
So far, we’ve been looking at the existing capabilities. Let’s go ahead and see more updates!
Better with C# 11
Putting text or code into a C# string literal is mostly straightforward. However, there are some unfortunate limitations: you can’t use certain characters as is. You have to escape them to make sure the surrounding string literal is still valid C# syntax.
For instance, in regular string literals you have to put a backslash before characters like quotes, and use \r\n
escape sequences to insert line breaks. Additionally, in case of regular interpolated strings, curly braces need to be doubled to not be confused with interpolation holes.
var regularLiteral = "{ \"name\": \"Microsoft.NETCore.App\" }"; var regularInterpolated = $"{{ \"version\": \"{version}\" }}";
Another kind of C# strings, verbatim strings, make things easier to express. They can be multiline and don’t require escaping by backslash. The only character that is still required to be escaped there is a double quote: you need to use the ""
sequence to express a single "
. Also, the same rule about curly braces inside interpolated strings applies here as well.
var verbatimLiteral = @"{ ""name"": ""Microsoft.NETCore.App"" }"; var verbatimInterpolated = $@"{{ ""version"": ""{version}"" }}";
To overcome all of these inconveniences, C# 11 introduced a new kind of strings: raw strings. They are basically verbatim strings on steroids, and don’t apply any sort of escaping.
Raw strings start and end with the delimiter containing at least three quotes, and any clashes with the content can be solved by adding more quotes (or dollar signs in case of interpolated strings).
var rawSingleLine = """{ "name": "Microsoft.NETCore.App" }"""; var rawMultiLineInterpolated = $$""" { "version": "{{version}}" } """;
Finally, you don’t have to sacrifice the indentation when text is multiline, because the indentation whitespace counted from the last line is not included in the resulting value. All this makes raw strings a perfect host for language injections!
Rider 2022.3 not only supports language injection inside raw strings but also has a convenient action to transform existing regular or verbatim string literal into a raw representation. On any string literal, press Alt+Enter and use the To raw string action to change the type of string.
But that’s not all! The newest version of Rider not only provides language injection inside raw string literals, it also supports any kind of interpolated strings along with string concatenation. Previously Rider could only do some of these in case of automatic detection of SQL language, but at last all limitations are gone.
Conversion from regular/verbatim interpolation into a raw one can be done automatically with the new context action To raw interpolation.
We’ve seen how the newest C# vastly improves the language injection experience with raw strings. Now let’s check out how code annotations can enhance it even further!
Better with JetBrains Annotations
We’ve already covered some JetBrains Annotations before on our blog. In short, they are attributes that you can apply to your code to inform the IDE about some additional semantics which should be taken into account. For instance, the [NotNull]
and [CanBeNull]
attributes have been used to express the nullability and improve code analysis way before the nullable reference types came around in C# 8.
A year ago, in ReSharper 2021.3, we added the LanguageInjectionAttribute
annotation to denote that a field, property, or parameter must contain a text in a specified language, and therefore ReSharper can automatically perform a language injection into a value passed to the annotated entity. The annotation even lets you set up a prefix and suffix for the embedded text so the user could focus only on the most essential parts.
However this annotation had certain limitations. First and foremost, unfortunately it didn’t work in Rider. Second, the annotation used the InjectedLanguage
enum as a parameter which included only HTML, CSS, JavaScript, XML, and JSON as possible options.
We’re glad to announce that the latest Rider 2022.3 not only supports the LanguageInjectionAttribute
annotation but also allows using a much wider variety of languages! In fact, you can specify the name of any language supported by the IntelliJ platform out of the box or with a plugin.
The nice thing about JetBrains Annotations is that you can add them for any existing libraries without modifying their source code in any way. Both ReSharper and Rider come with a bundled package of External Annotations that JetBrains maintains on GitHub, but it’s absolutely possible to complement it with additional annotations.
The latest version of the External Annotations package already includes LanguageInjectionAttribute
definitions for some popular .NET APIs and we’re eager to add even more with your support, so don’t hesitate to create pull requests!
In this section we’ve learned about recent updates in JetBrains Annotations further facilitating language injection. But as it sometimes happens, similar annotations made their way into the base class library of the .NET itself!
Better with .NET 7
The latest version of .NET added the StringSyntaxAttribute
which, like LanguageInjectionAttribute
, allows to specify the expected format of string-typed fields, properties, and parameters. This attribute class defines several standard syntax constants which include not only language syntaxes such as Regex
, Xml
, and Json
, but also a number of other ones.
For instance, the CompositeFormat
syntax can be applied to String.Format
-like methods taking a string with {index[,alignment][:formatString]}
format items.
Using [StringSyntax(StringSyntaxAttribute.CompositeFormat)]
is basically equivalent to using the StringFormatMethodAttribute
JetBrains annotation, with one difference. The former one can be put on the parameter itself, while the latter one marks the whole method (e.g. [StringFormatMethod("message"))]
.) Just like with the JetBrains annotation, Rider validates format strings, highlights matching arguments, and warns about missing ones.
Other constants from the StringSyntaxAttribute
class correspond to various categories of format specifiers: NumericFormat
, GuidFormat
, DateTimeFormat
, etc. Needless to say that the newest version of Rider supports all of that, and shows us the relevant code completion items.
Rider is not bound only to the predefined syntaxes. Everything we previously wrote about LanguageInjectionAttribute
is applicable to the StringSyntaxAttribute
as well! An interesting example of that would be C# language injection inside C# strings. Such “Inception”-like experience is totally indispensable when authoring Source Generators.
What’s next?
We’ve gone through a number of improvements to language injections: the lang
comment, raw strings, concatenations and interpolations, LanguageInjectionAttribute
, external annotations, and StringSyntaxAttribute
.
At JetBrains, we never stop improving developer experience, so you can expect more goodies in the upcoming versions, and not only for the C# language! We’d love to hear your feedback.