IntelliJ Gem: Structural Search

Posted on Apr 19, 2015

As an engineer, I’ve always been obsessed with productivity and efficiency. I frequently experiment with new tools and methodologies and try to find what works best for me. The IDE is no exception… and out of everything that’s out there, IntelliJ is hard to beat.

One of the hidden (and hardly documented) gems in IntelliJ is the structured search. In a sentence, it allows you to leverage IntelliJ’s understanding of code to perform complex search and replace scenarios. There are lots of cool usages for this - although the real power hides under the Script constraints section.

Finding usages in annotated classes

Some might say that the controller layer should not access the data layer directly. How would you search for these instances? Structured search makes it easy. In this example, we’re searching for all classes that are annotated with @Controller and have a private member that is (or inherits from) a type that contains “Repo”:

ss1 code

ss1 annotation

ss1 type

Finding candidates for God objects

Every legacy code ends up suffering from the God object code smell. Classes with a large number of methods are very likely to fall into this category. Here’s how it’s done with structural search.

ss2 code

ss2 code

Finding inefficient log statements

SLF4J recommends we use parameterized log entries to improve efficiency. How do we find log entries that use concatenation? Enter Script constraints:

ss3 code

ss3 logger

ss3 call

ss3 params1

ss3 params2

Wait… what?

IntelliJ’s help states the following:

Script constraints are used when items to search for are more than a plain match. If you are looking for certain language constructs (for example, constructors with the specified number of parameters, or members with the specified visibility modifiers), apply constraints described as Groovy scripts.

Granted, this leaves something to be desired. In essence, when Script constraints is enabled, IntelliJ executes the Groovy script against the variable that was found (in this case, $FirstParam$). If the script evaluates to true, the condition is met. As you might have guessed, the expression is passed to the Groovy code as __context__.

The type of __context__ depends on the variable in question, although it will always be one of IntelliJ’s internal Program Structure Interface (PSI) types. For me, the easiest way to find this out was to output the class name to a file while evaluating the Groovy script:

new File("/Users/user/filetype.txt").append(__context__.class.canonicalName)

Finally, going back to our example, in order to find non-parameterized log statements, we are looking for statements that include a non-literal expression. Anything other than a literal expression (effectively a regular string) would be considered inefficient, as it would force string concatenation for all log statements.

Here are some links for more information about the different types of PsiExpressions: