Writing a recipe, part 2¶
The first tutorial explained how to search for code and how to automatically give
suggestions. This second tutorial will focus mainly on improving our existing recipe and making it a bit
more generic in order for it to also work for System.out.print
statements. Afterwards, we'll learn how to limit
the scope of our recipe.
Making the recipe more generic¶
An important aspect of Sensei is creating recipes that are generic enough as not to miss any unwanted bits of code. This can be done through multiple mechanisms. First we will use the logic operators, and secondly, we will use string matching techniques to produce a similar result.
Let's open up our recipe again by putting the cursor on the highlighted piece of code and pressing the Show intention actions and quick-fixes shortcut (by default: Alt+Enter or ⌘↵). Choose and the recipe editor window should open up.
As said during the introduction of this tutorial, we'll first use the logical operators. There are 3
operators that we can use: anyOf
, allOf
and not
. In our example, we'll use anyOf
.
The goal is to mark the code if the name is equal to println
and to print
. The way to configure this
is as follows:
search:
methodcall:
anyOf:
- name: "println"
- name: "print"
on:
field:
name: "out"
in:
class:
name: "java.lang.System"
Another way to make this recipe more generic is by not specifying that the name should be equal to
println
or print
but rather configuring that the name should start with print
.
This is a very common pattern, and something that we've created UI-helper for. If you look at the
UI View
you will see that there is a button next to the text box of name. Once
you've clicked this, a more advanced configuration of the name target will be shown:
name:
is: println
We can remove the is
option and click on the to choose matches
. Inside this
field we have the ability to specify a regular expression. To check if the name starts with print
choose the pattern print.*
. This pattern tells Sensei to look for a name that starts
with print
and contains 0 or more characters after that (.*
).
search:
methodcall:
name:
matches: "print.*"
on:
field:
name: "out"
in:
class:
name: "java.lang.System"
As seen in the previous example, certain options can be configured as a string but also as another target. These options are indicated by the
button inside the UI builder. Take a look at these, since they contain powerful features, especially when matching types.Scoping the recipe¶
Of course, not every recipe is valid for the entire project. That's why we've provided an easy and flexible way to
limit the scope of the recipe. It is always possible to specify on the root level that a certain recipe should only
trigger if the element is in
another element. This option is again a target specification that can
be nested and configured the same way as all the others. An example of this would be:
search:
methodcall:
name:
matches: "print.*"
on:
field:
name: "out"
in:
class:
name: "java.lang.System"
in:
class:
name: "Example"
What to read next?¶
We've come to the end of our quick introductory tutorial. Keep in mind that we've only scratched the surface of the possibilities that Sensei provides. To continue learning how to use Sensei we highly recommend reading starting with the Topics section, where certain high-level concepts are explained to help search for common patterns.