Static Type Checking

Static type checking (STC) provides information about whether your script is correctly written.

Groovy is a dynamic language, which means that method and property names are looked up when your code is run, not when it’s compiled (like Java).

Let’s look at the following simple, but complete, script:

foo.bar()

We call the method bar() on the object foo. This script compiles without errors, but you get a MissingPropertyException when you run the script because foo hasn’t been defined. This behaviour is useful because there are circumstances that could make this code execute successfully, like an object called foo or a closure getFoo() being passed to the script’s binding.

Although Groovy is a dynamic language, we can compile scripts in a manner that checks method and property references at compilation. The STC feature shows you problems in your scripts when you are writing them, as opposed to when they execute.

When your scripts are executed, they are always compiled dynamically. When they are compiled for the STC, the resulting generated bytecode is thrown away.

Limitations

There are limitations to the type checker. It is possible to write code that shows errors, but it is valid and executes fine. Some of these situations are:

  • Using certain builders

  • Using closures where the parameter types can’t be inferred

However, if you write code like this, you probably use an IDE, which does not work with the STC.

Additionally, your code could have runtime errors that won’t be found until the code executes.

Let us know if you find code that should compile but doesn’t.

Examples

The code for a script fragment condition to check that the user is in a specific space might look like the following:

type checked condition

The STC shows that we are trying to set the space key, rather than retrieve it. We should have used the equality operator:

type checked condition ok

Note the green dot, which tells us that the code is syntactically correct after checking the methods and properties.

Deprecations

The STC also shows methods and properties that are deprecated. Deprecations are parts of the API that Atlassian would prefer you not to use.

In the following image, there is one deprecated method:

type checked condition depr 2

It is advisable to change your code to the suggested alternative because Atlassian usually removes deprecated code on major version releases. If you switch to the non-deprecated code, there is a better chance of your script continuing to work on your next upgrade.

A fixed version of the above script might look like:

type checked condition depr fixed 1

Tips

Fields

To write classes, give information about the field types to the STC.

If you do not, you’ll receive the following STC errors:

type checked condition class depr

Instead, declare your field types like this instead:

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.applinks.api.ApplicationLinkService

class Foo {
    ApplicationLinkService applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService)

    void doSomething() {
        applicationLinkService.getApplicationLinks()
    }
}

Closures

When writing closures, you may need to provide additional type information.

For example:

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.applinks.api.ApplicationLinkService

def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService)

applicationLinkService.getApplicationLinks().findAll {
    it.name == "Confluence"
}

It is inferred from the context that the type of it is an ApplicationLink object.

While this code will execute without issues, the STC produces an error: No such property: name for class: java.lang.Object.

Using the following code, you can fix this error by explicitly informing the STC that the type of it is an ApplicatonLink object.

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.ApplicationLink

def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService)

applicationLinkService.getApplicationLinks().findAll { ApplicationLink it ->
    it.name == "Confluence"
}
Either version of the code is fine. If you want to ignore the STC, you can. The version of the bytecode that is actually executed is always compiled in "dynamic" mode.

If you wanted to take the closure in the previous example and reuse it, you should write code like this:

import com.atlassian.sal.api.component.ComponentLocator
import com.atlassian.applinks.api.ApplicationLinkService
import com.atlassian.applinks.api.ApplicationLink

def applicationLinkService = ComponentLocator.getComponent(ApplicationLinkService)

def findApplicationByName = { ApplicationLink it -> it.name == "Confluence" }

applicationLinkService.getApplicationLinks().findAll(findApplicationByName)

Type Information

Certain parts of Atlassian’s API are not strongly typed.

For example, the method for cloning certain objects are defined to return a java.lang.Object. The type checker only has access to this information, and it is not aware of the types of objects you’re cloning.

The following script, which retrieves an existing space, clones it, and then attemps to access the key, is flagged as having errors:

type checked customfields
Remember, despite the STC error, the script will still run successfully.

The only information that the Confluence API gives the STC is that clonedSpace has the type of java.lang.Object. Fix this by casting the object returned from the clone to a Space:

type checked customfields fixed

layout: default title: Script Roots ---

Script Roots

Script Roots are directories that ScriptRunner will automatically scan for scripts. The scripts you store here will be available across all of ScriptRunner.

Dependent classes are automatically detected and recompiled. The one exception is when you first change a dependent class without changing the class/script that’s actually called. However if you make a small change like adding a space to comment in the calling script, the changes to the base or dependent class trigger recompilation of both.

Set Up

When the ScriptRunner plugin is first installed, it creates a directory called Scripts under your Confluence home directory and registers this directory as one of its script roots. This directory is sufficient for most users and no other configuration need be made. This is a logical place to store your scripts because it is preserved during Confluence upgrades, and it is accessible by all nodes in a clustered Confluence.

You can create subdirectories for your scripts. You could divide them into the business process they support. You could also create supporting or utility classes to be used by the subdirectories.

Make sure the supporting and utility classes have the correct package name or you will get a compilation error.

For example, a script and a class:

<confluence-home>/scripts/foo.groovy
import util.Bollo

log.debug ("Hello from the script")
Bollo.sayHello()
<confluence-home>/scripts/util/Bollo.groovy
package util

public class Bollo {
    public static String sayHello() {
        "hello sailor!!!"
    }
}

Absolute paths outside of script roots continue to work, but changes to dependent classes may not be detected.

Relative Paths

Relative paths are resolved to the script root until a file is found. If you want to use a path you had set up previously, you can add a script root that points to the working directory or to a place where your scripts were kept.

Let’s say that your Confluence instance is in /usr/opt/Confluence and your scripts are in /usr/opt/scripts, so you refer to them like this: ../scripts/foo.groovy.

When you implement script roots, you need a new property that points to your scripts directory, which would be set JAVA_OPTS=%JAVA_OPTS% -Dplugin.script.roots=/usr/opt/scripts.

Resolving ../scripts/foo.groovy relative to this script path has the same result.

Tips

  • If you have multiple roots, use a comma to delimit them.

  • If you work on a script locally before deploying to production, set breakpoints in scripts or classes and attach the debugger.

  • If you work on the ScriptRunner plugin, add the SRC and test directories from the checkout so you can work on the scripts without having to recompile.

    For example: set JAVA_OPTS=%JAVA_OPTS% -Dplugin.script.roots=checkout-directory\src\main\resources,checkout-directory\src\test\resources

You can search for scripts contained in your configured script roots. Wherever you can enter the path of a script to run, you can search for the script directly in the script file input. You can search by script name, or you can browse the sub-directory structure to find the script you’re looking for.

Search by Name

In the file entry, type the filename of the script and select it when you find it. The script loads for use, and the static type checking begins.

ScriptRootsNameSearch

Browse the Script Root Directory

This is a good option if you don’t know the filename of the script but think it exists in a script root.

  1. Type the name of the subdirectory you want to browse.

    If there are lots of directories returned, try to keep refining the search. As you add characters to your search phrase, the results will be refined.
  2. Select the directory you want to browse.

    Now you will see a list of scripts and subdirectories in that directory.

    When you navigate into a directory, the results only show the scripts in that directory. If you want to search all scripts, remove all characters from the input and start again.

You can use tab completion to navigate the directory structure. If you go too far, remove the characters to the previous slash to return the search to the previous directory.

Result Types

The following table contains the different types of result that the search could return:

Table 1. Result Types
Icon Description
IconScript

An executable groovy script

IconDirectory

A sub-directory within one of your script roots

Have questions? Visit the Atlassian Community to connect, share, and learn with other Atlassian users and experts, including Adaptavist staff.

Ask a question about ScriptRunner for JIRA, Bitbucket Server, or Confluence.

Want to learn more? Check out courses on Adaptavist Learn, an online platform to onboard and train new users for Atlassian solutions.