Structural Search and Replace: What, Why and How-to

Template variables

Simple code templates allow finding and replacing exact source code. For instance, searching with the template aaa instanceof String will always find exactly this code. However, what if we want to find <any_expression>  instanceof String? To deal with such problems, SSR has template variables, by which I mean 'some source code reference'. Any reference that looks like $NameOfReference$ is template variable, where NameOfReference is any user defined name permitted by the Java language. For instance, $arg$ on Figure 2 is such a template variable.

figure 2
Figure 2 Structural Replace dialog

A template variable matches a source code expression if the expression fits within the user defined constraints (if any) which specify what possible values the template variable may have. Returning to the previous example of finding source code like <any_expression>  instanceof String, we will simply use the template $AnyExpression$ instanceof String. For example, assume that we have the following piece of code in the project:

    Log.assert( context.getProvider().getAccessToken() instanceof String );
	

In this case the search result according to the above template will be:

    context.getProvider().getAccessToken() instanceof String
	

And the AnyExpression template variable will have the match: context.getProvider().getAccessToken().

Another important usage of template variables is that whatever expression gets bound to the template variable during the search, it can be used again in the search or replace templates.

Yet another use of template variables is to mark the variable with the 'This variable is the target of the search' option (Figure 3). This means that each match of this particular variable is included in the output of the search. If in our previous example this option was enabled for the AnyVariable template variable, the search would have produced context.getProvider().getAccessToken() as search output.
By default, this option is off, and the result of the search is each match of the entire search template.

Note: SSR supports template variables only in certain places, namely Java code references or names, strings, simple comment contents, names of javadoc tags and their values, and name/value pairs after a javadoc tag.

Introduction to template variable constraints

Consider we want to find all getters declared in the particular class. To find them we could try to use method search template like the one below:

class ClassOfInterest { $GetterType$ $GetterName$(); }

We will also enable the 'This variable is the target of the search' option for the GetterName variable, since we are looking for methods.

figure 3
Figure 3 Edit variables dialog

However, we also need to specify that we are looking for all getter methods. This is where the template variable constraints come into play. We could specify that a getter method is one that has GetterName text which matches the regular expression get.* so we enter this expression into the 'Text / regular expression' field for the GetterName variable (Figure 3). One more constraint that we need to set is how many occurrences of the particular variable we should match. By default, the minimum occurrences count and maximum occurrences count are set to 1. If we set the maximum value for GetterType and GetterName to some larger value (say 1000), our method declaration will be able to match any number of methods from 1 to 1000.

Note: The constraints for specific template variables can be set by pressing the Edit Variables button (Figure 2).

Let's explore the 'Text / regular expression' constraint work in more detail. As it is performing a search, the SSR matches template variables to source code expressions. The source code expression corresponds to some source code text. The 'Text / regular expression' constraint means that the source code text must also match a given regular expression for the match to be valid.

Note: Using regular expressions for text constraints also means that you need to escape the regular expression meta-characters like . () [] − ^ $ \ with one slash (e.g. com\.intellij\.openapi\.editor\.Editor).

Returning to the previous example, we may want to find all getters declared by a particular class and all its descendants. In order to do so, we will set 'Apply constraint within hierarchy' in the 'Text constraints' group for the GetterName variable. Effectively, the option tells the matcher to go into the type hierarchy when the match is not found locally.

Yet another useful application of this option is to set it for a template variable which represents some type. When checking the constraint, the matcher attempts to compare the source code type's name with the given regular expression. If it fails, the matcher will then check the type's super-class and so on. For instance, search templates ($Type$) $Expr$ or $Expr$ instanceof $Type$ with a text constraint for the Type variable set to 'PsiElement' with the option 'Apply the constraint within hierarchy' enabled will search for all casts to or instances of classes that implement the interface PsiElement.

Let's cover in more detail the previously mentioned constraints of minimum and maximum occurrences of a template variable (in 'Occurrence count' group, Figure 3). While matching the search template, the template variable could receive not just one value (though this is the default behaviour), but several ones from the same context, or receive no value at all. We allow several matches by setting the maximum occurrence count to a value larger than one, and we allow zero matches by setting the minimum occurrence count to 0. For instance, when searching for 'any static method call of a particular class', we may be not interested in the number of parameters passed, so we can use the following search template: Utils.assertTrue($Parameter$);

For the Parameter template variable, we specify minimum occurrences as 0 and maximum occurrences as some large value (say, 1000). While matching, the Parameter variable will pick up zero or more parameter expressions of the particular call. The commas separating the particular parameters (if any) are not mentioned in the search template, nor taken into account during the match, since their presence has only a textual syntax meaning.

Yet another example of using the occurrence count values is when searching for if statements. The search template looks as follows:

        if ($BooleanExpr$) {
          $ThenStatement$;
        } else {
          $ElseStatement$;
        }
    

For the ThenStatement and ElseStatement template variables the minimum count constraint is set to 0 and the maximum count is set to Integer.MAX_VALUE.

Note: This search pattern will also find an if without an else branch, since the latter is semantically equivalent to an empty else.

Page
Maxim's photo

Maxim Mossienko
JetBrains

Maxim is a Senior Software Developer working in IntelliJ IDEA project where he is responsible for the support of serverside programming. Currently he builds css and javascript integration within the IDE.

Contact Maxim via email: maxim(.)mossienko
(at) jetbrains.com