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 Edit recipe 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"