Language injections in ReSharper Ultimate 2016.3

ReSharper Ultimate 2016.3 introduces a new infrastructure for language injections in string literals, which enables ReSharper to treat particular string literal contents as a piece of code written in one of the supported programming languages: C# or ECMAScript regular expressions language, CSS, HTML, JSON or JavaScript.

You can inject a language into a string literal in one of the following ways.

Method 1: “Mark as injected language” context action

When you open the Alt+Enter menu on a string literal, you’ll see a “Mark as injected language” item. In the submenu, choose one of the supported languages. The contents of this string literal will be recognized as a piece of code in that language:

Mark as injected language

Even though this kind of injection tracks code changes and persists in many ordinary cases, it can disappear if you perform huge code reorganization, such as moving to another file or performing Extract method refactoring. Please take into account that tracking code changes has a performance penalty, so if you have many items injected using “Mark as” in the same file, in some cases you may notice a slowdown.

If you need to remove language injection inserted using the “Mark as injected language” context action, you can press Alt+Enter and select “Remove <language> injection mark” there:

Remove injection mark

Method 2: injection comment

You can use a comment to inject a language into a string literal.

The syntax is mostly compatible with the syntax used by IntelliJ IDEA for the same feature. That means that if your team members use both IntelliJ-based products and ReSharper, all of them will have the same experience for injected languages.

A language injection comment has the following form:

//language=CSS prefix=body{ postfix=}

where language specifies a language ID to inject. It is case-insensitive. The following language IDs are currently supported:

  • CSS – CSS language injection
  • HTML – HTML language injection
  • JSON – JSON language injection
  • JSREGEXP – ECMAScript regular expression language injection
  • REGEXP – .NET regular expression language injection
  • JAVASCRIPT – JavaScript language injection

Please note that some of language injections are available only for particular target languages.

Availability of language injections using code comments is shown in the following table:

Injected language In C# strings In JavaScript strings
CSS Yes Yes
HTML Yes Yes
JSON Yes Yes
ECMAScript regular expression No Yes (prefix/postfix unsupported)
.NET regular expression Yes (prefix/postfix unsupported) No
JavaScript Yes Yes

prefix= and postfix= options provide a way to specify a string that “precedes” your string and a string that “follows” your string, respectively. This is very helpful if you want to have language assistance only for a code fragment. For example, you can inject a CSS color value into a string by using the following comment:

HTML injected into JavaScript

It is important to know that prefix= and postfix= are matched textually, so that all the leading and trailing whitespaces specified in these options will be included in the target string. For example, if you want to have a prefix { and a postfix } without any whitespaces, then you need to specify the following options:

//language=LANG prefix={postfix=}

Language injections using code comments don’t have the problems of the “Mark as” action described in Method 1. They are stable as long as your comments are there, and they don’t cause any visible performance impact.

There are, however, several differences in how IntelliJ Platform implements the same feature (which is used in IntelliJ IDEA, WebStorm, and other IntelliJ-based products):

  1. In ReSharper implementation, quotes are not trimmed, neither from language=, nor from prefix= and postfix=. This is done intentionally, because you may want to have prefix/postfix with quotes, and also this is done to encourage using simple injected language IDs.
  2. Comment positioning in ReSharper is more relaxed than in IntelliJ implementation. In IntelliJ, the injection comment always matches the nearest following literal, so you’ll locate it as close as possible to the target literal. In ReSharper, language specifics are taken into account, and the comment may be located after the literal itself, or after some simple construct embracing the literal, or even at the end of the line.

Method 3: automatic injections

This kind of injection is performed by ReSharper itself, so in most cases you will work with injections without even noticing it. For example, if you set the innerHTML property of a JavaScript object, you’ll get HTML assistance in your assigned string value. Also, HTML and CSS injections are automatically provided for Angular templates in JavaScript code:

Automatic injection in AngularJS

ReSharper has a dedicated Options page where you can see all the available automatic injections and disable or re-enable them as needed.

Language injection settings

This mechanism is extensible, and it’s easy to provide your own places for language injections for supported languages in your ReSharper plugin. You’ll need to implement the interface called IInjectionNodeProvider as a SolutionComponent.

  • In the Check() method, provide your logic for checking the particular syntax tree node. If it returns true, the target language will be injected in this node. You can save some intermediate computation results into “data”, to use them for prefix and postfix computation.
  • GetPrefix()/GetPostfix() provide an ability to specify prefix and postfix for language injection (check ‘Comment’ section of this article to learn more about prefixes and postfixes). You can use a previously computed state “data” here.
  • ProvidedLanguageID should be one of the IDs described in the section ‘Method 2: Injection Comment’ above (using InjectedLanguageIDs static class properties is recommended).

As your plugin is loaded by ReSharper, you’ll see your own injections implementing IInjectionNodeProvider in the same options page where built-in injections are shown.

Current limitations and plans

Currently you can inject a language only into a single proper string literal. That means that you can’t inject a language into a string concatenation. Also currently you can’t inject a language into a C# 6 interpolated string or a ECMAScript 2015 template string if they have template arguments (for argument-less template strings, injection works). Note that even though injection works with C# string.Format string, the string content is treated literally, without substituting the arguments. So, depending on your target language, it may or may not be a syntax error.

Currently there is no mechanism to provide language injections using ReSharper external annotations. One of the upcoming ReSharper releases will introduce a [Language] external annotation for C# to provide you with one more way to manage language injections.

Please note that using prefix= and postfix= for language injections can cause some features to behave unexpectedly in certain situations. This may happen if you have significant constructs (such as variable declarations) in prefix/postfix code, or if some parts of the same syntax element are split between prefix/postfix and the real string contents. Use this possibility with care.

All the symbols defined inside language injections (for example, JavaScript variables or CSS classes defined inside string literals) are local to the injected piece of code, and are not guaranteed to be reachable from code external to the inject.

Language injections can be implemented only for the languages that are natively supported by ReSharper. For example, that means that until SQL is natively supported by ReSharper, it’s impossible to have injected SQL, despite the fact that it is a highly requested feature. But for any language natively supported by ReSharper language injections are doable. Please create a feature request if you’re missing one.

We’re going to address many of these limitations in our further releases. Stay tuned!

image description